Browse Source

rewamped js tests for new approach to ui

Rafał Pitoń 9 years ago
parent
commit
78cc828b9d
52 changed files with 616 additions and 1366 deletions
  1. 1 4
      README.rst
  2. 18 3
      misago/frontend/gulpfile.js
  3. 1 1
      misago/frontend/misago/activation/request-activation-link-component.js
  4. 1 1
      misago/frontend/misago/forgotten-password/request-link-component.js
  5. 1 1
      misago/frontend/misago/forgotten-password/reset-password-component.js
  6. 1 1
      misago/frontend/misago/forgotten-password/reset-password-form.js
  7. 1 1
      misago/frontend/misago/forms/request-link.js
  8. 2 2
      misago/frontend/misago/register/form.js
  9. 37 6
      misago/frontend/misago/services/ajax.js
  10. 0 142
      misago/frontend/misago/services/api.js
  11. 14 12
      misago/frontend/misago/services/auth.js
  12. 1 1
      misago/frontend/misago/services/captcha.js
  13. 8 17
      misago/frontend/misago/services/modal-controller.js
  14. 1 2
      misago/frontend/misago/services/mount-page.js
  15. 1 1
      misago/frontend/misago/services/mounts.js
  16. 2 2
      misago/frontend/misago/sign-in/form.js
  17. 0 40
      misago/frontend/misago/utils/urlconf.js
  18. 0 9
      misago/frontend/misago/utils/views-shortcuts.js
  19. 3 1
      misago/frontend/package.json
  20. 10 4
      misago/frontend/test/index.html
  21. 0 87
      misago/frontend/test/tests/acceptance/account-activation.js
  22. 0 43
      misago/frontend/test/tests/acceptance/auth-exceptions.js
  23. 0 218
      misago/frontend/test/tests/acceptance/change-password.js
  24. 0 141
      misago/frontend/test/tests/acceptance/error-pages.js
  25. 62 0
      misago/frontend/test/tests/acceptance/guest-nav.js
  26. 0 156
      misago/frontend/test/tests/acceptance/legal-pages.js
  27. 54 40
      misago/frontend/test/tests/acceptance/register.js
  28. 50 36
      misago/frontend/test/tests/acceptance/request-activation.js
  29. 33 48
      misago/frontend/test/tests/acceptance/request-password-change.js
  30. 121 0
      misago/frontend/test/tests/acceptance/reset-password.js
  31. 53 14
      misago/frontend/test/tests/acceptance/sign-in.js
  32. 0 19
      misago/frontend/test/tests/acceptance/tests-utils.js
  33. 60 10
      misago/frontend/test/tests/unit/ajax.js
  34. 0 140
      misago/frontend/test/tests/unit/api.js
  35. 25 0
      misago/frontend/test/tests/unit/links.js
  36. 0 66
      misago/frontend/test/tests/unit/router.js
  37. 2 2
      misago/frontend/test/tests/unit/service-container.js
  38. 0 53
      misago/frontend/test/tests/unit/urlconf.js
  39. 7 24
      misago/frontend/test/utils/async-utils.js
  40. 15 0
      misago/frontend/test/utils/mock-mount.js
  41. 19 4
      misago/frontend/test/utils/qunit-boilerplate.js
  42. 1 1
      misago/frontend/test/utils/selectors.js
  43. 0 1
      misago/project_template/requirements.txt
  44. 2 3
      misago/static/misago/js/misago.js
  45. 1 1
      misago/static/misago/js/misago.js.map
  46. 0 1
      misago/templates/misago/base.html
  47. 0 1
      misago/templates/misago/extra.js
  48. 1 2
      misago/users/api/auth.py
  49. 3 0
      misago/users/context_processors.py
  50. 1 1
      misago/users/urls/api.py
  51. 1 1
      misago/users/views/activation.py
  52. 2 2
      misago/users/views/forgottenpassword.py

+ 1 - 4
README.rst

@@ -71,10 +71,7 @@ With exception of Admin Panel, Misago frontend is powered by Mithril.js applicat
 
 
 To start work on custom frontend for Misago, fork and install it locally to have development forum setup. You can now develop custom theme by modifing assets in misago/frontend directory, however special care should be taken when changing source javascripts.
 To start work on custom frontend for Misago, fork and install it locally to have development forum setup. You can now develop custom theme by modifing assets in misago/frontend directory, however special care should be taken when changing source javascripts.
 
 
-Misago defines two templates that allow you to include custom html and js code before Misago's JavaScript app is ran:
-
-* **scripts.html** template is included before final ``<script>`` element, allowing you to include and/or init 3rd party libraries such as Google Analytics, Facebook SDK, etc ect.
-* **extra.js** template is included after ``misago`` javascript object is created, but before ``misago.init`` is called, allowing you to run custom code registering new services adding custom features or modifing existing ones.
+Misago defines template that allows you to include custom html and js code before Misago's JavaScript app is ran, named **scripts.html**.
 
 
 
 
 Bug reports, features and feedback
 Bug reports, features and feedback

+ 18 - 3
misago/frontend/gulpfile.js

@@ -41,9 +41,14 @@ var ace = [
 ];
 ];
 
 
 var testLibs = [
 var testLibs = [
+  'node_modules/qunitjs/qunit/qunit.js',
   'bower_components/jquery-mockjax/dist/jquery.mockjax.js'
   'bower_components/jquery-mockjax/dist/jquery.mockjax.js'
 ];
 ];
 
 
+var testStyles = [
+  'node_modules/qunitjs/qunit/qunit.css',
+];
+
 var zxcvbn = 'bower_components/zxcvbn/dist/*.js';
 var zxcvbn = 'bower_components/zxcvbn/dist/*.js';
 
 
 gulp.task('lint', function() {
 gulp.task('lint', function() {
@@ -222,11 +227,16 @@ gulp.task('collecttestslibs', ['cleantest'], function() {
     .pipe(gulp.dest('test/dist'));
     .pipe(gulp.dest('test/dist'));
 });
 });
 
 
+gulp.task('collecttestsstyles', ['cleantest'], function() {
+  return gulp.src(testStyles)
+    .pipe(concat('test-styles.css'))
+    .pipe(gulp.dest('test/dist'));
+});
+
 gulp.task('collecttestsutils', ['cleantest'], function() {
 gulp.task('collecttestsutils', ['cleantest'], function() {
   return gulp.src('test/utils/**/*.js')
   return gulp.src('test/utils/**/*.js')
     .pipe(jshint(packageJSON.jshintConfig))
     .pipe(jshint(packageJSON.jshintConfig))
     .pipe(jshint.reporter('default'))
     .pipe(jshint.reporter('default'))
-    .pipe(jshint.reporter('fail'))
     .pipe(concat('utils.js'))
     .pipe(concat('utils.js'))
     .pipe(gulp.dest('test/dist'));
     .pipe(gulp.dest('test/dist'));
 });
 });
@@ -243,6 +253,7 @@ gulp.task('starttestserver', [
     'collecttests',
     'collecttests',
     'collecttestsutils',
     'collecttestsutils',
     'collecttestslibs',
     'collecttestslibs',
+    'collecttestsstyles',
     'collecttestjs',
     'collecttestjs',
     'collecttestcss',
     'collecttestcss',
     'collecttestfonts',
     'collecttestfonts',
@@ -259,7 +270,10 @@ gulp.task('starttestserver', [
 
 
 gulp.task('test', ['starttestserver'], function() {
 gulp.task('test', ['starttestserver'], function() {
   gulp.watch([
   gulp.watch([
-    'test/tests/**/*.js', 'misago/**/*.js', 'misago/**/*.less'
+    'test/tests/**/*.js',
+    'test/utils/**/*.js',
+    'misago/**/*.js',
+    'misago/**/*.less'
   ], [
   ], [
     'collecttests',
     'collecttests',
     'collecttestjs',
     'collecttestjs',
@@ -267,6 +281,7 @@ gulp.task('test', ['starttestserver'], function() {
     'collecttestfonts',
     'collecttestfonts',
     'collecttestimg',
     'collecttestimg',
     'collecttestsutils',
     'collecttestsutils',
-    'collecttestslibs'
+    'collecttestslibs',
+    'collecttestsstyles'
   ]);
   ]);
 });
 });

+ 1 - 1
misago/frontend/misago/activation/request-activation-link-component.js

@@ -29,7 +29,7 @@
 
 
   var form = {
   var form = {
     controller: function(_) {
     controller: function(_) {
-      var vm = new ViewModel(_.context.SEND_ACTIVATION_API_URL);
+      var vm = new ViewModel(_.context.SEND_ACTIVATION_API);
 
 
       return {
       return {
         vm: vm,
         vm: vm,

+ 1 - 1
misago/frontend/misago/forgotten-password/request-link-component.js

@@ -36,7 +36,7 @@
 
 
   var component = {
   var component = {
     controller: function(_) {
     controller: function(_) {
-      var vm = new ViewModel(_.context.SEND_PASSWORD_RESET_API_URL);
+      var vm = new ViewModel(_.context.SEND_PASSWORD_RESET_API);
 
 
       return {
       return {
         vm: vm,
         vm: vm,

+ 1 - 1
misago/frontend/misago/forgotten-password/reset-password-component.js

@@ -14,7 +14,7 @@
 
 
   var component = {
   var component = {
     controller: function(_) {
     controller: function(_) {
-      var vm = new ViewModel(_.context.CHANGE_PASSWORD_API_URL);
+      var vm = new ViewModel(_.context.CHANGE_PASSWORD_API);
 
 
       return {
       return {
         form: _.form('reset-password', vm)
         form: _.form('reset-password', vm)

+ 1 - 1
misago/frontend/misago/forgotten-password/reset-password-form.js

@@ -40,7 +40,7 @@
     };
     };
 
 
     this.error = function(rejection) {
     this.error = function(rejection) {
-      _.api.alert(rejection);
+      _.ajax.error(rejection);
     };
     };
   };
   };
 
 

+ 1 - 1
misago/frontend/misago/forms/request-link.js

@@ -39,7 +39,7 @@
       if (rejection.status === 400) {
       if (rejection.status === 400) {
           vm.error(rejection, _);
           vm.error(rejection, _);
       } else {
       } else {
-        _.api.alert(rejection);
+        _.ajax.error(rejection);
       }
       }
     };
     };
 
 

+ 2 - 2
misago/frontend/misago/register/form.js

@@ -45,7 +45,7 @@
     };
     };
 
 
     this.submit = function() {
     this.submit = function() {
-      _.api.model('user').post({
+      _.ajax.post(_.context.USERS_API, {
         username: this.username(),
         username: this.username(),
         email: this.email(),
         email: this.email(),
         password: this.password(),
         password: this.password(),
@@ -62,7 +62,7 @@
         _.alert.error(gettext("Form contains errors."));
         _.alert.error(gettext("Form contains errors."));
         $.extend(self.errors, rejection);
         $.extend(self.errors, rejection);
       } else {
       } else {
-        _.api.alert(rejection);
+        _.ajax.error(rejection);
       }
       }
     };
     };
   };
   };

+ 37 - 6
misago/frontend/misago/services/ajax.js

@@ -65,12 +65,7 @@
     };
     };
 
 
     this.get = function(url) {
     this.get = function(url) {
-      var preloaded = Misago.pop(_.context, url);
-      if (preloaded) {
-        var deferred = m.deferred();
-        deferred.resolve(preloaded);
-        return deferred.promise;
-      } else if (runningGets[url] !== undefined) {
+      if (runningGets[url] !== undefined) {
         return runningGets[url];
         return runningGets[url];
       } else {
       } else {
         runningGets[url] = this.ajax('GET', url);
         runningGets[url] = this.ajax('GET', url);
@@ -93,6 +88,42 @@
     this.delete = function(url) {
     this.delete = function(url) {
       return this.ajax('DELETE', url);
       return this.ajax('DELETE', url);
     };
     };
+
+    // Shorthand for handling backend errors
+    this.error = function(rejection) {
+      if (rejection.ban) {
+        _.showBannedPage(rejection.ban);
+        _.modal();
+      } else {
+        this.alert(rejection);
+      }
+    };
+
+    this.alert = function(rejection) {
+      var message = gettext("Unknown error has occured.");
+
+      if (rejection.status === 0) {
+        message = gettext("Lost connection with application.");
+      }
+
+      if (rejection.status === 400 && rejection.detail) {
+        message = rejection.detail;
+      }
+
+      if (rejection.status === 403) {
+        message = rejection.detail;
+        if (message === "Permission denied") {
+          message = gettext(
+            "You don't have permission to perform this action.");
+        }
+      }
+
+      if (rejection.status === 404) {
+        message = gettext("Action link is invalid.");
+      }
+
+      _.alert.error(message);
+    };
   };
   };
 
 
   Misago.addService('ajax', function(_) {
   Misago.addService('ajax', function(_) {

+ 0 - 142
misago/frontend/misago/services/api.js

@@ -1,142 +0,0 @@
-(function (Misago) {
-  'use strict';
-
-  var filtersUrl = function(filters) {
-    if (typeof filters === 'object') {
-      var values = [];
-      for (var key in filters) {
-        if (filters.hasOwnProperty(key)) {
-          var encodedKey = encodeURIComponent(key);
-          var encodedValue = encodeURIComponent(filters[key]);
-          values.push(encodedKey + '=' + encodedValue);
-        }
-      }
-      return '?' + values.join('&');
-    } else {
-      return filters + '/';
-    }
-  };
-
-  var Query = function(_, call) {
-    this.url = call.url || _.setup.api;
-
-    if (call.path) {
-      this.url += call.path + '/';
-    } else if (call.related) {
-      this.url += call.related + '/';
-    } else {
-      this.url += call.model + 's' + '/';
-    }
-
-    if (call.filters) {
-      this.url += filtersUrl(call.filters);
-    }
-
-    if (call.model) {
-      this.related = function(model, filters) {
-        return new Query(_, {
-          url: this.url,
-          relation: call.model,
-          related: model,
-          filters: filters,
-        });
-      };
-    }
-
-    this.endpoint = function(path, filters) {
-      return new Query(_, {
-        url: this.url,
-        path: path,
-        filters: filters
-      });
-    };
-
-    this.get = function() {
-      var model = null;
-      if (call.related) {
-        model = call.relation + ':' + call.related;
-      } else if (call.model) {
-        model = call.model;
-      }
-
-      return _.ajax.get(this.url).then(function(data) {
-        if (model) {
-          if (data.results) {
-            data.results.map(function(item) {
-              return _.models.new(model, item);
-            });
-            return data;
-          } else {
-            return _.models.new(model, data);
-          }
-        } else {
-          return data;
-        }
-      });
-    };
-
-    this.post = function(data) {
-      return _.ajax.post(this.url, data);
-    };
-
-    this.patch = function(data) {
-      return _.ajax.patch(this.url, data);
-    };
-
-    this.put = function(data) {
-      return _.ajax.put(this.url, data);
-    };
-
-    this.delete = function() {
-      return _.ajax.delete(this.url);
-    };
-
-    // shortcut for get()
-    this.then = function(resolve, reject) {
-      return this.get().then(resolve, reject);
-    };
-  };
-
-  var Api = function(_) {
-    this.model = function(model, filters) {
-      return new Query(_, {
-        model: model,
-        filters: filters,
-      });
-    };
-
-    this.endpoint = function(path, filters) {
-      return new Query(_, {
-        path: path,
-        filters: filters
-      });
-    };
-
-    this.alert = function(rejection) {
-      // Shorthand for API errors
-      var message = gettext("Unknown error has occured.");
-
-      if (rejection.status === 0) {
-        message = gettext("Lost connection with application.");
-      }
-
-      if (rejection.status === 403) {
-        message = rejection.detail;
-        if (message === "Permission denied") {
-          message = gettext(
-            "You don't have permission to perform this action.");
-        }
-      }
-
-      if (rejection.status === 404) {
-        message = gettext("Action link is invalid.");
-      }
-
-      _.alert.error(message);
-    };
-  };
-
-  Misago.addService('api', function(_) {
-    return new Api(_);
-  });
-}(Misago.prototype));

+ 14 - 12
misago/frontend/misago/services/auth.js

@@ -50,24 +50,26 @@
 
 
     syncSession();
     syncSession();
 
 
-    // Shorthand for signing user out
+    // Shorthand for signing user components out
+    var switchMount = function(mountId) {
+      var mount = document.getElementById(mountId);
+      var component = null;
+
+      if (mount) {
+        component = mount.dataset.componentName;
+        m.mount(
+          mount, _.component(component.replace('user-nav', 'guest-nav')));
+      }
+    };
+
     this.signOut = function() {
     this.signOut = function() {
       _.user.isAuthenticated = false;
       _.user.isAuthenticated = false;
       _.user.isAnonymous = true;
       _.user.isAnonymous = true;
 
 
       syncSession();
       syncSession();
 
 
-      var desktopMount = document.getElementById('user-menu-mount');
-      var compactMount = document.getElementById('user-menu-compact-mount');
-
-      var desktopComponent = desktopMount.dataset.componentName;
-      var compactComponent = compactMount.dataset.componentName;
-
-      var newDesktopName = desktopComponent.replace('user-nav', 'guest-nav');
-      var newCompactName = compactComponent.replace('user-nav', 'guest-nav');
-
-      m.mount(desktopMount, _.component(newDesktopName));
-      m.mount(compactMount, _.component(newCompactName));
+      switchMount('user-menu-mount');
+      switchMount('user-menu-compact-mount');
     };
     };
   };
   };
 
 

+ 1 - 1
misago/frontend/misago/services/captcha.js

@@ -33,7 +33,7 @@
           self.question = question;
           self.question = question;
           deferred.resolve();
           deferred.resolve();
         }, function() {
         }, function() {
-          _.api.alert(gettext('Failed to load CAPTCHA.'));
+          _.ajax.alert(gettext('Failed to load CAPTCHA.'));
           deferred.reject();
           deferred.reject();
         }).then(function() {
         }).then(function() {
           self.loading = true;
           self.loading = true;

+ 8 - 17
misago/frontend/misago/services/modal-controller.js

@@ -1,23 +1,19 @@
 (function (Misago) {
 (function (Misago) {
   'use strict';
   'use strict';
 
 
-  var Modal = function() {
+  var Modal = function(_) {
     var self = this;
     var self = this;
 
 
     var element = document.getElementById('modal-mount');
     var element = document.getElementById('modal-mount');
 
 
-    this.destroy = function() {
-      $(element).off();
-      $('body').removeClass('modal-open');
-      $('.modal-backdrop').remove();
-    };
-
     // Open/close modal
     // Open/close modal
     var modal = $(element).modal({show: false});
     var modal = $(element).modal({show: false});
     this.open = false;
     this.open = false;
 
 
     modal.on('hidden.bs.modal', function () {
     modal.on('hidden.bs.modal', function () {
-      if (self.open) {
+      // m() object is stateful, so in tests we don't
+      // unmount the components, as this breaks it
+      if (self.open && !_.setup.test) {
         m.mount(element, null);
         m.mount(element, null);
         this.open = false;
         this.open = false;
       }
       }
@@ -25,8 +21,8 @@
 
 
     this.show = function(component) {
     this.show = function(component) {
       this.open = true;
       this.open = true;
-      m.mount(element, component);
-      modal.modal('show');
+      m.mount(document.getElementById('modal-mount'), component);
+      $(element).modal('show');
     };
     };
 
 
     this.hide = function() {
     this.hide = function() {
@@ -34,13 +30,8 @@
     };
     };
   };
   };
 
 
-  Misago.addService('_modal', {
-    factory: function() {
-      return new Modal();
-    },
-    destroy: function(_) {
-      _._modal.destroy();
-    }
+  Misago.addService('_modal', function(_) {
+    return new Modal(_);
   },
   },
   {
   {
     before: 'mount-components'
     before: 'mount-components'

+ 1 - 2
misago/frontend/misago/services/mount-page.js

@@ -1,10 +1,9 @@
 (function (Misago) {
 (function (Misago) {
   'use strict';
   'use strict';
 
 
-  var mount = document.getElementById('page-mount');
-
   Misago.addService('mount-page', function(_) {
   Misago.addService('mount-page', function(_) {
     _.mountPage = function(component) {
     _.mountPage = function(component) {
+      var mount = document.getElementById('page-mount');
       m.mount(mount, component);
       m.mount(mount, component);
     };
     };
   });
   });

+ 1 - 1
misago/frontend/misago/services/mounts.js

@@ -34,7 +34,7 @@
         }
         }
       });
       });
     },
     },
-    destroy: function() {
+    destroy: function(_) {
       _._mounts.forEach(function(elementId) {
       _._mounts.forEach(function(elementId) {
         var mount = document.getElementById(elementId);
         var mount = document.getElementById(elementId);
         if (mount && mount.hasChildNodes()) {
         if (mount && mount.hasChildNodes()) {

+ 2 - 2
misago/frontend/misago/sign-in/form.js

@@ -24,7 +24,7 @@
     };
     };
 
 
     this.submit = function() {
     this.submit = function() {
-      _.api.endpoint('auth').post({
+      _.ajax.post(_.context.AUTH_API, {
         username: self.username(),
         username: self.username(),
         password: self.password()
         password: self.password()
       }).then(function() {
       }).then(function() {
@@ -66,7 +66,7 @@
           _.alert.error(rejection.detail);
           _.alert.error(rejection.detail);
         }
         }
       } else {
       } else {
-        _.api.alert(rejection);
+        _.ajax.error(rejection);
       }
       }
     };
     };
   };
   };

+ 0 - 40
misago/frontend/misago/utils/urlconf.js

@@ -1,40 +0,0 @@
-(function (Misago) {
-  'use strict';
-
-  Misago.UrlConf = function() {
-    var self = this;
-    this._patterns = [];
-
-    this.patterns = function() {
-      return this._patterns;
-    };
-
-    var prefixPattern = function(prefix, pattern) {
-      return (prefix + pattern).replace('//', '/');
-    };
-
-    var include = function(prefix, patterns) {
-      for (var i = 0; i < patterns.length; i ++) {
-        self.url(prefixPattern(prefix, patterns[i].pattern),
-                 patterns[i].component,
-                 patterns[i].name);
-      }
-    };
-
-    this.url = function(pattern, component, name) {
-      if (pattern === '') {
-        pattern = '/';
-      }
-
-      if (component instanceof Misago.UrlConf) {
-        include(pattern, component.patterns());
-      } else {
-        this._patterns.push({
-          pattern: pattern,
-          component: component.replace(/_/g, '-'),
-          name: name || component
-        });
-      }
-    };
-  };
-}(Misago.prototype));

+ 0 - 9
misago/frontend/misago/utils/views-shortcuts.js

@@ -1,9 +0,0 @@
-(function (Misago) {
-  'use strict';
-
-  Misago.loadingPage = function(_) {
-    return m('.page.page-loading',
-      _.component('loader')
-    );
-  };
-}(Misago.prototype));

+ 3 - 1
misago/frontend/package.json

@@ -19,6 +19,7 @@
       "initTestMisago",
       "initTestMisago",
       "getMisagoService",
       "getMisagoService",
       "mockUser",
       "mockUser",
+      "mockMount",
       "resetTestPromise",
       "resetTestPromise",
       "then",
       "then",
       "onElement",
       "onElement",
@@ -57,7 +58,8 @@
     "gulp-open": "^1.0.0",
     "gulp-open": "^1.0.0",
     "gulp-sourcemaps": "^1.5.2",
     "gulp-sourcemaps": "^1.5.2",
     "gulp-uglify": "^1.2.0",
     "gulp-uglify": "^1.2.0",
-    "less": "^2.5.1"
+    "less": "^2.5.1",
+    "qunitjs": "^1.20.0"
   },
   },
   "devDependencies": {
   "devDependencies": {
     "del": "^1.2.1",
     "del": "^1.2.1",

+ 10 - 4
misago/frontend/test/index.html

@@ -3,15 +3,20 @@
 <head>
 <head>
   <meta charset="utf-8">
   <meta charset="utf-8">
   <title>Misago QUnit</title>
   <title>Misago QUnit</title>
-  <link rel="stylesheet" href="//code.jquery.com/qunit/qunit-1.18.0.css">
+  <link rel="stylesheet" href="/dist/test-styles.css">
   <link rel="stylesheet" href="/dist/misago/css/misago.css">
   <link rel="stylesheet" href="/dist/misago/css/misago.css">
   <base href="/">
   <base href="/">
 </head>
 </head>
 <body>
 <body>
   <div id="qunit"></div>
   <div id="qunit"></div>
-  <div id="qunit-fixture"></div>
-  <div id="misago-fixture"></div>
-  <script src="//code.jquery.com/qunit/qunit-1.18.0.js"></script>
+
+  <div id="qunit-fixture">
+    <div id="alert-mount" data-component-name="alert"></div>
+    <div id="component-mount"></div>
+    <div id="page-mount"></div>
+    <div class="modal fade" id="modal-mount" tabindex="-1" role="dialog" aria-labelledby="misago-modal-label"></div>
+  </div>
+
   <script src="/dist/misago/js/vendor.js"></script>
   <script src="/dist/misago/js/vendor.js"></script>
   <script src="/dist/libs.js"></script>
   <script src="/dist/libs.js"></script>
   <script src="/dist/utils.js"></script>
   <script src="/dist/utils.js"></script>
@@ -25,6 +30,7 @@
     <input name="password" type="password">
     <input name="password" type="password">
     <input type="submit" id="signin-button" value="Sign in">
     <input type="submit" id="signin-button" value="Sign in">
   </form>
   </form>
+
   <form id="hidden-logout-form" method="post" style="display: none;">
   <form id="hidden-logout-form" method="post" style="display: none;">
     <input name="csrf_token" type="hidden">
     <input name="csrf_token" type="hidden">
     <input type="submit" id="signin-button" value="Log out">
     <input type="submit" id="signin-button" value="Log out">

+ 0 - 87
misago/frontend/test/tests/acceptance/account-activation.js

@@ -1,87 +0,0 @@
-(function () {
-  'use strict';
-
-  var app = null;
-
-  QUnit.acceptance("Activate Account", {
-    beforeEach: function() {
-      app = initTestMisago();
-    },
-    afterEach: function() {
-      app.destroy();
-    }
-  });
-
-  QUnit.test('with backend banned', function(assert) {
-    $.mockjax({
-      url: '/test-api/auth/activate-account/123/som3token/',
-      status: 403,
-      responseText: {
-        'detail': 'You are banned!',
-        'ban': {
-          'expires_on': null,
-          'message': {
-            'plain': 'This is test ban.',
-            'html': '<p>This is test ban.</p>'
-          }
-        }
-      }
-    });
-
-    app.router.route('/activation/123/som3token/');
-
-    var done = assert.async();
-
-    onElement('.page-error.page-error-banned .message-body', function() {
-      assert.ok(true, "Permission denied error page was displayed.");
-      assert.equal(
-        getElementText('.page .message-body .lead'),
-        "This is test ban.",
-        "Banned page displayed ban message.");
-      done();
-    });
-  });
-
-  QUnit.test('with backend rejection', function(assert) {
-    var message = 'Some activation error.';
-    $.mockjax({
-      url: '/test-api/auth/activate-account/123/som3token/',
-      status: 400,
-      responseText: {
-        'detail': message
-      }
-    });
-
-    app.router.route('/activation/123/som3token/');
-
-    var done = assert.async();
-
-    onElement('.message-body p', function() {
-      assert.equal(getElementText('.message-body p:last-child'), message,
-        "activation failed with backend message.");
-      done();
-    });
-  });
-
-  QUnit.test('with success', function(assert) {
-    $.mockjax({
-      url: '/test-api/auth/activate-account/123/som3token/',
-      status: 200,
-      responseText: {
-        'username': 'Bob'
-      }
-    });
-
-    app.router.route('/activation/123/som3token/');
-
-    var done = assert.async();
-
-    onElement('.message-body p.lead', function() {
-      assert.equal(
-        getElementText('.message-body p.lead'),
-        "Bob, your account has been successfully activated!",
-        "activation succeded.");
-      done();
-    });
-  });
-}());

+ 0 - 43
misago/frontend/test/tests/acceptance/auth-exceptions.js

@@ -1,43 +0,0 @@
-(function () {
-  'use strict';
-
-  var app = null;
-
-  QUnit.acceptance("Auth Exceptions", {
-    afterEach: function() {
-      app.destroy();
-    }
-  });
-
-  QUnit.test("denyAuthenticated denied authenticated", function(assert) {
-    app = initTestMisago({
-      user: mockUser()
-    });
-
-    var done = assert.async();
-
-    app.router.route('/activation/');
-
-    onElement('.page-error.page-error-403 .message-body', function() {
-      assert.ok(true, "Permission denied error page was displayed.");
-      assert.equal(
-        getElementText('.page .message-body p:last-child'),
-        "You have to be signed out to activate account.",
-        "Route access was denied with valid error.");
-      done();
-    });
-  });
-
-  QUnit.test("denyAuthenticated passed anonymous", function(assert) {
-    app = initTestMisago();
-
-    var done = assert.async();
-
-    app.router.route('/activation/');
-
-    onElement('.page-request-activation', function() {
-      assert.ok(true, "Access to route was granted for anonymous user.");
-      done();
-    });
-  });
-}());

+ 0 - 218
misago/frontend/test/tests/acceptance/change-password.js

@@ -1,218 +0,0 @@
-(function () {
-  'use strict';
-
-  var app = null;
-
-  QUnit.acceptance("Change Password", {
-    beforeEach: function() {
-      app = initTestMisago();
-    },
-    afterEach: function() {
-      app.destroy();
-    }
-  });
-
-  QUnit.test('with backend banned', function(assert) {
-    $.mockjax({
-      url: '/test-api/auth/change-password/123/som3token/',
-      status: 403,
-      responseText: {
-        'detail': 'You are banned!',
-        'ban': {
-          'expires_on': null,
-          'message': {
-            'plain': 'This is test ban.',
-            'html': '<p>This is test ban.</p>'
-          }
-        }
-      }
-    });
-
-    app.router.route('/forgotten-password/123/som3token/');
-
-    var done = assert.async();
-
-    onElement('.page-error.page-error-banned .message-body', function() {
-      assert.ok(true, "Permission denied error page was displayed.");
-      assert.equal(
-        getElementText('.page .message-body .lead'),
-        "This is test ban.",
-        "Banned page displayed ban message.");
-      done();
-    });
-  });
-
-  QUnit.test('with backend rejection', function(assert) {
-    var message = 'Some activation error.';
-    $.mockjax({
-      url: '/test-api/auth/change-password/123/som3token/',
-      status: 400,
-      responseText: {
-        'detail': message
-      }
-    });
-
-    app.router.route('/forgotten-password/123/som3token/');
-
-    var done = assert.async();
-
-    onElement('.message-body p', function() {
-      assert.equal(getElementText('.message-body p:last-child'), message,
-        "activation failed with backend message.");
-      done();
-    });
-  });
-
-  QUnit.test('form', function(assert) {
-    $.mockjax({
-      url: '/test-api/auth/change-password/123/som3token/',
-      status: 200,
-      responseText: {
-        'username': 'Bob',
-        'email': 'bob@boberson.com'
-      }
-    });
-
-    app.router.route('/forgotten-password/123/som3token/');
-
-    var done = assert.async();
-
-    onElement('.well-form', function() {
-      assert.ok(true, "change password for was displayed.");
-      done();
-    });
-  });
-
-  QUnit.test('empty form submission', function(assert) {
-    $.mockjax({
-      url: '/test-api/auth/change-password/123/som3token/',
-      status: 200,
-      responseText: {
-        'username': 'Bob',
-        'email': 'bob@boberson.com'
-      }
-    });
-
-    app.router.route('/forgotten-password/123/som3token/');
-
-    var done = assert.async();
-
-    waitForElement('.well-form .btn-primary');
-    click('.well-form .btn-primary');
-
-    onElement('.alerts .alert-danger', function() {
-      assert.equal(getAlertMessage(), "Enter new password.",
-        "form raised alert about empty input.");
-      done();
-    });
-  });
-
-  QUnit.test('invalid form submission', function(assert) {
-    $.mockjax({
-      url: '/test-api/auth/change-password/123/som3token/',
-      status: 200,
-      responseText: {
-        'username': 'Bob',
-        'email': 'bob@boberson.com'
-      }
-    });
-
-    app.router.route('/forgotten-password/123/som3token/');
-
-    var done = assert.async();
-
-    waitForElement('.well-form input');
-
-    fillIn('.well-form input', 'no');
-    click('.well-form .btn-primary');
-
-    onElement('.alerts .alert-danger', function() {
-      assert.equal(
-        getAlertMessage(),
-        "Valid password must be at least 5 characters long.",
-        "form raised alert about invalid new password.");
-      done();
-    });
-  });
-
-  QUnit.test('backend-rejected form submission', function(assert) {
-    $.mockjax({
-      url: '/test-api/auth/change-password/123/som3token/',
-      type: 'get',
-      status: 200,
-      responseText: {
-        'username': 'Bob',
-        'email': 'bob@boberson.com'
-      }
-    });
-
-    var message = "Backend error is raised!";
-    $.mockjax({
-      url: '/test-api/auth/change-password/123/som3token/',
-      type: 'post',
-      status: 400,
-      responseText: {
-        'detail': message
-      }
-    });
-
-    app.router.route('/forgotten-password/123/som3token/');
-
-    var done = assert.async();
-
-    waitForElement('.well-form input');
-
-    fillIn('.well-form input', 'pass123');
-    click('.well-form .btn-primary');
-
-    onElement('.alerts .alert-danger', function() {
-      assert.equal(getAlertMessage(), message,
-        "form raised alert with error returned by backend.");
-      done();
-    });
-  });
-
-  QUnit.test('backend-accepted form submission', function(assert) {
-    $.mockjax({
-      url: '/test-api/auth/change-password/123/som3token/',
-      type: 'get',
-      status: 200,
-      responseText: {
-        'username': 'Bob',
-        'email': 'bob@boberson.com'
-      }
-    });
-
-    $.mockjax({
-      url: '/test-api/auth/change-password/123/som3token/',
-      type: 'post',
-      status: 200,
-      responseText: {
-        'username': 'Bob'
-      }
-    });
-
-    app.router.route('/forgotten-password/123/som3token/');
-
-    var doneMessage = assert.async();
-    var doneButton = assert.async();
-
-    waitForElement('.well-form input');
-
-    fillIn('.well-form input', 'pass123');
-    click('.well-form .btn-primary');
-
-    onElement('.message-body p.lead', function() {
-      assert.equal(
-        getElementText('.message-body p.lead'),
-        "Bob, your password has been changed successfully.",
-        "form displayed success message.");
-      doneMessage();
-    });
-
-    onElement('.message-body .btn-default', function() {
-      assert.ok(true, "form displayed sign-in button.");
-      doneButton();
-    });
-  });
-}());

+ 0 - 141
misago/frontend/test/tests/acceptance/error-pages.js

@@ -1,141 +0,0 @@
-(function () {
-  'use strict';
-
-  var app = null;
-
-  QUnit.acceptance("Error Pages", {
-    beforeEach: function() {
-      app = initTestMisago();
-    },
-    afterEach: function() {
-      app.destroy();
-    }
-  });
-
-  QUnit.test("Error banned", function(assert) {
-    $.mockjax({
-      url: '/test-api/legal-pages/terms-of-service/',
-      status: 403,
-      responseText: {
-        'detail': 'You are banned!',
-        'ban': {
-          'expires_on': null,
-          'message': {
-            'plain': 'This is test ban.',
-            'html': '<p>This is test ban.</p>'
-          }
-        }
-      }
-    });
-
-    var done = assert.async();
-
-    click('.footer-nav li:first-child a');
-    onElement('.page-error.page-error-banned .message-body', function() {
-      assert.ok(true, "Permission denied error page was displayed.");
-      assert.equal(
-        getElementText('.page .message-body .lead'),
-        "This is test ban.",
-        "Banned page displayed ban message.");
-      done();
-    });
-  });
-
-  QUnit.test("Error 403 with message", function(assert) {
-    $.mockjax({
-      url: '/test-api/legal-pages/terms-of-service/',
-      status: 403,
-      responseText: {
-        'detail': "I can't let you do this Dave."
-      }
-    });
-
-    var done = assert.async();
-
-    click('.footer-nav li:first-child a');
-    onElement('.page-error.page-error-403 .message-body', function() {
-      assert.ok(true, "Permission denied error page was displayed.");
-      assert.equal(
-        getElementText('.page .message-body .help'),
-        "I can't let you do this Dave.",
-        "Permission denied error page used backend message.");
-      done();
-    });
-  });
-
-  QUnit.test("Error 403 without message", function(assert) {
-    $.mockjax({
-      url: '/test-api/legal-pages/terms-of-service/',
-      status: 403,
-      responseText: {
-        'detail': 'Permission denied'
-      }
-    });
-
-    var done = assert.async();
-
-    click('.footer-nav li:first-child a');
-    onElement('.page-error.page-error-403 .message-body', function() {
-      assert.ok(true, "Permission denied error page was displayed.");
-      assert.equal(
-        getElementText('.page .message-body .help'),
-        "You don't have permission to access this page.",
-        "Permission denied error page used default message.");
-      done();
-    });
-  });
-
-  QUnit.test("Error 404", function(assert) {
-    $.mockjax({
-      url: '/test-api/legal-pages/terms-of-service/',
-      status: 404
-    });
-
-    var done = assert.async();
-
-    click('.footer-nav li:first-child a');
-    onElement('.page-error.page-error-404', function() {
-      assert.ok(true, "Not found error page was displayed.");
-      done();
-    });
-  });
-
-  QUnit.test("Error 404 on invalid url", function(assert) {
-    var done = assert.async();
-    m.route('/some-invalid-url-is-her!');
-    onElement('.page-error.page-error-404', function() {
-      assert.ok(true, "Not found error page was displayed.");
-      done();
-    });
-  });
-
-  QUnit.test("Error 500", function(assert) {
-    $.mockjax({
-      url: '/test-api/legal-pages/terms-of-service/',
-      status: 500
-    });
-
-    var done = assert.async();
-
-    click('.footer-nav li:first-child a');
-    onElement('.page-error.page-error-500', function() {
-      assert.ok(true, "Backend error page was displayed.");
-      done();
-    });
-  });
-
-  QUnit.test("Error 0", function(assert) {
-    $.mockjax({
-      url: '/test-api/legal-pages/terms-of-service/',
-      isTimeout: true
-    });
-
-    var done = assert.async();
-
-    click('.footer-nav li:first-child a');
-    onElement('.page-error.page-error-0', function() {
-      assert.ok(true, "Timeout error page was displayed.");
-      done();
-    });
-  });
-}());

+ 62 - 0
misago/frontend/test/tests/acceptance/guest-nav.js

@@ -0,0 +1,62 @@
+(function () {
+  'use strict';
+
+  var app = null;
+
+  var mockNavbar = {
+    view: function() {
+      return m('nav.navbar.navbar-misago.navbar-default.navbar-static-top',
+        m('.container.navbar-full',
+          mockMount('user-menu-mount', 'navbar:desktop:guest-nav')
+        )
+      );
+    }
+  };
+
+  QUnit.acceptance("Guest Nav", {
+    beforeEach: function() {
+      m.mount(document.getElementById('component-mount'), mockNavbar);
+      app = initTestMisago();
+    },
+    afterEach: function() {
+      app.destroy();
+    }
+  });
+
+  QUnit.test('menu opens sign-in modal', function(assert) {
+    var done = assert.async();
+
+    click('.nav-guest .btn-default');
+
+    onElement('.modal-signin', function() {
+      assert.ok(true, "sign-in modal was shown after pressing button.");
+      done();
+    });
+  });
+
+  QUnit.test('menu opens registration modal', function(assert) {
+    var done = assert.async();
+
+    click('.nav-guest .btn-primary');
+
+    onElement('.modal-register', function() {
+      assert.ok(true, "registration modal was shown after pressing button.");
+      done();
+    });
+  });
+
+  QUnit.test('menu alerts about registration being off', function(assert) {
+    var done = assert.async();
+
+    app.settings.account_activation = 'closed';
+
+    click('.nav-guest .btn-primary');
+
+    onElement('.alerts .alert-info', function() {
+      assert.equal(getAlertMessage(),
+        "New registrations are currently disabled.",
+        "alert about registrations being off was displayed.");
+      done();
+    });
+  });
+}());

+ 0 - 156
misago/frontend/test/tests/acceptance/legal-pages.js

@@ -1,156 +0,0 @@
-(function () {
-  'use strict';
-
-  var app = null;
-
-  QUnit.acceptance("Legal Pages", {
-    beforeEach: function() {
-      app = initTestMisago();
-    },
-    afterEach: function() {
-      app.destroy();
-    }
-  });
-
-  QUnit.test('privacy policy link', function(assert) {
-    var doneHidden = assert.async();
-    var doneVisible = assert.async();
-
-    app.settings.terms_of_service = false;
-    app.settings.privacy_policy = false;
-
-    m.redraw();
-
-    onElement('.forum-footer', function() {
-      assert.equal(
-        getElementText('.footer-nav'), "",
-        "Privacy policy link is hidden in footer.");
-      doneHidden();
-
-      app.settings.privacy_policy = true;
-
-      m.redraw();
-
-      onElement('.footer-nav a', function() {
-        assert.equal(
-          getElementText('.footer-nav a'), "Test Privacy Policy",
-          "Privacy policy link is displayed in footer.");
-        doneVisible();
-      });
-    });
-  });
-
-  QUnit.test('terms of service link', function(assert) {
-    var doneHidden = assert.async();
-    var doneVisible = assert.async();
-
-    app.settings.privacy_policy = false;
-    app.settings.terms_of_service = false;
-
-    m.redraw();
-
-    onElement('.forum-footer', function() {
-      assert.equal(
-        getElementText('.footer-nav'), "",
-        "Terms link is hidden in footer.");
-      doneHidden();
-
-      app.settings.terms_of_service = true;
-
-      m.redraw();
-
-      onElement('.footer-nav a', function() {
-        assert.equal(
-          getElementText('.footer-nav a'), "Test Terms",
-          "Terms link is displayed in footer.");
-        doneVisible();
-      });
-    });
-  });
-
-  QUnit.test('legals pages disabled', function(assert) {
-    app.settings.privacy_policy = null;
-    app.settings.terms_of_service = null;
-
-    $.mockjax({
-      url: '/test-api/legal-pages/privacy-policy/',
-      status: 404,
-      responseText: {'detail': 'Not found'}
-    });
-    $.mockjax({
-      url: '/test-api/legal-pages/terms-of-service/',
-      status: 404,
-      responseText: {'detail': 'Not found'}
-    });
-
-    var donePrivacyPolicy = assert.async();
-    var doneTermsOfService = assert.async();
-
-    assert.ok(!getElement('.footer-nav a').length,
-      "footer nav has no legal pages links");
-
-    m.route('/privacy-policy/');
-    onElement('.page-error.page-error-404', function() {
-      assert.ok(true, "Unset privacy policy returned 404 page.");
-      donePrivacyPolicy();
-    });
-
-    m.route('/terms-of-service/');
-    onElement('.page-error.page-error-404', function() {
-      assert.ok(true, "Unset terms of service returned 404 page.");
-      doneTermsOfService();
-    });
-  });
-
-  QUnit.test('privacy policy page', function(assert) {
-    $.mockjax({
-      url: '/test-api/legal-pages/privacy-policy/',
-      status: 200,
-      responseText: {
-        'id': 'privacy-policy',
-        'link': '',
-        'title': 'Backend Policy',
-        'body': '<p>Lorem ipsum dolor met sit amet elit.</p>'
-      }
-    });
-
-    var done = assert.async();
-
-    m.route('/privacy-policy/');
-    onElement('.page-legal', function() {
-      assert.equal(
-        getElementText('.page-header h1'), 'Backend Policy',
-        "Privacy Policy page has been rendered.");
-      assert.equal(
-        getElementText('.page .container p'), 'Lorem ipsum dolor met sit amet elit.',
-        "Privacy Policy body has been rendered.");
-      done();
-    });
-  });
-
-  QUnit.test('terms of service page', function(assert) {
-    $.mockjax({
-      url: '/test-api/legal-pages/terms-of-service/',
-      status: 200,
-      responseText: {
-        'id': 'terms-of-service',
-        'link': '',
-        'title': 'Backend Terms',
-        'body': '<p>Lorem ipsum dolor met sit amet elit.</p>'
-      }
-    });
-
-    var done = assert.async();
-
-    m.route('/terms-of-service/');
-    onElement('.page-legal', function() {
-      assert.equal(
-        getElementText('.page-header h1'), 'Backend Terms',
-        "Terms of Service page has been rendered.");
-      assert.equal(
-        getElementText('.page .container p'), 'Lorem ipsum dolor met sit amet elit.',
-        "Terms of Service body has been rendered.");
-      done();
-    });
-  });
-}());

+ 54 - 40
misago/frontend/test/tests/acceptance/register-modal.js → misago/frontend/test/tests/acceptance/register.js

@@ -6,35 +6,20 @@
   QUnit.acceptance("Register", {
   QUnit.acceptance("Register", {
     beforeEach: function() {
     beforeEach: function() {
       app = initTestMisago();
       app = initTestMisago();
+
+      app.settings.account_activation = 'none';
+      app.context.USERS_API = '/test-api/users/';
     },
     },
     afterEach: function() {
     afterEach: function() {
       app.destroy();
       app.destroy();
     }
     }
   });
   });
 
 
-  QUnit.test('registration disabled', function(assert) {
-    app.settings.account_activation = 'closed';
-
-    var done = assert.async();
-
-    waitForElement('.navbar .nav-guest .btn-primary');
-    click('.navbar .nav-guest .btn-primary');
-
-    onElement('.alerts .alert-info', function() {
-      assert.equal(
-        getAlertMessage(), "New registrations are currently disabled.",
-        "registration closed message was displayed.");
-      done();
-    });
-  });
-
   QUnit.test('registration with empty credentials', function(assert) {
   QUnit.test('registration with empty credentials', function(assert) {
-    app.settings.account_activation = 'none';
-
     var done = assert.async();
     var done = assert.async();
 
 
-    waitForElement('.navbar .nav-guest .btn-primary');
-    click('.navbar .nav-guest .btn-primary');
+    app.modal('register');
+
     click('.modal-register .btn-primary');
     click('.modal-register .btn-primary');
 
 
     onElement('.alerts .alert-danger', function() {
     onElement('.alerts .alert-danger', function() {
@@ -45,12 +30,10 @@
   });
   });
 
 
   QUnit.test('registration with invalid credentials', function(assert) {
   QUnit.test('registration with invalid credentials', function(assert) {
-    app.settings.account_activation = 'none';
-
     var done = assert.async();
     var done = assert.async();
 
 
-    waitForElement('.navbar .nav-guest .btn-primary');
-    click('.navbar .nav-guest .btn-primary');
+    app.modal('register');
+
     waitForElement('.modal-register');
     waitForElement('.modal-register');
     fillIn('#id_username', '###');
     fillIn('#id_username', '###');
     fillIn('#id_email', 'notemail');
     fillIn('#id_email', 'notemail');
@@ -79,6 +62,45 @@
     });
     });
   });
   });
 
 
+  QUnit.test('from banned ip', function(assert) {
+    $.mockjax({
+      url: '/test-api/users/',
+      status: 403,
+      responseText: {
+        'ban': {
+          'expires_on': null,
+          'message': {
+            'plain': 'Your ip is banned for spamming.',
+            'html': '<p>Your ip is banned for spamming.</p>',
+          }
+        }
+      }
+    });
+
+    var done = assert.async();
+
+    app.modal('register');
+
+    waitForElement('.modal-register');
+    fillIn('#id_username', 'bob');
+    fillIn('#id_email', 'bob@boberson.com');
+    fillIn('#id_password', 'Som3S3cureP4ss!!!');
+    click('.modal-register .btn-primary');
+
+    onElement('.page-error-banned .lead', function() {
+      assert.equal(
+        getElementText('.page .message-body .lead'),
+        "Your ip is banned for spamming.",
+        "login form displayed error banned page with ban message.");
+
+      waitForElementRemoval('.modal-signin');
+      then(function() {
+        assert.ok(true, "signin modal was closed.");
+        done();
+      });
+    });
+  });
+
   QUnit.test('registration with backend-rejected credentials', function(assert) {
   QUnit.test('registration with backend-rejected credentials', function(assert) {
     $.mockjax({
     $.mockjax({
       url: '/test-api/users/',
       url: '/test-api/users/',
@@ -90,12 +112,10 @@
       }
       }
     });
     });
 
 
-    app.settings.account_activation = 'none';
-
     var done = assert.async();
     var done = assert.async();
 
 
-    waitForElement('.navbar .nav-guest .btn-primary');
-    click('.navbar .nav-guest .btn-primary');
+    app.modal('register');
+
     waitForElement('.modal-register');
     waitForElement('.modal-register');
     fillIn('#id_username', 'bob');
     fillIn('#id_username', 'bob');
     fillIn('#id_email', 'bob@boberson.com');
     fillIn('#id_email', 'bob@boberson.com');
@@ -125,12 +145,10 @@
       }
       }
     });
     });
 
 
-    app.settings.account_activation = 'none';
-
     var done = assert.async();
     var done = assert.async();
 
 
-    waitForElement('.navbar .nav-guest .btn-primary');
-    click('.navbar .nav-guest .btn-primary');
+    app.modal('register');
+
     waitForElement('.modal-register');
     waitForElement('.modal-register');
     fillIn('#id_username', 'bob');
     fillIn('#id_username', 'bob');
     fillIn('#id_email', 'bob@boberson.com');
     fillIn('#id_email', 'bob@boberson.com');
@@ -159,12 +177,10 @@
       }
       }
     });
     });
 
 
-    app.settings.account_activation = 'none';
-
     var done = assert.async();
     var done = assert.async();
 
 
-    waitForElement('.navbar .nav-guest .btn-primary');
-    click('.navbar .nav-guest .btn-primary');
+    app.modal('register');
+
     waitForElement('.modal-register');
     waitForElement('.modal-register');
     fillIn('#id_username', 'bob');
     fillIn('#id_username', 'bob');
     fillIn('#id_email', 'bob@boberson.com');
     fillIn('#id_email', 'bob@boberson.com');
@@ -193,12 +209,10 @@
       }
       }
     });
     });
 
 
-    app.settings.account_activation = 'none';
-
     var done = assert.async();
     var done = assert.async();
 
 
-    waitForElement('.navbar .nav-guest .btn-primary');
-    click('.navbar .nav-guest .btn-primary');
+    app.modal('register');
+
     waitForElement('.modal-register');
     waitForElement('.modal-register');
     fillIn('#id_username', 'bob');
     fillIn('#id_username', 'bob');
     fillIn('#id_email', 'bob@boberson.com');
     fillIn('#id_email', 'bob@boberson.com');

+ 50 - 36
misago/frontend/test/tests/acceptance/request-activation.js

@@ -3,40 +3,40 @@
 
 
   var app = null;
   var app = null;
 
 
-  QUnit.acceptance("Request Activation Link", {
+  QUnit.acceptance("Request Activation E-mail", {
     beforeEach: function() {
     beforeEach: function() {
       app = initTestMisago();
       app = initTestMisago();
+      app.context.SEND_ACTIVATION_API = '/test-api/auth/send-activation/';
+
+      var mount = document.getElementById('component-mount');
+      m.mount(mount, app.component('request-activation-link-form'));
     },
     },
     afterEach: function() {
     afterEach: function() {
       app.destroy();
       app.destroy();
     }
     }
   });
   });
 
 
-  QUnit.test('with empty input', function(assert) {
-    app.router.route('/activation/');
-
+  QUnit.test('submit empty form', function(assert) {
     var done = assert.async();
     var done = assert.async();
 
 
-    click('.well-form .btn-primary');
+    click('#component-mount .btn-primary');
 
 
     onElement('.alerts .alert-danger', function() {
     onElement('.alerts .alert-danger', function() {
       assert.equal(getAlertMessage(), "Enter a valid email address.",
       assert.equal(getAlertMessage(), "Enter a valid email address.",
-        "request form raised alert about empty input.");
+        "form raised alert about empty input.");
       done();
       done();
     });
     });
   });
   });
 
 
-  QUnit.test('with invalid email', function(assert) {
-    app.router.route('/activation/');
-
+  QUnit.test('submit invalid form', function(assert) {
     var done = assert.async();
     var done = assert.async();
 
 
-    fillIn('.well-form input', 'not-email');
-    click('.well-form .btn-primary');
+    fillIn('#component-mount input[type="text"]', 'not-an-email!');
+    click('#component-mount .btn-primary');
 
 
     onElement('.alerts .alert-danger', function() {
     onElement('.alerts .alert-danger', function() {
       assert.equal(getAlertMessage(), "Enter a valid email address.",
       assert.equal(getAlertMessage(), "Enter a valid email address.",
-        "request form raised alert about empty input.");
+        "form raised alert about invalid input.");
       done();
       done();
     });
     });
   });
   });
@@ -46,7 +46,6 @@
       url: '/test-api/auth/send-activation/',
       url: '/test-api/auth/send-activation/',
       status: 403,
       status: 403,
       responseText: {
       responseText: {
-        'detail': 'You are banned!',
         'ban': {
         'ban': {
           'expires_on': null,
           'expires_on': null,
           'message': {
           'message': {
@@ -57,14 +56,12 @@
       }
       }
     });
     });
 
 
-    app.router.route('/activation/');
-
     var done = assert.async();
     var done = assert.async();
 
 
-    fillIn('.well-form input', 'valid@email.com');
-    click('.well-form .btn-primary');
+    fillIn('#component-mount input[type="text"]', 'valid@email.com');
+    click('#component-mount .btn-primary');
 
 
-    onElement('.page-error.page-error-banned .message-body', function() {
+    onElement('.page-error-banned .message-body', function() {
       assert.ok(true, "Permission denied error page was displayed.");
       assert.ok(true, "Permission denied error page was displayed.");
       assert.equal(
       assert.equal(
         getElementText('.page .message-body .lead'),
         getElementText('.page .message-body .lead'),
@@ -84,12 +81,10 @@
       }
       }
     });
     });
 
 
-    app.router.route('/activation/');
-
     var done = assert.async();
     var done = assert.async();
 
 
-    fillIn('.well-form input', 'valid@email.com');
-    click('.well-form .btn-primary');
+    fillIn('#component-mount input[type="text"]', 'valid@email.com');
+    click('#component-mount .btn-primary');
 
 
     onElement('.alerts .alert-danger', function() {
     onElement('.alerts .alert-danger', function() {
       assert.equal(getAlertMessage(), message,
       assert.equal(getAlertMessage(), message,
@@ -98,6 +93,30 @@
     });
     });
   });
   });
 
 
+  QUnit.test('with admin activation', function(assert) {
+    var message = "Admin has to activate your account.";
+
+    $.mockjax({
+      url: '/test-api/auth/send-activation/',
+      status: 400,
+      responseText: {
+        'detail': message,
+        'code': 'inactive_admin'
+      }
+    });
+
+    var done = assert.async();
+
+    fillIn('#component-mount input[type="text"]', 'valid@email.com');
+    click('#component-mount .btn-primary');
+
+    onElement('.alerts .alert-info', function() {
+      assert.equal(getAlertMessage(), message,
+        "request form raised alert returned by backend.");
+      done();
+    });
+  });
+
   QUnit.test('with success', function(assert) {
   QUnit.test('with success', function(assert) {
     $.mockjax({
     $.mockjax({
       url: '/test-api/auth/send-activation/',
       url: '/test-api/auth/send-activation/',
@@ -108,17 +127,15 @@
       }
       }
     });
     });
 
 
-    app.router.route('/activation/');
-
     var done = assert.async();
     var done = assert.async();
 
 
-    fillIn('.well-form input', 'valid@email.com');
-    click('.well-form .btn-primary');
+    fillIn('#component-mount input[type="text"]', 'valid@email.com');
+    click('#component-mount .btn-primary');
 
 
-    onElement('.message-body p', function() {
+    onElement('.well-done .message-body p', function() {
       assert.equal(
       assert.equal(
-        getElementText('.message-body p:nth-child(2)'),
-        "Bob, we have sent your activation link to bob@boberson.com.",
+        getElementText('.message-body p'),
+        "Activation link sent to bob@boberson.com.",
         "request form displayed success message.");
         "request form displayed success message.");
       done();
       done();
     });
     });
@@ -134,17 +151,14 @@
       }
       }
     });
     });
 
 
-    app.router.route('/activation/');
-
     var done = assert.async();
     var done = assert.async();
 
 
-    fillIn('.well-form input', 'valid@email.com');
-    click('.well-form .btn-primary');
-    waitForElement('.message-body .btn-default');
-    click('.message-body .btn-default');
+    fillIn('#component-mount input[type="text"]', 'valid@email.com');
+    click('#component-mount .btn-primary');
+    click('#component-mount .btn-default');
 
 
     onElement('.well-form', function() {
     onElement('.well-form', function() {
-      assert.ok(true, 'reset button took client back to previous screen.');
+      assert.ok(true, 'reset button reset component state.');
       done();
       done();
     });
     });
   });
   });

+ 33 - 48
misago/frontend/test/tests/acceptance/request-password-change.js

@@ -3,40 +3,40 @@
 
 
   var app = null;
   var app = null;
 
 
-  QUnit.acceptance("Request Password Change Link", {
+  QUnit.acceptance("Request Change Password E-mail", {
     beforeEach: function() {
     beforeEach: function() {
       app = initTestMisago();
       app = initTestMisago();
+      app.context.SEND_PASSWORD_RESET_API = '/test-api/auth/send-password-form/';
+
+      var mount = document.getElementById('component-mount');
+      m.mount(mount, app.component('forgotten-password:request-link'));
     },
     },
     afterEach: function() {
     afterEach: function() {
       app.destroy();
       app.destroy();
     }
     }
   });
   });
 
 
-  QUnit.test('with empty input', function(assert) {
-    app.router.route('/forgotten-password/');
-
+  QUnit.test('submit empty form', function(assert) {
     var done = assert.async();
     var done = assert.async();
 
 
-    click('.well-form .btn-primary');
+    click('#component-mount .btn-primary');
 
 
     onElement('.alerts .alert-danger', function() {
     onElement('.alerts .alert-danger', function() {
       assert.equal(getAlertMessage(), "Enter a valid email address.",
       assert.equal(getAlertMessage(), "Enter a valid email address.",
-        "request form raised alert about empty input.");
+        "form raised alert about empty input.");
       done();
       done();
     });
     });
   });
   });
 
 
-  QUnit.test('with invalid email', function(assert) {
-    app.router.route('/forgotten-password/');
-
+  QUnit.test('submit invalid form', function(assert) {
     var done = assert.async();
     var done = assert.async();
 
 
-    fillIn('.well-form input', 'not-email');
-    click('.well-form .btn-primary');
+    fillIn('#component-mount input[type="text"]', 'not-an-email!');
+    click('#component-mount .btn-primary');
 
 
     onElement('.alerts .alert-danger', function() {
     onElement('.alerts .alert-danger', function() {
       assert.equal(getAlertMessage(), "Enter a valid email address.",
       assert.equal(getAlertMessage(), "Enter a valid email address.",
-        "request form raised alert about empty input.");
+        "form raised alert about invalid input.");
       done();
       done();
     });
     });
   });
   });
@@ -46,7 +46,6 @@
       url: '/test-api/auth/send-password-form/',
       url: '/test-api/auth/send-password-form/',
       status: 403,
       status: 403,
       responseText: {
       responseText: {
-        'detail': 'You are banned!',
         'ban': {
         'ban': {
           'expires_on': null,
           'expires_on': null,
           'message': {
           'message': {
@@ -57,14 +56,12 @@
       }
       }
     });
     });
 
 
-    app.router.route('/forgotten-password/');
-
     var done = assert.async();
     var done = assert.async();
 
 
-    fillIn('.well-form input', 'valid@email.com');
-    click('.well-form .btn-primary');
+    fillIn('#component-mount input[type="text"]', 'valid@email.com');
+    click('#component-mount .btn-primary');
 
 
-    onElement('.page-error.page-error-banned .message-body', function() {
+    onElement('.page-error-banned .message-body', function() {
       assert.ok(true, "Permission denied error page was displayed.");
       assert.ok(true, "Permission denied error page was displayed.");
       assert.equal(
       assert.equal(
         getElementText('.page .message-body .lead'),
         getElementText('.page .message-body .lead'),
@@ -84,12 +81,10 @@
       }
       }
     });
     });
 
 
-    app.router.route('/forgotten-password/');
-
     var done = assert.async();
     var done = assert.async();
 
 
-    fillIn('.well-form input', 'valid@email.com');
-    click('.well-form .btn-primary');
+    fillIn('#component-mount input[type="text"]', 'valid@email.com');
+    click('#component-mount .btn-primary');
 
 
     onElement('.alerts .alert-danger', function() {
     onElement('.alerts .alert-danger', function() {
       assert.equal(getAlertMessage(), message,
       assert.equal(getAlertMessage(), message,
@@ -109,12 +104,10 @@
       }
       }
     });
     });
 
 
-    app.router.route('/forgotten-password/');
-
     var done = assert.async();
     var done = assert.async();
 
 
-    fillIn('.well-form input', 'valid@email.com');
-    click('.well-form .btn-primary');
+    fillIn('#component-mount input[type="text"]', 'valid@email.com');
+    click('#component-mount .btn-primary');
 
 
     onElement('.message-body p', function() {
     onElement('.message-body p', function() {
       assert.equal(
       assert.equal(
@@ -125,25 +118,22 @@
   });
   });
 
 
   QUnit.test('without user activation', function(assert) {
   QUnit.test('without user activation', function(assert) {
-    var message = "Your account needs activation!";
+    var message = "Your account needs admin activation!";
     $.mockjax({
     $.mockjax({
       url: '/test-api/auth/send-password-form/',
       url: '/test-api/auth/send-password-form/',
       status: 400,
       status: 400,
       responseText: {
       responseText: {
-        'code': 'inactive_user',
+        'code': 'inactive_admin',
         'detail': message
         'detail': message
       }
       }
     });
     });
 
 
-    app.router.route('/forgotten-password/');
-
     var done = assert.async();
     var done = assert.async();
 
 
-    fillIn('.well-form input', 'valid@email.com');
-    click('.well-form .btn-primary');
+    fillIn('#component-mount input[type="text"]', 'valid@email.com');
+    click('#component-mount .btn-primary');
 
 
-    onElement('.message-body .btn-primary', function() {
-      assert.ok(true, "link to activation form displayed.");
+    onElement('.message-body p', function() {
       assert.equal(
       assert.equal(
         getElementText('.message-body p:nth-child(2)'), message,
         getElementText('.message-body p:nth-child(2)'), message,
         "request form displayed user activation message.");
         "request form displayed user activation message.");
@@ -161,17 +151,15 @@
       }
       }
     });
     });
 
 
-    app.router.route('/forgotten-password/');
-
     var done = assert.async();
     var done = assert.async();
 
 
-    fillIn('.well-form input', 'valid@email.com');
-    click('.well-form .btn-primary');
+    fillIn('#component-mount input[type="text"]', 'valid@email.com');
+    click('#component-mount .btn-primary');
 
 
-    onElement('.message-body p', function() {
+    onElement('.well-done .message-body p', function() {
       assert.equal(
       assert.equal(
-        getElementText('.message-body p:nth-child(2)'),
-        "Bob, we have sent link to your password change form to bob@boberson.com.",
+        getElementText('.message-body p'),
+        "Reset password link sent to bob@boberson.com.",
         "request form displayed success message.");
         "request form displayed success message.");
       done();
       done();
     });
     });
@@ -187,17 +175,14 @@
       }
       }
     });
     });
 
 
-    app.router.route('/forgotten-password/');
-
     var done = assert.async();
     var done = assert.async();
 
 
-    fillIn('.well-form input', 'valid@email.com');
-    click('.well-form .btn-primary');
-    waitForElement('.message-body .btn-default');
-    click('.message-body .btn-default');
+    fillIn('#component-mount input[type="text"]', 'valid@email.com');
+    click('#component-mount .btn-primary');
+    click('#component-mount .btn-default');
 
 
     onElement('.well-form', function() {
     onElement('.well-form', function() {
-      assert.ok(true, 'reset button took client back to previous screen.');
+      assert.ok(true, 'reset button reset component state.');
       done();
       done();
     });
     });
   });
   });

+ 121 - 0
misago/frontend/test/tests/acceptance/reset-password.js

@@ -0,0 +1,121 @@
+(function () {
+  'use strict';
+
+  var app = null;
+
+  QUnit.acceptance("Reset Forgotten Password", {
+    beforeEach: function() {
+      app = initTestMisago();
+      app.context.CHANGE_PASSWORD_API = '/test-api/auth/change-password/';
+
+      var mount = document.getElementById('component-mount');
+      m.mount(mount, app.component('forgotten-password:reset-password'));
+    },
+    afterEach: function() {
+      app.destroy();
+    }
+  });
+
+  QUnit.test('submit empty form', function(assert) {
+    var done = assert.async();
+
+    click('#component-mount .btn-primary');
+
+    onElement('.alerts .alert-danger', function() {
+      assert.equal(getAlertMessage(), "Enter new password.",
+        "form raised alert about empty input.");
+      done();
+    });
+  });
+
+  QUnit.test('submit too short password', function(assert) {
+    var done = assert.async();
+
+    fillIn('#component-mount input[type="password"]', 'no');
+    click('#component-mount .btn-primary');
+
+    onElement('.alerts .alert-danger', function() {
+      assert.equal(
+        getAlertMessage(),
+        "Valid password must be at least 5 characters long.",
+        "form raised alert about invalid new password.");
+      done();
+    });
+  });
+
+  QUnit.test('with backend banned', function(assert) {
+    $.mockjax({
+      url: app.context.CHANGE_PASSWORD_API,
+      status: 403,
+      responseText: {
+        'ban': {
+          'expires_on': null,
+          'message': {
+            'plain': 'This is test ban.',
+            'html': '<p>This is test ban.</p>'
+          }
+        }
+      }
+    });
+
+    var done = assert.async();
+
+    fillIn('#component-mount input[type="password"]', 'L0r3m1p5um');
+    click('#component-mount .btn-primary');
+
+    onElement('.page-error-banned .message-body', function() {
+      assert.ok(true, "Permission denied error page was displayed.");
+      assert.equal(
+        getElementText('.page .message-body .lead'),
+        "This is test ban.",
+        "Banned page displayed ban message.");
+      done();
+    });
+  });
+
+  QUnit.test('with backend error', function(assert) {
+    var message = "Some evil rejection happened!";
+    $.mockjax({
+      url: app.context.CHANGE_PASSWORD_API,
+      status: 400,
+      responseText: {
+        'detail': message
+      }
+    });
+
+    var done = assert.async();
+
+    fillIn('#component-mount input[type="password"]', 'L0r3m1p5um');
+    click('#component-mount .btn-primary');
+
+    onElement('.alerts .alert-danger', function() {
+      assert.equal(getAlertMessage(), message,
+        "reset password form raised alert returned by backend.");
+      done();
+    });
+  });
+
+  QUnit.test('with success', function(assert) {
+    $.mockjax({
+      url: app.context.CHANGE_PASSWORD_API,
+      status: 200,
+      responseText: {
+        'username': 'Bob',
+        'email': 'bob@boberson.com'
+      }
+    });
+
+    var done = assert.async();
+
+    fillIn('#component-mount input[type="password"]', 'L0r3m1p5um');
+    click('#component-mount .btn-primary');
+
+    onElement('.page-forgotten-password-done .message-body p', function() {
+      assert.equal(
+        getElementText('.message-body p.lead'),
+        "Bob, your password has been changed successfully.",
+        "reset form displayed success page.");
+      done();
+    });
+  });
+}());

+ 53 - 14
misago/frontend/test/tests/acceptance/signin-modal.js → misago/frontend/test/tests/acceptance/sign-in.js

@@ -10,6 +10,7 @@
         return false;
         return false;
       });
       });
       app = initTestMisago();
       app = initTestMisago();
+      app.context.AUTH_API = '/test-api/auth/';
     },
     },
     afterEach: function() {
     afterEach: function() {
       $('#hidden-login-form').off('submit.stopInTest');
       $('#hidden-login-form').off('submit.stopInTest');
@@ -20,8 +21,8 @@
   QUnit.test('with empty credentials', function(assert) {
   QUnit.test('with empty credentials', function(assert) {
     var done = assert.async();
     var done = assert.async();
 
 
-    waitForElement('.navbar .nav-guest .btn-default');
-    click('.navbar .nav-guest .btn-default');
+    app.modal('sign-in');
+
     waitForElement('.modal-signin');
     waitForElement('.modal-signin');
     click('.modal-signin .btn-primary');
     click('.modal-signin .btn-primary');
 
 
@@ -40,8 +41,8 @@
 
 
     var done = assert.async();
     var done = assert.async();
 
 
-    waitForElement('.navbar .nav-guest .btn-default');
-    click('.navbar .nav-guest .btn-default');
+    app.modal('sign-in');
+
     waitForElement('.modal-signin');
     waitForElement('.modal-signin');
     fillIn('.modal-signin .form-group:first-child input', 'SomeFake');
     fillIn('.modal-signin .form-group:first-child input', 'SomeFake');
     fillIn('.modal-signin .form-group:last-child input', 'pass1234');
     fillIn('.modal-signin .form-group:last-child input', 'pass1234');
@@ -67,8 +68,8 @@
 
 
     var done = assert.async();
     var done = assert.async();
 
 
-    waitForElement('.navbar .nav-guest .btn-default');
-    click('.navbar .nav-guest .btn-default');
+    app.modal('sign-in');
+
     waitForElement('.modal-signin');
     waitForElement('.modal-signin');
     fillIn('.modal-signin .form-group:first-child input', 'SomeFake');
     fillIn('.modal-signin .form-group:first-child input', 'SomeFake');
     fillIn('.modal-signin .form-group:last-child input', 'pass1234');
     fillIn('.modal-signin .form-group:last-child input', 'pass1234');
@@ -94,8 +95,8 @@
 
 
     var done = assert.async();
     var done = assert.async();
 
 
-    waitForElement('.navbar .nav-guest .btn-default');
-    click('.navbar .nav-guest .btn-default');
+    app.modal('sign-in');
+
     waitForElement('.modal-signin');
     waitForElement('.modal-signin');
     fillIn('.modal-signin .form-group:first-child input', 'SomeFake');
     fillIn('.modal-signin .form-group:first-child input', 'SomeFake');
     fillIn('.modal-signin .form-group:last-child input', 'pass1234');
     fillIn('.modal-signin .form-group:last-child input', 'pass1234');
@@ -122,8 +123,8 @@
     var doneAlert = assert.async();
     var doneAlert = assert.async();
     var doneBtn = assert.async();
     var doneBtn = assert.async();
 
 
-    waitForElement('.navbar .nav-guest .btn-default');
-    click('.navbar .nav-guest .btn-default');
+    app.modal('sign-in');
+
     waitForElement('.modal-signin');
     waitForElement('.modal-signin');
     fillIn('.modal-signin .form-group:first-child input', 'SomeFake');
     fillIn('.modal-signin .form-group:first-child input', 'SomeFake');
     fillIn('.modal-signin .form-group:last-child input', 'pass1234');
     fillIn('.modal-signin .form-group:last-child input', 'pass1234');
@@ -141,6 +142,44 @@
     });
     });
   });
   });
 
 
+  QUnit.test('from banned ip', function(assert) {
+    $.mockjax({
+      url: '/test-api/auth/',
+      status: 403,
+      responseText: {
+        'ban': {
+          'expires_on': null,
+          'message': {
+            'plain': 'Your ip is banned for spamming.',
+            'html': '<p>Your ip is banned for spamming.</p>',
+          }
+        }
+      }
+    });
+
+    var done = assert.async();
+
+    app.modal('sign-in');
+
+    waitForElement('.modal-signin');
+    fillIn('.modal-signin .form-group:first-child input', 'SomeFake');
+    fillIn('.modal-signin .form-group:last-child input', 'pass1234');
+    click('.modal-signin .btn-primary');
+
+    onElement('.page-error-banned .lead', function() {
+      assert.equal(
+        getElementText('.page .message-body .lead'),
+        "Your ip is banned for spamming.",
+        "login form displayed error banned page with ban message.");
+
+      waitForElementRemoval('.modal-signin');
+      then(function() {
+        assert.ok(true, "signin modal was closed.");
+        done();
+      });
+    });
+  });
+
   QUnit.test('to banned account', function(assert) {
   QUnit.test('to banned account', function(assert) {
     $.mockjax({
     $.mockjax({
       url: '/test-api/auth/',
       url: '/test-api/auth/',
@@ -159,8 +198,8 @@
 
 
     var done = assert.async();
     var done = assert.async();
 
 
-    waitForElement('.navbar .nav-guest .btn-default');
-    click('.navbar .nav-guest .btn-default');
+    app.modal('sign-in');
+
     waitForElement('.modal-signin');
     waitForElement('.modal-signin');
     fillIn('.modal-signin .form-group:first-child input', 'SomeFake');
     fillIn('.modal-signin .form-group:first-child input', 'SomeFake');
     fillIn('.modal-signin .form-group:last-child input', 'pass1234');
     fillIn('.modal-signin .form-group:last-child input', 'pass1234');
@@ -191,8 +230,8 @@
 
 
     var done = assert.async();
     var done = assert.async();
 
 
-    waitForElement('.navbar .nav-guest .btn-default');
-    click('.navbar .nav-guest .btn-default');
+    app.modal('sign-in');
+
     waitForElement('.modal-signin');
     waitForElement('.modal-signin');
     fillIn('.modal-signin .form-group:first-child input', 'SomeFake');
     fillIn('.modal-signin .form-group:first-child input', 'SomeFake');
     fillIn('.modal-signin .form-group:last-child input', 'pass1234');
     fillIn('.modal-signin .form-group:last-child input', 'pass1234');

+ 0 - 19
misago/frontend/test/tests/acceptance/tests-utils.js

@@ -1,19 +0,0 @@
-(function () {
-  'use strict';
-
-  QUnit.module("Acceptance Tests Utils");
-
-  QUnit.test("create and destroy Misago app", function(assert) {
-    var done = assert.async();
-    var app = initTestMisago();
-
-    assert.equal($('#router-fixture').length, 1, "#router-fixture created");
-
-    app.destroy();
-
-    window.setTimeout(function() {
-      assert.equal($('#router-fixture').length, 0, "#router-fixture removed");
-      done();
-    }, 100);
-  });
-}());

+ 60 - 10
misago/frontend/test/tests/unit/ajax.js

@@ -87,10 +87,7 @@
 
 
     var container = {
     var container = {
       context: {
       context: {
-        CSRF_COOKIE_NAME: 'doesnt-matter',
-        '/test-url/': {
-          'detail': 'preloaded'
-        }
+        CSRF_COOKIE_NAME: 'doesnt-matter'
       }
       }
     };
     };
 
 
@@ -100,12 +97,8 @@
     var done = assert.async();
     var done = assert.async();
 
 
     ajax.get('/test-url/').then(function(data) {
     ajax.get('/test-url/').then(function(data) {
-      assert.equal(data.detail, 'preloaded', 'get() read preloaded data');
-
-      ajax.get('/test-url/').then(function(data) {
-        assert.equal(data.detail, 'backend', 'get() read backend data');
-        done();
-      });
+      assert.equal(data.detail, 'backend', 'get() read backend data');
+      done();
     });
     });
   });
   });
 
 
@@ -183,4 +176,61 @@
       doneDelete();
       doneDelete();
     });
     });
   });
   });
+
+  QUnit.test("alert", function(assert) {
+    var unknownError = assert.async();
+    var disconnectedError = assert.async();
+    var deniedError = assert.async();
+    var notFoundError = assert.async();
+
+    var container = {
+      context: {
+        CSRF_COOKIE_NAME: 'doesnt-matter'
+      },
+      setup: {
+        api: '/test-api/'
+      },
+      alert: {
+        error: function(message) {
+          if (message === "Unknown error has occured.") {
+            assert.ok(true, "unknown error was handled.");
+            unknownError();
+          }
+
+          if (message === "Lost connection with application.") {
+            assert.ok(true, "error 0 was handled.");
+            disconnectedError();
+          }
+
+          if (message === "You don't have permission to perform this action.") {
+            assert.ok(true, "error 403 was handled.");
+            deniedError();
+          }
+
+          if (message === "Action link is invalid.") {
+            assert.ok(true, "error 404 was handled.");
+            notFoundError();
+          }
+        }
+      }
+    };
+
+    var service = getMisagoService('ajax');
+    var ajax = service(container);
+
+    ajax.alert({
+      status: 500
+    });
+    ajax.alert({
+      status: 0
+    });
+    ajax.alert({
+      status: 403,
+      detail: "Permission denied"
+    });
+    ajax.alert({
+      status: 404,
+      detail: "Not found"
+    });
+  });
 }());
 }());

+ 0 - 140
misago/frontend/test/tests/unit/api.js

@@ -1,140 +0,0 @@
-(function () {
-  'use strict';
-
-  var service = getMisagoService('api');
-  var container = {
-      setup: {
-        api: '/test-api/'
-      }
-    };
-
-  QUnit.module("API", {
-    afterEach: function() {
-      $.mockjax.clear();
-    }
-  });
-
-  QUnit.test("service factory", function(assert) {
-    var api = service({});
-    assert.ok(api, "service factory has returned service instance.");
-  });
-
-  QUnit.test("model", function(assert) {
-    var api = service(container);
-
-    assert.equal(api.model('user', 'admin').url, '/test-api/users/admin/',
-      "model constructed valid url with string pk.");
-
-    assert.equal(api.model('user', 123).url, '/test-api/users/123/',
-      "model constructed valid url with integer pk.");
-
-    assert.equal(
-      api.model('user', {token: 'abc&df', uid: 123}).url,
-      '/test-api/users/?token=abc%26df&uid=123',
-      "model constructed valid url with querystring.");
-
-    assert.equal(api.model('user', 123).related('follows', 1).url,
-      '/test-api/users/123/follows/1/',
-      "model constructed valid related url.");
-
-    assert.equal(api.model('user', 123).endpoint('avatar').url,
-      '/test-api/users/123/avatar/',
-      "model constructed valid endpoint url.");
-
-    assert.ok(
-      !api.model('user', 123).endpoint('avatar').related,
-      "model can't have relation to endpoint.");
-
-    assert.ok(
-      !api.model('user', 123).related('avatar').related,
-      "model can't have relation to relation.");
-  });
-
-  QUnit.test("endpoint", function(assert) {
-    var api = service(container);
-
-    assert.equal(api.endpoint('auth').url, '/test-api/auth/',
-      "endpoint constructed valid url.");
-
-    assert.equal(
-      api.endpoint('auth', 'string-pk').url,
-      '/test-api/auth/string-pk/',
-      "endpoint constructed valid url with string pk.");
-
-    assert.equal(
-      api.endpoint('auth', 124).url,
-      '/test-api/auth/124/',
-      "endpoint constructed valid url with integer pk.");
-
-    assert.equal(
-      api.endpoint('auth', {token: 'abc&df', uid: 123}).url,
-      '/test-api/auth/?token=abc%26df&uid=123',
-      "endpoint constructed valid url with querystring.");
-
-    assert.ok(
-      !api.endpoint('auth', 124).related,
-      "endpoint can't have nested relation.");
-
-    assert.equal(
-      api.endpoint('auth', 124).endpoint('change-password').url,
-      '/test-api/auth/124/change-password/',
-      "nested endpoint constructed valid url with integer pk.");
-
-    assert.ok(
-      api.endpoint('auth', 124).endpoint('change-password').endpoint,
-      "nested endpoint can be nested further.");
-  });
-
-  QUnit.test("alert", function(assert) {
-
-    var unknownError = assert.async();
-    var disconnectedError = assert.async();
-    var deniedError = assert.async();
-    var notFoundError = assert.async();
-
-    var container = {
-      setup: {
-        api: '/test-api/'
-      },
-      alert: {
-        error: function(message) {
-          if (message === "Unknown error has occured.") {
-            assert.ok(true, "unknown error was handled.");
-            unknownError();
-          }
-
-          if (message === "Lost connection with application.") {
-            assert.ok(true, "error 0 was handled.");
-            disconnectedError();
-          }
-
-          if (message === "You don't have permission to perform this action.") {
-            assert.ok(true, "error 403 was handled.");
-            deniedError();
-          }
-
-          if (message === "Action link is invalid.") {
-            assert.ok(true, "error 404 was handled.");
-            notFoundError();
-          }
-        }
-      }
-    };
-
-    var api = service(container);
-    api.alert({
-      status: 500
-    });
-    api.alert({
-      status: 0
-    });
-    api.alert({
-      status: 403,
-      detail: "Permission denied"
-    });
-    api.alert({
-      status: 404,
-      detail: "Not found"
-    });
-  });
-}());

+ 25 - 0
misago/frontend/test/tests/unit/links.js

@@ -0,0 +1,25 @@
+(function () {
+  'use strict';
+
+  var service = getMisagoService('links');
+
+  QUnit.module("Links");
+
+  QUnit.test("staticUrl and mediaUrl", function(assert) {
+    var container = {
+      context: {
+        'STATIC_URL': '/static/',
+        'MEDIA_URL': 'http://nocookie.somewhere.com/'
+      }
+    };
+
+    service(container);
+
+    assert.equal(container.staticUrl('logo.png'), '/static/logo.png',
+      'staticUrl correctly prefixed url to static asset.');
+    assert.equal(
+      container.mediaUrl('avatar_1.png'),
+      'http://nocookie.somewhere.com/avatar_1.png',
+      'mediaUrl correctly prefixed url to media asset.');
+  });
+}());

+ 0 - 66
misago/frontend/test/tests/unit/router.js

@@ -1,66 +0,0 @@
-(function () {
-  'use strict';
-
-  var service = getMisagoService('router');
-
-  QUnit.module("Router");
-
-  QUnit.test("cleanUrl", function(assert) {
-    var container = {
-      context: {
-        'STATIC_URL': '/static/',
-        'MEDIA_URL': 'http://nocookie.somewhere.com/'
-      }
-    };
-    var router = service(container);
-
-    assert.equal(router.cleanUrl('/'), '/');
-    assert.equal(router.cleanUrl('/lorem-ipsum/'), '/lorem-ipsum/');
-    assert.equal(
-      router.cleanUrl('/lorem-ipsum/dolor/'), '/lorem-ipsum/dolor/');
-
-    assert.equal(router.cleanUrl('/static/'), undefined);
-    assert.equal(router.cleanUrl('http://nocookie.somewhere.com/'), undefined);
-    assert.equal(router.cleanUrl('/static/test.png'), undefined);
-    assert.equal(
-      router.cleanUrl('http://nocookie.somewhere.com/test.png'), undefined);
-
-    container.context.STATIC_URL = '/misago/static/';
-    router = service(container);
-    router.baseUrl = '/misago/';
-
-    assert.equal(router.cleanUrl('/misago/'), '/misago/');
-    assert.equal(router.cleanUrl('/misago/lorem-ipsum/'),
-                 '/misago/lorem-ipsum/');
-    assert.equal(router.cleanUrl('/misago/lorem-ipsum/dolor/'),
-                 '/misago/lorem-ipsum/dolor/');
-
-    assert.equal(router.cleanUrl('/'), undefined);
-    assert.equal(router.cleanUrl('/lorem-ipsum/'), undefined);
-    assert.equal(router.cleanUrl('/lorem-ipsum/dolor/'), undefined);
-
-    assert.equal(router.cleanUrl('/misago/static/'), undefined);
-    assert.equal(router.cleanUrl('http://nocookie.somewhere.com/'), undefined);
-    assert.equal(router.cleanUrl('/misago/static/test.png'), undefined);
-    assert.equal(
-      router.cleanUrl('http://nocookie.somewhere.com/test.png'), undefined);
-  });
-
-  QUnit.test("staticUrl and mediaUrl", function(assert) {
-    var container = {
-      context: {
-        'STATIC_URL': '/static/',
-        'MEDIA_URL': 'http://nocookie.somewhere.com/'
-      }
-    };
-
-    var router = service(container);
-
-    assert.equal(router.staticUrl('logo.png'), '/static/logo.png',
-      'staticUrl correctly prefixed url to static asset.');
-    assert.equal(
-      router.mediaUrl('avatar_1.png'),
-      'http://nocookie.somewhere.com/avatar_1.png',
-      'mediaUrl correctly prefixed url to media asset.');
-  });
-}());

+ 2 - 2
misago/frontend/test/tests/unit/service-container.js

@@ -130,9 +130,9 @@
   QUnit.test("initialization data storage", function(assert) {
   QUnit.test("initialization data storage", function(assert) {
     assert.expect();
     assert.expect();
 
 
-    container.init({fixture: 'test'});
+    container.init({api: 'test-api/'});
 
 
-    assert.equal(container.setup.fixture, 'test',
+    assert.equal(container.setup.api, 'test-api/',
       'container stored initialization data');
       'container stored initialization data');
   });
   });
 }());
 }());

+ 0 - 53
misago/frontend/test/tests/unit/urlconf.js

@@ -1,53 +0,0 @@
-(function (Misago) {
-  'use strict';
-
-  var urlconf = null;
-
-  QUnit.module("UrlConf", {
-    beforeEach: function() {
-      urlconf = new Misago.UrlConf();
-    }
-  });
-
-  QUnit.test("url registers pattern in config", function(assert) {
-    urlconf.url('/test/', 'route', 'test_url');
-    var patterns = urlconf.patterns();
-
-    assert.equal(patterns.length, 1,
-      'url() has registered single URL in config');
-    assert.deepEqual(
-      patterns[0],
-      {pattern: '/test/', component: 'route', name: 'test_url'},
-      'url() has registered valid URL in config');
-  });
-
-  QUnit.test("url normalizes route component name", function(assert) {
-    urlconf.url('/test/', 'some_test_route', 'test_url');
-    var patterns = urlconf.patterns();
-
-    assert.equal(patterns[0].component, 'some-test-route',
-      'url() has normalized component name underscores to comas');
-  });
-
-  QUnit.test("UrlConf.url(conf) includes child config", function(assert) {
-    var component = 'some-test';
-    urlconf.url('', component, 'test_url');
-    urlconf.url('/test/', component, 'test_url');
-
-    var childconf = new Misago.UrlConf();
-    childconf.url('/', component, 'users');
-    childconf.url('/user/', component, 'user');
-
-    urlconf.url('/users/', childconf);
-
-    var expected_patterns = [
-      {pattern: '/', component: component, name: 'test_url'},
-      {pattern: '/test/', component: component, name: 'test_url'},
-      {pattern: '/users/', component: component, name: 'users'},
-      {pattern: '/users/user/', component: component, name: 'user'}
-    ];
-
-    assert.deepEqual(urlconf.patterns(), expected_patterns,
-      'url() has included other urlconf');
-  });
-}(Misago.prototype));

+ 7 - 24
misago/frontend/test/utils/async-utils.js

@@ -16,12 +16,18 @@
   };
   };
   resetTestPromise();
   resetTestPromise();
 
 
+  var queueAction = function(action) {
+    window._promise.then(function() {
+      window._promise = action();
+    });
+  };
+
   var getElement = function(selector) {
   var getElement = function(selector) {
     var deferred = m.deferred();
     var deferred = m.deferred();
 
 
     var _getElement = function() {
     var _getElement = function() {
       window.setTimeout(function() {
       window.setTimeout(function() {
-        var $element = $('#misago-fixture ' + selector);
+        var $element = $('#qunit-fixture ' + selector);
         if ($element.length >= 1) {
         if ($element.length >= 1) {
           deferred.resolve($element);
           deferred.resolve($element);
         } else {
         } else {
@@ -35,12 +41,6 @@
     return deferred.promise;
     return deferred.promise;
   };
   };
 
 
-  var queueAction = function(action) {
-    window._promise.then(function() {
-      window._promise = action();
-    });
-  };
-
   window.click = function(selector) {
   window.click = function(selector) {
     queueAction(function() {
     queueAction(function() {
       var deferred = m.deferred();
       var deferred = m.deferred();
@@ -129,21 +129,4 @@
       return deferred.promise;
       return deferred.promise;
     });
     });
   };
   };
-
-  window.onCleanUp = function(callback) {
-    var waitForFixtureCleanUp = function() {
-      window.setTimeout(function() {
-        var content = $.trim($('#misago-fixture').html());
-        if (!content) {
-          window.setTimeout(function() {
-            callback();
-          }, 250);
-        } else {
-          waitForFixtureCleanUp();
-        }
-      }, 50);
-    };
-
-    waitForFixtureCleanUp();
-  };
 }());
 }());

+ 15 - 0
misago/frontend/test/utils/mock-mount.js

@@ -0,0 +1,15 @@
+(function () {
+  'use strict';
+
+  var persistent = function(el, isInit, context) {
+    context.retain = true;
+  };
+
+  window.mockMount = function(elementId, componentName) {
+    return m('#' + elementId, {
+      config: persistent,
+
+      'data-component-name': componentName
+    }, '-');
+  };
+}());

+ 19 - 4
misago/frontend/test/utils/qunit-boilerplate.js

@@ -1,6 +1,8 @@
 (function () {
 (function () {
   'use strict';
   'use strict';
 
 
+  m.deps(window.mock());
+
   // Boilerplate QUnit acceptance test
   // Boilerplate QUnit acceptance test
   QUnit.acceptance = function(name, conf) {
   QUnit.acceptance = function(name, conf) {
     var title = document.title;
     var title = document.title;
@@ -8,7 +10,10 @@
     var wrappedBeforeEach = conf.beforeEach;
     var wrappedBeforeEach = conf.beforeEach;
     conf.beforeEach = function() {
     conf.beforeEach = function() {
       resetTestPromise();
       resetTestPromise();
-      m.deps(window.mock());
+
+      var modal = document.getElementById('modal-mount');
+      $(modal).off();
+
       if (wrappedBeforeEach) {
       if (wrappedBeforeEach) {
         wrappedBeforeEach();
         wrappedBeforeEach();
       }
       }
@@ -17,13 +22,23 @@
     var wrappedAfterEach = conf.afterEach;
     var wrappedAfterEach = conf.afterEach;
     conf.afterEach = function(assert) {
     conf.afterEach = function(assert) {
       var cleaned = assert.async();
       var cleaned = assert.async();
+      var modal = document.getElementById('modal-mount');
+
       wrappedAfterEach();
       wrappedAfterEach();
+
       document.title = title;
       document.title = title;
+
       $.mockjax.clear();
       $.mockjax.clear();
 
 
-      window.onCleanUp(function() {
-        cleaned();
-      });
+      if(!$(modal).hasClass('in')) {
+        window.setTimeout(cleaned, 300);
+      } else {
+        $(modal).on('hidden.bs.modal', function () {
+          window.setTimeout(cleaned, 50);
+        });
+      }
+
+      $(modal).modal('hide');
     };
     };
 
 
     QUnit.module('Acceptance: ' + name, conf);
     QUnit.module('Acceptance: ' + name, conf);

+ 1 - 1
misago/frontend/test/utils/selectors.js

@@ -2,7 +2,7 @@
   'use strict';
   'use strict';
 
 
   window.getElement = function(selector) {
   window.getElement = function(selector) {
-    return $('#misago-fixture ' + selector);
+    return $('#qunit-fixture ' + selector);
   };
   };
 
 
   window.getElementText = function(selector) {
   window.getElementText = function(selector) {

+ 0 - 1
misago/project_template/requirements.txt

@@ -4,7 +4,6 @@ beautifulsoup4==4.3.2
 bleach==1.4.1
 bleach==1.4.1
 django-debug-toolbar==1.3.2
 django-debug-toolbar==1.3.2
 django-crispy-forms==1.4.0
 django-crispy-forms==1.4.0
-django-hbs-makemessages==0.9.6
 django-htmlmin==0.7
 django-htmlmin==0.7
 django-mptt==0.6.1
 django-mptt==0.6.1
 django-pipeline==1.4.2
 django-pipeline==1.4.2

+ 2 - 3
misago/static/misago/js/misago.js

@@ -1,4 +1,3 @@
-!function(){"use strict";window.Misago=function(){var t=Object.getPrototypeOf(this),e=this;this._initServices=function(n){var o=new t.OrderedList(n).order(!1);o.forEach(function(t){var n=null;n=void 0!==t.item.factory?t.item.factory:t.item;var o=n(e);o&&(e[t.key]=o)})},this._destroyServices=function(n){var o=new t.OrderedList(n).order();o.reverse(),o.forEach(function(t){void 0!==t.destroy&&t.destroy(e)})},this.context={SETTINGS:{}},this.setup=!1,this.init=function(e,n){this.setup={test:t.get(e,"test",!1),api:t.get(e,"api","/api/")},n&&(this.context=n),this._initServices(t._services)},this.destroy=function(){this._destroyServices(t._services)}};var t=window.Misago.prototype;t._services=[],t.addService=function(e,n,o){t._services.push({key:e,item:n,after:t.get(o,"after"),before:t.get(o,"before")})},t.PermissionDenied=function(t){this.detail=t,this.status=403,this.toString=function(){return this.detail||"Permission denied"}}}(),function(t){"use strict";t.has=function(t,e){return t?t.hasOwnProperty(e):!1},t.get=function(e,n,o){return t.has(e,n)?e[n]:void 0!==o?o:void 0},t.pop=function(e,n,o){var i=t.get(e,n,o);return t.has(e,n)&&(e[n]=null),i}}(Misago.prototype),function(t){"use strict";function e(t,e,n){n.retain=!0}t.input=function(t){var n={disabled:t.disabled||!1,config:t.config||e};t.placeholder&&(n.placeholder=t.placeholder),t.autocomplete===!1&&(n.autocomplete="off");var o="input";return t.id&&(o+="#"+t.id,n.key="field-"+t.id),o+=".form-control"+(t["class"]||""),o+='[type="'+(t.type||"text")+'"]',t.value&&(n.value=t.value(),n.oninput=m.withAttr("value",t.value)),m(o,n)}}(Misago.prototype),function(t){"use strict";var e=function(){};t.stateHooks=function(t,n,o){if(t._hasLifecycleHooks)return t;t._hasLifecycleHooks=!0,t.isActive=!0;var i=o.bind(t),r=t.controller||e;if(t.controller=function(){try{t.isActive=!0;var n=r.apply(t,arguments)||{},o=n.onunload||e;return n.onunload=function(){o.apply(t,arguments),t.isActive=!1},n}catch(s){i(s)}},t.vm&&t.vm.init){if(!t.loading){var s=n.bind(t);t.loading=s}var a=t.view;t.view=function(){return t.vm.isReady?a.apply(t,arguments):t.loading.apply(t,arguments)};var u=t.vm.init;t.vm.init=function(){var e=arguments,n=u.apply(t.vm,e);n&&n.then(function(){if(t.isActive&&t.vm.ondata){for(var n=[],o=0;o<arguments.length;o++)n.push(arguments[o]);for(var i=0;i<e.length;i++)n.push(e[i]);t.vm.ondata.apply(t.vm,n)}},function(e){t.isActive&&i(e)})}}return t}}(Misago.prototype),function(t){"use strict";t.OrderedList=function(e){this.isOrdered=!1,this._items=e||[],this.add=function(e,n,o){this._items.push({key:e,item:n,after:t.get(o,"after"),before:t.get(o,"before")})},this.get=function(t,e){for(var n=0;n<this._items.length;n++)if(this._items[n].key===t)return this._items[n].item;return e},this.has=function(t){return void 0!==this.get(t)},this.values=function(){for(var t=[],e=0;e<this._items.length;e++)t.push(this._items[e].item);return t},this.order=function(t){return this.isOrdered||(this._items=this._order(this._items),this.isOrdered=!0),t||"undefined"==typeof t?this.values():this._items},this._order=function(t){function e(t){var e=-1;-1===i.indexOf(t.key)&&(t.after?(e=i.indexOf(t.after),-1!==e&&(e+=1)):t.before&&(e=i.indexOf(t.before)),-1!==e&&(o.splice(e,0,t),i.splice(e,0,t.key)))}var n=[];t.forEach(function(t){n.push(t.key)});var o=[],i=[];t.forEach(function(t){t.after||t.before||(o.push(t),i.push(t.key))}),t.forEach(function(t){"_end"===t.before&&(o.push(t),i.push(t.key))});for(var r=200;r>0&&n.length!==i.length;)r-=1,t.forEach(e);return o}}}(Misago.prototype),function(t){"use strict";t.Page=function(e,n){var o=this;this.name=e,this.isFinalized=!1,this._sections=[];var i=function(){if(!o.isFinalized){o.isFinalized=!0;var e=[];o._sections.forEach(function(t){(!t.visibleIf||t.visibleIf(n))&&e.push(t)}),o._sections=new t.OrderedList(e).order(!0)}};this.addSection=function(t){if(this.isFinalized)throw this.name+" page was initialized already and no longer accepts new sections";this._sections.push({key:t.link,item:t,after:t.after,before:t.before})},this.getSections=function(){return i(),this._sections},this.getDefaultLink=function(){return i(),this._sections[0].link}}}(Misago.prototype),function(t){t.serializeDatetime=function(t){return t?t.format():null},t.deserializeDatetime=function(t){return t?moment(t):null}}(Misago.prototype),function(t){"use strict";t.startsWith=function(t,e){return 0===t.indexOf(e)},t.endsWith=function(t,e){return-1!==t.indexOf(e,t.length-e.length)}}(Misago.prototype),function(t){"use strict";t.UrlConf=function(){var e=this;this._patterns=[],this.patterns=function(){return this._patterns};var n=function(t,e){return(t+e).replace("//","/")},o=function(t,o){for(var i=0;i<o.length;i++)e.url(n(t,o[i].pattern),o[i].component,o[i].name)};this.url=function(e,n,i){""===e&&(e="/"),n instanceof t.UrlConf?o(e,n.patterns()):this._patterns.push({pattern:e,component:n.replace(/_/g,"-"),name:i||n})}}}(Misago.prototype),function(t){"use strict";t.loadingPage=function(t){return m(".page.page-loading",t.component("loader"))}}(Misago.prototype),function(t){"use strict";var e=function(e){if(-1!==document.cookie.indexOf(e)){var n=new RegExp(e+"=([^;]*)"),o=t.get(document.cookie.match(n),0);return o.split("=")[1]}return null},n=function(n){this.refreshCsrfToken=function(){this.csrfToken=e(n.context.CSRF_COOKIE_NAME)},this.refreshCsrfToken();var o={};this.ajax=function(e,n,i,r){var s=m.deferred(),a={url:n,method:e,headers:{"X-CSRFToken":this.csrfToken},data:i||{},dataType:"json",success:function(i){"GET"===e&&t.pop(o,n),s.resolve(i)},error:function(i){"GET"===e&&t.pop(o,n);var r=i.responseJSON||{};r.status=i.status,r.statusText=i.statusText,s.reject(r)}};return r?void 0:($.ajax(a),s.promise)},this.get=function(e){var i=t.pop(n.context,e);if(i){var r=m.deferred();return r.resolve(i),r.promise}return void 0!==o[e]?o[e]:(o[e]=this.ajax("GET",e),o[e])},this.post=function(t,e){return this.ajax("POST",t,e)},this.patch=function(t,e){return this.ajax("PATCH",t,e)},this.put=function(t,e){return this.ajax("PUT",t,e)},this["delete"]=function(t){return this.ajax("DELETE",t)}};t.addService("ajax",function(t){return new n(t)})}(Misago.prototype),function(t){"use strict";var e=5e3,n=70,o=9e3,i=300,r=function(t){var r=this;this.type="",this.message=null,this.isVisible=!1;var s=function(i,s){r.type=i,r.message=s,r.isVisible=!0;var a=e;a+=s.length*n,a>o&&(a=o),t.runloop.runOnce(function(){m.startComputation(),r.isVisible=!1,m.endComputation()},"flash-message-hide",a)},a=function(e,n){t.runloop.stop("flash-message-hide"),t.runloop.stop("flash-message-show"),r.isVisible?(r.isVisible=!1,t.runloop.runOnce(function(){m.startComputation(),s(e,n),m.endComputation()},"flash-message-show",i)):s(e,n)};this.info=function(t){a("info",t)},this.success=function(t){a("success",t)},this.warning=function(t){a("warning",t)},this.error=function(t){a("error",t)}};t.addService("alert",{factory:function(t){return new r(t)}})}(Misago.prototype),function(t){"use strict";var e=function(t){if("object"==typeof t){var e=[];for(var n in t)if(t.hasOwnProperty(n)){var o=encodeURIComponent(n),i=encodeURIComponent(t[n]);e.push(o+"="+i)}return"?"+e.join("&")}return t+"/"},n=function(t,o){this.url=o.url||t.setup.api,this.url+=o.path?o.path+"/":o.related?o.related+"/":o.model+"s/",o.filters&&(this.url+=e(o.filters)),o.model&&(this.related=function(e,i){return new n(t,{url:this.url,relation:o.model,related:e,filters:i})}),this.endpoint=function(e,o){return new n(t,{url:this.url,path:e,filters:o})},this.get=function(){var e=null;return o.related?e=o.relation+":"+o.related:o.model&&(e=o.model),t.ajax.get(this.url).then(function(n){return e?n.results?(n.results.map(function(n){return t.models["new"](e,n)}),n):t.models["new"](e,n):n})},this.post=function(e){return t.ajax.post(this.url,e)},this.patch=function(e){return t.ajax.patch(this.url,e)},this.put=function(e){return t.ajax.put(this.url,e)},this["delete"]=function(){return t.ajax["delete"](this.url)},this.then=function(t,e){return this.get().then(t,e)}},o=function(t){this.model=function(e,o){return new n(t,{model:e,filters:o})},this.endpoint=function(e,o){return new n(t,{path:e,filters:o})},this.alert=function(e){var n=gettext("Unknown error has occured.");0===e.status&&(n=gettext("Lost connection with application.")),403===e.status&&(n=e.detail,"Permission denied"===n&&(n=gettext("You don't have permission to perform this action."))),404===e.status&&(n=gettext("Action link is invalid.")),t.alert.error(n)}};t.addService("api",function(t){return new o(t)})}(Misago.prototype),function(t){"use strict";var e=function(t){var e=this;t.user=t.models.deserialize("user",t.context.user),this.isDesynced=!1,this.newUser=null;var n=function(n){e.isDesynced||(m.startComputation(),e.isDesynced=!0,n&&(e.newUser=t.localstore.get("auth-user")),m.endComputation())},o=function(n){e.isDesynced||(m.startComputation(),t.user.id!==n.id?(e.isDesynced=!0,e.newUser=n):n&&(t.user=$.extend(t.user,n)),m.endComputation())},i=function(){t.localstore.set("auth-user",t.user),t.localstore.set("auth-is-authenticated",t.user.isAuthenticated),t.localstore.watch("auth-is-authenticated",n),t.localstore.watch("auth-user",o)};i(),this.signOut=function(){t.user.isAuthenticated=!1,t.user.isAnonymous=!0,i();var e=document.getElementById("user-menu-mount"),n=document.getElementById("user-menu-compact-mount"),o=e.dataset.componentName,r=n.dataset.componentName,s=o.replace("user-nav","guest-nav"),a=r.replace("user-nav","guest-nav");m.mount(e,t.component(s)),m.mount(n,t.component(a))}};t.addService("auth",function(t){return new e(t)},{after:"model:user"})}(Misago.prototype),function(t){"use strict";var e=function(){var t=m.deferred();t.resolve(),this.load=function(){return t.promise},this.value=function(){return null}},n=function(t){var e=this;this.loading=!1,this.question=null,this.value=m.prop("");var n=m.deferred();this.load=function(){return this.value(""),this.question||this.loading||(this.loading=!0,t.api.endpoint("captcha-question").get().then(function(t){e.question=t,n.resolve()},function(){t.api.alert(gettext("Failed to load CAPTCHA.")),n.reject()}).then(function(){e.loading=!0})),n.promise},this.component=function(e){return t.component("form-group",{label:this.question.question,labelClass:e.labelClass||null,controlClass:e.controlClass||null,control:t.input({value:t.validate(e.form,"captcha"),id:"id_captcha",disabled:e.form.isBusy}),validation:e.form.errors,validationKey:"captcha",helpText:this.question.help_text})},this.validator=function(){return[]}},o=function(t){this.included=!1,this.question=null;var e=m.deferred(),n=function(e){"undefined"!=typeof grecaptcha?e.resolve():t.runloop.runOnce(function(){n(e)},"loading-grecaptcha",150)};this.load=function(){return"undefined"!=typeof grecaptcha&&grecaptcha.reset(),this.included||(t.include("https://www.google.com/recaptcha/api.js",!0),this.included=!0),n(e),e.promise};var o=function(e,n,o){o.retain=!0,n||grecaptcha.render("recaptcha",{sitekey:t.settings.recaptcha_site_key})};this.component=function(e){var n=m("#recaptcha",{config:o});return t.component("form-group",{label:gettext("Security test"),labelClass:e.labelClass||null,controlClass:e.controlClass||null,control:n,validation:e.form.errors,validationKey:"captcha"})},this.value=function(){return"undefined"!=typeof grecaptcha?grecaptcha.getResponse():""},this.clean=function(t){t.errors.captcha=this.value()?!0:[gettext("This field is required.")]},this.validator=function(){return[]}},i=function(t){var i={no:e,qa:n,re:o},r=new i[t.settings.captcha_type](t);this.value=r.value,this.load=function(){return r.load()},this.component=function(t){return r.component?r.component(t):null},this.validator=function(){return r.validator?r.validator():null},this.clean=function(t){r.clean?r.clean(t):t.errors.captcha=!0}};t.addService("captcha",function(t){return new i(t)},{after:"include"})}(Misago.prototype),function(t){"use strict";var e=function(t,e){if(this._components[t]){if(arguments.length>1){for(var n=[this._components[t]],o=1;o<arguments.length;o+=1)n.push(arguments[o]);return n.push(this),m.component.apply(void 0,n)}return m.component(this._components[t],this)}if(!e)throw'"'+t+"\" component is not registered and can't be created";this._components[t]=e};t.addService("components",function(t){t._components={},t.component=e})}(Misago.prototype),function(t){"use strict";t.addService("conf",function(e){e.settings=t.get(e.context,"SETTINGS",{})})}(Misago.prototype),function(t){"use strict";var e=function(t){var e={};this.toggle=function(n,o){var i=document.getElementById(n);i.hasChildNodes()&&e[n]===o?(e[n]=null,m.mount(i,null),$(i).removeClass("open")):(console.log(i.hasChildNodes()),e[n]=o,m.mount(i,t.component(o)),$(i).addClass("open"))},this.destroy=function(){var t=null;for(var n in e)e.hasOwnProperty(n)&&(t=document.getElementById(n),t&&t.hasChildNodes()&&m.mount(t,null))}};t.addService("dropdown",{factory:function(t){return new e(t)},destroy:function(t){t.dropdown.destroy()}},{before:"components"})}(Misago.prototype),function(t){"use strict";var e=function(t){var e=t.submit,n=t.success,o=t.error;return t.isBusy=!1,t.errors=null,t.submit=function(){return t.isBusy?!1:(t.clean?t.clean()&&(t.isBusy=!0,e.apply(t)):t.isBusy=!0,!1)},t.success=function(){m.startComputation(),n.apply(t,arguments),t.isBusy=!1,m.endComputation()},t.error=function(){m.startComputation(),o.apply(t,arguments),t.isBusy=!1,m.endComputation()},t.hasErrors=function(){if(null===t.errors)return!1;for(var e in t.validation)if(t.validation.hasOwnProperty(e)&&t.errors[e]!==!0)return!0;return!1},t},n=function(t,n){return this._forms[t]?e(n?new this._forms[t](n,this):new this._forms[t](this)):void(this._forms[t]=n)};t.addService("forms",function(t){t._forms={},t.form=n})}(Misago.prototype),function(t){"use strict";var e=function(t,e){e||(t=this.context.STATIC_URL+t),$.ajax({url:t,cache:!0,dataType:"script"})};t.addService("include",function(t){t.include=e},{after:"conf"})}(Misago.prototype),function(t){"use strict";var e=function(e){e.baseUrl=$("base").attr("href");var n=t.get(e.context,"STATIC_URL","/"),o=t.get(e.context,"MEDIA_URL","/"),i=function(t){return function(e){return t+e}};e.staticUrl=i(n),e.mediaUrl=i(o)};t.addService("links",function(t){e(t)})}(Misago.prototype),function(t){"use strict";var e=function(){var t=window.localStorage,e="_misago_",n=[],o=function(t){var e=JSON.parse(t.newValue);$.each(n,function(n,o){o.keyName===t.key&&t.oldValue!==t.newValue&&o.callback(e)})};window.addEventListener("storage",o);var i=function(t){return e+t};this.set=function(e,n){t.setItem(i(e),JSON.stringify(n))},this.get=function(e){var n=t.getItem(i(e));return n?JSON.parse(n):null},this.watch=function(t,e){n.push({keyName:i(t),callback:e})},this.destroy=function(){this.watchers=[]}};t.addService("localstore",{factory:function(){return new e},destroy:function(t){t.localstore.destroy()}})}(Misago.prototype),function(t){"use strict";var e=function(){var t=this,e=document.getElementById("modal-mount");this.destroy=function(){$(e).off(),$("body").removeClass("modal-open"),$(".modal-backdrop").remove()};var n=$(e).modal({show:!1});this.open=!1,n.on("hidden.bs.modal",function(){t.open&&(m.mount(e,null),this.open=!1)}),this.show=function(t){this.open=!0,m.mount(e,t),n.modal("show")},this.hide=function(){n.modal("hide")}};t.addService("_modal",{factory:function(){return new e},destroy:function(t){t._modal.destroy()}},{before:"mount-components"})}(Misago.prototype),function(t){"use strict";var e=function(t,e){if(this._modals[t]){for(var n=[this._modals[t]],o=1;o<arguments.length;o+=1)n.push(arguments[o]);n.push(this),this._modal.show(m.component.apply(m,n))}else t?this._modals[t]=e:this._modal.hide()};t.addService("modals",function(t){t._modals={},t.modal=e},{after:"_modal"})}(Misago.prototype),function(t){"use strict";var e=function(){this.classes={},this.deserializers={},this.add=function(t,e){e["class"]&&(this.classes[t]=e["class"]),e.deserialize&&(this.deserializers[t]=e.deserialize)},this["new"]=function(t,e){return this.classes[t]?(e.id=e.id?String(e.id):null,new this.classes[t](e)):e},this.deserialize=function(t,e){return this.deserializers[t]?this["new"](t,this.deserializers[t](e,this)):this["new"](t,e)}};t.addService("models",function(){return new e})}(Misago.prototype),function(t){"use strict";t.addService("set-momentjs-locale",function(){moment.locale($("html").attr("lang"))})}(Misago.prototype),function(t){"use strict";var e=document.getElementById("page-mount");t.addService("mount-page",function(t){t.mountPage=function(t){m.mount(e,t)}})}(Misago.prototype),function(t){"use strict";var e=function(t){-1===this._mounts.indexOf(t)&&this._mounts.push(t)};t.addService("mounts",function(t){t._mounts=["alert-mount","auth-changed-message-mount","page-component","user-menu-mount","user-menu-compact-mount"],t.addMount=e,t.activeMounts={}}),t.addService("mount-components",{factory:function(t){t._mounts.forEach(function(e){var n=document.getElementById(e);n&&(t.activeMounts[e]=n.dataset.componentName,m.mount(n,t.component(n.dataset.componentName)))})},destroy:function(){_._mounts.forEach(function(t){var e=document.getElementById(t);e&&e.hasChildNodes()&&m.mount(e,null)})}},{before:"_end"})}(Misago.prototype),function(t){"use strict";var e=function(t){var e=this;this._intervals={};var n=function(t){e._intervals[t]&&(window.clearTimeout(e._intervals[t]),e._intervals[t]=null)};this.run=function(o,i,r){this._intervals[i]=window.setTimeout(function(){n(i);var s=o(t);s!==!1&&e.run(o,i,r)},r)},this.runOnce=function(e,o,i){this._intervals[o]=window.setTimeout(function(){n(o),e(t)},i)},this.stop=function(t){for(var e in this._intervals)t&&t!==e||n(e)}};t.addService("runloop",{factory:function(t){return new e(t)},destroy:function(t){t.runloop.stop()}})}(Misago.prototype),function(t){"use strict";t.addService("show-banned-page",function(t){t.showBannedPage=function(e,n){var o=t.component("banned-page",t.models.deserialize("ban",e));"undefined"!=typeof n&&n||(t.title.set(gettext("You are banned")),window.history.pushState({},"",t.context.BANNED_URL)),t.mountPage(o)}})}(Misago.prototype),function(t){"use strict";t.addService("start-tick",function(t){var e=m.prop();t.runloop.run(function(){m.startComputation(),e(e()+1),m.endComputation()},"tick",6e4)})}(Misago.prototype),function(t){"use strict";var e=function(t){this.set=function(e){e?this._set_complex(e):document.title=t},this._set_complex=function(e){"string"==typeof e&&(e={title:e});var n=e.title;if("undefined"!=typeof e.page&&e.page>1){var o=interpolate(gettext("page %(page)s"),{page:e.page},!0);n+=" ("+o+")"}"undefined"!=typeof e.parent&&(n+=" | "+e.parent),document.title=n+" | "+t}};t.addService("page-title",function(t){t.title=new e(t.settings.forum_name)})}(Misago.prototype),function(t){"use strict";var e=/^(([^<>()[\]\.,;:\s@\"]+(\.[^<>()[\]\.,;:\s@\"]+)*)|(\".+\"))@(([^<>()[\]\.,;:\s@\"]+\.)+[^<>()[\]\.,;:\s@\"]{2,})$/i,n=new RegExp("^[0-9a-z]+$","i");t.validators={required:function(){return function(t){return 0===$.trim(t).length?gettext("This field is required."):void 0}},email:function(t){return function(n){return e.test(n)?void 0:t||gettext("Enter a valid email address.")}},minLength:function(t,e){return function(n){var o="",i=$.trim(n).length;return t>i?(o=e?e(t,i):ngettext("Ensure this value has at least %(limit_value)s character (it has %(show_value)s).","Ensure this value has at least %(limit_value)s characters (it has %(show_value)s).",t),interpolate(o,{limit_value:t,show_value:i},!0)):void 0}},maxLength:function(t,e){return function(n){var o="",i=$.trim(n).length;return i>t?(o=e?e(t,i):ngettext("Ensure this value has at most %(limit_value)s character (it has %(show_value)s).","Ensure this value has at most %(limit_value)s characters (it has %(show_value)s).",t),interpolate(o,{limit_value:t,show_value:i},!0)):void 0}},usernameMinLength:function(t){var e=function(t){return ngettext("Username must be at least %(limit_value)s character long.","Username must be at least %(limit_value)s characters long.",t)};return this.minLength(t.username_length_min,e)},usernameMaxLength:function(t){var e=function(t){return ngettext("Username cannot be longer than %(limit_value)s character.","Username cannot be longer than %(limit_value)s characters.",t)};return this.maxLength(t.username_length_max,e)},usernameContent:function(){return function(t){return n.test($.trim(t))?void 0:gettext("Username can only contain latin alphabet letters and digits.")}},passwordMinLength:function(t){var e=function(t){return ngettext("Valid password must be at least %(limit_value)s character long.","Valid password must be at least %(limit_value)s characters long.",t)};return this.minLength(t.password_length_min,e)}};var o=function(e,n){var o=t.validators.required()(e),i=[];if(o)return[o];for(var r in n)o=n[r](e),o&&i.push(o);return i.length?i:!0},i=function(t){var e={},n=null,i=!0;for(var r in t.validation)t.validation.hasOwnProperty(r)&&(n=t[r](),e[r]=o(t[r](),t.validation[r]),e[r]!==!0&&(i=!1));return t.errors=e,i},r=function(t,e){return e?function(n){var i=null;return"undefined"!=typeof n?(i=o(n,t.validation[e]),i&&(t.errors||(t.errors={}),t.errors[e]=i),t[e](n),t[e](n)):t[e]()}:i(t)};t.addService("validate",{factory:function(){return r}})}(Misago.prototype),function(t){"use strict";var e=function(t){this.included=!1,this.scorePassword=function(t,e){return zxcvbn(t,e).score},this.include=function(){t.include("misago/js/zxcvbn.js"),this.included=!0};var e=function(n){"undefined"!=typeof zxcvbn?n.resolve():t.runloop.runOnce(function(){e(n)},"loading-zxcvbn",150)},n=m.deferred();this.load=function(){return this.included||this.include(),e(n),n.promise}};t.addService("zxcvbn",function(t){return new e(t)},{after:"include"})}(Misago.prototype),function(t){"use strict";var e=function(t){this.message={html:t.message.html,plain:t.message.plain},this.expires_on=t.expires_on},n=function(e){return e.expires_on=t.deserializeDatetime(e.expires_on),e};t.addService("model:ban",function(t){t.models.add("ban",{"class":e,deserialize:n})},{after:"models"})}(Misago.prototype),function(t){"use strict";var e=function(t){this.title=t.title,this.body=t.body,this.link=t.link};t.addService("model:legal-page",function(t){t.models.add("legal-page",{"class":e})},{after:"models"})}(Misago.prototype),function(t){"use strict";var e=function(t){this.id=t.id,this.name=t.name,this.slug=t.slug,this.description=t.description,this.title=t.title,this.css_class=t.css_class,this.is_tab=t.is_tab};t.addService("model:rank",function(t){t.models.add("rank",{"class":e})},{after:"models"})}(Misago.prototype),function(t){"use strict";var e=function(t){this.id=t.id,this.isAuthenticated=!!this.id,this.isAnonymous=!this.isAuthenticated,this.username=t.username,this.slug=t.slug,this.email=t.email,this.full_title=t.full_title,this.rank=t.rank,this.avatar_hash=t.avatar_hash,this.acl=t.acl,this.absolute_url=t.absolute_url},n=function(e,n){return e.joined_on&&(e.joined_on=t.deserializeDatetime(e.joined_on)),e.rank&&(e.rank=n.deserialize("rank",e.rank)),e};t.addService("model:user",function(t){t.models.add("user",{"class":e,deserialize:n})},{after:"model:rank"})}(Misago.prototype),function(t){"use strict";function e(t,e,n){n.retain=!0}var n={classes:{info:"alert-info",success:"alert-success",warning:"alert-warning",error:"alert-danger"},view:function(t,n){var o={config:e,"class":n.alert.isVisible?"in":"out"};return m(".alerts",o,m("p.alert",{"class":this.classes[n.alert.type]},n.alert.message))}};t.addService("component:alert",function(t){t.component("alert",n)},{after:"components"})}(Misago.prototype),function(t){"use strict";function e(t,e,n){n.retain=!0}var n={refresh:function(){window.location.reload()},view:function(t,n){var o="",i={config:e,"class":n.auth.isDesynced?"show":null};return n.auth.isDesynced&&(n.auth.newUser&&n.auth.newUser.isAuthenticated?(o=gettext("You have signed in as %(username)s. Please refresh this page before continuing."),o=interpolate(o,{username:n.auth.newUser.username},!0)):(o=gettext("%(username)s, you have been signed out. Please refresh this page before continuing."),o=interpolate(o,{username:n.user.username},!0))),m(".auth-changed-message",i,m("",m(".container",[m("p",o),m("p",[m('button.btn.btn-default[type="button"]',{onclick:this.refresh},gettext("Reload page")),m("span.hidden-xs.hidden-sm.text-muted",gettext("or press F5 key."))])])))}};t.addService("component:auth-changed-message",function(t){t.component("auth-changed-message",n)},{after:"components"})}(Misago.prototype),function(t){"use strict";var e={controller:function(t,e){var n=e||t;return e?t:n.models.deserialize("ban",n.context.ban)},view:function(t){var e=null;return e=t.expires_on?t.expires_on.isAfter(moment())?interpolate(gettext("This ban expires %(expires_on)s."),{expires_on:t.expires_on.fromNow()},!0):gettext("This ban has expired."):gettext("This ban is permanent."),m("p",e)}};t.addService("component:ban-expiration-message",function(t){t.component("ban-expiration-message",e)},{after:"components"})}(Misago.prototype),function(t){"use strict";var e={view:function(t,e,n){var o=[];return o.push(e.message.html?m(".lead",m.trust(e.message.html)):m("p.lead",e.message.plain)),o.push(n.component("ban-expiration-message",e)),m(".page.page-error.page-error-banned",m(".container",m(".message-panel",[m(".message-icon",m("span.material-icon","highlight_off")),m(".message-body",o)])))}};t.addService("component:banned-page",function(t){t.component("banned-page",e)},{after:"components"})}(Misago.prototype),function(t){"use strict";var e={view:function(t,e){var n={disabled:e.disabled||e.loading||!1,config:e.config||null,loading:e.loading||!1,type:e.submit?"submit":"button",onclick:e.onclick||null},o='button[type="'+n.type+'"].btn';n.loading&&(o+=".btn-loading"),e.id&&(o+="#"+e.id),o+=e["class"]||"";var i=e.label;return n.loading&&(i=[i,m(".loader-compact",[m(".bounce1"),m(".bounce2"),m(".bounce3")])]),m(o,n,i)}};t.addService("component:button",function(t){t.component("button",e)},{after:"components"})}(Misago.prototype),function(t){"use strict";var e=["text","password","email"],n={view:function(t,n){var o=".form-group",i=null,r=null,s=n.control.attrs.type,a=n.control.attrs.id,u=a+"_feedback",c=null,l=null,d=n.validationKey&&null!==n.validation;return n.control.attrs["aria-describedby"]="",d&&n.validation[n.validationKey]&&(l=e.indexOf(s)>=0,n.control.attrs["aria-describedby"]=u,n.validation[n.validationKey]===!0?(o+=".has-success",c=[m("span.material-icon.form-control-feedback",{"aria-hidden":"true"},"check"),m("span.sr-only#"+u,gettext("(success)"))]):(o+=".has-error",i=n.validation[n.validationKey],c=[m("span.material-icon.form-control-feedback",{"aria-hidden":"true"},"clear"),m("span.sr-only#"+u,gettext("(error)"))])),n.helpText&&(r="string"==typeof n.helpText||n.helpText instanceof String?m("p.help-block",n.helpText):n.helpText),m(o,[m("label.control-label"+(n.labelClass||""),{"for":n.labelFor||a},n.label+":"),m(n.controlClass||"",[n.control,l?c:null,i?m(".help-block.errors",i.map(function(t){return m("p",t)})):null,r])])}};t.addService("component:form-group",function(t){t.component("form-group",n)},{after:"components"})}(Misago.prototype),function(t){"use strict";var e={view:function(){return m(".loader.sk-folding-cube",[m(".sk-cube1.sk-cube"),m(".sk-cube2.sk-cube"),m(".sk-cube4.sk-cube"),m(".sk-cube3.sk-cube")])}};t.addService("component:loader",function(t){t.component("loader",e)},{after:"components"})}(Misago.prototype),function(t){"use strict";var e=function(t,e,n){n.retain=!0},n={view:function(t,n){return m("article.misago-markup",{config:e},m.trust(n))}};t.addService("component:markup",function(t){t.component("markup",n)},{after:"components"})}(Misago.prototype),function(t){"use strict";var e={view:function(t,e){return m(".modal-header",[m('button.close[type="button"]',{"data-dismiss":"modal","aria-label":gettext("Close")},m("span",{"aria-hidden":"true"},m.trust("&times;"))),m("h4#misago-modal-label.modal-title",e)])}};t.addService("component:modal:header",function(t){t.component("modal:header",e)},{after:"components"})}(Misago.prototype),function(t){"use strict";var e={view:function(t,e){return m(".page-header",m(".container",[m("h1",e.title)]))}};t.addService("component:header",function(t){t.component("header",e)},{after:"components"})}(Misago.prototype),function(t){"use strict";var e=function(t,e,n){n.retain=!0},n=["progress-bar-danger","progress-bar-warning","progress-bar-warning","progress-bar-primary","progress-bar-success"],o=[gettext("Entered password is very weak."),gettext("Entered password is weak."),gettext("Entered password is average."),gettext("Entered password is strong."),gettext("Entered password is very strong.")],i={view:function(t,i,r){var s=r.zxcvbn.scorePassword(i.password,i.inputs),a={config:e,"class":n[s],style:"width: "+(20+20*s)+"%",role:"progressbar","aria-valuenow":s,"aria-valuemin":"0","aria-valuemax":"4"};return m(".help-block.password-strength",{key:"password-strength"},[m(".progress",m(".progress-bar",a,m("span.sr-only",o[s]))),m("p.text-small",o[s])])}};t.addService("component:password-strength",function(t){t.component("password-strength",i)},{after:"components"})}(Misago.prototype),function(t){"use strict";var e={defaultSize:100,src:function(t,e,n){var o=n.baseUrl+"user-avatar/";return o+=t&&t.id?t.avatar_hash+"/"+e+"/"+t.id+".png":e+".png"},view:function(t,e,n,o){var i=n||this.defaultSize;return m("img",{alt:e&&e.username?e.username:gettext("Unregistered"),width:i,height:i,src:this.src(e,i,o)})}};t.addService("component:user-avatar",function(t){t.component("user-avatar",e)},{after:"components"})}(Misago.prototype),function(t){"use strict";var e=function(e,n){var o=this;this.email=m.prop(""),this.validation={email:[t.validators.email()]},this.clean=function(){return n.validate(this)?!0:(n.alert.error(gettext("Enter a valid email address.")),!1)},this.submit=function(){n.ajax.post(e.api,{email:o.email()}).then(function(t){o.success(t)},function(t){o.error(t)})},this.success=function(t){e.success(t)},this.error=function(t){400===t.status?e.error(t,n):n.api.alert(t)},this.reset=function(){this.email(""),e.reset()}};t.addService("form:request-link",function(t){t.form("request-link",e)},{after:"forms"})}(Misago.prototype),function(t){"use strict";var e=".well.well-form.well-form-request-activation-link",n=function(t){this.api=t,this.user=null,this.success=function(t){this.user=t},this.error=function(t,e){"already_active"===t.code?(e.alert.info(t.detail),e.modal("sign-in")):"inactive_admin"===t.code?e.alert.info(t.detail):e.alert.error(t.detail)},this.reset=function(){this.user=null}},o={controller:function(t){var e=new n(t.context.SEND_ACTIVATION_API_URL);return{vm:e,form:t.form("request-link",e)}},view:function(t,e){return t.vm.user?this.done(t.vm,t.form,e):this.form(t.form,e)},done:function(t,n,o){var i=gettext("Activation link sent to %(email)s.");return m(e+".well-done",m(".done-message",[m(".message-icon",m("span.material-icon","check")),m(".message-body",m("p",interpolate(i,{email:t.user.email},!0))),o.component("button",{"class":".btn-default.btn-block",submit:!1,label:gettext("Request another link"),onclick:n.reset.bind(n)})]))},form:function(n,o){return m(e,m("form",{onsubmit:n.submit},[m(".form-group",m(".control-input",t.input({disabled:n.isBusy,value:n.email,placeholder:gettext("Your e-mail address")}))),o.component("button",{"class":".btn-primary.btn-block",submit:!0,loading:n.isBusy,label:gettext("Send link")})]))}};t.addService("component:request-activation-link-form",function(t){t.component("request-activation-link-form",o)},{after:"components"})}(Misago.prototype),function(t){"use strict";var e={controller:function(t,e){return e.title.set(gettext("Your password has been changed.")),{showSignIn:function(){e.modal("sign-in")}}},view:function(t,e){var n='button.btn.btn-primary[type="button"]',o=gettext("%(username)s, your password has been changed successfully.");
-
-return m(".page.page-message.page-message-success.page-forgotten-password-done",m(".container",m(".message-panel",[m(".message-icon",m("span.material-icon","check")),m(".message-body",[m("p.lead",interpolate(o,{username:e.username},!0)),m("p",gettext("You will have to sign in using new password before continuing.")),m("p",m(n,{onclick:t.showSignIn},gettext("Sign in")))])])))}};t.addService("component:forgotten-password:done-page",function(t){t.component("forgotten-password:done-page",e)},{after:"components"})}(Misago.prototype),function(t){"use strict";var e={view:function(t,e,n){var o=null;return"inactive_user"===e.type&&(o=m("p",m("a",{href:n.context.REQUEST_ACTIVATION_URL},gettext("Activate your account.")))),m(".page.page-message.page-message-info.page-forgotten-password-inactive",m(".container",m(".message-panel",[m(".message-icon",m("span.material-icon","info_outline")),m(".message-body",[m("p.lead",gettext("Your account is inactive.")),m("p",e.message),o])])))}};t.addService("component:forgotten-password:inactive-page",function(t){t.component("forgotten-password:inactive-page",e)},{after:"components"})}(Misago.prototype),function(t){"use strict";var e=".well.well-form.well-form-request-password-reset-link",n=function(t){this.api=t,this.user=null,this.activation=null,this.activationMessage=null,this.success=function(t){this.user=t},this.error=function(t,e){if(["inactive_user","inactive_admin"].indexOf(t.code)>-1){var n=e.component("forgotten-password:inactive-page",{type:t.code,message:t.detail});e.mountPage(n)}else e.alert.error(t.detail)},this.reset=function(){this.user=null,this.activation=null,this.activationMessage=null}},o={controller:function(t){var e=new n(t.context.SEND_PASSWORD_RESET_API_URL);return{vm:e,form:t.form("request-link",e)}},view:function(t,e){return t.vm.user?this.done(t.vm,t.form,e):this.form(t.form,e)},done:function(t,n,o){var i=gettext("Reset password link sent to %(email)s.");return m(e+".well-done",m(".done-message",[m(".message-icon",m("span.material-icon","check")),m(".message-body",m("p",interpolate(i,{email:t.user.email},!0))),o.component("button",{"class":".btn-default.btn-block",submit:!1,label:gettext("Request another link"),onclick:n.reset.bind(n)})]))},form:function(n,o){return m(e,m("form",{onsubmit:n.submit},[m(".form-group",m(".control-input",t.input({disabled:n.isBusy,value:n.email,placeholder:gettext("Your e-mail address")}))),o.component("button",{"class":".btn-primary.btn-block",submit:!0,loading:n.isBusy,label:gettext("Send link")})]))}};t.addService("component:forgotten-password:request-link",function(t){t.component("forgotten-password:request-link",o)},{after:"components"})}(Misago.prototype),function(t){"use strict";var e=".well.well-form.well-form-reset-password",n=function(t){this.api=t,this.success=function(t,e){e.auth.signOut(),e.mountPage(e.component("forgotten-password:done-page",t))}},o={controller:function(t){var e=new n(t.context.CHANGE_PASSWORD_API_URL);return{form:t.form("reset-password",e)}},view:function(n,o){return m(e,m("form",{onsubmit:n.form.submit},[m(".form-group",m(".control-input",t.input({disabled:n.form.isBusy,value:n.form.password,type:"password",placeholder:gettext("Enter new password")}))),o.component("button",{"class":".btn-primary.btn-block",submit:!0,loading:n.form.isBusy,label:gettext("Change password")})]))}};t.addService("component:forgotten-password:reset-password",function(t){t.component("forgotten-password:reset-password",o)},{after:"components"})}(Misago.prototype),function(t){"use strict";var e=function(e,n){var o=this;this.password=m.prop(""),this.validation={password:[t.validators.passwordMinLength(n.settings)]},this.clean=function(){return n.validate(this)?!0:(n.alert.error($.trim(this.password()).length?this.errors.password:gettext("Enter new password.")),!1)},this.submit=function(){n.ajax.post(e.api,{password:o.password()}).then(function(t){o.success(t)},function(t){o.error(t)})},this.success=function(t){e.success(t,n)},this.error=function(t){n.api.alert(t)}};t.addService("form:reset-password",function(t){t.form("reset-password",e)},{after:"forms"})}(Misago.prototype),function(t){"use strict";var e={controller:function(t){return{openUserMenu:function(){t.dropdown.toggle("navbar-dropdown","navbar:dropdown:guest")}}},view:function(t,e){return m("button",{type:"button",onclick:t.openUserMenu},e.component("user-avatar",null,64))}};t.addService("component:navbar:compact:guest-nav",function(t){t.component("navbar:compact:guest-nav",e)},{after:"components"})}(Misago.prototype),function(t){"use strict";var e={controller:function(t){return{openUserMenu:function(){return t.dropdown.toggle("navbar-dropdown","navbar:dropdown:user"),!1}}},view:function(t,e){var n={onclick:t.openUserMenu,href:e.user.absolute_url};return m("a",n,e.component("user-avatar",e.user,64))}};t.addService("component:navbar:compact:user-nav",function(t){t.component("navbar:compact:user-nav",e)},{after:"components"})}(Misago.prototype),function(t){"use strict";var e={controller:function(t){return{showSignIn:function(){t.modal("sign-in")}}},view:function(t,e){return m("div.nav.nav-guest",[e.component("button",{"class":".navbar-btn.btn-default",onclick:t.showSignIn,disabled:t.isBusy,label:gettext("Sign in")}),e.component("navbar:register-button",".navbar-btn.btn-primary")])}};t.addService("component:navbar:desktop:guest-nav",function(t){t.component("navbar:desktop:guest-nav",e)},{after:"components"})}(Misago.prototype),function(t){"use strict";var e={controller:function(t){return{dropdownToggle:{href:t.user.absolute_url,"data-toggle":"dropdown","data-misago-routed":"false","aria-haspopup":"true","aria-expanded":"false"}}},view:function(t,e){return m("ul.nav.navbar-nav.nav-user",[m("li.dropdown",[m('a.dropdown-toggle[role="button"]',t.dropdownToggle,e.component("user-avatar",e.user,64)),e.component("navbar:dropdown:user")])])}};t.addService("component:navbar:desktop:user-nav",function(t){t.component("navbar:desktop:user-nav",e)},{after:"components"})}(Misago.prototype),function(t){"use strict";var e={controller:function(t){return{showSignIn:function(){t.modal("sign-in")}}},view:function(t,e){return m('ul.dropdown-menu.user-dropdown.dropdown-menu-right[role="menu"]',m("li.guest-preview",[m("h4",gettext("You are browsing as guest.")),m("p",gettext("Sign in or register to start and participate in discussions.")),m(".row",[m(".col-xs-6",e.component("button",{"class":".btn.btn-default.btn-block",onclick:t.showSignIn,disabled:t.isBusy,label:gettext("Sign in")})),m(".col-xs-6",e.component("navbar:register-button",".btn.btn-primary.btn-block"))])]))}};t.addService("component:navbar:dropdown:guest",function(t){t.component("navbar:dropdown:guest",e)},{after:"components"})}(Misago.prototype),function(t){"use strict";var e={controller:function(t,e){return{isBusy:!1,showRegister:function(){if("closed"===e.settings.account_activation)e.alert.info(gettext("New registrations are currently disabled."));else{m.startComputation(),this.isBusy=!0,m.endComputation();var t=this;m.sync([e.zxcvbn.load(),e.captcha.load()]).then(function(){e.modal("register")},function(){e.alert.error(gettext("Registation is not available now due to an error."))}).then(function(){m.startComputation(),t.isBusy=!1,m.endComputation()})}}}},view:function(t,e,n){return n.component("button",{"class":e,onclick:t.showRegister.bind(t),loading:t.isBusy,label:gettext("Register")})}};t.addService("component:navbar:register-button",function(t){t.component("navbar:register-button",e)},{after:"components"})}(Misago.prototype),function(t){"use strict";var e={"class":".dropdown-menu.user-dropdown.dropdown-menu-right",controller:function(){return{logout:function(){var t=confirm(gettext("Are you sure you want to sign out?"));t&&$("#hidden-logout-form").submit()}}},view:function(t,e){return m("ul"+this["class"]+'[role="menu"]',[m("li.dropdown-header",m("strong",e.user.username)),m("li.divider"),m("li",m("a",{href:e.user.absolute_url},[m("span.material-icon","account_circle"),gettext("See your profile")])),m("li",m("a",{href:e.context.USERCP_URL},[m("span.material-icon","done_all"),gettext("Change options")])),m("li",m('button.btn-link[type="button"]',[m("span.material-icon","face"),gettext("Change avatar")])),m("li.divider"),m("li.dropdown-footer",m("button.btn.btn-default.btn-block",{onclick:t.logout},gettext("Logout")))])}};t.addService("component:navbar:dropdown:user",function(t){t.component("navbar:dropdown:user",e)},{after:"components"})}(Misago.prototype),function(t){"use strict";var e=function(t,e,n){n.retain=!0},n=function(){document.location.reload()},o={controller:function(t,e){"active"===t.activation&&e.runloop.runOnce(n,"refresh-after-registration",1e4)},view:function(t,n,o){var i=null;return i="active"===n.activation?this.active(n):this.inactive(n),m('.modal-dialog.modal-message.modal-register[role="document"]',{config:e},m(".modal-content",[o.component("modal:header",gettext("Registration complete")),m(".modal-body",i)]))},active:function(t){var e=gettext("%(username)s, your account has been created and you were signed in.");return[m(".message-icon",m("span.material-icon","check")),m(".message-body",[m("p.lead",interpolate(e,{username:t.username},!0)),m("p",gettext("The page will refresh automatically in 10 seconds.")),m("p",m('button[type="button"].btn.btn-default',{onclick:n},gettext("Refresh page")))])]},inactive:function(t){var e=null,n=null;return"user"===t.activation?(e=gettext("%(username)s, your account has been created but you need to activate it before you will be able to sign in."),n=gettext("We have sent an e-mail to %(email)s with link that you have to click to activate your account.")):"admin"===t.activation&&(e=gettext("%(username)s, your account has been created but board administrator will have to activate it before you will be able to sign in."),n=gettext("We will send an e-mail to %(email)s when this takes place.")),[m(".message-icon",m("span.material-icon","info_outline")),m(".message-body",[m("p.lead",interpolate(e,{username:t.username},!0)),m("p",interpolate(n,{email:t.email},!0))])]}};t.addService("modal:register:complete",function(t){t.modal("register:complete",o)},{after:"modals"})}(Misago.prototype),function(t){"use strict";var e=function(t,e,n){n.retain=!0},n={controller:function(t){return{form:t.form("register")}},view:function(t,n){var o=n.captcha.component({form:t.form,labelClass:".col-md-4",controlClass:".col-md-8"}),i=null;return n.context.TERMS_OF_SERVICE_URL&&(i=m("a",{href:n.context.TERMS_OF_SERVICE_URL},m.trust(interpolate(gettext("By registering you agree to site's %(terms)s."),{terms:"<strong>"+gettext("terms and conditions")+"</strong>"},!0)))),m('.modal-dialog.modal-form.modal-register[role="document"]',{config:e},m(".modal-content",[n.component("modal:header",gettext("Register")),m("form.form-horizontal",{onsubmit:t.form.submit},[m('input[type="text"]',{name:"_username",style:"display: none"}),m('input[type="password"]',{name:"_password",style:"display: none"}),m(".modal-body",[n.component("form-group",{label:gettext("Username"),labelClass:".col-md-4",controlClass:".col-md-8",control:n.input({value:n.validate(t.form,"username"),id:"id_username",disabled:t.form.isBusy}),validation:t.form.errors,validationKey:"username"}),n.component("form-group",{label:gettext("E-mail"),labelClass:".col-md-4",controlClass:".col-md-8",control:n.input({value:n.validate(t.form,"email"),id:"id_email",disabled:t.form.isBusy}),validation:t.form.errors,validationKey:"email"}),n.component("form-group",{label:gettext("Password"),labelClass:".col-md-4",controlClass:".col-md-8",control:n.input({value:n.validate(t.form,"password"),type:"password",id:"id_password",disabled:t.form.isBusy}),validation:t.form.errors,validationKey:"password",helpText:n.component("password-strength",{inputs:[t.form.username(),t.form.email()],password:t.form.password()})}),o]),m(".modal-footer",[i,n.component("button",{"class":".btn-primary",submit:!0,loading:t.form.isBusy,label:gettext("Register account")})])])]))}};t.addService("modal:register",function(t){t.modal("register",n)},{after:"modals"})}(Misago.prototype),function(t){"use strict";var e=function(e){var n=this;this.showActivation=!1,this.username=m.prop(""),this.email=m.prop(""),this.password=m.prop(""),this.captcha=e.captcha.value,this.errors=null,this.validation={username:[t.validators.usernameContent(),t.validators.usernameMinLength(e.settings),t.validators.usernameMaxLength(e.settings)],email:[t.validators.email()],password:[t.validators.passwordMinLength(e.settings)],captcha:e.captcha.validator()},this.clean=function(){return null===this.errors&&e.validate(this),e.captcha.clean(this),this.hasErrors()?(e.alert.error(gettext("Form contains errors.")),!1):!0},this.submit=function(){e.api.model("user").post({username:this.username(),email:this.email(),password:this.password(),captcha:this.captcha()}).then(this.success,this.error)},this.success=function(t){e.modal("register:complete",t)},this.error=function(t){400===t.status?(e.alert.error(gettext("Form contains errors.")),$.extend(n.errors,t)):e.api.alert(t)}};t.addService("form:register",function(t){t.form("register",e)},{after:"forms"})}(Misago.prototype),function(t){"use strict";var e=function(t){var e=this;this.showActivation=!1,this.username=m.prop(""),this.password=m.prop(""),this.validation={username:[],password:[]},this.clean=function(){return t.validate(this)?!0:(t.alert.error(gettext("Fill out both fields.")),!1)},this.submit=function(){t.api.endpoint("auth").post({username:e.username(),password:e.password()}).then(function(){e.success()},function(t){e.error(t)})},this.success=function(){t.modal();var e=$("#hidden-login-form");t.ajax.refreshCsrfToken(),e.find('input[type="hidden"]').val(t.ajax.csrfToken),e.find('input[name="redirect_to"]').val(window.location.pathname),e.find('input[name="username"]').val(this.username()),e.find('input[name="password"]').val(this.password()),e.submit()},this.error=function(n){400===n.status?"inactive_admin"===n.code?t.alert.info(n.detail):"inactive_user"===n.code?(t.alert.info(n.detail),e.showActivation=!0):"banned"===n.code?(t.showBannedPage(n.detail),t.modal()):t.alert.error(n.detail):t.api.alert(n)}};t.addService("form:sign-in",function(t){t.form("sign-in",e)},{after:"forms"})}(Misago.prototype),function(t){"use strict";function e(t,e,n){n.retain=!0}var n={controller:function(t){return{form:t.form("sign-in")}},view:function(n,o){var i=null;return n.form.showActivation&&(i=m("a.btn.btn-block.btn-success",{href:o.context.REQUEST_ACTIVATION_URL},gettext("Activate account"))),m('.modal-dialog.modal-sm.modal-signin[role="document"]',{config:e},m(".modal-content",[o.component("modal:header",gettext("Sign in")),m("form",{onsubmit:n.form.submit},[m(".modal-body",[m(".form-group",m(".control-input",t.input({disabled:n.form.isBusy,value:n.form.username,placeholder:gettext("Username or e-mail")}))),m(".form-group",m(".control-input",t.input({type:"password",disabled:n.form.isBusy,value:n.form.password,placeholder:gettext("Password")})))]),m(".modal-footer",[i,o.component("button",{"class":".btn-primary.btn-block",submit:!0,loading:n.form.isBusy,label:gettext("Sign in")}),m("a.btn.btn-block.btn-default",{href:o.context.FORGOTTEN_PASSWORD_URL},gettext("Forgot password?"))])])]))}};t.addService("modal:sign-in",function(t){t.modal("sign-in",n)},{after:"modals"})}(Misago.prototype);
+!function(){"use strict";window.Misago=function(){var t=Object.getPrototypeOf(this),e=this;this._initServices=function(n){var o=new t.OrderedList(n).order(!1);o.forEach(function(t){var n=null;n=void 0!==t.item.factory?t.item.factory:t.item;var o=n(e);o&&(e[t.key]=o)})},this._destroyServices=function(n){var o=new t.OrderedList(n).order();o.reverse(),o.forEach(function(t){void 0!==t.destroy&&t.destroy(e)})},this.context={SETTINGS:{}},this.setup=!1,this.init=function(e,n){this.setup={test:t.get(e,"test",!1),api:t.get(e,"api","/api/")},n&&(this.context=n),this._initServices(t._services)},this.destroy=function(){this._destroyServices(t._services)}};var t=window.Misago.prototype;t._services=[],t.addService=function(e,n,o){t._services.push({key:e,item:n,after:t.get(o,"after"),before:t.get(o,"before")})},t.PermissionDenied=function(t){this.detail=t,this.status=403,this.toString=function(){return this.detail||"Permission denied"}}}(),function(t){"use strict";t.has=function(t,e){return t?t.hasOwnProperty(e):!1},t.get=function(e,n,o){return t.has(e,n)?e[n]:void 0!==o?o:void 0},t.pop=function(e,n,o){var i=t.get(e,n,o);return t.has(e,n)&&(e[n]=null),i}}(Misago.prototype),function(t){"use strict";function e(t,e,n){n.retain=!0}t.input=function(t){var n={disabled:t.disabled||!1,config:t.config||e};t.placeholder&&(n.placeholder=t.placeholder),t.autocomplete===!1&&(n.autocomplete="off");var o="input";return t.id&&(o+="#"+t.id,n.key="field-"+t.id),o+=".form-control"+(t["class"]||""),o+='[type="'+(t.type||"text")+'"]',t.value&&(n.value=t.value(),n.oninput=m.withAttr("value",t.value)),m(o,n)}}(Misago.prototype),function(t){"use strict";var e=function(){};t.stateHooks=function(t,n,o){if(t._hasLifecycleHooks)return t;t._hasLifecycleHooks=!0,t.isActive=!0;var i=o.bind(t),r=t.controller||e;if(t.controller=function(){try{t.isActive=!0;var n=r.apply(t,arguments)||{},o=n.onunload||e;return n.onunload=function(){o.apply(t,arguments),t.isActive=!1},n}catch(s){i(s)}},t.vm&&t.vm.init){if(!t.loading){var s=n.bind(t);t.loading=s}var a=t.view;t.view=function(){return t.vm.isReady?a.apply(t,arguments):t.loading.apply(t,arguments)};var u=t.vm.init;t.vm.init=function(){var e=arguments,n=u.apply(t.vm,e);n&&n.then(function(){if(t.isActive&&t.vm.ondata){for(var n=[],o=0;o<arguments.length;o++)n.push(arguments[o]);for(var i=0;i<e.length;i++)n.push(e[i]);t.vm.ondata.apply(t.vm,n)}},function(e){t.isActive&&i(e)})}}return t}}(Misago.prototype),function(t){"use strict";t.OrderedList=function(e){this.isOrdered=!1,this._items=e||[],this.add=function(e,n,o){this._items.push({key:e,item:n,after:t.get(o,"after"),before:t.get(o,"before")})},this.get=function(t,e){for(var n=0;n<this._items.length;n++)if(this._items[n].key===t)return this._items[n].item;return e},this.has=function(t){return void 0!==this.get(t)},this.values=function(){for(var t=[],e=0;e<this._items.length;e++)t.push(this._items[e].item);return t},this.order=function(t){return this.isOrdered||(this._items=this._order(this._items),this.isOrdered=!0),t||"undefined"==typeof t?this.values():this._items},this._order=function(t){function e(t){var e=-1;-1===i.indexOf(t.key)&&(t.after?(e=i.indexOf(t.after),-1!==e&&(e+=1)):t.before&&(e=i.indexOf(t.before)),-1!==e&&(o.splice(e,0,t),i.splice(e,0,t.key)))}var n=[];t.forEach(function(t){n.push(t.key)});var o=[],i=[];t.forEach(function(t){t.after||t.before||(o.push(t),i.push(t.key))}),t.forEach(function(t){"_end"===t.before&&(o.push(t),i.push(t.key))});for(var r=200;r>0&&n.length!==i.length;)r-=1,t.forEach(e);return o}}}(Misago.prototype),function(t){"use strict";t.Page=function(e,n){var o=this;this.name=e,this.isFinalized=!1,this._sections=[];var i=function(){if(!o.isFinalized){o.isFinalized=!0;var e=[];o._sections.forEach(function(t){(!t.visibleIf||t.visibleIf(n))&&e.push(t)}),o._sections=new t.OrderedList(e).order(!0)}};this.addSection=function(t){if(this.isFinalized)throw this.name+" page was initialized already and no longer accepts new sections";this._sections.push({key:t.link,item:t,after:t.after,before:t.before})},this.getSections=function(){return i(),this._sections},this.getDefaultLink=function(){return i(),this._sections[0].link}}}(Misago.prototype),function(t){t.serializeDatetime=function(t){return t?t.format():null},t.deserializeDatetime=function(t){return t?moment(t):null}}(Misago.prototype),function(t){"use strict";t.startsWith=function(t,e){return 0===t.indexOf(e)},t.endsWith=function(t,e){return-1!==t.indexOf(e,t.length-e.length)}}(Misago.prototype),function(t){"use strict";var e=function(e){if(-1!==document.cookie.indexOf(e)){var n=new RegExp(e+"=([^;]*)"),o=t.get(document.cookie.match(n),0);return o.split("=")[1]}return null},n=function(n){this.refreshCsrfToken=function(){this.csrfToken=e(n.context.CSRF_COOKIE_NAME)},this.refreshCsrfToken();var o={};this.ajax=function(e,n,i,r){var s=m.deferred(),a={url:n,method:e,headers:{"X-CSRFToken":this.csrfToken},data:i||{},dataType:"json",success:function(i){"GET"===e&&t.pop(o,n),s.resolve(i)},error:function(i){"GET"===e&&t.pop(o,n);var r=i.responseJSON||{};r.status=i.status,r.statusText=i.statusText,s.reject(r)}};return r?void 0:($.ajax(a),s.promise)},this.get=function(t){return void 0!==o[t]?o[t]:(o[t]=this.ajax("GET",t),o[t])},this.post=function(t,e){return this.ajax("POST",t,e)},this.patch=function(t,e){return this.ajax("PATCH",t,e)},this.put=function(t,e){return this.ajax("PUT",t,e)},this["delete"]=function(t){return this.ajax("DELETE",t)},this.error=function(t){t.ban?(n.showBannedPage(t.ban),n.modal()):this.alert(t)},this.alert=function(t){var e=gettext("Unknown error has occured.");0===t.status&&(e=gettext("Lost connection with application.")),400===t.status&&t.detail&&(e=t.detail),403===t.status&&(e=t.detail,"Permission denied"===e&&(e=gettext("You don't have permission to perform this action."))),404===t.status&&(e=gettext("Action link is invalid.")),n.alert.error(e)}};t.addService("ajax",function(t){return new n(t)})}(Misago.prototype),function(t){"use strict";var e=5e3,n=70,o=9e3,i=300,r=function(t){var r=this;this.type="",this.message=null,this.isVisible=!1;var s=function(i,s){r.type=i,r.message=s,r.isVisible=!0;var a=e;a+=s.length*n,a>o&&(a=o),t.runloop.runOnce(function(){m.startComputation(),r.isVisible=!1,m.endComputation()},"flash-message-hide",a)},a=function(e,n){t.runloop.stop("flash-message-hide"),t.runloop.stop("flash-message-show"),r.isVisible?(r.isVisible=!1,t.runloop.runOnce(function(){m.startComputation(),s(e,n),m.endComputation()},"flash-message-show",i)):s(e,n)};this.info=function(t){a("info",t)},this.success=function(t){a("success",t)},this.warning=function(t){a("warning",t)},this.error=function(t){a("error",t)}};t.addService("alert",{factory:function(t){return new r(t)}})}(Misago.prototype),function(t){"use strict";var e=function(t){var e=this;t.user=t.models.deserialize("user",t.context.user),this.isDesynced=!1,this.newUser=null;var n=function(n){e.isDesynced||(m.startComputation(),e.isDesynced=!0,n&&(e.newUser=t.localstore.get("auth-user")),m.endComputation())},o=function(n){e.isDesynced||(m.startComputation(),t.user.id!==n.id?(e.isDesynced=!0,e.newUser=n):n&&(t.user=$.extend(t.user,n)),m.endComputation())},i=function(){t.localstore.set("auth-user",t.user),t.localstore.set("auth-is-authenticated",t.user.isAuthenticated),t.localstore.watch("auth-is-authenticated",n),t.localstore.watch("auth-user",o)};i();var r=function(e){var n=document.getElementById(e),o=null;n&&(o=n.dataset.componentName,m.mount(n,t.component(o.replace("user-nav","guest-nav"))))};this.signOut=function(){t.user.isAuthenticated=!1,t.user.isAnonymous=!0,i(),r("user-menu-mount"),r("user-menu-compact-mount")}};t.addService("auth",function(t){return new e(t)},{after:"model:user"})}(Misago.prototype),function(t){"use strict";var e=function(){var t=m.deferred();t.resolve(),this.load=function(){return t.promise},this.value=function(){return null}},n=function(t){var e=this;this.loading=!1,this.question=null,this.value=m.prop("");var n=m.deferred();this.load=function(){return this.value(""),this.question||this.loading||(this.loading=!0,t.api.endpoint("captcha-question").get().then(function(t){e.question=t,n.resolve()},function(){t.ajax.alert(gettext("Failed to load CAPTCHA.")),n.reject()}).then(function(){e.loading=!0})),n.promise},this.component=function(e){return t.component("form-group",{label:this.question.question,labelClass:e.labelClass||null,controlClass:e.controlClass||null,control:t.input({value:t.validate(e.form,"captcha"),id:"id_captcha",disabled:e.form.isBusy}),validation:e.form.errors,validationKey:"captcha",helpText:this.question.help_text})},this.validator=function(){return[]}},o=function(t){this.included=!1,this.question=null;var e=m.deferred(),n=function(e){"undefined"!=typeof grecaptcha?e.resolve():t.runloop.runOnce(function(){n(e)},"loading-grecaptcha",150)};this.load=function(){return"undefined"!=typeof grecaptcha&&grecaptcha.reset(),this.included||(t.include("https://www.google.com/recaptcha/api.js",!0),this.included=!0),n(e),e.promise};var o=function(e,n,o){o.retain=!0,n||grecaptcha.render("recaptcha",{sitekey:t.settings.recaptcha_site_key})};this.component=function(e){var n=m("#recaptcha",{config:o});return t.component("form-group",{label:gettext("Security test"),labelClass:e.labelClass||null,controlClass:e.controlClass||null,control:n,validation:e.form.errors,validationKey:"captcha"})},this.value=function(){return"undefined"!=typeof grecaptcha?grecaptcha.getResponse():""},this.clean=function(t){t.errors.captcha=this.value()?!0:[gettext("This field is required.")]},this.validator=function(){return[]}},i=function(t){var i={no:e,qa:n,re:o},r=new i[t.settings.captcha_type](t);this.value=r.value,this.load=function(){return r.load()},this.component=function(t){return r.component?r.component(t):null},this.validator=function(){return r.validator?r.validator():null},this.clean=function(t){r.clean?r.clean(t):t.errors.captcha=!0}};t.addService("captcha",function(t){return new i(t)},{after:"include"})}(Misago.prototype),function(t){"use strict";var e=function(t,e){if(this._components[t]){if(arguments.length>1){for(var n=[this._components[t]],o=1;o<arguments.length;o+=1)n.push(arguments[o]);return n.push(this),m.component.apply(void 0,n)}return m.component(this._components[t],this)}if(!e)throw'"'+t+"\" component is not registered and can't be created";this._components[t]=e};t.addService("components",function(t){t._components={},t.component=e})}(Misago.prototype),function(t){"use strict";t.addService("conf",function(e){e.settings=t.get(e.context,"SETTINGS",{})})}(Misago.prototype),function(t){"use strict";var e=function(t){var e={};this.toggle=function(n,o){var i=document.getElementById(n);i.hasChildNodes()&&e[n]===o?(e[n]=null,m.mount(i,null),$(i).removeClass("open")):(console.log(i.hasChildNodes()),e[n]=o,m.mount(i,t.component(o)),$(i).addClass("open"))},this.destroy=function(){var t=null;for(var n in e)e.hasOwnProperty(n)&&(t=document.getElementById(n),t&&t.hasChildNodes()&&m.mount(t,null))}};t.addService("dropdown",{factory:function(t){return new e(t)},destroy:function(t){t.dropdown.destroy()}},{before:"components"})}(Misago.prototype),function(t){"use strict";var e=function(t){var e=t.submit,n=t.success,o=t.error;return t.isBusy=!1,t.errors=null,t.submit=function(){return t.isBusy?!1:(t.clean?t.clean()&&(t.isBusy=!0,e.apply(t)):t.isBusy=!0,!1)},t.success=function(){m.startComputation(),n.apply(t,arguments),t.isBusy=!1,m.endComputation()},t.error=function(){m.startComputation(),o.apply(t,arguments),t.isBusy=!1,m.endComputation()},t.hasErrors=function(){if(null===t.errors)return!1;for(var e in t.validation)if(t.validation.hasOwnProperty(e)&&t.errors[e]!==!0)return!0;return!1},t},n=function(t,n){return this._forms[t]?e(n?new this._forms[t](n,this):new this._forms[t](this)):void(this._forms[t]=n)};t.addService("forms",function(t){t._forms={},t.form=n})}(Misago.prototype),function(t){"use strict";var e=function(t,e){e||(t=this.context.STATIC_URL+t),$.ajax({url:t,cache:!0,dataType:"script"})};t.addService("include",function(t){t.include=e},{after:"conf"})}(Misago.prototype),function(t){"use strict";var e=function(e){e.baseUrl=$("base").attr("href");var n=t.get(e.context,"STATIC_URL","/"),o=t.get(e.context,"MEDIA_URL","/"),i=function(t){return function(e){return t+e}};e.staticUrl=i(n),e.mediaUrl=i(o)};t.addService("links",function(t){e(t)})}(Misago.prototype),function(t){"use strict";var e=function(){var t=window.localStorage,e="_misago_",n=[],o=function(t){var e=JSON.parse(t.newValue);$.each(n,function(n,o){o.keyName===t.key&&t.oldValue!==t.newValue&&o.callback(e)})};window.addEventListener("storage",o);var i=function(t){return e+t};this.set=function(e,n){t.setItem(i(e),JSON.stringify(n))},this.get=function(e){var n=t.getItem(i(e));return n?JSON.parse(n):null},this.watch=function(t,e){n.push({keyName:i(t),callback:e})},this.destroy=function(){this.watchers=[]}};t.addService("localstore",{factory:function(){return new e},destroy:function(t){t.localstore.destroy()}})}(Misago.prototype),function(t){"use strict";var e=function(t){var e=this,n=document.getElementById("modal-mount"),o=$(n).modal({show:!1});this.open=!1,o.on("hidden.bs.modal",function(){e.open&&!t.setup.test&&(m.mount(n,null),this.open=!1)}),this.show=function(t){this.open=!0,m.mount(document.getElementById("modal-mount"),t),$(n).modal("show")},this.hide=function(){o.modal("hide")}};t.addService("_modal",function(t){return new e(t)},{before:"mount-components"})}(Misago.prototype),function(t){"use strict";var e=function(t,e){if(this._modals[t]){for(var n=[this._modals[t]],o=1;o<arguments.length;o+=1)n.push(arguments[o]);n.push(this),this._modal.show(m.component.apply(m,n))}else t?this._modals[t]=e:this._modal.hide()};t.addService("modals",function(t){t._modals={},t.modal=e},{after:"_modal"})}(Misago.prototype),function(t){"use strict";var e=function(){this.classes={},this.deserializers={},this.add=function(t,e){e["class"]&&(this.classes[t]=e["class"]),e.deserialize&&(this.deserializers[t]=e.deserialize)},this["new"]=function(t,e){return this.classes[t]?(e.id=e.id?String(e.id):null,new this.classes[t](e)):e},this.deserialize=function(t,e){return this.deserializers[t]?this["new"](t,this.deserializers[t](e,this)):this["new"](t,e)}};t.addService("models",function(){return new e})}(Misago.prototype),function(t){"use strict";t.addService("set-momentjs-locale",function(){moment.locale($("html").attr("lang"))})}(Misago.prototype),function(t){"use strict";t.addService("mount-page",function(t){t.mountPage=function(t){var e=document.getElementById("page-mount");m.mount(e,t)}})}(Misago.prototype),function(t){"use strict";var e=function(t){-1===this._mounts.indexOf(t)&&this._mounts.push(t)};t.addService("mounts",function(t){t._mounts=["alert-mount","auth-changed-message-mount","page-component","user-menu-mount","user-menu-compact-mount"],t.addMount=e,t.activeMounts={}}),t.addService("mount-components",{factory:function(t){t._mounts.forEach(function(e){var n=document.getElementById(e);n&&(t.activeMounts[e]=n.dataset.componentName,m.mount(n,t.component(n.dataset.componentName)))})},destroy:function(t){t._mounts.forEach(function(t){var e=document.getElementById(t);e&&e.hasChildNodes()&&m.mount(e,null)})}},{before:"_end"})}(Misago.prototype),function(t){"use strict";var e=function(t){var e=this;this._intervals={};var n=function(t){e._intervals[t]&&(window.clearTimeout(e._intervals[t]),e._intervals[t]=null)};this.run=function(o,i,r){this._intervals[i]=window.setTimeout(function(){n(i);var s=o(t);s!==!1&&e.run(o,i,r)},r)},this.runOnce=function(e,o,i){this._intervals[o]=window.setTimeout(function(){n(o),e(t)},i)},this.stop=function(t){for(var e in this._intervals)t&&t!==e||n(e)}};t.addService("runloop",{factory:function(t){return new e(t)},destroy:function(t){t.runloop.stop()}})}(Misago.prototype),function(t){"use strict";t.addService("show-banned-page",function(t){t.showBannedPage=function(e,n){var o=t.component("banned-page",t.models.deserialize("ban",e));"undefined"!=typeof n&&n||(t.title.set(gettext("You are banned")),window.history.pushState({},"",t.context.BANNED_URL)),t.mountPage(o)}})}(Misago.prototype),function(t){"use strict";t.addService("start-tick",function(t){var e=m.prop();t.runloop.run(function(){m.startComputation(),e(e()+1),m.endComputation()},"tick",6e4)})}(Misago.prototype),function(t){"use strict";var e=function(t){this.set=function(e){e?this._set_complex(e):document.title=t},this._set_complex=function(e){"string"==typeof e&&(e={title:e});var n=e.title;if("undefined"!=typeof e.page&&e.page>1){var o=interpolate(gettext("page %(page)s"),{page:e.page},!0);n+=" ("+o+")"}"undefined"!=typeof e.parent&&(n+=" | "+e.parent),document.title=n+" | "+t}};t.addService("page-title",function(t){t.title=new e(t.settings.forum_name)})}(Misago.prototype),function(t){"use strict";var e=/^(([^<>()[\]\.,;:\s@\"]+(\.[^<>()[\]\.,;:\s@\"]+)*)|(\".+\"))@(([^<>()[\]\.,;:\s@\"]+\.)+[^<>()[\]\.,;:\s@\"]{2,})$/i,n=new RegExp("^[0-9a-z]+$","i");t.validators={required:function(){return function(t){return 0===$.trim(t).length?gettext("This field is required."):void 0}},email:function(t){return function(n){return e.test(n)?void 0:t||gettext("Enter a valid email address.")}},minLength:function(t,e){return function(n){var o="",i=$.trim(n).length;return t>i?(o=e?e(t,i):ngettext("Ensure this value has at least %(limit_value)s character (it has %(show_value)s).","Ensure this value has at least %(limit_value)s characters (it has %(show_value)s).",t),interpolate(o,{limit_value:t,show_value:i},!0)):void 0}},maxLength:function(t,e){return function(n){var o="",i=$.trim(n).length;return i>t?(o=e?e(t,i):ngettext("Ensure this value has at most %(limit_value)s character (it has %(show_value)s).","Ensure this value has at most %(limit_value)s characters (it has %(show_value)s).",t),interpolate(o,{limit_value:t,show_value:i},!0)):void 0}},usernameMinLength:function(t){var e=function(t){return ngettext("Username must be at least %(limit_value)s character long.","Username must be at least %(limit_value)s characters long.",t)};return this.minLength(t.username_length_min,e)},usernameMaxLength:function(t){var e=function(t){return ngettext("Username cannot be longer than %(limit_value)s character.","Username cannot be longer than %(limit_value)s characters.",t)};return this.maxLength(t.username_length_max,e)},usernameContent:function(){return function(t){return n.test($.trim(t))?void 0:gettext("Username can only contain latin alphabet letters and digits.")}},passwordMinLength:function(t){var e=function(t){return ngettext("Valid password must be at least %(limit_value)s character long.","Valid password must be at least %(limit_value)s characters long.",t)};return this.minLength(t.password_length_min,e)}};var o=function(e,n){var o=t.validators.required()(e),i=[];if(o)return[o];for(var r in n)o=n[r](e),o&&i.push(o);return i.length?i:!0},i=function(t){var e={},n=null,i=!0;for(var r in t.validation)t.validation.hasOwnProperty(r)&&(n=t[r](),e[r]=o(t[r](),t.validation[r]),e[r]!==!0&&(i=!1));return t.errors=e,i},r=function(t,e){return e?function(n){var i=null;return"undefined"!=typeof n?(i=o(n,t.validation[e]),i&&(t.errors||(t.errors={}),t.errors[e]=i),t[e](n),t[e](n)):t[e]()}:i(t)};t.addService("validate",{factory:function(){return r}})}(Misago.prototype),function(t){"use strict";var e=function(t){this.included=!1,this.scorePassword=function(t,e){return zxcvbn(t,e).score},this.include=function(){t.include("misago/js/zxcvbn.js"),this.included=!0};var e=function(n){"undefined"!=typeof zxcvbn?n.resolve():t.runloop.runOnce(function(){e(n)},"loading-zxcvbn",150)},n=m.deferred();this.load=function(){return this.included||this.include(),e(n),n.promise}};t.addService("zxcvbn",function(t){return new e(t)},{after:"include"})}(Misago.prototype),function(t){"use strict";var e=function(t){this.message={html:t.message.html,plain:t.message.plain},this.expires_on=t.expires_on},n=function(e){return e.expires_on=t.deserializeDatetime(e.expires_on),e};t.addService("model:ban",function(t){t.models.add("ban",{"class":e,deserialize:n})},{after:"models"})}(Misago.prototype),function(t){"use strict";var e=function(t){this.title=t.title,this.body=t.body,this.link=t.link};t.addService("model:legal-page",function(t){t.models.add("legal-page",{"class":e})},{after:"models"})}(Misago.prototype),function(t){"use strict";var e=function(t){this.id=t.id,this.name=t.name,this.slug=t.slug,this.description=t.description,this.title=t.title,this.css_class=t.css_class,this.is_tab=t.is_tab};t.addService("model:rank",function(t){t.models.add("rank",{"class":e})},{after:"models"})}(Misago.prototype),function(t){"use strict";var e=function(t){this.id=t.id,this.isAuthenticated=!!this.id,this.isAnonymous=!this.isAuthenticated,this.username=t.username,this.slug=t.slug,this.email=t.email,this.full_title=t.full_title,this.rank=t.rank,this.avatar_hash=t.avatar_hash,this.acl=t.acl,this.absolute_url=t.absolute_url},n=function(e,n){return e.joined_on&&(e.joined_on=t.deserializeDatetime(e.joined_on)),e.rank&&(e.rank=n.deserialize("rank",e.rank)),e};t.addService("model:user",function(t){t.models.add("user",{"class":e,deserialize:n})},{after:"model:rank"})}(Misago.prototype),function(t){"use strict";function e(t,e,n){n.retain=!0}var n={classes:{info:"alert-info",success:"alert-success",warning:"alert-warning",error:"alert-danger"},view:function(t,n){var o={config:e,"class":n.alert.isVisible?"in":"out"};return m(".alerts",o,m("p.alert",{"class":this.classes[n.alert.type]},n.alert.message))}};t.addService("component:alert",function(t){t.component("alert",n)},{after:"components"})}(Misago.prototype),function(t){"use strict";function e(t,e,n){n.retain=!0}var n={refresh:function(){window.location.reload()},view:function(t,n){var o="",i={config:e,"class":n.auth.isDesynced?"show":null};return n.auth.isDesynced&&(n.auth.newUser&&n.auth.newUser.isAuthenticated?(o=gettext("You have signed in as %(username)s. Please refresh this page before continuing."),o=interpolate(o,{username:n.auth.newUser.username},!0)):(o=gettext("%(username)s, you have been signed out. Please refresh this page before continuing."),o=interpolate(o,{username:n.user.username},!0))),m(".auth-changed-message",i,m("",m(".container",[m("p",o),m("p",[m('button.btn.btn-default[type="button"]',{onclick:this.refresh},gettext("Reload page")),m("span.hidden-xs.hidden-sm.text-muted",gettext("or press F5 key."))])])))}};t.addService("component:auth-changed-message",function(t){t.component("auth-changed-message",n)},{after:"components"})}(Misago.prototype),function(t){"use strict";var e={controller:function(t,e){var n=e||t;return e?t:n.models.deserialize("ban",n.context.ban)},view:function(t){var e=null;return e=t.expires_on?t.expires_on.isAfter(moment())?interpolate(gettext("This ban expires %(expires_on)s."),{expires_on:t.expires_on.fromNow()},!0):gettext("This ban has expired."):gettext("This ban is permanent."),m("p",e)}};t.addService("component:ban-expiration-message",function(t){t.component("ban-expiration-message",e)},{after:"components"})}(Misago.prototype),function(t){"use strict";var e={view:function(t,e,n){var o=[];return o.push(e.message.html?m(".lead",m.trust(e.message.html)):m("p.lead",e.message.plain)),o.push(n.component("ban-expiration-message",e)),m(".page.page-error.page-error-banned",m(".container",m(".message-panel",[m(".message-icon",m("span.material-icon","highlight_off")),m(".message-body",o)])))}};t.addService("component:banned-page",function(t){t.component("banned-page",e)},{after:"components"})}(Misago.prototype),function(t){"use strict";var e={view:function(t,e){var n={disabled:e.disabled||e.loading||!1,config:e.config||null,loading:e.loading||!1,type:e.submit?"submit":"button",onclick:e.onclick||null},o='button[type="'+n.type+'"].btn';n.loading&&(o+=".btn-loading"),e.id&&(o+="#"+e.id),o+=e["class"]||"";var i=e.label;return n.loading&&(i=[i,m(".loader-compact",[m(".bounce1"),m(".bounce2"),m(".bounce3")])]),m(o,n,i)}};t.addService("component:button",function(t){t.component("button",e)},{after:"components"})}(Misago.prototype),function(t){"use strict";var e=["text","password","email"],n={view:function(t,n){var o=".form-group",i=null,r=null,s=n.control.attrs.type,a=n.control.attrs.id,u=a+"_feedback",c=null,l=null,d=n.validationKey&&null!==n.validation;return n.control.attrs["aria-describedby"]="",d&&n.validation[n.validationKey]&&(l=e.indexOf(s)>=0,n.control.attrs["aria-describedby"]=u,n.validation[n.validationKey]===!0?(o+=".has-success",c=[m("span.material-icon.form-control-feedback",{"aria-hidden":"true"},"check"),m("span.sr-only#"+u,gettext("(success)"))]):(o+=".has-error",i=n.validation[n.validationKey],c=[m("span.material-icon.form-control-feedback",{"aria-hidden":"true"},"clear"),m("span.sr-only#"+u,gettext("(error)"))])),n.helpText&&(r="string"==typeof n.helpText||n.helpText instanceof String?m("p.help-block",n.helpText):n.helpText),m(o,[m("label.control-label"+(n.labelClass||""),{"for":n.labelFor||a},n.label+":"),m(n.controlClass||"",[n.control,l?c:null,i?m(".help-block.errors",i.map(function(t){return m("p",t)})):null,r])])}};t.addService("component:form-group",function(t){t.component("form-group",n)},{after:"components"})}(Misago.prototype),function(t){"use strict";var e={view:function(){return m(".loader.sk-folding-cube",[m(".sk-cube1.sk-cube"),m(".sk-cube2.sk-cube"),m(".sk-cube4.sk-cube"),m(".sk-cube3.sk-cube")])}};t.addService("component:loader",function(t){t.component("loader",e)},{after:"components"})}(Misago.prototype),function(t){"use strict";var e=function(t,e,n){n.retain=!0},n={view:function(t,n){return m("article.misago-markup",{config:e},m.trust(n))}};t.addService("component:markup",function(t){t.component("markup",n)},{after:"components"})}(Misago.prototype),function(t){"use strict";var e={view:function(t,e){return m(".modal-header",[m('button.close[type="button"]',{"data-dismiss":"modal","aria-label":gettext("Close")},m("span",{"aria-hidden":"true"},m.trust("&times;"))),m("h4#misago-modal-label.modal-title",e)])}};t.addService("component:modal:header",function(t){t.component("modal:header",e)},{after:"components"})}(Misago.prototype),function(t){"use strict";var e={view:function(t,e){return m(".page-header",m(".container",[m("h1",e.title)]))}};t.addService("component:header",function(t){t.component("header",e)},{after:"components"})}(Misago.prototype),function(t){"use strict";var e=function(t,e,n){n.retain=!0},n=["progress-bar-danger","progress-bar-warning","progress-bar-warning","progress-bar-primary","progress-bar-success"],o=[gettext("Entered password is very weak."),gettext("Entered password is weak."),gettext("Entered password is average."),gettext("Entered password is strong."),gettext("Entered password is very strong.")],i={view:function(t,i,r){var s=r.zxcvbn.scorePassword(i.password,i.inputs),a={config:e,"class":n[s],style:"width: "+(20+20*s)+"%",role:"progressbar","aria-valuenow":s,"aria-valuemin":"0","aria-valuemax":"4"};return m(".help-block.password-strength",{key:"password-strength"},[m(".progress",m(".progress-bar",a,m("span.sr-only",o[s]))),m("p.text-small",o[s])])}};t.addService("component:password-strength",function(t){t.component("password-strength",i)},{after:"components"})}(Misago.prototype),function(t){"use strict";var e={defaultSize:100,src:function(t,e,n){var o=n.baseUrl+"user-avatar/";return o+=t&&t.id?t.avatar_hash+"/"+e+"/"+t.id+".png":e+".png"},view:function(t,e,n,o){var i=n||this.defaultSize;return m("img",{alt:e&&e.username?e.username:gettext("Unregistered"),width:i,height:i,src:this.src(e,i,o)})}};t.addService("component:user-avatar",function(t){t.component("user-avatar",e)},{after:"components"})}(Misago.prototype),function(t){"use strict";var e=function(e,n){var o=this;this.email=m.prop(""),this.validation={email:[t.validators.email()]},this.clean=function(){return n.validate(this)?!0:(n.alert.error(gettext("Enter a valid email address.")),!1)},this.submit=function(){n.ajax.post(e.api,{email:o.email()}).then(function(t){o.success(t)},function(t){o.error(t)})},this.success=function(t){e.success(t)},this.error=function(t){400===t.status?e.error(t,n):n.ajax.error(t)},this.reset=function(){this.email(""),e.reset()}};t.addService("form:request-link",function(t){t.form("request-link",e)},{after:"forms"})}(Misago.prototype),function(t){"use strict";var e=".well.well-form.well-form-request-activation-link",n=function(t){this.api=t,this.user=null,this.success=function(t){this.user=t},this.error=function(t,e){"already_active"===t.code?(e.alert.info(t.detail),e.modal("sign-in")):"inactive_admin"===t.code?e.alert.info(t.detail):e.alert.error(t.detail)},this.reset=function(){this.user=null}},o={controller:function(t){var e=new n(t.context.SEND_ACTIVATION_API);return{vm:e,form:t.form("request-link",e)}},view:function(t,e){return t.vm.user?this.done(t.vm,t.form,e):this.form(t.form,e)},done:function(t,n,o){var i=gettext("Activation link sent to %(email)s.");return m(e+".well-done",m(".done-message",[m(".message-icon",m("span.material-icon","check")),m(".message-body",m("p",interpolate(i,{email:t.user.email},!0))),o.component("button",{"class":".btn-default.btn-block",submit:!1,label:gettext("Request another link"),onclick:n.reset.bind(n)})]))},form:function(n,o){return m(e,m("form",{onsubmit:n.submit},[m(".form-group",m(".control-input",t.input({disabled:n.isBusy,value:n.email,placeholder:gettext("Your e-mail address")}))),o.component("button",{"class":".btn-primary.btn-block",submit:!0,loading:n.isBusy,label:gettext("Send link")})]))}};t.addService("component:request-activation-link-form",function(t){t.component("request-activation-link-form",o)},{after:"components"})}(Misago.prototype),function(t){"use strict";var e={controller:function(t,e){return e.title.set(gettext("Your password has been changed.")),{showSignIn:function(){e.modal("sign-in")}}},view:function(t,e){var n='button.btn.btn-primary[type="button"]',o=gettext("%(username)s, your password has been changed successfully.");return m(".page.page-message.page-message-success.page-forgotten-password-done",m(".container",m(".message-panel",[m(".message-icon",m("span.material-icon","check")),m(".message-body",[m("p.lead",interpolate(o,{username:e.username},!0)),m("p",gettext("You will have to sign in using new password before continuing.")),m("p",m(n,{onclick:t.showSignIn},gettext("Sign in")))])])))}};t.addService("component:forgotten-password:done-page",function(t){t.component("forgotten-password:done-page",e)},{after:"components"})}(Misago.prototype),function(t){"use strict";var e={view:function(t,e,n){var o=null;return"inactive_user"===e.type&&(o=m("p",m("a",{href:n.context.REQUEST_ACTIVATION_URL},gettext("Activate your account.")))),m(".page.page-message.page-message-info.page-forgotten-password-inactive",m(".container",m(".message-panel",[m(".message-icon",m("span.material-icon","info_outline")),m(".message-body",[m("p.lead",gettext("Your account is inactive.")),m("p",e.message),o])])))}};t.addService("component:forgotten-password:inactive-page",function(t){t.component("forgotten-password:inactive-page",e)},{after:"components"})}(Misago.prototype),function(t){"use strict";var e=".well.well-form.well-form-request-password-reset-link",n=function(t){this.api=t,this.user=null,this.activation=null,this.activationMessage=null,this.success=function(t){this.user=t},this.error=function(t,e){if(["inactive_user","inactive_admin"].indexOf(t.code)>-1){var n=e.component("forgotten-password:inactive-page",{type:t.code,message:t.detail});e.mountPage(n)}else e.alert.error(t.detail)},this.reset=function(){this.user=null,this.activation=null,this.activationMessage=null}},o={controller:function(t){var e=new n(t.context.SEND_PASSWORD_RESET_API);return{vm:e,form:t.form("request-link",e)}},view:function(t,e){return t.vm.user?this.done(t.vm,t.form,e):this.form(t.form,e)},done:function(t,n,o){var i=gettext("Reset password link sent to %(email)s.");return m(e+".well-done",m(".done-message",[m(".message-icon",m("span.material-icon","check")),m(".message-body",m("p",interpolate(i,{
+email:t.user.email},!0))),o.component("button",{"class":".btn-default.btn-block",submit:!1,label:gettext("Request another link"),onclick:n.reset.bind(n)})]))},form:function(n,o){return m(e,m("form",{onsubmit:n.submit},[m(".form-group",m(".control-input",t.input({disabled:n.isBusy,value:n.email,placeholder:gettext("Your e-mail address")}))),o.component("button",{"class":".btn-primary.btn-block",submit:!0,loading:n.isBusy,label:gettext("Send link")})]))}};t.addService("component:forgotten-password:request-link",function(t){t.component("forgotten-password:request-link",o)},{after:"components"})}(Misago.prototype),function(t){"use strict";var e=".well.well-form.well-form-reset-password",n=function(t){this.api=t,this.success=function(t,e){e.auth.signOut(),e.mountPage(e.component("forgotten-password:done-page",t))}},o={controller:function(t){var e=new n(t.context.CHANGE_PASSWORD_API);return{form:t.form("reset-password",e)}},view:function(n,o){return m(e,m("form",{onsubmit:n.form.submit},[m(".form-group",m(".control-input",t.input({disabled:n.form.isBusy,value:n.form.password,type:"password",placeholder:gettext("Enter new password")}))),o.component("button",{"class":".btn-primary.btn-block",submit:!0,loading:n.form.isBusy,label:gettext("Change password")})]))}};t.addService("component:forgotten-password:reset-password",function(t){t.component("forgotten-password:reset-password",o)},{after:"components"})}(Misago.prototype),function(t){"use strict";var e=function(e,n){var o=this;this.password=m.prop(""),this.validation={password:[t.validators.passwordMinLength(n.settings)]},this.clean=function(){return n.validate(this)?!0:(n.alert.error($.trim(this.password()).length?this.errors.password:gettext("Enter new password.")),!1)},this.submit=function(){n.ajax.post(e.api,{password:o.password()}).then(function(t){o.success(t)},function(t){o.error(t)})},this.success=function(t){e.success(t,n)},this.error=function(t){n.ajax.error(t)}};t.addService("form:reset-password",function(t){t.form("reset-password",e)},{after:"forms"})}(Misago.prototype),function(t){"use strict";var e={controller:function(t){return{openUserMenu:function(){t.dropdown.toggle("navbar-dropdown","navbar:dropdown:guest")}}},view:function(t,e){return m("button",{type:"button",onclick:t.openUserMenu},e.component("user-avatar",null,64))}};t.addService("component:navbar:compact:guest-nav",function(t){t.component("navbar:compact:guest-nav",e)},{after:"components"})}(Misago.prototype),function(t){"use strict";var e={controller:function(t){return{openUserMenu:function(){return t.dropdown.toggle("navbar-dropdown","navbar:dropdown:user"),!1}}},view:function(t,e){var n={onclick:t.openUserMenu,href:e.user.absolute_url};return m("a",n,e.component("user-avatar",e.user,64))}};t.addService("component:navbar:compact:user-nav",function(t){t.component("navbar:compact:user-nav",e)},{after:"components"})}(Misago.prototype),function(t){"use strict";var e={controller:function(t){return{showSignIn:function(){t.modal("sign-in")}}},view:function(t,e){return m("div.nav.nav-guest",[e.component("button",{"class":".navbar-btn.btn-default",onclick:t.showSignIn,disabled:t.isBusy,label:gettext("Sign in")}),e.component("navbar:register-button",".navbar-btn.btn-primary")])}};t.addService("component:navbar:desktop:guest-nav",function(t){t.component("navbar:desktop:guest-nav",e)},{after:"components"})}(Misago.prototype),function(t){"use strict";var e={controller:function(t){return{dropdownToggle:{href:t.user.absolute_url,"data-toggle":"dropdown","data-misago-routed":"false","aria-haspopup":"true","aria-expanded":"false"}}},view:function(t,e){return m("ul.nav.navbar-nav.nav-user",[m("li.dropdown",[m('a.dropdown-toggle[role="button"]',t.dropdownToggle,e.component("user-avatar",e.user,64)),e.component("navbar:dropdown:user")])])}};t.addService("component:navbar:desktop:user-nav",function(t){t.component("navbar:desktop:user-nav",e)},{after:"components"})}(Misago.prototype),function(t){"use strict";var e={controller:function(t){return{showSignIn:function(){t.modal("sign-in")}}},view:function(t,e){return m('ul.dropdown-menu.user-dropdown.dropdown-menu-right[role="menu"]',m("li.guest-preview",[m("h4",gettext("You are browsing as guest.")),m("p",gettext("Sign in or register to start and participate in discussions.")),m(".row",[m(".col-xs-6",e.component("button",{"class":".btn.btn-default.btn-block",onclick:t.showSignIn,disabled:t.isBusy,label:gettext("Sign in")})),m(".col-xs-6",e.component("navbar:register-button",".btn.btn-primary.btn-block"))])]))}};t.addService("component:navbar:dropdown:guest",function(t){t.component("navbar:dropdown:guest",e)},{after:"components"})}(Misago.prototype),function(t){"use strict";var e={controller:function(t,e){return{isBusy:!1,showRegister:function(){if("closed"===e.settings.account_activation)e.alert.info(gettext("New registrations are currently disabled."));else{m.startComputation(),this.isBusy=!0,m.endComputation();var t=this;m.sync([e.zxcvbn.load(),e.captcha.load()]).then(function(){e.modal("register")},function(){e.alert.error(gettext("Registation is not available now due to an error."))}).then(function(){m.startComputation(),t.isBusy=!1,m.endComputation()})}}}},view:function(t,e,n){return n.component("button",{"class":e,onclick:t.showRegister.bind(t),loading:t.isBusy,label:gettext("Register")})}};t.addService("component:navbar:register-button",function(t){t.component("navbar:register-button",e)},{after:"components"})}(Misago.prototype),function(t){"use strict";var e={"class":".dropdown-menu.user-dropdown.dropdown-menu-right",controller:function(){return{logout:function(){var t=confirm(gettext("Are you sure you want to sign out?"));t&&$("#hidden-logout-form").submit()}}},view:function(t,e){return m("ul"+this["class"]+'[role="menu"]',[m("li.dropdown-header",m("strong",e.user.username)),m("li.divider"),m("li",m("a",{href:e.user.absolute_url},[m("span.material-icon","account_circle"),gettext("See your profile")])),m("li",m("a",{href:e.context.USERCP_URL},[m("span.material-icon","done_all"),gettext("Change options")])),m("li",m('button.btn-link[type="button"]',[m("span.material-icon","face"),gettext("Change avatar")])),m("li.divider"),m("li.dropdown-footer",m("button.btn.btn-default.btn-block",{onclick:t.logout},gettext("Logout")))])}};t.addService("component:navbar:dropdown:user",function(t){t.component("navbar:dropdown:user",e)},{after:"components"})}(Misago.prototype),function(t){"use strict";var e=function(t,e,n){n.retain=!0},n=function(){document.location.reload()},o={controller:function(t,e){"active"===t.activation&&e.runloop.runOnce(n,"refresh-after-registration",1e4)},view:function(t,n,o){var i=null;return i="active"===n.activation?this.active(n):this.inactive(n),m('.modal-dialog.modal-message.modal-register[role="document"]',{config:e},m(".modal-content",[o.component("modal:header",gettext("Registration complete")),m(".modal-body",i)]))},active:function(t){var e=gettext("%(username)s, your account has been created and you were signed in.");return[m(".message-icon",m("span.material-icon","check")),m(".message-body",[m("p.lead",interpolate(e,{username:t.username},!0)),m("p",gettext("The page will refresh automatically in 10 seconds.")),m("p",m('button[type="button"].btn.btn-default',{onclick:n},gettext("Refresh page")))])]},inactive:function(t){var e=null,n=null;return"user"===t.activation?(e=gettext("%(username)s, your account has been created but you need to activate it before you will be able to sign in."),n=gettext("We have sent an e-mail to %(email)s with link that you have to click to activate your account.")):"admin"===t.activation&&(e=gettext("%(username)s, your account has been created but board administrator will have to activate it before you will be able to sign in."),n=gettext("We will send an e-mail to %(email)s when this takes place.")),[m(".message-icon",m("span.material-icon","info_outline")),m(".message-body",[m("p.lead",interpolate(e,{username:t.username},!0)),m("p",interpolate(n,{email:t.email},!0))])]}};t.addService("modal:register:complete",function(t){t.modal("register:complete",o)},{after:"modals"})}(Misago.prototype),function(t){"use strict";var e=function(t,e,n){n.retain=!0},n={controller:function(t){return{form:t.form("register")}},view:function(t,n){var o=n.captcha.component({form:t.form,labelClass:".col-md-4",controlClass:".col-md-8"}),i=null;return n.context.TERMS_OF_SERVICE_URL&&(i=m("a",{href:n.context.TERMS_OF_SERVICE_URL},m.trust(interpolate(gettext("By registering you agree to site's %(terms)s."),{terms:"<strong>"+gettext("terms and conditions")+"</strong>"},!0)))),m('.modal-dialog.modal-form.modal-register[role="document"]',{config:e},m(".modal-content",[n.component("modal:header",gettext("Register")),m("form.form-horizontal",{onsubmit:t.form.submit},[m('input[type="text"]',{name:"_username",style:"display: none"}),m('input[type="password"]',{name:"_password",style:"display: none"}),m(".modal-body",[n.component("form-group",{label:gettext("Username"),labelClass:".col-md-4",controlClass:".col-md-8",control:n.input({value:n.validate(t.form,"username"),id:"id_username",disabled:t.form.isBusy}),validation:t.form.errors,validationKey:"username"}),n.component("form-group",{label:gettext("E-mail"),labelClass:".col-md-4",controlClass:".col-md-8",control:n.input({value:n.validate(t.form,"email"),id:"id_email",disabled:t.form.isBusy}),validation:t.form.errors,validationKey:"email"}),n.component("form-group",{label:gettext("Password"),labelClass:".col-md-4",controlClass:".col-md-8",control:n.input({value:n.validate(t.form,"password"),type:"password",id:"id_password",disabled:t.form.isBusy}),validation:t.form.errors,validationKey:"password",helpText:n.component("password-strength",{inputs:[t.form.username(),t.form.email()],password:t.form.password()})}),o]),m(".modal-footer",[i,n.component("button",{"class":".btn-primary",submit:!0,loading:t.form.isBusy,label:gettext("Register account")})])])]))}};t.addService("modal:register",function(t){t.modal("register",n)},{after:"modals"})}(Misago.prototype),function(t){"use strict";var e=function(e){var n=this;this.showActivation=!1,this.username=m.prop(""),this.email=m.prop(""),this.password=m.prop(""),this.captcha=e.captcha.value,this.errors=null,this.validation={username:[t.validators.usernameContent(),t.validators.usernameMinLength(e.settings),t.validators.usernameMaxLength(e.settings)],email:[t.validators.email()],password:[t.validators.passwordMinLength(e.settings)],captcha:e.captcha.validator()},this.clean=function(){return null===this.errors&&e.validate(this),e.captcha.clean(this),this.hasErrors()?(e.alert.error(gettext("Form contains errors.")),!1):!0},this.submit=function(){e.ajax.post(e.context.USERS_API,{username:this.username(),email:this.email(),password:this.password(),captcha:this.captcha()}).then(this.success,this.error)},this.success=function(t){e.modal("register:complete",t)},this.error=function(t){400===t.status?(e.alert.error(gettext("Form contains errors.")),$.extend(n.errors,t)):e.ajax.error(t)}};t.addService("form:register",function(t){t.form("register",e)},{after:"forms"})}(Misago.prototype),function(t){"use strict";var e=function(t){var e=this;this.showActivation=!1,this.username=m.prop(""),this.password=m.prop(""),this.validation={username:[],password:[]},this.clean=function(){return t.validate(this)?!0:(t.alert.error(gettext("Fill out both fields.")),!1)},this.submit=function(){t.ajax.post(t.context.AUTH_API,{username:e.username(),password:e.password()}).then(function(){e.success()},function(t){e.error(t)})},this.success=function(){t.modal();var e=$("#hidden-login-form");t.ajax.refreshCsrfToken(),e.find('input[type="hidden"]').val(t.ajax.csrfToken),e.find('input[name="redirect_to"]').val(window.location.pathname),e.find('input[name="username"]').val(this.username()),e.find('input[name="password"]').val(this.password()),e.submit()},this.error=function(n){400===n.status?"inactive_admin"===n.code?t.alert.info(n.detail):"inactive_user"===n.code?(t.alert.info(n.detail),e.showActivation=!0):"banned"===n.code?(t.showBannedPage(n.detail),t.modal()):t.alert.error(n.detail):t.ajax.error(n)}};t.addService("form:sign-in",function(t){t.form("sign-in",e)},{after:"forms"})}(Misago.prototype),function(t){"use strict";function e(t,e,n){n.retain=!0}var n={controller:function(t){return{form:t.form("sign-in")}},view:function(n,o){var i=null;return n.form.showActivation&&(i=m("a.btn.btn-block.btn-success",{href:o.context.REQUEST_ACTIVATION_URL},gettext("Activate account"))),m('.modal-dialog.modal-sm.modal-signin[role="document"]',{config:e},m(".modal-content",[o.component("modal:header",gettext("Sign in")),m("form",{onsubmit:n.form.submit},[m(".modal-body",[m(".form-group",m(".control-input",t.input({disabled:n.form.isBusy,value:n.form.username,placeholder:gettext("Username or e-mail")}))),m(".form-group",m(".control-input",t.input({type:"password",disabled:n.form.isBusy,value:n.form.password,placeholder:gettext("Password")})))]),m(".modal-footer",[i,o.component("button",{"class":".btn-primary.btn-block",submit:!0,loading:n.form.isBusy,label:gettext("Sign in")}),m("a.btn.btn-block.btn-default",{href:o.context.FORGOTTEN_PASSWORD_URL},gettext("Forgot password?"))])])]))}};t.addService("modal:sign-in",function(t){t.modal("sign-in",n)},{after:"modals"})}(Misago.prototype);
 //# sourceMappingURL=/misago.js.map
 //# sourceMappingURL=/misago.js.map

+ 1 - 1
misago/static/misago/js/misago.js.map

@@ -1 +1 @@
-{"version":3,"sources":["misago.js"],"names":["window","Misago","ns","Object","getPrototypeOf","this","self","_initServices","services","orderedServices","OrderedList","order","forEach","item","factory","undefined","serviceInstance","key","_destroyServices","reverse","destroy","context","SETTINGS","setup","init","test","get","api","_services","proto","prototype","addService","name","push","after","before","PermissionDenied","message","detail","status","toString","has","obj","hasOwnProperty","value","pop","returnValue","persistent","el","isInit","retain","input","kwargs","options","disabled","config","placeholder","autocomplete","element","id","type","oninput","m","withAttr","noop","stateHooks","component","loadingState","errorState","_hasLifecycleHooks","isActive","errorHandler","bind","_controller","controller","apply","arguments","_onunload","onunload","e","vm","loading","loadingHandler","_view","view","isReady","_init","initArgs","promise","then","ondata","finalArgs","i","length","f","error","items","isOrdered","_items","add","values","values_only","_order","unordered","insertItem","insertAt","ordering","indexOf","ordered","splice","index","iterations","Page","_","isFinalized","_sections","finalize","visible","visibleIf","addSection","section","link","getSections","getDefaultLink","serializeDatetime","serialized","format","deserializeDatetime","deserialized","moment","startsWith","string","beginning","endsWith","tail","UrlConf","_patterns","patterns","prefixPattern","prefix","pattern","replace","include","url","loadingPage","getCsrfToken","cookie_name","document","cookie","cookieRegex","RegExp","match","split","Ajax","refreshCsrfToken","csrfToken","CSRF_COOKIE_NAME","runningGets","ajax","method","data","progress","deferred","ajax_settings","headers","X-CSRFToken","dataType","success","resolve","jqXHR","rejection","responseJSON","statusText","reject","$","preloaded","post","patch","put","ALERT_BASE_DISPLAY_TIME","ALERT_LENGTH_FACTOR","ALERT_MAX_DISPLAY_TIME","ALERT_HIDE_ANIMATION_LENGTH","Alert","isVisible","show","displayTime","runloop","runOnce","startComputation","endComputation","set","stop","info","warning","filtersUrl","filters","encodedKey","encodeURIComponent","encodedValue","join","Query","call","path","related","model","relation","endpoint","results","map","models","Api","alert","gettext","Auth","user","deserialize","isDesynced","newUser","handleAuthChange","isAuthenticated","localstore","handleUserChange","extend","syncSession","watch","signOut","isAnonymous","desktopMount","getElementById","compactMount","desktopComponent","dataset","componentName","compactComponent","newDesktopName","newCompactName","mount","NoCaptcha","load","QACaptcha","question","prop","label","labelClass","controlClass","control","validate","form","isBusy","validation","errors","validationKey","helpText","help_text","validator","ReCaptcha","included","wait","grecaptcha","reset","controlConfig","render","sitekey","settings","recaptcha_site_key","getResponse","clean","captcha","Captcha","types","no","qa","re","captcha_type","_components","argumentsArray","Dropdown","slots","toggle","elementId","hasChildNodes","removeClass","console","log","addClass","dropdown","boilerplate","_submit","submit","_success","_error","hasErrors","constructor","_forms","script","remote","STATIC_URL","cache","setLinks","baseUrl","attr","staticUrl","mediaUrl","prefixUrl","LocalStore","storage","localStorage","watchers","handleStorageEvent","newValueJson","JSON","parse","newValue","each","watcher","keyName","oldValue","callback","addEventListener","prefixKey","setItem","stringify","itemString","getItem","Modal","off","remove","modal","open","on","hide","_modal","_modals","Models","classes","deserializers","String","json","locale","mountPage","addMount","_mounts","activeMounts","RunLoop","_intervals","stopInterval","clearTimeout","run","callable","delay","setTimeout","result","loop","showBannedPage","ban","changeState","title","history","pushState","BANNED_URL","ticks","PageTitle","forum_name","_set_complex","completeTitle","page","page_label","interpolate","parent","EMAIL","USERNAME","validators","required","trim","email","minLength","limit_value","returnMessage","ngettext","show_value","maxLength","usernameMinLength","username_length_min","usernameMaxLength","username_length_max","usernameContent","passwordMinLength","password_length_min","validateField","validateForm","isValid","Zxcvbn","scorePassword","password","inputs","zxcvbn","score","Ban","html","plain","expires_on","deserializeBan","class","LegalPage","body","Rank","slug","description","css_class","is_tab","User","username","full_title","rank","avatar_hash","acl","absolute_url","deserializeUser","joined_on","ctrl","authChanged","refresh","location","reload","auth","onclick","banExpirationMessage","container","expirationMessage","isAfter","fromNow","bannedPage","error_message","trust","button","textFields","formGroup","groupClass","controlType","attrs","controlId","feedbackId","feedbackIcon","showFeedbackIcon","isValidated","aria-hidden","for","labelFor","loader","markup","content","header","data-dismiss","aria-label","styles","labels","passwordStrength","style","role","aria-valuenow","aria-valuemin","aria-valuemax","avatar","defaultSize","src","size","finalSize","alt","width","height","RequestLink","ViewModel","code","SEND_ACTIVATION_API_URL","done","onsubmit","donePage","showSignIn","inactivePage","activation","activateButton","href","REQUEST_ACTIVATION_URL","activationMessage","SEND_PASSWORD_RESET_API_URL","CHANGE_PASSWORD_API_URL","ResetPassword","nav","openUserMenu","dropdownToggle","data-toggle","data-misago-routed","aria-haspopup","aria-expanded","showRegister","account_activation","sync","logout","decision","confirm","USERCP_URL","messageHtml","active","inactive","lead","help","footnote","TERMS_OF_SERVICE_URL","terms","Register","showActivation","SignIn","$form","find","val","pathname","FORGOTTEN_PASSWORD_URL"],"mappings":"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","file":"misago.js","sourcesContent":["/* global -Misago */\n/* exported Misago */\n(function () {\n  'use strict';\n\n  window.Misago = function() {\n    var ns = Object.getPrototypeOf(this);\n    var self = this;\n\n    // Services init/destroy\n    this._initServices = function(services) {\n      var orderedServices = new ns.OrderedList(services).order(false);\n      orderedServices.forEach(function (item) {\n        var factory = null;\n        if (item.item.factory !== undefined) {\n          factory = item.item.factory;\n        } else {\n          factory = item.item;\n        }\n\n        var serviceInstance = factory(self);\n        if (serviceInstance) {\n          self[item.key] = serviceInstance;\n        }\n      });\n    };\n\n    this._destroyServices = function(services) {\n      var orderedServices = new ns.OrderedList(services).order();\n      orderedServices.reverse();\n      orderedServices.forEach(function (item) {\n        if (item.destroy !== undefined) {\n          item.destroy(self);\n        }\n      });\n    };\n\n    // Context data\n    this.context = {\n      // Empty settings\n      SETTINGS: {}\n    };\n\n    // App init/destory\n    this.setup = false;\n    this.init = function(setup, context) {\n      this.setup = {\n        test: ns.get(setup, 'test', false),\n        api: ns.get(setup, 'api', '/api/')\n      };\n\n      if (context) {\n        this.context = context;\n      }\n\n      this._initServices(ns._services);\n    };\n\n    this.destroy = function() {\n      this._destroyServices(ns._services);\n    };\n  };\n\n  // Services\n  var proto = window.Misago.prototype;\n\n  proto._services = [];\n  proto.addService = function(name, factory, order) {\n    proto._services.push({\n      key: name,\n      item: factory,\n      after: proto.get(order, 'after'),\n      before: proto.get(order, 'before')\n    });\n  };\n\n  // Exceptions\n  proto.PermissionDenied = function(message) {\n    this.detail = message;\n    this.status = 403;\n\n    this.toString = function() {\n      return this.detail || 'Permission denied';\n    };\n  };\n}());\n\n(function (Misago) {\n  'use strict';\n\n  Misago.has = function(obj, key) {\n    if (obj) {\n      return obj.hasOwnProperty(key);\n    } else {\n      return false;\n    }\n  };\n\n  Misago.get = function(obj, key, value) {\n    if (Misago.has(obj, key)) {\n      return obj[key];\n    } else if (value !== undefined) {\n      return value;\n    } else {\n      return undefined;\n    }\n  };\n\n  Misago.pop = function(obj, key, value) {\n    var returnValue = Misago.get(obj, key, value);\n    if (Misago.has(obj, key)) {\n      obj[key] = null;\n    }\n    return returnValue;\n  };\n}(Misago.prototype));\n\n(function (Misago) {\n  'use strict';\n\n  function persistent(el, isInit, context) {\n    context.retain = true;\n  }\n\n  Misago.input = function(kwargs) {\n    var options = {\n      disabled: kwargs.disabled || false,\n      config: kwargs.config || persistent\n    };\n\n    if (kwargs.placeholder) {\n      options.placeholder = kwargs.placeholder;\n    }\n\n    if (kwargs.autocomplete === false) {\n      options.autocomplete = 'off';\n    }\n\n    var element = 'input';\n\n    if (kwargs.id) {\n      element += '#' + kwargs.id;\n      options.key = 'field-' + kwargs.id;\n    }\n\n    element += '.form-control' + (kwargs.class || '');\n    element += '[type=\"' + (kwargs.type || 'text') + '\"]';\n\n    if (kwargs.value) {\n      options.value = kwargs.value();\n      options.oninput = m.withAttr('value', kwargs.value);\n    }\n\n    return m(element, options);\n  };\n}(Misago.prototype));\n\n(function (Misago) {\n  'use strict';\n\n  var noop = function() {};\n\n  Misago.stateHooks = function(component, loadingState, errorState) {\n    /*\n      Boilerplate for Misago components with lifecycles\n    */\n\n    // Component boilerplated (this may happen in tests)\n    if (component._hasLifecycleHooks) {\n      return component;\n    }\n    component._hasLifecycleHooks = true;\n\n    // Component active state\n    component.isActive = true;\n\n    var errorHandler = errorState.bind(component);\n\n    // Wrap controller to store lifecycle methods\n    var _controller = component.controller || noop;\n    component.controller = function() {\n      try {\n        component.isActive = true;\n        var controller = _controller.apply(component, arguments) || {};\n\n        // wrap onunload for lifestate\n        var _onunload = controller.onunload || noop;\n        controller.onunload = function() {\n          _onunload.apply(component, arguments);\n          component.isActive = false;\n        };\n\n        return controller;\n      } catch (e) {\n        errorHandler(e);\n      }\n    };\n\n    // Add state callbacks to View-Model\n    if (component.vm && component.vm.init) {\n      // setup default loading view\n      if (!component.loading) {\n        var loadingHandler = loadingState.bind(component);\n        component.loading = loadingHandler;\n      }\n\n      var _view = component.view;\n      component.view = function() {\n        if (component.vm.isReady) {\n          return _view.apply(component, arguments);\n        } else {\n          return component.loading.apply(component, arguments);\n        }\n      };\n\n      // wrap vm.init in promise handler\n      var _init = component.vm.init;\n      component.vm.init = function() {\n        var initArgs = arguments;\n        var promise = _init.apply(component.vm, initArgs);\n\n        if (promise) {\n          promise.then(function() {\n            if (component.isActive && component.vm.ondata) {\n              var finalArgs = [];\n              for (var i = 0; i < arguments.length; i++) {\n                finalArgs.push(arguments[i]);\n              }\n              for (var f = 0; f < initArgs.length; f++) {\n                finalArgs.push(initArgs[f]);\n              }\n\n              component.vm.ondata.apply(component.vm, finalArgs);\n            }\n          }, function(error) {\n            if (component.isActive) {\n              errorHandler(error);\n            }\n          });\n        }\n      };\n    }\n\n    return component;\n  };\n}(Misago.prototype));\n\n(function (Misago) {\n  'use strict';\n\n  Misago.OrderedList = function(items) {\n    this.isOrdered = false;\n    this._items = items || [];\n\n    this.add = function(key, item, order) {\n      this._items.push({\n        key: key,\n        item: item,\n\n        after: Misago.get(order, 'after'),\n        before: Misago.get(order, 'before')\n      });\n    };\n\n    this.get = function(key, value) {\n      for (var i = 0; i < this._items.length; i++) {\n        if (this._items[i].key === key) {\n          return this._items[i].item;\n        }\n      }\n\n      return value;\n    };\n\n    this.has = function(key) {\n      return this.get(key) !== undefined;\n    };\n\n    this.values = function() {\n      var values = [];\n      for (var i = 0; i < this._items.length; i++) {\n        values.push(this._items[i].item);\n      }\n      return values;\n    };\n\n    this.order = function(values_only) {\n      if (!this.isOrdered) {\n        this._items = this._order(this._items);\n        this.isOrdered = true;\n      }\n\n      if (values_only || typeof values_only === 'undefined') {\n        return this.values();\n      } else {\n        return this._items;\n      }\n    };\n\n    this._order = function(unordered) {\n      // Index of unordered items\n      var index = [];\n      unordered.forEach(function (item) {\n        index.push(item.key);\n      });\n\n      // Ordered items\n      var ordered = [];\n      var ordering = [];\n\n      // First pass: register items that\n      // don't specify their order\n      unordered.forEach(function (item) {\n        if (!item.after && !item.before) {\n          ordered.push(item);\n          ordering.push(item.key);\n        }\n      });\n\n      // Second pass: register items that\n      // specify their before to \"_end\"\n      unordered.forEach(function (item) {\n        if (item.before === \"_end\") {\n          ordered.push(item);\n          ordering.push(item.key);\n        }\n      });\n\n      // Third pass: keep iterating items\n      // until we hit iterations limit or finish\n      // ordering list\n      function insertItem(item) {\n        var insertAt = -1;\n        if (ordering.indexOf(item.key) === -1) {\n          if (item.after) {\n            insertAt = ordering.indexOf(item.after);\n            if (insertAt !== -1) {\n              insertAt += 1;\n            }\n          } else if (item.before) {\n            insertAt = ordering.indexOf(item.before);\n          }\n\n          if (insertAt !== -1) {\n            ordered.splice(insertAt, 0, item);\n            ordering.splice(insertAt, 0, item.key);\n          }\n        }\n      }\n\n      var iterations = 200;\n      while (iterations > 0 && index.length !== ordering.length) {\n        iterations -= 1;\n        unordered.forEach(insertItem);\n      }\n\n      return ordered;\n    };\n  };\n} (Misago.prototype));\n\n(function (Misago) {\n  'use strict';\n\n  Misago.Page = function(name, _) {\n    var self = this;\n\n    this.name = name;\n    this.isFinalized = false;\n    this._sections = [];\n\n    var finalize = function() {\n      if (!self.isFinalized) {\n        self.isFinalized = true;\n\n        var visible = [];\n        self._sections.forEach(function (item) {\n          if (!item.visibleIf || item.visibleIf(_)) {\n            visible.push(item);\n          }\n        });\n        self._sections = new Misago.OrderedList(visible).order(true);\n      }\n    };\n\n    this.addSection = function(section) {\n      if (this.isFinalized) {\n        throw (this.name + \" page was initialized already and no longer accepts new sections\");\n      }\n\n      this._sections.push({\n        key: section.link,\n        item: section,\n\n        after: section.after,\n        before: section.before\n      });\n    };\n\n    this.getSections = function() {\n      finalize();\n      return this._sections;\n    };\n\n    this.getDefaultLink = function() {\n      finalize();\n      return this._sections[0].link;\n    };\n  };\n}(Misago.prototype));\n\n(function (Misago) {\n  Misago.serializeDatetime = function(serialized) {\n    return serialized ? serialized.format() : null;\n  };\n\n  Misago.deserializeDatetime = function(deserialized) {\n    return deserialized ? moment(deserialized) : null;\n  };\n}(Misago.prototype));\n\n(function (Misago) {\n  'use strict';\n\n  Misago.startsWith = function(string, beginning) {\n    return string.indexOf(beginning) === 0;\n  };\n\n  Misago.endsWith = function(string, tail) {\n    return string.indexOf(tail, string.length - tail.length) !== -1;\n  };\n}(Misago.prototype));\n\n(function (Misago) {\n  'use strict';\n\n  Misago.UrlConf = function() {\n    var self = this;\n    this._patterns = [];\n\n    this.patterns = function() {\n      return this._patterns;\n    };\n\n    var prefixPattern = function(prefix, pattern) {\n      return (prefix + pattern).replace('//', '/');\n    };\n\n    var include = function(prefix, patterns) {\n      for (var i = 0; i < patterns.length; i ++) {\n        self.url(prefixPattern(prefix, patterns[i].pattern),\n                 patterns[i].component,\n                 patterns[i].name);\n      }\n    };\n\n    this.url = function(pattern, component, name) {\n      if (pattern === '') {\n        pattern = '/';\n      }\n\n      if (component instanceof Misago.UrlConf) {\n        include(pattern, component.patterns());\n      } else {\n        this._patterns.push({\n          pattern: pattern,\n          component: component.replace(/_/g, '-'),\n          name: name || component\n        });\n      }\n    };\n  };\n}(Misago.prototype));\n\n(function (Misago) {\n  'use strict';\n\n  Misago.loadingPage = function(_) {\n    return m('.page.page-loading',\n      _.component('loader')\n    );\n  };\n}(Misago.prototype));\n\n(function (Misago) {\n  'use strict';\n\n  var getCsrfToken = function(cookie_name) {\n    if (document.cookie.indexOf(cookie_name) !== -1) {\n      var cookieRegex = new RegExp(cookie_name + '\\=([^;]*)');\n      var cookie = Misago.get(document.cookie.match(cookieRegex), 0);\n      return cookie.split('=')[1];\n    } else {\n      return null;\n    }\n  };\n\n  var Ajax = function(_) {\n    this.refreshCsrfToken = function() {\n      this.csrfToken = getCsrfToken(_.context.CSRF_COOKIE_NAME);\n    };\n    this.refreshCsrfToken();\n\n    /*\n      List of GETs underway\n      We are limiting number of GETs to API to 1 per url\n    */\n    var runningGets = {};\n\n    this.ajax = function(method, url, data, progress) {\n      var promise = m.deferred();\n\n      var ajax_settings = {\n        url: url,\n        method: method,\n        headers: {\n          'X-CSRFToken': this.csrfToken\n        },\n\n        data: data || {},\n        dataType: 'json',\n\n        success: function(data) {\n          if (method === 'GET') {\n            Misago.pop(runningGets, url);\n          }\n          promise.resolve(data);\n        },\n        error: function(jqXHR) {\n          if (method === 'GET') {\n            Misago.pop(runningGets, url);\n          }\n\n          var rejection = jqXHR.responseJSON || {};\n\n          rejection.status = jqXHR.status;\n          rejection.statusText = jqXHR.statusText;\n\n          promise.reject(rejection);\n        }\n      };\n\n      if (progress) {\n        return; // not implemented... yet!\n      }\n\n      $.ajax(ajax_settings);\n      return promise.promise;\n    };\n\n    this.get = function(url) {\n      var preloaded = Misago.pop(_.context, url);\n      if (preloaded) {\n        var deferred = m.deferred();\n        deferred.resolve(preloaded);\n        return deferred.promise;\n      } else if (runningGets[url] !== undefined) {\n        return runningGets[url];\n      } else {\n        runningGets[url] = this.ajax('GET', url);\n        return runningGets[url];\n      }\n    };\n\n    this.post = function(url, data) {\n      return this.ajax('POST', url, data);\n    };\n\n    this.patch = function(url, data) {\n      return this.ajax('PATCH', url, data);\n    };\n\n    this.put = function(url, data) {\n      return this.ajax('PUT', url, data);\n    };\n\n    this.delete = function(url) {\n      return this.ajax('DELETE', url);\n    };\n  };\n\n  Misago.addService('ajax', function(_) {\n    return new Ajax(_);\n  });\n}(Misago.prototype));\n\n(function (Misago) {\n  'use strict';\n\n  var ALERT_BASE_DISPLAY_TIME = 5 * 1000;\n  var ALERT_LENGTH_FACTOR = 70;\n  var ALERT_MAX_DISPLAY_TIME = 9 * 1000;\n  var ALERT_HIDE_ANIMATION_LENGTH = 300;\n\n  var Alert = function(_) {\n    var self = this;\n\n    this.type = '';\n    this.message = null;\n    this.isVisible = false;\n\n    var show = function(type, message) {\n      self.type = type;\n      self.message = message;\n      self.isVisible = true;\n\n      var displayTime = ALERT_BASE_DISPLAY_TIME;\n      displayTime += message.length * ALERT_LENGTH_FACTOR;\n      if (displayTime > ALERT_MAX_DISPLAY_TIME) {\n        displayTime = ALERT_MAX_DISPLAY_TIME;\n      }\n\n      _.runloop.runOnce(function () {\n        m.startComputation();\n        self.isVisible = false;\n        m.endComputation();\n      }, 'flash-message-hide', displayTime);\n    };\n\n    var set = function(type, message) {\n      _.runloop.stop('flash-message-hide');\n      _.runloop.stop('flash-message-show');\n\n      if (self.isVisible) {\n        self.isVisible = false;\n        _.runloop.runOnce(function () {\n          m.startComputation();\n          show(type, message);\n          m.endComputation();\n        }, 'flash-message-show', ALERT_HIDE_ANIMATION_LENGTH);\n      } else {\n        show(type, message);\n      }\n    };\n\n    this.info = function(message) {\n      set('info', message);\n    };\n\n    this.success = function(message) {\n      set('success', message);\n    };\n\n    this.warning = function(message) {\n      set('warning', message);\n    };\n\n    this.error = function(message) {\n      set('error', message);\n    };\n  };\n\n  Misago.addService('alert', {\n    factory: function(_) {\n      return new Alert(_);\n    }\n  });\n}(Misago.prototype));\n\n(function (Misago) {\n  'use strict';\n\n  var filtersUrl = function(filters) {\n    if (typeof filters === 'object') {\n      var values = [];\n      for (var key in filters) {\n        if (filters.hasOwnProperty(key)) {\n          var encodedKey = encodeURIComponent(key);\n          var encodedValue = encodeURIComponent(filters[key]);\n          values.push(encodedKey + '=' + encodedValue);\n        }\n      }\n      return '?' + values.join('&');\n    } else {\n      return filters + '/';\n    }\n  };\n\n  var Query = function(_, call) {\n    this.url = call.url || _.setup.api;\n\n    if (call.path) {\n      this.url += call.path + '/';\n    } else if (call.related) {\n      this.url += call.related + '/';\n    } else {\n      this.url += call.model + 's' + '/';\n    }\n\n    if (call.filters) {\n      this.url += filtersUrl(call.filters);\n    }\n\n    if (call.model) {\n      this.related = function(model, filters) {\n        return new Query(_, {\n          url: this.url,\n          relation: call.model,\n          related: model,\n          filters: filters,\n        });\n      };\n    }\n\n    this.endpoint = function(path, filters) {\n      return new Query(_, {\n        url: this.url,\n        path: path,\n        filters: filters\n      });\n    };\n\n    this.get = function() {\n      var model = null;\n      if (call.related) {\n        model = call.relation + ':' + call.related;\n      } else if (call.model) {\n        model = call.model;\n      }\n\n      return _.ajax.get(this.url).then(function(data) {\n        if (model) {\n          if (data.results) {\n            data.results.map(function(item) {\n              return _.models.new(model, item);\n            });\n            return data;\n          } else {\n            return _.models.new(model, data);\n          }\n        } else {\n          return data;\n        }\n      });\n    };\n\n    this.post = function(data) {\n      return _.ajax.post(this.url, data);\n    };\n\n    this.patch = function(data) {\n      return _.ajax.patch(this.url, data);\n    };\n\n    this.put = function(data) {\n      return _.ajax.put(this.url, data);\n    };\n\n    this.delete = function() {\n      return _.ajax.delete(this.url);\n    };\n\n    // shortcut for get()\n    this.then = function(resolve, reject) {\n      return this.get().then(resolve, reject);\n    };\n  };\n\n  var Api = function(_) {\n    this.model = function(model, filters) {\n      return new Query(_, {\n        model: model,\n        filters: filters,\n      });\n    };\n\n    this.endpoint = function(path, filters) {\n      return new Query(_, {\n        path: path,\n        filters: filters\n      });\n    };\n\n    this.alert = function(rejection) {\n      // Shorthand for API errors\n      var message = gettext(\"Unknown error has occured.\");\n\n      if (rejection.status === 0) {\n        message = gettext(\"Lost connection with application.\");\n      }\n\n      if (rejection.status === 403) {\n        message = rejection.detail;\n        if (message === \"Permission denied\") {\n          message = gettext(\n            \"You don't have permission to perform this action.\");\n        }\n      }\n\n      if (rejection.status === 404) {\n        message = gettext(\"Action link is invalid.\");\n      }\n\n      _.alert.error(message);\n    };\n  };\n\n  Misago.addService('api', function(_) {\n    return new Api(_);\n  });\n}(Misago.prototype));\n\n(function (Misago) {\n  'use strict';\n\n  var Auth = function(_) {\n    var self = this;\n\n    _.user = _.models.deserialize('user', _.context.user);\n\n    // Auth state synchronization across tabs\n    this.isDesynced = false; // becomes true if auth state between tabs differs\n    this.newUser = null; // becomes user obj to which we want to sync\n\n    var handleAuthChange = function(isAuthenticated) {\n      if (!self.isDesynced) {\n        m.startComputation();\n\n        // display annoying \"you were desynced\" message\n        self.isDesynced = true;\n\n        if (isAuthenticated) {\n          self.newUser = _.localstore.get('auth-user');\n        }\n\n        m.endComputation();\n      }\n    };\n\n    var handleUserChange = function(newUser) {\n      if (!self.isDesynced) {\n        m.startComputation();\n\n        if (_.user.id !== newUser.id) {\n          self.isDesynced = true;\n          self.newUser = newUser;\n        } else if (newUser) {\n          _.user = $.extend(_.user, newUser);\n        }\n\n        m.endComputation();\n      }\n    };\n\n    var syncSession = function() {\n      _.localstore.set('auth-user', _.user);\n      _.localstore.set('auth-is-authenticated', _.user.isAuthenticated);\n\n      _.localstore.watch('auth-is-authenticated', handleAuthChange);\n      _.localstore.watch('auth-user', handleUserChange);\n    };\n\n    syncSession();\n\n    // Shorthand for signing user out\n    this.signOut = function() {\n      _.user.isAuthenticated = false;\n      _.user.isAnonymous = true;\n\n      syncSession();\n\n      var desktopMount = document.getElementById('user-menu-mount');\n      var compactMount = document.getElementById('user-menu-compact-mount');\n\n      var desktopComponent = desktopMount.dataset.componentName;\n      var compactComponent = compactMount.dataset.componentName;\n\n      var newDesktopName = desktopComponent.replace('user-nav', 'guest-nav');\n      var newCompactName = compactComponent.replace('user-nav', 'guest-nav');\n\n      m.mount(desktopMount, _.component(newDesktopName));\n      m.mount(compactMount, _.component(newCompactName));\n    };\n  };\n\n  Misago.addService('auth',\n  function(_) {\n    return new Auth(_);\n  },\n  {\n    after: 'model:user'\n  });\n}(Misago.prototype));\n\n/* global grecaptcha */\n(function (Misago) {\n  'use strict';\n\n  var NoCaptcha = function() {\n    var deferred = m.deferred();\n    deferred.resolve();\n\n    this.load = function() {\n      return deferred.promise;\n    };\n\n    this.value = function() {\n      return null;\n    };\n  };\n\n  var QACaptcha = function(_) {\n    var self = this;\n\n    this.loading = false;\n    this.question = null;\n    this.value = m.prop('');\n\n    var deferred = m.deferred();\n    this.load = function() {\n      this.value('');\n\n      if (!this.question && !this.loading) {\n        this.loading = true;\n\n        _.api.endpoint('captcha-question').get().then(function(question) {\n          self.question = question;\n          deferred.resolve();\n        }, function() {\n          _.api.alert(gettext('Failed to load CAPTCHA.'));\n          deferred.reject();\n        }).then(function() {\n          self.loading = true;\n        });\n      }\n\n      return deferred.promise;\n    };\n\n    this.component = function(kwargs) {\n      return _.component('form-group', {\n        label: this.question.question,\n        labelClass: kwargs.labelClass || null,\n        controlClass: kwargs.controlClass || null,\n        control: _.input({\n          value: _.validate(kwargs.form, 'captcha'),\n          id: 'id_captcha',\n          disabled: kwargs.form.isBusy\n        }),\n        validation: kwargs.form.errors,\n        validationKey: 'captcha',\n        helpText: this.question.help_text\n      });\n    };\n\n    this.validator = function() {\n      return [];\n    };\n  };\n\n  var ReCaptcha = function(_) {\n    this.included = false;\n    this.question = null;\n\n    var deferred = m.deferred();\n\n    var wait = function(promise) {\n      if (typeof grecaptcha !== \"undefined\") {\n        promise.resolve();\n      } else {\n        _.runloop.runOnce(function() {\n          wait(promise);\n        }, 'loading-grecaptcha', 150);\n      }\n    };\n\n    this.load = function() {\n      if (typeof grecaptcha !== \"undefined\") {\n        grecaptcha.reset();\n      }\n\n      if (!this.included) {\n        _.include('https://www.google.com/recaptcha/api.js', true);\n        this.included = true;\n      }\n\n      wait(deferred);\n\n      return deferred.promise;\n    };\n\n    var controlConfig = function(el, isInit, context) {\n      context.retain = true;\n\n      if (!isInit) {\n        grecaptcha.render('recaptcha', {\n          'sitekey': _.settings.recaptcha_site_key\n        });\n      }\n    };\n\n    this.component = function(kwargs) {\n      var control = m('#recaptcha', {\n        config: controlConfig\n      });\n\n      return _.component('form-group', {\n        label: gettext(\"Security test\"),\n        labelClass: kwargs.labelClass || null,\n        controlClass: kwargs.controlClass || null,\n        control: control,\n        validation: kwargs.form.errors,\n        validationKey: 'captcha'\n      });\n    };\n\n    this.value = function() {\n      if (typeof grecaptcha !== \"undefined\") {\n        return grecaptcha.getResponse();\n      } else {\n        return '';\n      }\n    };\n\n    this.clean = function(form) {\n      if (!this.value()) {\n        form.errors.captcha = [\n          gettext('This field is required.')\n        ];\n      } else {\n        form.errors.captcha = true;\n      }\n    };\n\n    this.validator = function() {\n      return [];\n    };\n  };\n\n  var Captcha = function(_) {\n    var types = {\n      'no': NoCaptcha,\n      'qa': QACaptcha,\n      're': ReCaptcha\n    };\n\n    var captcha = new types[_.settings.captcha_type](_);\n\n    this.value = captcha.value;\n\n    this.load = function() {\n      return captcha.load();\n    };\n\n    this.component = function(kwargs) {\n      if (captcha.component) {\n        return captcha.component(kwargs);\n      } else {\n        return null;\n      }\n    };\n\n    this.validator = function() {\n      if (captcha.validator) {\n        return captcha.validator();\n      } else {\n        return null;\n      }\n    };\n\n    this.clean = function(form) {\n      if (captcha.clean) {\n        captcha.clean(form);\n      } else {\n        form.errors.captcha = true;\n      }\n    };\n  };\n\n  Misago.addService('captcha', function(_) {\n    return new Captcha(_);\n  },\n  {\n    after: 'include'\n  });\n}(Misago.prototype));\n\n(function (Misago) {\n  'use strict';\n\n  var component = function(name, component) {\n    if (this._components[name]) {\n      if (arguments.length > 1) {\n        var argumentsArray = [this._components[name]];\n        for (var i = 1; i < arguments.length; i += 1) {\n          argumentsArray.push(arguments[i]);\n        }\n        argumentsArray.push(this);\n        return m.component.apply(undefined, argumentsArray);\n      } else {\n        return m.component(this._components[name], this);\n      }\n    } else if (component) {\n      this._components[name] = component;\n    } else {\n      throw '\"' + name + '\" component is not registered and can\\'t be created';\n    }\n  };\n\n  Misago.addService('components', function(_) {\n    _._components = {};\n    _.component = component;\n  });\n}(Misago.prototype));\n\n(function (Misago) {\n  'use strict';\n\n  Misago.addService('conf', function(_) {\n    _.settings = Misago.get(_.context, 'SETTINGS', {});\n  });\n}(Misago.prototype));\n\n(function (Misago) {\n  'use strict';\n\n  var Dropdown = function(_) {\n    var slots = {};\n\n    this.toggle = function(elementId, component) {\n      var element = document.getElementById(elementId);\n\n      if (element.hasChildNodes() && slots[elementId] === component) {\n        slots[elementId] = null;\n        m.mount(element, null);\n        $(element).removeClass('open');\n      } else {\n        console.log(element.hasChildNodes());\n        slots[elementId] = component;\n        m.mount(element, _.component(component));\n        $(element).addClass('open');\n      }\n    };\n\n    this.destroy = function() {\n      var element = null;\n\n      for (var elementId in slots) {\n        if (slots.hasOwnProperty(elementId)) {\n          element = document.getElementById(elementId);\n          if (element && element.hasChildNodes()) {\n            m.mount(element, null);\n          }\n        }\n      }\n    };\n  };\n\n  Misago.addService('dropdown', {\n    factory: function(_) {\n      return new Dropdown(_);\n    },\n    destroy: function(_) {\n      _.dropdown.destroy();\n    }\n  },\n  {\n    before: 'components'\n  });\n}(Misago.prototype));\n\n(function (Misago) {\n  'use strict';\n\n  var boilerplate = function(form) {\n    var _submit = form.submit;\n    var _success = form.success;\n    var _error = form.error;\n\n    form.isBusy = false;\n\n    form.errors = null;\n\n    form.submit = function() {\n      if (form.isBusy) {\n        return false;\n      }\n\n      if (form.clean) {\n        if (form.clean()) {\n          form.isBusy = true;\n          _submit.apply(form);\n        }\n      } else {\n        form.isBusy = true;\n      }\n      return false;\n    };\n\n    form.success = function() {\n      m.startComputation();\n\n      _success.apply(form, arguments);\n      form.isBusy = false;\n\n      m.endComputation();\n    };\n\n    form.error = function() {\n      m.startComputation();\n\n      _error.apply(form, arguments);\n      form.isBusy = false;\n\n      m.endComputation();\n    };\n\n    form.hasErrors = function() {\n      if (form.errors === null) {\n        return false;\n      }\n\n      for (var key in form.validation) {\n        if (form.validation.hasOwnProperty(key)) {\n          if (form.errors[key] !== true) {\n            return true;\n          }\n        }\n      }\n\n      return false;\n    };\n\n    return form;\n  };\n\n  var form = function(name, constructor) {\n    if (this._forms[name]) {\n      if (constructor) {\n        return boilerplate(new this._forms[name](constructor, this));\n      } else {\n        return boilerplate(new this._forms[name](this));\n      }\n    } else {\n      this._forms[name] = constructor;\n    }\n  };\n\n  Misago.addService('forms', function(_) {\n    _._forms = {};\n    _.form = form;\n  });\n}(Misago.prototype));\n\n(function (Misago) {\n  'use strict';\n\n  var include = function(script, remote) {\n    if (!remote) {\n      script = this.context.STATIC_URL + script;\n    }\n\n    $.ajax({\n      url: script,\n      cache: true,\n      dataType: 'script'\n    });\n  };\n\n  Misago.addService('include', function(_) {\n    _.include = include;\n  },\n  {\n    after: 'conf'\n  });\n}(Misago.prototype));\n\n(function (Misago) {\n  'use strict';\n\n  var setLinks = function(_) {\n    _.baseUrl = $('base').attr('href');\n\n    var staticUrl = Misago.get(_.context, 'STATIC_URL', '/');\n    var mediaUrl = Misago.get(_.context, 'MEDIA_URL', '/');\n\n    // Media/Static urls\n    var prefixUrl = function(prefix) {\n      return function(url) {\n        return prefix + url;\n      };\n    };\n\n    _.staticUrl = prefixUrl(staticUrl);\n    _.mediaUrl = prefixUrl(mediaUrl);\n  };\n\n  Misago.addService('links', function(_) {\n    setLinks(_);\n  });\n}(Misago.prototype));\n\n(function (Misago) {\n  'use strict';\n\n  var LocalStore = function() {\n    var storage = window.localStorage;\n    var prefix = '_misago_';\n    var watchers = [];\n\n    var handleStorageEvent = function(e) {\n      var newValueJson = JSON.parse(e.newValue);\n      $.each(watchers, function(i, watcher) {\n        if (watcher.keyName === e.key && e.oldValue !== e.newValue) {\n          watcher.callback(newValueJson);\n        }\n      });\n    };\n\n    window.addEventListener('storage', handleStorageEvent);\n\n    var prefixKey = function(keyName) {\n      return prefix + keyName;\n    };\n\n    this.set = function(keyName, value) {\n      storage.setItem(prefixKey(keyName), JSON.stringify(value));\n    };\n\n    this.get = function(keyName) {\n      var itemString = storage.getItem(prefixKey(keyName));\n      if (itemString) {\n        return JSON.parse(itemString);\n      } else {\n        return null;\n      }\n    };\n\n    this.watch = function(keyName, callback) {\n      watchers.push({keyName: prefixKey(keyName), callback: callback});\n    };\n\n    this.destroy = function() {\n      this.watchers = [];\n    };\n  };\n\n  Misago.addService('localstore', {\n    factory: function() {\n      return new LocalStore();\n    },\n    destroy: function(_) {\n      _.localstore.destroy();\n    }\n  });\n}(Misago.prototype));\n\n(function (Misago) {\n  'use strict';\n\n  var Modal = function() {\n    var self = this;\n\n    var element = document.getElementById('modal-mount');\n\n    this.destroy = function() {\n      $(element).off();\n      $('body').removeClass('modal-open');\n      $('.modal-backdrop').remove();\n    };\n\n    // Open/close modal\n    var modal = $(element).modal({show: false});\n    this.open = false;\n\n    modal.on('hidden.bs.modal', function () {\n      if (self.open) {\n        m.mount(element, null);\n        this.open = false;\n      }\n    });\n\n    this.show = function(component) {\n      this.open = true;\n      m.mount(element, component);\n      modal.modal('show');\n    };\n\n    this.hide = function() {\n      modal.modal('hide');\n    };\n  };\n\n  Misago.addService('_modal', {\n    factory: function() {\n      return new Modal();\n    },\n    destroy: function(_) {\n      _._modal.destroy();\n    }\n  },\n  {\n    before: 'mount-components'\n  });\n}(Misago.prototype));\n\n(function (Misago) {\n  'use strict';\n\n  var modal = function(name, component) {\n    if (this._modals[name]) {\n      var argumentsArray = [this._modals[name]];\n      for (var i = 1; i < arguments.length; i += 1) {\n        argumentsArray.push(arguments[i]);\n      }\n      argumentsArray.push(this);\n      this._modal.show(m.component.apply(m, argumentsArray));\n    } else if (name) {\n      this._modals[name] = component;\n    } else {\n      this._modal.hide();\n    }\n  };\n\n  Misago.addService('modals', function(_) {\n    _._modals = {};\n    _.modal = modal;\n  },\n  {\n    after: '_modal'\n  });\n}(Misago.prototype));\n\n(function (Misago) {\n  'use strict';\n\n  var Models = function() {\n    this.classes = {};\n    this.deserializers = {};\n\n    this.add = function(name, kwargs) {\n      if (kwargs.class) {\n        this.classes[name] = kwargs.class;\n      }\n\n      if (kwargs.deserialize) {\n        this.deserializers[name] = kwargs.deserialize;\n      }\n    };\n\n    this.new = function(name, data) {\n      if (this.classes[name]) {\n        // Coerce ID to string\n        // This is done to avoid type comparisions gotchas\n        // later into app\n        data.id = data.id ? String(data.id) : null;\n\n        return new this.classes[name](data);\n      } else {\n        return data;\n      }\n    };\n\n    this.deserialize = function(name, json) {\n      if (this.deserializers[name]) {\n        return this.new(name, this.deserializers[name](json, this));\n      } else {\n        return this.new(name, json);\n      }\n    };\n  };\n\n  Misago.addService('models', function() {\n    return new Models();\n  });\n}(Misago.prototype));\n\n(function (Misago) {\n  'use strict';\n\n  Misago.addService('set-momentjs-locale', function() {\n    moment.locale($('html').attr('lang'));\n  });\n}(Misago.prototype));\n\n(function (Misago) {\n  'use strict';\n\n  var mount = document.getElementById('page-mount');\n\n  Misago.addService('mount-page', function(_) {\n    _.mountPage = function(component) {\n      m.mount(mount, component);\n    };\n  });\n}(Misago.prototype));\n\n(function (Misago) {\n  'use strict';\n\n  var addMount = function(name) {\n    if (this._mounts.indexOf(name) === -1) {\n      this._mounts.push(name);\n    }\n  };\n\n  Misago.addService('mounts', function(_) {\n    // Default monts\n    _._mounts = [\n      'alert-mount',\n      'auth-changed-message-mount',\n      'page-component',\n      'user-menu-mount',\n      'user-menu-compact-mount'\n    ];\n\n    // Function for including new mounts\n    _.addMount = addMount;\n\n    // List of active mounts, for debugging\n    _.activeMounts = {};\n  });\n\n  Misago.addService('mount-components', {\n    factory: function(_) {\n      _._mounts.forEach(function(elementId) {\n        var mount = document.getElementById(elementId);\n        if (mount) {\n          _.activeMounts[elementId] = mount.dataset.componentName;\n          m.mount(mount, _.component(mount.dataset.componentName));\n        }\n      });\n    },\n    destroy: function() {\n      _._mounts.forEach(function(elementId) {\n        var mount = document.getElementById(elementId);\n        if (mount && mount.hasChildNodes()) {\n          m.mount(mount, null);\n        }\n      });\n    }\n  },\n  {\n    before: '_end'\n  });\n}(Misago.prototype));\n\n(function (Misago) {\n  'use strict';\n\n  var RunLoop = function(_) {\n    var self = this;\n\n    this._intervals = {};\n\n    var stopInterval = function(name) {\n      if (self._intervals[name]) {\n        window.clearTimeout(self._intervals[name]);\n        self._intervals[name] = null;\n      }\n    };\n\n    this.run = function(callable, name, delay) {\n      this._intervals[name] = window.setTimeout(function() {\n        stopInterval(name);\n        var result = callable(_);\n        if (result !== false) {\n          self.run(callable, name, delay);\n        }\n      }, delay);\n    };\n\n    this.runOnce = function(callable, name, delay) {\n      this._intervals[name] = window.setTimeout(function() {\n        stopInterval(name);\n        callable(_);\n      }, delay);\n    };\n\n    this.stop = function(name) {\n      for (var loop in this._intervals) {\n        if (!name || name === loop) {\n          stopInterval(loop);\n        }\n      }\n    };\n  };\n\n  Misago.addService('runloop', {\n    factory: function(_) {\n      return new RunLoop(_);\n    },\n    destroy: function(_) {\n      _.runloop.stop();\n    }\n  });\n}(Misago.prototype));\n\n(function (Misago) {\n  'use strict';\n\n  Misago.addService('show-banned-page', function(_) {\n    _.showBannedPage = function(ban, changeState) {\n      var component = _.component(\n        'banned-page', _.models.deserialize('ban', ban));\n\n      if (typeof changeState === 'undefined' || !changeState) {\n        _.title.set(gettext(\"You are banned\"));\n        window.history.pushState({}, \"\", _.context.BANNED_URL);\n      }\n\n      _.mountPage(component);\n    };\n  });\n}(Misago.prototype));\n\n(function (Misago) {\n  'use strict';\n\n  Misago.addService('start-tick', function(_) {\n    var ticks = m.prop();\n\n    _.runloop.run(function() {\n      m.startComputation();\n      // just tick once a minute so stuff gets rerendered\n      ticks(ticks() + 1);\n      // syncing dynamic timestamps, etc ect\n      m.endComputation();\n    }, 'tick', 60000);\n  });\n}(Misago.prototype));\n\n(function (Misago) {\n  'use strict';\n\n  var PageTitle = function(forum_name) {\n    this.set = function(title) {\n      if (title) {\n        this._set_complex(title);\n      } else {\n        document.title = forum_name;\n      }\n    };\n\n    this._set_complex = function(title) {\n      if (typeof title === 'string') {\n        title = {title: title};\n      }\n\n      var completeTitle = title.title;\n\n      if (typeof title.page !== 'undefined' && title.page > 1) {\n        var page_label = interpolate(\n          gettext('page %(page)s'), { page:title.page }, true);\n        completeTitle += ' (' + page_label + ')';\n      }\n\n      if (typeof title.parent !== 'undefined') {\n        completeTitle += ' | ' + title.parent;\n      }\n\n      document.title = completeTitle + ' | ' + forum_name;\n    };\n  };\n\n  Misago.addService('page-title', function(_) {\n    _.title = new PageTitle(_.settings.forum_name);\n  });\n}(Misago.prototype));\n\n(function (Misago) {\n  'use strict';\n\n  var EMAIL = /^(([^<>()[\\]\\.,;:\\s@\\\"]+(\\.[^<>()[\\]\\.,;:\\s@\\\"]+)*)|(\\\".+\\\"))@(([^<>()[\\]\\.,;:\\s@\\\"]+\\.)+[^<>()[\\]\\.,;:\\s@\\\"]{2,})$/i;\n  var USERNAME = new RegExp('^[0-9a-z]+$', 'i');\n\n  // Validators namespace\n  Misago.validators = {\n    required: function() {\n      return function(value) {\n        if ($.trim(value).length === 0) {\n          return gettext(\"This field is required.\");\n        }\n      };\n    },\n    email: function(message) {\n      return function(value) {\n        if (!EMAIL.test(value)) {\n          return message || gettext(\"Enter a valid email address.\");\n        }\n      };\n    },\n    minLength: function(limit_value, message) {\n      return function(value) {\n        var returnMessage = '';\n        var length = $.trim(value).length;\n\n        if (length < limit_value) {\n          if (message) {\n            returnMessage = message(limit_value, length);\n          } else {\n            returnMessage = ngettext(\n              \"Ensure this value has at least %(limit_value)s character (it has %(show_value)s).\",\n              \"Ensure this value has at least %(limit_value)s characters (it has %(show_value)s).\",\n              limit_value);\n          }\n          return interpolate(returnMessage, {\n            limit_value: limit_value,\n            show_value: length\n          }, true);\n        }\n      };\n    },\n    maxLength: function(limit_value, message) {\n      return function(value) {\n        var returnMessage = '';\n        var length = $.trim(value).length;\n\n        if (length > limit_value) {\n          if (message) {\n            returnMessage = message(limit_value, length);\n          } else {\n            returnMessage = ngettext(\n              \"Ensure this value has at most %(limit_value)s character (it has %(show_value)s).\",\n              \"Ensure this value has at most %(limit_value)s characters (it has %(show_value)s).\",\n              limit_value);\n          }\n          return interpolate(returnMessage, {\n            limit_value: limit_value,\n            show_value: length\n          }, true);\n        }\n      };\n    },\n    usernameMinLength: function(settings) {\n      var message = function(limit_value) {\n        return ngettext(\n          \"Username must be at least %(limit_value)s character long.\",\n          \"Username must be at least %(limit_value)s characters long.\",\n          limit_value);\n      };\n      return this.minLength(settings.username_length_min, message);\n    },\n    usernameMaxLength: function(settings) {\n      var message = function(limit_value) {\n        return ngettext(\n          \"Username cannot be longer than %(limit_value)s character.\",\n          \"Username cannot be longer than %(limit_value)s characters.\",\n          limit_value);\n      };\n      return this.maxLength(settings.username_length_max, message);\n    },\n    usernameContent: function() {\n      return function(value) {\n        if (!USERNAME.test($.trim(value))) {\n          return gettext(\"Username can only contain latin alphabet letters and digits.\");\n        }\n      };\n    },\n    passwordMinLength: function(settings) {\n      var message = function(limit_value) {\n        return ngettext(\n          \"Valid password must be at least %(limit_value)s character long.\",\n          \"Valid password must be at least %(limit_value)s characters long.\",\n          limit_value);\n      };\n      return this.minLength(settings.password_length_min, message);\n    }\n  };\n\n  var validateField = function(value, validators) {\n    var result = Misago.validators.required()(value);\n    var errors = [];\n\n    if (result) {\n      return [result];\n    } else {\n      for (var i in validators) {\n        result = validators[i](value);\n\n        if (result) {\n          errors.push(result);\n        }\n      }\n    }\n\n    return errors.length ? errors : true;\n  };\n\n  var validateForm = function(form) {\n    var errors = {};\n    var value = null;\n\n    var isValid = true;\n\n    for (var key in form.validation) {\n      if (form.validation.hasOwnProperty(key)) {\n        value = form[key]();\n        errors[key] = validateField(form[key](), form.validation[key]);\n        if (errors[key] !== true) {\n          isValid = false;\n        }\n      }\n    }\n\n    form.errors = errors;\n    return isValid;\n  };\n\n  var validate = function(form, name) {\n    if (name) {\n      return function(value) {\n        var errors = null;\n        if (typeof value !== 'undefined') {\n          errors = validateField(value, form.validation[name]);\n          if (errors) {\n            if (!form.errors) {\n              form.errors = {};\n            }\n            form.errors[name] = errors;\n          }\n          form[name](value);\n          return form[name](value);\n        } else {\n          return form[name]();\n        }\n      };\n    } else {\n      return validateForm(form);\n    }\n  };\n\n  Misago.addService('validate', {\n    factory: function() {\n      return validate;\n    }\n  });\n}(Misago.prototype));\n\n/* global zxcvbn */\n(function (Misago) {\n  'use strict';\n\n  var Zxcvbn = function(_) {\n    this.included = false;\n\n    this.scorePassword = function(password, inputs) {\n      // 0-4 score, the more the stronger password\n      return zxcvbn(password, inputs).score;\n    };\n\n    // loading\n    this.include = function() {\n      _.include('misago/js/zxcvbn.js');\n      this.included = true;\n    };\n\n    var wait = function(promise) {\n      if (typeof zxcvbn !== \"undefined\") {\n        promise.resolve();\n      } else {\n        _.runloop.runOnce(function() {\n          wait(promise);\n        }, 'loading-zxcvbn', 150);\n      }\n    };\n\n    var deferred = m.deferred();\n    this.load = function() {\n      if (!this.included) {\n        this.include();\n      }\n      wait(deferred);\n      return deferred.promise;\n    };\n  };\n\n  Misago.addService('zxcvbn', function(_) {\n    return new Zxcvbn(_);\n  },\n  {\n    after: 'include'\n  });\n}(Misago.prototype));\n\n(function (Misago) {\n  'use strict';\n\n  var Ban = function(data) {\n    this.message = {\n      html: data.message.html,\n      plain: data.message.plain,\n    };\n\n    this.expires_on = data.expires_on;\n  };\n\n  var deserializeBan = function(data) {\n    data.expires_on = Misago.deserializeDatetime(data.expires_on);\n\n    return data;\n  };\n\n  Misago.addService('model:ban', function(_) {\n    _.models.add('ban', {\n      class: Ban,\n      deserialize: deserializeBan\n    });\n  },\n  {\n    after: 'models'\n  });\n} (Misago.prototype));\n\n(function (Misago) {\n  'use strict';\n\n  var LegalPage = function(data) {\n    this.title = data.title;\n    this.body = data.body;\n    this.link = data.link;\n  };\n\n  Misago.addService('model:legal-page', function(_) {\n    _.models.add('legal-page', {\n      class: LegalPage\n    });\n  },\n  {\n    after: 'models'\n  });\n} (Misago.prototype));\n\n(function (Misago) {\n  'use strict';\n\n  var Rank = function(data) {\n    this.id = data.id;\n\n    this.name = data.name;\n    this.slug = data.slug;\n\n    this.description = data.description;\n\n    this.title = data.title;\n    this.css_class = data.css_class;\n\n    this.is_tab = data.is_tab;\n  };\n\n  Misago.addService('model:rank', function(_) {\n    _.models.add('rank', {\n      class: Rank\n    });\n  },\n  {\n    after: 'models'\n  });\n} (Misago.prototype));\n\n(function (Misago) {\n  'use strict';\n\n  var User = function(data) {\n    this.id = data.id;\n\n    this.isAuthenticated = !!this.id;\n    this.isAnonymous = !this.isAuthenticated;\n\n    this.username = data.username;\n    this.slug = data.slug;\n\n    this.email = data.email;\n\n    this.full_title = data.full_title;\n    this.rank = data.rank;\n\n    this.avatar_hash = data.avatar_hash;\n\n    this.acl = data.acl;\n\n    this.absolute_url = data.absolute_url;\n  };\n\n  var deserializeUser = function(data, models) {\n    if (data.joined_on) {\n      data.joined_on = Misago.deserializeDatetime(data.joined_on);\n    }\n\n    if (data.rank) {\n      data.rank = models.deserialize('rank', data.rank);\n    }\n\n    return data;\n  };\n\n  Misago.addService('model:user', function(_) {\n    _.models.add('user', {\n      class: User,\n      deserialize: deserializeUser\n    });\n  },\n  {\n    after: 'model:rank'\n  });\n} (Misago.prototype));\n\n(function (Misago) {\n  'use strict';\n\n  function persistent(el, isInit, context) {\n    context.retain = true;\n  }\n\n  var alert = {\n    classes: {\n      'info': 'alert-info',\n      'success': 'alert-success',\n      'warning': 'alert-warning',\n      'error': 'alert-danger'\n    },\n    view: function(ctrl, _) {\n      var config = {\n        config: persistent,\n        class: _.alert.isVisible ? 'in' : 'out'\n      };\n\n      return m('.alerts', config,\n        m('p.alert', {class: this.classes[_.alert.type]},\n          _.alert.message\n        )\n      );\n    }\n  };\n\n  Misago.addService('component:alert', function(_) {\n    _.component('alert', alert);\n  },\n  {\n    after: 'components'\n  });\n}(Misago.prototype));\n\n(function (Misago) {\n  'use strict';\n\n  function persistent(el, isInit, context) {\n    context.retain = true;\n  }\n\n  var authChanged = {\n    refresh: function() {\n      window.location.reload();\n    },\n    view: function(ctrl, _) {\n      var message = '';\n\n      var options = {\n        config: persistent,\n        class: (_.auth.isDesynced ? 'show' : null)\n      };\n\n      if (_.auth.isDesynced) {\n        if (_.auth.newUser && _.auth.newUser.isAuthenticated) {\n          message = gettext(\"You have signed in as %(username)s. Please refresh this page before continuing.\");\n          message = interpolate(message, {username: _.auth.newUser.username}, true);\n        } else {\n          message = gettext(\"%(username)s, you have been signed out. Please refresh this page before continuing.\");\n          message = interpolate(message, {username: _.user.username}, true);\n        }\n      }\n\n      return m('.auth-changed-message', options,\n        m('',\n          m('.container', [\n            m('p',\n              message\n            ),\n            m('p', [\n              m('button.btn.btn-default[type=\"button\"]', {onclick: this.refresh},\n                gettext(\"Reload page\")\n              ),\n              m('span.hidden-xs.hidden-sm.text-muted',\n                gettext(\"or press F5 key.\")\n              )\n            ])\n          ])\n        )\n      );\n    }\n  };\n\n  Misago.addService('component:auth-changed-message', function(_) {\n    _.component('auth-changed-message', authChanged);\n  },\n  {\n    after: 'components'\n  });\n}(Misago.prototype));\n\n(function (Misago) {\n  'use strict';\n\n  var banExpirationMessage = {\n    controller: function(ban, container) {\n      var _ = container || ban;\n\n      if (container) {\n        return ban;\n      } else {\n        return _.models.deserialize('ban', _.context.ban);\n      }\n    },\n    view: function(ban) {\n      var expirationMessage = null;\n      if (ban.expires_on) {\n        if (ban.expires_on.isAfter(moment())) {\n          expirationMessage = interpolate(\n            gettext('This ban expires %(expires_on)s.'),\n            {'expires_on': ban.expires_on.fromNow()},\n            true);\n        } else {\n          expirationMessage = gettext('This ban has expired.');\n        }\n      } else {\n        expirationMessage = gettext('This ban is permanent.');\n      }\n\n      return m('p', expirationMessage);\n    }\n  };\n\n  Misago.addService('component:ban-expiration-message', function(_) {\n    _.component('ban-expiration-message', banExpirationMessage);\n  },\n  {\n    after: 'components'\n  });\n}(Misago.prototype));\n\n(function (Misago) {\n  'use strict';\n\n  var bannedPage = {\n    view: function(ctrl, ban, _) {\n      var error_message = [];\n\n      if (ban.message.html) {\n        error_message.push(m('.lead', m.trust(ban.message.html)));\n      } else {\n        error_message.push(m('p.lead', ban.message.plain));\n      }\n\n      error_message.push(_.component('ban-expiration-message', ban));\n\n      return m('.page.page-error.page-error-banned',\n        m('.container',\n          m('.message-panel', [\n            m('.message-icon',\n              m('span.material-icon', 'highlight_off')\n            ),\n            m('.message-body', error_message)\n          ])\n        )\n      );\n    }\n  };\n\n  Misago.addService('component:banned-page', function(_) {\n    _.component('banned-page', bannedPage);\n  },\n  {\n    after: 'components'\n  });\n}(Misago.prototype));\n\n(function (Misago) {\n  'use strict';\n\n  var button = {\n    view: function(ctrl, kwargs) {\n      var options = {\n        disabled: kwargs.disabled || kwargs.loading || false,\n        config: kwargs.config || null,\n        loading: kwargs.loading || false,\n        type: kwargs.submit ? 'submit' : 'button',\n        onclick: kwargs.onclick || null\n      };\n\n      var element = 'button[type=\"' + options.type + '\"].btn';\n      if (options.loading) {\n        element += '.btn-loading';\n      }\n\n      if (kwargs.id) {\n        element += '#' + kwargs.id;\n      }\n\n      element += (kwargs.class || '');\n\n      var label = kwargs.label;\n      if (options.loading) {\n        label = [\n          label,\n          m('.loader-compact', [\n            m('.bounce1'),\n            m('.bounce2'),\n            m('.bounce3')\n          ])\n        ];\n      }\n\n      return m(element, options, label);\n    },\n  };\n\n  Misago.addService('component:button', function(_) {\n    _.component('button', button);\n  },\n  {\n    after: 'components'\n  });\n} (Misago.prototype));\n\n(function (Misago) {\n  'use strict';\n\n  var textFields = ['text', 'password', 'email'];\n\n  var formGroup = {\n    view: function(ctrl, kwargs) {\n      var groupClass = '.form-group';\n      var errors = null;\n      var helpText = null;\n\n      var controlType = kwargs.control.attrs.type;\n      var controlId = kwargs.control.attrs.id;\n\n      var feedbackId = controlId + '_feedback';\n      var feedbackIcon = null;\n      var showFeedbackIcon = null;\n\n      var isValidated = kwargs.validationKey && kwargs.validation !== null;\n\n      kwargs.control.attrs['aria-describedby'] = '';\n\n      if (isValidated && kwargs.validation[kwargs.validationKey]) {\n        showFeedbackIcon = textFields.indexOf(controlType) >= 0;\n        kwargs.control.attrs['aria-describedby'] = feedbackId;\n\n        if (kwargs.validation[kwargs.validationKey] === true) {\n          groupClass += '.has-success';\n          feedbackIcon = [\n            m('span.material-icon.form-control-feedback',\n              {\n                'aria-hidden': 'true'\n              },\n              'check'\n            ),\n            m('span.sr-only#' + feedbackId, gettext(\"(success)\"))\n          ];\n        } else {\n          groupClass += '.has-error';\n          errors = kwargs.validation[kwargs.validationKey];\n          feedbackIcon = [\n            m('span.material-icon.form-control-feedback',\n              {\n                'aria-hidden': 'true'\n              },\n              'clear'\n            ),\n            m('span.sr-only#' + feedbackId, gettext(\"(error)\"))\n          ];\n        }\n      }\n\n      if (kwargs.helpText) {\n        if (typeof kwargs.helpText === 'string' ||\n            kwargs.helpText instanceof String) {\n          helpText = m('p.help-block', kwargs.helpText);\n        } else {\n          helpText = kwargs.helpText;\n        }\n      }\n\n      return m(groupClass, [\n        m('label.control-label' + (kwargs.labelClass || ''),\n          {\n            for: kwargs.labelFor || controlId\n          },\n          kwargs.label + ':'\n        ),\n        m(kwargs.controlClass || '', [\n          kwargs.control,\n          showFeedbackIcon ? feedbackIcon : null,\n          errors ? m('.help-block.errors', errors.map(function(item) {\n            return m('p', item);\n          })) : null,\n          helpText\n        ])\n      ]);\n    },\n  };\n\n  Misago.addService('component:form-group', function(_) {\n    _.component('form-group', formGroup);\n  },\n  {\n    after: 'components'\n  });\n} (Misago.prototype));\n\n(function (Misago) {\n  'use strict';\n\n  var loader = {\n    view: function() {\n      return m('.loader.sk-folding-cube', [\n        m('.sk-cube1.sk-cube'),\n        m('.sk-cube2.sk-cube'),\n        m('.sk-cube4.sk-cube'),\n        m('.sk-cube3.sk-cube')\n      ]);\n    }\n  };\n\n  Misago.addService('component:loader', function(_) {\n    _.component('loader', loader);\n  },\n  {\n    after: 'components'\n  });\n} (Misago.prototype));\n\n(function (Misago) {\n  'use strict';\n\n  var persistent = function(el, isInit, context) {\n    context.retain = true;\n  };\n\n  var markup = {\n    view: function(ctrl, content) {\n      return m('article.misago-markup', {config: persistent},\n        m.trust(content)\n      );\n    }\n  };\n\n  Misago.addService('component:markup', function(_) {\n    _.component('markup', markup);\n  },\n  {\n    after: 'components'\n  });\n}(Misago.prototype));\n\n(function (Misago) {\n  'use strict';\n\n  var header = {\n    view: function(ctrl, title) {\n      return m('.modal-header', [\n        m('button.close[type=\"button\"]',\n          {'data-dismiss': 'modal', 'aria-label': gettext('Close')},\n          m('span', {'aria-hidden': 'true'}, m.trust('&times;'))\n        ),\n        m('h4#misago-modal-label.modal-title', title)\n      ]);\n    }\n  };\n\n  Misago.addService('component:modal:header', function(_) {\n    _.component('modal:header', header);\n  },\n  {\n    after: 'components'\n  });\n}(Misago.prototype));\n\n(function (Misago) {\n  'use strict';\n\n  var header = {\n    view: function(ctrl, options) {\n      return m('.page-header',\n        m('.container', [\n          m('h1', options.title),\n        ])\n      );\n    }\n  };\n\n  Misago.addService('component:header', function(_) {\n    _.component('header', header);\n  },\n  {\n    after: 'components'\n  });\n}(Misago.prototype));\n\n(function (Misago) {\n  'use strict';\n\n  var persistent = function(el, isInit, context) {\n    context.retain = true;\n  };\n\n  var styles = [\n    'progress-bar-danger',\n    'progress-bar-warning',\n    'progress-bar-warning',\n    'progress-bar-primary',\n    'progress-bar-success'\n  ];\n\n  var labels = [\n    gettext('Entered password is very weak.'),\n    gettext('Entered password is weak.'),\n    gettext('Entered password is average.'),\n    gettext('Entered password is strong.'),\n    gettext('Entered password is very strong.')\n  ];\n\n  var passwordStrength = {\n    view: function(ctrl, kwargs, _) {\n      var score = _.zxcvbn.scorePassword(kwargs.password, kwargs.inputs);\n      var options = {\n        config: persistent,\n        class: styles[score],\n        style: \"width: \" + (20 + (20 * score)) + '%',\n        'role': \"progressbar\",\n        'aria-valuenow': score,\n        'aria-valuemin': \"0\",\n        'aria-valuemax': \"4\"\n      };\n\n      return m('.help-block.password-strength', {key: 'password-strength'}, [\n        m('.progress',\n          m('.progress-bar', options,\n            m('span.sr-only', labels[score])\n          )\n        ),\n        m('p.text-small', labels[score])\n      ]);\n    },\n  };\n\n  Misago.addService('component:password-strength', function(_) {\n    _.component('password-strength', passwordStrength);\n  },\n  {\n    after: 'components'\n  });\n} (Misago.prototype));\n\n(function (Misago) {\n  'use strict';\n\n  var avatar = {\n    defaultSize: 100,\n\n    src: function(user, size, _) {\n      var src = _.baseUrl + 'user-avatar/';\n\n      if (user && user.id) {\n        // just avatar hash, size and user id\n        src += user.avatar_hash + '/' + size + '/' + user.id + '.png';\n      } else {\n        // just append avatar size to file to produce no-avatar placeholder\n        src += size + '.png';\n      }\n\n      return src;\n    },\n    view: function(ctrl, user, size, _) {\n      var finalSize = size || this.defaultSize;\n      return m('img', {\n        alt: user && user.username ? user.username : gettext(\"Unregistered\"),\n        width: finalSize,\n        height: finalSize,\n        src: this.src(user, finalSize, _)\n      });\n    }\n  };\n\n  Misago.addService('component:user-avatar', function(_) {\n    _.component('user-avatar', avatar);\n  },\n  {\n    after: 'components'\n  });\n} (Misago.prototype));\n\n(function (Misago) {\n  'use strict';\n\n  var RequestLink = function(vm, _) {\n    var self = this;\n\n    this.email = m.prop('');\n\n    this.validation = {\n      'email': [\n        Misago.validators.email()\n      ]\n    };\n\n    this.clean = function() {\n      if (!_.validate(this)) {\n        _.alert.error(gettext(\"Enter a valid email address.\"));\n        return false;\n      } else {\n        return true;\n      }\n    };\n\n    this.submit = function() {\n      _.ajax.post(vm.api, {\n        email: self.email()\n      }).then(function(user) {\n        self.success(user);\n      }, function(error) {\n        self.error(error);\n      });\n    };\n\n    this.success = function(user) {\n      vm.success(user);\n    };\n\n    this.error = function(rejection) {\n      if (rejection.status === 400) {\n          vm.error(rejection, _);\n      } else {\n        _.api.alert(rejection);\n      }\n    };\n\n    this.reset = function() {\n      this.email('');\n      vm.reset();\n    };\n  };\n\n  Misago.addService('form:request-link', function(_) {\n    _.form('request-link', RequestLink);\n  },\n  {\n    after: 'forms'\n  });\n} (Misago.prototype));\n\n(function (Misago) {\n  'use strict';\n\n  var style = '.well.well-form.well-form-request-activation-link';\n\n  var ViewModel = function(api) {\n    this.api = api;\n    this.user = null;\n\n    this.success = function(user) {\n      this.user = user;\n    };\n\n    this.error = function(rejection, _) {\n      if (rejection.code === 'already_active') {\n        _.alert.info(rejection.detail);\n        _.modal('sign-in');\n      } else if (rejection.code === 'inactive_admin') {\n        _.alert.info(rejection.detail);\n      } else {\n        _.alert.error(rejection.detail);\n      }\n    };\n\n    this.reset = function() {\n      this.user = null;\n    };\n  };\n\n  var form = {\n    controller: function(_) {\n      var vm = new ViewModel(_.context.SEND_ACTIVATION_API_URL);\n\n      return {\n        vm: vm,\n        form: _.form('request-link', vm)\n      };\n    },\n    view: function(ctrl, _) {\n      if (ctrl.vm.user) {\n        return this.done(ctrl.vm, ctrl.form, _);\n      } else {\n        return this.form(ctrl.form, _);\n      }\n    },\n    done: function(vm, form, _) {\n      var message = gettext(\"Activation link sent to %(email)s.\");\n\n      return m(style + '.well-done',\n        m('.done-message', [\n          m('.message-icon',\n            m('span.material-icon', 'check')\n          ),\n          m('.message-body',\n            m('p',\n              interpolate(message, {\n                email: vm.user.email\n              }, true)\n            )\n          ),\n          _.component('button', {\n            class: '.btn-default.btn-block',\n            submit: false,\n            label: gettext(\"Request another link\"),\n            onclick: form.reset.bind(form)\n          })\n\n        ])\n      );\n    },\n    form: function(form, _) {\n      return m(style,\n        m('form', {onsubmit: form.submit}, [\n          m('.form-group',\n            m('.control-input',\n              Misago.input({\n                disabled: form.isBusy,\n                value: form.email,\n                placeholder: gettext(\"Your e-mail address\")\n              })\n            )\n          ),\n          _.component('button', {\n            class: '.btn-primary.btn-block',\n            submit: true,\n            loading: form.isBusy,\n            label: gettext(\"Send link\")\n          })\n        ])\n      );\n    }\n  };\n\n  Misago.addService('component:request-activation-link-form', function(_) {\n    _.component('request-activation-link-form', form);\n  },\n  {\n    after: 'components'\n  });\n}(Misago.prototype));\n\n(function (Misago) {\n  'use strict';\n\n  var donePage = {\n    controller: function(user, _) {\n      _.title.set(gettext(\"Your password has been changed.\"));\n\n      return {\n        showSignIn: function() {\n          _.modal('sign-in');\n        }\n      };\n    },\n    view: function(ctrl, user) {\n      var button = 'button.btn.btn-primary[type=\"button\"]';\n      var message = gettext(\"%(username)s, your password has been changed successfully.\");\n\n      return m('.page.page-message.page-message-success.page-forgotten-password-done',\n        m('.container',\n          m('.message-panel', [\n            m('.message-icon',\n              m('span.material-icon', 'check')\n            ),\n            m('.message-body', [\n              m('p.lead',\n                interpolate(message, {\n                  username: user.username\n                }, true)\n              ),\n              m('p',\n                gettext(\"You will have to sign in using new password before continuing.\")\n              ),\n              m('p',\n                m(button, {onclick: ctrl.showSignIn},\n                  gettext(\"Sign in\")\n                )\n              )\n            ])\n          ])\n        )\n      );\n    }\n  };\n\n  Misago.addService('component:forgotten-password:done-page', function(_) {\n    _.component('forgotten-password:done-page', donePage);\n  },\n  {\n    after: 'components'\n  });\n}(Misago.prototype));\n\n(function (Misago) {\n  'use strict';\n\n  var inactivePage = {\n    view: function(ctrl, activation, _) {\n      var activateButton = null;\n\n      if (activation.type === 'inactive_user') {\n        activateButton = m('p',\n          m('a', {href: _.context.REQUEST_ACTIVATION_URL},\n            gettext(\"Activate your account.\")\n          )\n        );\n      }\n\n      return m('.page.page-message.page-message-info.page-forgotten-password-inactive',\n        m('.container',\n          m('.message-panel', [\n            m('.message-icon',\n              m('span.material-icon', 'info_outline')\n            ),\n            m('.message-body', [\n              m('p.lead',\n                gettext(\"Your account is inactive.\")\n              ),\n              m('p',\n                activation.message\n              ),\n              activateButton\n            ])\n          ])\n        )\n      );\n    }\n  };\n\n  Misago.addService('component:forgotten-password:inactive-page', function(_) {\n    _.component('forgotten-password:inactive-page', inactivePage);\n  },\n  {\n    after: 'components'\n  });\n}(Misago.prototype));\n\n(function (Misago) {\n  'use strict';\n\n  var style = '.well.well-form.well-form-request-password-reset-link';\n\n  var ViewModel = function(api) {\n    this.api = api;\n    this.user = null;\n\n    this.activation = null;\n    this.activationMessage = null;\n\n    this.success = function(user) {\n      this.user = user;\n    };\n\n    this.error = function(rejection, _) {\n      if (['inactive_user', 'inactive_admin'].indexOf(rejection.code) > -1) {\n        var component = _.component('forgotten-password:inactive-page', {\n          'type': rejection.code,\n          'message': rejection.detail\n        });\n\n        _.mountPage(component);\n      } else {\n        _.alert.error(rejection.detail);\n      }\n    };\n\n    this.reset = function() {\n      this.user = null;\n      this.activation = null;\n      this.activationMessage = null;\n    };\n  };\n\n  var component = {\n    controller: function(_) {\n      var vm = new ViewModel(_.context.SEND_PASSWORD_RESET_API_URL);\n\n      return {\n        vm: vm,\n        form: _.form('request-link', vm)\n      };\n    },\n    view: function(ctrl, _) {\n      if (ctrl.vm.user) {\n        return this.done(ctrl.vm, ctrl.form, _);\n      } else {\n        return this.form(ctrl.form, _);\n      }\n    },\n    done: function(vm, form, _) {\n      var message = gettext(\"Reset password link sent to %(email)s.\");\n\n      return m(style + '.well-done',\n        m('.done-message', [\n          m('.message-icon',\n            m('span.material-icon', 'check')\n          ),\n          m('.message-body',\n            m('p',\n              interpolate(message, {\n                email: vm.user.email\n              }, true)\n            )\n          ),\n          _.component('button', {\n            class: '.btn-default.btn-block',\n            submit: false,\n            label: gettext(\"Request another link\"),\n            onclick: form.reset.bind(form)\n          })\n\n        ])\n      );\n    },\n    form: function(form, _) {\n      return m(style,\n        m('form', {onsubmit: form.submit}, [\n          m('.form-group',\n            m('.control-input',\n              Misago.input({\n                disabled: form.isBusy,\n                value: form.email,\n                placeholder: gettext(\"Your e-mail address\")\n              })\n            )\n          ),\n          _.component('button', {\n            class: '.btn-primary.btn-block',\n            submit: true,\n            loading: form.isBusy,\n            label: gettext(\"Send link\")\n          })\n        ])\n      );\n    }\n  };\n\n  Misago.addService('component:forgotten-password:request-link', function(_) {\n    _.component('forgotten-password:request-link', component);\n  },\n  {\n    after: 'components'\n  });\n}(Misago.prototype));\n\n(function (Misago) {\n  'use strict';\n\n  var style = '.well.well-form.well-form-reset-password';\n\n  var ViewModel = function(api) {\n    this.api = api;\n\n    this.success = function(user, _) {\n      _.auth.signOut();\n      _.mountPage(_.component('forgotten-password:done-page', user));\n    };\n  };\n\n  var component = {\n    controller: function(_) {\n      var vm = new ViewModel(_.context.CHANGE_PASSWORD_API_URL);\n\n      return {\n        form: _.form('reset-password', vm)\n      };\n    },\n    view: function(ctrl, _) {\n      return m(style,\n        m('form', {onsubmit: ctrl.form.submit}, [\n          m('.form-group',\n            m('.control-input',\n              Misago.input({\n                disabled: ctrl.form.isBusy,\n                value: ctrl.form.password,\n                type: 'password',\n                placeholder: gettext(\"Enter new password\")\n              })\n            )\n          ),\n          _.component('button', {\n            class: '.btn-primary.btn-block',\n            submit: true,\n            loading: ctrl.form.isBusy,\n            label: gettext(\"Change password\")\n          })\n        ])\n      );\n    }\n  };\n\n  Misago.addService('component:forgotten-password:reset-password', function(_) {\n    _.component('forgotten-password:reset-password', component);\n  },\n  {\n    after: 'components'\n  });\n}(Misago.prototype));\n\n(function (Misago) {\n  'use strict';\n\n  var ResetPassword = function(vm, _) {\n    var self = this;\n\n    this.password = m.prop('');\n\n    this.validation = {\n      'password': [\n        Misago.validators.passwordMinLength(_.settings)\n      ]\n    };\n\n    this.clean = function() {\n      if (!_.validate(this)) {\n        if ($.trim(this.password()).length) {\n          _.alert.error(this.errors.password);\n        } else {\n          _.alert.error(gettext(\"Enter new password.\"));\n        }\n        return false;\n      } else {\n        return true;\n      }\n    };\n\n    this.submit = function() {\n      _.ajax.post(vm.api, {\n        password: self.password()\n      }).then(function(user) {\n        self.success(user);\n      }, function(error) {\n        self.error(error);\n      });\n    };\n\n    this.success = function(user) {\n      vm.success(user, _);\n    };\n\n    this.error = function(rejection) {\n      _.api.alert(rejection);\n    };\n  };\n\n  Misago.addService('form:reset-password', function(_) {\n    _.form('reset-password', ResetPassword);\n  },\n  {\n    after: 'forms'\n  });\n} (Misago.prototype));\n\n(function (Misago) {\n  'use strict';\n\n  var nav = {\n    controller: function(_) {\n      return {\n        openUserMenu: function() {\n          _.dropdown.toggle('navbar-dropdown', 'navbar:dropdown:guest');\n        }\n      };\n    },\n    view: function(ctrl, _) {\n      return m('button', {type: 'button', onclick: ctrl.openUserMenu},\n        _.component('user-avatar', null, 64)\n      );\n    }\n  };\n\n  Misago.addService('component:navbar:compact:guest-nav', function(_) {\n    _.component('navbar:compact:guest-nav', nav);\n  },\n  {\n    after: 'components'\n  });\n}(Misago.prototype));\n\n(function (Misago) {\n  'use strict';\n\n  var nav = {\n    controller: function(_) {\n      return {\n        openUserMenu: function() {\n          _.dropdown.toggle('navbar-dropdown', 'navbar:dropdown:user');\n          return false;\n        }\n      };\n    },\n    view: function(ctrl, _) {\n      var config = {\n        onclick: ctrl.openUserMenu,\n        href: _.user.absolute_url\n      };\n\n      return m('a', config,\n        _.component('user-avatar', _.user, 64)\n      );\n    }\n  };\n\n  Misago.addService('component:navbar:compact:user-nav', function(_) {\n    _.component('navbar:compact:user-nav', nav);\n  },\n  {\n    after: 'components'\n  });\n}(Misago.prototype));\n\n(function (Misago) {\n  'use strict';\n\n  var nav = {\n    controller: function(_) {\n      return {\n        showSignIn: function() {\n          _.modal('sign-in');\n        }\n      };\n    },\n    view: function(ctrl, _) {\n      return m('div.nav.nav-guest', [\n        _.component('button', {\n          class: '.navbar-btn.btn-default',\n          onclick: ctrl.showSignIn,\n          disabled: ctrl.isBusy,\n          label: gettext('Sign in')\n        }),\n        _.component('navbar:register-button', '.navbar-btn.btn-primary')\n      ]);\n    }\n  };\n\n  Misago.addService('component:navbar:desktop:guest-nav', function(_) {\n    _.component('navbar:desktop:guest-nav', nav);\n  },\n  {\n    after: 'components'\n  });\n}(Misago.prototype));\n\n(function (Misago) {\n  'use strict';\n\n  var nav = {\n    controller: function(_) {\n      return {\n        dropdownToggle: {\n          href: _.user.absolute_url,\n\n          'data-toggle': 'dropdown',\n          'data-misago-routed': 'false',\n\n          'aria-haspopup': 'true',\n          'aria-expanded': 'false'\n        }\n      };\n    },\n    view: function(ctrl, _) {\n      return m('ul.nav.navbar-nav.nav-user', [\n        m('li.dropdown', [\n          m('a.dropdown-toggle[role=\"button\"]', ctrl.dropdownToggle,\n            _.component('user-avatar', _.user, 64)\n          ),\n          _.component('navbar:dropdown:user')\n        ])\n      ]);\n    }\n  };\n\n  Misago.addService('component:navbar:desktop:user-nav', function(_) {\n    _.component('navbar:desktop:user-nav', nav);\n  },\n  {\n    after: 'components'\n  });\n}(Misago.prototype));\n\n(function (Misago) {\n  'use strict';\n\n  var dropdown = {\n    controller: function(_) {\n      return {\n        showSignIn: function() {\n          _.modal('sign-in');\n        }\n      };\n    },\n    view: function(ctrl, _) {\n      return m('ul.dropdown-menu.user-dropdown.dropdown-menu-right[role=\"menu\"]',\n        m('li.guest-preview', [\n          m('h4',\n            gettext(\"You are browsing as guest.\")\n          ),\n          m('p',\n            gettext('Sign in or register to start and participate in discussions.')\n          ),\n          m('.row', [\n            m('.col-xs-6',\n              _.component('button', {\n                class: '.btn.btn-default.btn-block',\n                onclick: ctrl.showSignIn,\n                disabled: ctrl.isBusy,\n                label: gettext('Sign in')\n              })\n            ),\n            m('.col-xs-6',\n              _.component(\n                'navbar:register-button', '.btn.btn-primary.btn-block')\n            )\n          ])\n        ])\n      );\n    }\n  };\n\n  Misago.addService('component:navbar:dropdown:guest', function(_) {\n    _.component('navbar:dropdown:guest', dropdown);\n  },\n  {\n    after: 'components'\n  });\n}(Misago.prototype));\n\n(function (Misago) {\n  'use strict';\n\n  var button = {\n    controller: function(style, _) {\n      return {\n        isBusy: false,\n\n        showRegister: function() {\n          if (_.settings.account_activation === 'closed') {\n            _.alert.info(gettext(\"New registrations are currently disabled.\"));\n          } else {\n            m.startComputation();\n            this.isBusy = true;\n            m.endComputation();\n\n            var self = this;\n            m.sync([\n              _.zxcvbn.load(),\n              _.captcha.load()\n            ]).then(function() {\n              _.modal('register');\n            }, function() {\n              _.alert.error(gettext('Registation is not available now due to an error.'));\n            }).then(function() {\n              m.startComputation();\n              self.isBusy = false;\n              m.endComputation();\n            });\n          }\n        }\n      };\n    },\n    view: function(ctrl, style, _) {\n      return _.component('button', {\n        class: style,\n        onclick: ctrl.showRegister.bind(ctrl),\n        loading: ctrl.isBusy,\n        label: gettext('Register')\n      });\n    }\n  };\n\n  Misago.addService('component:navbar:register-button', function(_) {\n    _.component('navbar:register-button', button);\n  },\n  {\n    after: 'components'\n  });\n}(Misago.prototype));\n\n(function (Misago) {\n  'use strict';\n\n  var dropdown = {\n    class: '.dropdown-menu.user-dropdown.dropdown-menu-right',\n\n    controller: function() {\n      return {\n        logout: function() {\n          var decision = confirm(gettext(\"Are you sure you want to sign out?\"));\n          if (decision) {\n            $('#hidden-logout-form').submit();\n          }\n        }\n      };\n    },\n    view: function(ctrl, _) {\n      return m('ul' + this.class + '[role=\"menu\"]', [\n        m('li.dropdown-header',\n          m('strong',\n            _.user.username\n          )\n        ),\n        m('li.divider'),\n        m('li',\n          m('a', {href: _.user.absolute_url}, [\n            m('span.material-icon',\n              'account_circle'\n            ),\n            gettext(\"See your profile\")\n          ])\n        ),\n        m('li',\n          m('a', {href: _.context.USERCP_URL}, [\n            m('span.material-icon',\n              'done_all'\n            ),\n            gettext(\"Change options\")\n          ])\n        ),\n        m('li',\n          m('button.btn-link[type=\"button\"]', [\n            m('span.material-icon',\n              'face'\n            ),\n            gettext(\"Change avatar\")\n          ])\n        ),\n        m('li.divider'),\n        m('li.dropdown-footer',\n          m('button.btn.btn-default.btn-block', {onclick: ctrl.logout},\n            gettext(\"Logout\")\n          )\n        )\n      ]);\n    }\n  };\n\n  Misago.addService('component:navbar:dropdown:user', function(_) {\n    _.component('navbar:dropdown:user', dropdown);\n  },\n  {\n    after: 'components'\n  });\n}(Misago.prototype));\n\n(function (Misago) {\n  'use strict';\n\n  var persistent = function(el, isInit, context) {\n    context.retain = true;\n  };\n\n  var refresh = function() {\n    document.location.reload();\n  };\n\n  var modal = {\n    controller: function(message, _) {\n      if (message.activation === 'active') {\n        _.runloop.runOnce(\n          refresh, 'refresh-after-registration', 10000);\n      }\n    },\n    view: function(ctrl, message, _) {\n      var messageHtml = null;\n\n      if (message.activation === 'active') {\n        messageHtml = this.active(message);\n      } else {\n        messageHtml = this.inactive(message);\n      }\n\n      return m('.modal-dialog.modal-message.modal-register[role=\"document\"]',\n        {config: persistent},\n        m('.modal-content', [\n          _.component('modal:header', gettext('Registration complete')),\n          m('.modal-body',\n            messageHtml\n          )\n        ])\n      );\n    },\n    active: function(message) {\n      var lead = gettext(\"%(username)s, your account has been created and you were signed in.\");\n      return [\n        m('.message-icon',\n          m('span.material-icon', 'check')\n        ),\n        m('.message-body', [\n          m('p.lead',\n            interpolate(lead, {'username': message.username}, true)\n          ),\n          m('p',\n            gettext('The page will refresh automatically in 10 seconds.')\n          ),\n          m('p',\n            m('button[type=\"button\"].btn.btn-default', {onclick: refresh},\n              gettext('Refresh page')\n            )\n          )\n        ])\n      ];\n    },\n    inactive: function(message) {\n      var lead = null;\n      var help = null;\n\n      if (message.activation === 'user') {\n        lead = gettext(\"%(username)s, your account has been created but you need to activate it before you will be able to sign in.\");\n        help = gettext(\"We have sent an e-mail to %(email)s with link that you have to click to activate your account.\");\n      } else if (message.activation === 'admin') {\n        lead = gettext(\"%(username)s, your account has been created but board administrator will have to activate it before you will be able to sign in.\");\n        help = gettext(\"We will send an e-mail to %(email)s when this takes place.\");\n      }\n\n      return [\n        m('.message-icon',\n          m('span.material-icon', 'info_outline')\n        ),\n        m('.message-body', [\n          m('p.lead',\n            interpolate(lead, {'username': message.username}, true)\n          ),\n          m('p',\n            interpolate(help, {'email': message.email}, true)\n          )\n        ])\n      ];\n    }\n  };\n\n  Misago.addService('modal:register:complete', function(_) {\n    _.modal('register:complete', modal);\n  },\n  {\n    after: 'modals'\n  });\n}(Misago.prototype));\n\n(function (Misago) {\n  'use strict';\n\n  var persistent = function(el, isInit, context) {\n    context.retain = true;\n  };\n\n  var modal = {\n    controller: function(_) {\n      return {\n        form: _.form('register')\n      };\n    },\n    view: function(ctrl, _) {\n      var captcha = _.captcha.component({\n        form: ctrl.form,\n\n        labelClass: '.col-md-4',\n        controlClass: '.col-md-8'\n      });\n\n      var footnote = null;\n\n      if (_.context.TERMS_OF_SERVICE_URL) {\n        footnote = m('a', {href: _.context.TERMS_OF_SERVICE_URL},\n          m.trust(interpolate(gettext(\"By registering you agree to site's %(terms)s.\"), {\n            terms: '<strong>' + gettext(\"terms and conditions\") + '</strong>'\n          }, true))\n        );\n      }\n\n      return m('.modal-dialog.modal-form.modal-register[role=\"document\"]',\n        {config: persistent},\n        m('.modal-content', [\n          _.component('modal:header', gettext('Register')),\n          m('form.form-horizontal',\n          {\n            onsubmit: ctrl.form.submit\n          },\n          [\n            m('input[type=\"text\"]', {\n              name:'_username',\n              style: 'display: none'\n            }),\n            m('input[type=\"password\"]', {\n              name:'_password',\n              style: 'display: none'\n            }),\n            m('.modal-body', [\n              _.component('form-group', {\n                label: gettext(\"Username\"),\n                labelClass: '.col-md-4',\n                controlClass: '.col-md-8',\n                control: _.input({\n                  value: _.validate(ctrl.form, 'username'),\n                  id: 'id_username',\n                  disabled: ctrl.form.isBusy\n                }),\n                validation: ctrl.form.errors,\n                validationKey: 'username'\n              }),\n              _.component('form-group', {\n                label: gettext(\"E-mail\"),\n                labelClass: '.col-md-4',\n                controlClass: '.col-md-8',\n                control: _.input({\n                  value: _.validate(ctrl.form, 'email'),\n                  id: 'id_email',\n                  disabled: ctrl.form.isBusy\n                }),\n                validation: ctrl.form.errors,\n                validationKey: 'email'\n              }),\n              _.component('form-group', {\n                label: gettext(\"Password\"),\n                labelClass: '.col-md-4',\n                controlClass: '.col-md-8',\n                control: _.input({\n                  value: _.validate(ctrl.form, 'password'),\n                  type: 'password',\n                  id: 'id_password',\n                  disabled: ctrl.form.isBusy\n                }),\n                validation: ctrl.form.errors,\n                validationKey: 'password',\n                helpText: _.component('password-strength', {\n                  inputs: [\n                    ctrl.form.username(),\n                    ctrl.form.email()\n                  ],\n                  password: ctrl.form.password()\n                })\n              }),\n              captcha\n            ]),\n            m('.modal-footer', [\n              footnote,\n              _.component('button', {\n                class: '.btn-primary',\n                submit: true,\n                loading: ctrl.form.isBusy,\n                label: gettext(\"Register account\")\n              })\n            ])\n          ])\n        ])\n      );\n    }\n  };\n\n  Misago.addService('modal:register', function(_) {\n    _.modal('register', modal);\n  },\n  {\n    after: 'modals'\n  });\n}(Misago.prototype));\n\n(function (Misago) {\n  'use strict';\n\n  var Register = function(_) {\n    var self = this;\n\n    this.showActivation = false;\n\n    this.username = m.prop('');\n    this.email = m.prop('');\n    this.password = m.prop('');\n\n    this.captcha = _.captcha.value;\n\n    this.errors = null;\n\n    this.validation = {\n      'username': [\n        Misago.validators.usernameContent(),\n        Misago.validators.usernameMinLength(_.settings),\n        Misago.validators.usernameMaxLength(_.settings)\n      ],\n      'email': [\n        Misago.validators.email()\n      ],\n      'password': [\n        Misago.validators.passwordMinLength(_.settings)\n      ],\n      'captcha': _.captcha.validator()\n    };\n\n    this.clean = function() {\n      if (this.errors === null) {\n        _.validate(this);\n      }\n\n      _.captcha.clean(this);\n\n      if (this.hasErrors()) {\n        _.alert.error(gettext(\"Form contains errors.\"));\n        return false;\n      } else {\n        return true;\n      }\n    };\n\n    this.submit = function() {\n      _.api.model('user').post({\n        username: this.username(),\n        email: this.email(),\n        password: this.password(),\n        captcha: this.captcha()\n      }).then(this.success, this.error);\n    };\n\n    this.success = function(data) {\n      _.modal('register:complete', data);\n    };\n\n    this.error = function(rejection) {\n      if (rejection.status === 400) {\n        _.alert.error(gettext(\"Form contains errors.\"));\n        $.extend(self.errors, rejection);\n      } else {\n        _.api.alert(rejection);\n      }\n    };\n  };\n\n  Misago.addService('form:register', function(_) {\n    _.form('register', Register);\n  },\n  {\n    after: 'forms'\n  });\n} (Misago.prototype));\n\n(function (Misago) {\n  'use strict';\n\n  var SignIn = function(_) {\n    var self = this;\n\n    this.showActivation = false;\n\n    this.username = m.prop('');\n    this.password = m.prop('');\n\n    this.validation = {\n      'username': [],\n      'password': []\n    };\n\n    this.clean = function() {\n      if (!_.validate(this)) {\n        _.alert.error(gettext(\"Fill out both fields.\"));\n        return false;\n      } else {\n        return true;\n      }\n    };\n\n    this.submit = function() {\n      _.api.endpoint('auth').post({\n        username: self.username(),\n        password: self.password()\n      }).then(function() {\n        self.success();\n      }, function(error) {\n        self.error(error);\n      });\n    };\n\n    this.success = function() {\n      _.modal();\n\n      var $form = $('#hidden-login-form');\n\n      // refresh CSRF token because api call to /auth/ changed it\n      _.ajax.refreshCsrfToken();\n\n      // fill out form with user credentials and submit it, this will tell\n      // misago to redirect user back to right page, and will trigger browser's\n      // key ring feature\n      $form.find('input[type=\"hidden\"]').val(_.ajax.csrfToken);\n      $form.find('input[name=\"redirect_to\"]').val(window.location.pathname);\n      $form.find('input[name=\"username\"]').val(this.username());\n      $form.find('input[name=\"password\"]').val(this.password());\n      $form.submit();\n    };\n\n    this.error = function(rejection) {\n      if (rejection.status === 400) {\n        if (rejection.code === 'inactive_admin') {\n          _.alert.info(rejection.detail);\n        } else if (rejection.code === 'inactive_user') {\n          _.alert.info(rejection.detail);\n          self.showActivation = true;\n        } else if (rejection.code === 'banned') {\n          _.showBannedPage(rejection.detail);\n          _.modal();\n        } else {\n          _.alert.error(rejection.detail);\n        }\n      } else {\n        _.api.alert(rejection);\n      }\n    };\n  };\n\n  Misago.addService('form:sign-in', function(_) {\n    _.form('sign-in', SignIn);\n  },\n  {\n    after: 'forms'\n  });\n} (Misago.prototype));\n\n(function (Misago) {\n  'use strict';\n\n  function persistent(el, isInit, context) {\n    context.retain = true;\n  }\n\n  var modal = {\n    controller: function(_) {\n      return {\n        form: _.form('sign-in')\n      };\n    },\n    view: function(ctrl, _) {\n      var activateButton = null;\n\n      if (ctrl.form.showActivation) {\n        activateButton = m('a.btn.btn-block.btn-success',\n          {href: _.context.REQUEST_ACTIVATION_URL},\n          gettext(\"Activate account\")\n        );\n      }\n\n      return m('.modal-dialog.modal-sm.modal-signin[role=\"document\"]',\n        {config: persistent},\n        m('.modal-content', [\n          _.component('modal:header', gettext(\"Sign in\")),\n          m('form', {onsubmit: ctrl.form.submit}, [\n            m('.modal-body', [\n              m('.form-group',\n                m('.control-input',\n                  Misago.input({\n                    disabled: ctrl.form.isBusy,\n                    value: ctrl.form.username,\n                    placeholder: gettext(\"Username or e-mail\")\n                  })\n                )\n              ),\n              m('.form-group',\n                m('.control-input',\n                  Misago.input({\n                    type: 'password',\n                    disabled: ctrl.form.isBusy,\n                    value: ctrl.form.password,\n                    placeholder: gettext(\"Password\")\n                  })\n                )\n              )\n            ]),\n            m('.modal-footer', [\n              activateButton,\n              _.component('button', {\n                class: '.btn-primary.btn-block',\n                submit: true,\n                loading: ctrl.form.isBusy,\n                label: gettext(\"Sign in\")\n              }),\n              m('a.btn.btn-block.btn-default',\n                {href: _.context.FORGOTTEN_PASSWORD_URL},\n                gettext(\"Forgot password?\")\n              )\n            ])\n          ])\n        ])\n      );\n    }\n  };\n\n  Misago.addService('modal:sign-in', function(_) {\n    _.modal('sign-in', modal);\n  },\n  {\n    after: 'modals'\n  });\n}(Misago.prototype));\n"],"sourceRoot":"/source/"}
+{"version":3,"sources":["misago.js"],"names":["window","Misago","ns","Object","getPrototypeOf","this","self","_initServices","services","orderedServices","OrderedList","order","forEach","item","factory","undefined","serviceInstance","key","_destroyServices","reverse","destroy","context","SETTINGS","setup","init","test","get","api","_services","proto","prototype","addService","name","push","after","before","PermissionDenied","message","detail","status","toString","has","obj","hasOwnProperty","value","pop","returnValue","persistent","el","isInit","retain","input","kwargs","options","disabled","config","placeholder","autocomplete","element","id","type","oninput","m","withAttr","noop","stateHooks","component","loadingState","errorState","_hasLifecycleHooks","isActive","errorHandler","bind","_controller","controller","apply","arguments","_onunload","onunload","e","vm","loading","loadingHandler","_view","view","isReady","_init","initArgs","promise","then","ondata","finalArgs","i","length","f","error","items","isOrdered","_items","add","values","values_only","_order","unordered","insertItem","insertAt","ordering","indexOf","ordered","splice","index","iterations","Page","_","isFinalized","_sections","finalize","visible","visibleIf","addSection","section","link","getSections","getDefaultLink","serializeDatetime","serialized","format","deserializeDatetime","deserialized","moment","startsWith","string","beginning","endsWith","tail","getCsrfToken","cookie_name","document","cookie","cookieRegex","RegExp","match","split","Ajax","refreshCsrfToken","csrfToken","CSRF_COOKIE_NAME","runningGets","ajax","method","url","data","progress","deferred","ajax_settings","headers","X-CSRFToken","dataType","success","resolve","jqXHR","rejection","responseJSON","statusText","reject","$","post","patch","put","ban","showBannedPage","modal","alert","gettext","ALERT_BASE_DISPLAY_TIME","ALERT_LENGTH_FACTOR","ALERT_MAX_DISPLAY_TIME","ALERT_HIDE_ANIMATION_LENGTH","Alert","isVisible","show","displayTime","runloop","runOnce","startComputation","endComputation","set","stop","info","warning","Auth","user","models","deserialize","isDesynced","newUser","handleAuthChange","isAuthenticated","localstore","handleUserChange","extend","syncSession","watch","switchMount","mountId","mount","getElementById","dataset","componentName","replace","signOut","isAnonymous","NoCaptcha","load","QACaptcha","question","prop","endpoint","label","labelClass","controlClass","control","validate","form","isBusy","validation","errors","validationKey","helpText","help_text","validator","ReCaptcha","included","wait","grecaptcha","reset","include","controlConfig","render","sitekey","settings","recaptcha_site_key","getResponse","clean","captcha","Captcha","types","no","qa","re","captcha_type","_components","argumentsArray","Dropdown","slots","toggle","elementId","hasChildNodes","removeClass","console","log","addClass","dropdown","boilerplate","_submit","submit","_success","_error","hasErrors","constructor","_forms","script","remote","STATIC_URL","cache","setLinks","baseUrl","attr","staticUrl","mediaUrl","prefixUrl","prefix","LocalStore","storage","localStorage","watchers","handleStorageEvent","newValueJson","JSON","parse","newValue","each","watcher","keyName","oldValue","callback","addEventListener","prefixKey","setItem","stringify","itemString","getItem","Modal","open","on","hide","_modals","_modal","Models","classes","deserializers","String","json","locale","mountPage","addMount","_mounts","activeMounts","RunLoop","_intervals","stopInterval","clearTimeout","run","callable","delay","setTimeout","result","loop","changeState","title","history","pushState","BANNED_URL","ticks","PageTitle","forum_name","_set_complex","completeTitle","page","page_label","interpolate","parent","EMAIL","USERNAME","validators","required","trim","email","minLength","limit_value","returnMessage","ngettext","show_value","maxLength","usernameMinLength","username_length_min","usernameMaxLength","username_length_max","usernameContent","passwordMinLength","password_length_min","validateField","validateForm","isValid","Zxcvbn","scorePassword","password","inputs","zxcvbn","score","Ban","html","plain","expires_on","deserializeBan","class","LegalPage","body","Rank","slug","description","css_class","is_tab","User","username","full_title","rank","avatar_hash","acl","absolute_url","deserializeUser","joined_on","ctrl","authChanged","refresh","location","reload","auth","onclick","banExpirationMessage","container","expirationMessage","isAfter","fromNow","bannedPage","error_message","trust","button","textFields","formGroup","groupClass","controlType","attrs","controlId","feedbackId","feedbackIcon","showFeedbackIcon","isValidated","aria-hidden","for","labelFor","map","loader","markup","content","header","data-dismiss","aria-label","styles","labels","passwordStrength","style","role","aria-valuenow","aria-valuemin","aria-valuemax","avatar","defaultSize","src","size","finalSize","alt","width","height","RequestLink","ViewModel","code","SEND_ACTIVATION_API","done","onsubmit","donePage","showSignIn","inactivePage","activation","activateButton","href","REQUEST_ACTIVATION_URL","activationMessage","SEND_PASSWORD_RESET_API","CHANGE_PASSWORD_API","ResetPassword","nav","openUserMenu","dropdownToggle","data-toggle","data-misago-routed","aria-haspopup","aria-expanded","showRegister","account_activation","sync","logout","decision","confirm","USERCP_URL","messageHtml","active","inactive","lead","help","footnote","TERMS_OF_SERVICE_URL","terms","Register","showActivation","USERS_API","SignIn","AUTH_API","$form","find","val","pathname","FORGOTTEN_PASSWORD_URL"],"mappings":"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","file":"misago.js","sourcesContent":["/* global -Misago */\n/* exported Misago */\n(function () {\n  'use strict';\n\n  window.Misago = function() {\n    var ns = Object.getPrototypeOf(this);\n    var self = this;\n\n    // Services init/destroy\n    this._initServices = function(services) {\n      var orderedServices = new ns.OrderedList(services).order(false);\n      orderedServices.forEach(function (item) {\n        var factory = null;\n        if (item.item.factory !== undefined) {\n          factory = item.item.factory;\n        } else {\n          factory = item.item;\n        }\n\n        var serviceInstance = factory(self);\n        if (serviceInstance) {\n          self[item.key] = serviceInstance;\n        }\n      });\n    };\n\n    this._destroyServices = function(services) {\n      var orderedServices = new ns.OrderedList(services).order();\n      orderedServices.reverse();\n      orderedServices.forEach(function (item) {\n        if (item.destroy !== undefined) {\n          item.destroy(self);\n        }\n      });\n    };\n\n    // Context data\n    this.context = {\n      // Empty settings\n      SETTINGS: {}\n    };\n\n    // App init/destory\n    this.setup = false;\n    this.init = function(setup, context) {\n      this.setup = {\n        test: ns.get(setup, 'test', false),\n        api: ns.get(setup, 'api', '/api/')\n      };\n\n      if (context) {\n        this.context = context;\n      }\n\n      this._initServices(ns._services);\n    };\n\n    this.destroy = function() {\n      this._destroyServices(ns._services);\n    };\n  };\n\n  // Services\n  var proto = window.Misago.prototype;\n\n  proto._services = [];\n  proto.addService = function(name, factory, order) {\n    proto._services.push({\n      key: name,\n      item: factory,\n      after: proto.get(order, 'after'),\n      before: proto.get(order, 'before')\n    });\n  };\n\n  // Exceptions\n  proto.PermissionDenied = function(message) {\n    this.detail = message;\n    this.status = 403;\n\n    this.toString = function() {\n      return this.detail || 'Permission denied';\n    };\n  };\n}());\n\n(function (Misago) {\n  'use strict';\n\n  Misago.has = function(obj, key) {\n    if (obj) {\n      return obj.hasOwnProperty(key);\n    } else {\n      return false;\n    }\n  };\n\n  Misago.get = function(obj, key, value) {\n    if (Misago.has(obj, key)) {\n      return obj[key];\n    } else if (value !== undefined) {\n      return value;\n    } else {\n      return undefined;\n    }\n  };\n\n  Misago.pop = function(obj, key, value) {\n    var returnValue = Misago.get(obj, key, value);\n    if (Misago.has(obj, key)) {\n      obj[key] = null;\n    }\n    return returnValue;\n  };\n}(Misago.prototype));\n\n(function (Misago) {\n  'use strict';\n\n  function persistent(el, isInit, context) {\n    context.retain = true;\n  }\n\n  Misago.input = function(kwargs) {\n    var options = {\n      disabled: kwargs.disabled || false,\n      config: kwargs.config || persistent\n    };\n\n    if (kwargs.placeholder) {\n      options.placeholder = kwargs.placeholder;\n    }\n\n    if (kwargs.autocomplete === false) {\n      options.autocomplete = 'off';\n    }\n\n    var element = 'input';\n\n    if (kwargs.id) {\n      element += '#' + kwargs.id;\n      options.key = 'field-' + kwargs.id;\n    }\n\n    element += '.form-control' + (kwargs.class || '');\n    element += '[type=\"' + (kwargs.type || 'text') + '\"]';\n\n    if (kwargs.value) {\n      options.value = kwargs.value();\n      options.oninput = m.withAttr('value', kwargs.value);\n    }\n\n    return m(element, options);\n  };\n}(Misago.prototype));\n\n(function (Misago) {\n  'use strict';\n\n  var noop = function() {};\n\n  Misago.stateHooks = function(component, loadingState, errorState) {\n    /*\n      Boilerplate for Misago components with lifecycles\n    */\n\n    // Component boilerplated (this may happen in tests)\n    if (component._hasLifecycleHooks) {\n      return component;\n    }\n    component._hasLifecycleHooks = true;\n\n    // Component active state\n    component.isActive = true;\n\n    var errorHandler = errorState.bind(component);\n\n    // Wrap controller to store lifecycle methods\n    var _controller = component.controller || noop;\n    component.controller = function() {\n      try {\n        component.isActive = true;\n        var controller = _controller.apply(component, arguments) || {};\n\n        // wrap onunload for lifestate\n        var _onunload = controller.onunload || noop;\n        controller.onunload = function() {\n          _onunload.apply(component, arguments);\n          component.isActive = false;\n        };\n\n        return controller;\n      } catch (e) {\n        errorHandler(e);\n      }\n    };\n\n    // Add state callbacks to View-Model\n    if (component.vm && component.vm.init) {\n      // setup default loading view\n      if (!component.loading) {\n        var loadingHandler = loadingState.bind(component);\n        component.loading = loadingHandler;\n      }\n\n      var _view = component.view;\n      component.view = function() {\n        if (component.vm.isReady) {\n          return _view.apply(component, arguments);\n        } else {\n          return component.loading.apply(component, arguments);\n        }\n      };\n\n      // wrap vm.init in promise handler\n      var _init = component.vm.init;\n      component.vm.init = function() {\n        var initArgs = arguments;\n        var promise = _init.apply(component.vm, initArgs);\n\n        if (promise) {\n          promise.then(function() {\n            if (component.isActive && component.vm.ondata) {\n              var finalArgs = [];\n              for (var i = 0; i < arguments.length; i++) {\n                finalArgs.push(arguments[i]);\n              }\n              for (var f = 0; f < initArgs.length; f++) {\n                finalArgs.push(initArgs[f]);\n              }\n\n              component.vm.ondata.apply(component.vm, finalArgs);\n            }\n          }, function(error) {\n            if (component.isActive) {\n              errorHandler(error);\n            }\n          });\n        }\n      };\n    }\n\n    return component;\n  };\n}(Misago.prototype));\n\n(function (Misago) {\n  'use strict';\n\n  Misago.OrderedList = function(items) {\n    this.isOrdered = false;\n    this._items = items || [];\n\n    this.add = function(key, item, order) {\n      this._items.push({\n        key: key,\n        item: item,\n\n        after: Misago.get(order, 'after'),\n        before: Misago.get(order, 'before')\n      });\n    };\n\n    this.get = function(key, value) {\n      for (var i = 0; i < this._items.length; i++) {\n        if (this._items[i].key === key) {\n          return this._items[i].item;\n        }\n      }\n\n      return value;\n    };\n\n    this.has = function(key) {\n      return this.get(key) !== undefined;\n    };\n\n    this.values = function() {\n      var values = [];\n      for (var i = 0; i < this._items.length; i++) {\n        values.push(this._items[i].item);\n      }\n      return values;\n    };\n\n    this.order = function(values_only) {\n      if (!this.isOrdered) {\n        this._items = this._order(this._items);\n        this.isOrdered = true;\n      }\n\n      if (values_only || typeof values_only === 'undefined') {\n        return this.values();\n      } else {\n        return this._items;\n      }\n    };\n\n    this._order = function(unordered) {\n      // Index of unordered items\n      var index = [];\n      unordered.forEach(function (item) {\n        index.push(item.key);\n      });\n\n      // Ordered items\n      var ordered = [];\n      var ordering = [];\n\n      // First pass: register items that\n      // don't specify their order\n      unordered.forEach(function (item) {\n        if (!item.after && !item.before) {\n          ordered.push(item);\n          ordering.push(item.key);\n        }\n      });\n\n      // Second pass: register items that\n      // specify their before to \"_end\"\n      unordered.forEach(function (item) {\n        if (item.before === \"_end\") {\n          ordered.push(item);\n          ordering.push(item.key);\n        }\n      });\n\n      // Third pass: keep iterating items\n      // until we hit iterations limit or finish\n      // ordering list\n      function insertItem(item) {\n        var insertAt = -1;\n        if (ordering.indexOf(item.key) === -1) {\n          if (item.after) {\n            insertAt = ordering.indexOf(item.after);\n            if (insertAt !== -1) {\n              insertAt += 1;\n            }\n          } else if (item.before) {\n            insertAt = ordering.indexOf(item.before);\n          }\n\n          if (insertAt !== -1) {\n            ordered.splice(insertAt, 0, item);\n            ordering.splice(insertAt, 0, item.key);\n          }\n        }\n      }\n\n      var iterations = 200;\n      while (iterations > 0 && index.length !== ordering.length) {\n        iterations -= 1;\n        unordered.forEach(insertItem);\n      }\n\n      return ordered;\n    };\n  };\n} (Misago.prototype));\n\n(function (Misago) {\n  'use strict';\n\n  Misago.Page = function(name, _) {\n    var self = this;\n\n    this.name = name;\n    this.isFinalized = false;\n    this._sections = [];\n\n    var finalize = function() {\n      if (!self.isFinalized) {\n        self.isFinalized = true;\n\n        var visible = [];\n        self._sections.forEach(function (item) {\n          if (!item.visibleIf || item.visibleIf(_)) {\n            visible.push(item);\n          }\n        });\n        self._sections = new Misago.OrderedList(visible).order(true);\n      }\n    };\n\n    this.addSection = function(section) {\n      if (this.isFinalized) {\n        throw (this.name + \" page was initialized already and no longer accepts new sections\");\n      }\n\n      this._sections.push({\n        key: section.link,\n        item: section,\n\n        after: section.after,\n        before: section.before\n      });\n    };\n\n    this.getSections = function() {\n      finalize();\n      return this._sections;\n    };\n\n    this.getDefaultLink = function() {\n      finalize();\n      return this._sections[0].link;\n    };\n  };\n}(Misago.prototype));\n\n(function (Misago) {\n  Misago.serializeDatetime = function(serialized) {\n    return serialized ? serialized.format() : null;\n  };\n\n  Misago.deserializeDatetime = function(deserialized) {\n    return deserialized ? moment(deserialized) : null;\n  };\n}(Misago.prototype));\n\n(function (Misago) {\n  'use strict';\n\n  Misago.startsWith = function(string, beginning) {\n    return string.indexOf(beginning) === 0;\n  };\n\n  Misago.endsWith = function(string, tail) {\n    return string.indexOf(tail, string.length - tail.length) !== -1;\n  };\n}(Misago.prototype));\n\n(function (Misago) {\n  'use strict';\n\n  var getCsrfToken = function(cookie_name) {\n    if (document.cookie.indexOf(cookie_name) !== -1) {\n      var cookieRegex = new RegExp(cookie_name + '\\=([^;]*)');\n      var cookie = Misago.get(document.cookie.match(cookieRegex), 0);\n      return cookie.split('=')[1];\n    } else {\n      return null;\n    }\n  };\n\n  var Ajax = function(_) {\n    this.refreshCsrfToken = function() {\n      this.csrfToken = getCsrfToken(_.context.CSRF_COOKIE_NAME);\n    };\n    this.refreshCsrfToken();\n\n    /*\n      List of GETs underway\n      We are limiting number of GETs to API to 1 per url\n    */\n    var runningGets = {};\n\n    this.ajax = function(method, url, data, progress) {\n      var promise = m.deferred();\n\n      var ajax_settings = {\n        url: url,\n        method: method,\n        headers: {\n          'X-CSRFToken': this.csrfToken\n        },\n\n        data: data || {},\n        dataType: 'json',\n\n        success: function(data) {\n          if (method === 'GET') {\n            Misago.pop(runningGets, url);\n          }\n          promise.resolve(data);\n        },\n        error: function(jqXHR) {\n          if (method === 'GET') {\n            Misago.pop(runningGets, url);\n          }\n\n          var rejection = jqXHR.responseJSON || {};\n\n          rejection.status = jqXHR.status;\n          rejection.statusText = jqXHR.statusText;\n\n          promise.reject(rejection);\n        }\n      };\n\n      if (progress) {\n        return; // not implemented... yet!\n      }\n\n      $.ajax(ajax_settings);\n      return promise.promise;\n    };\n\n    this.get = function(url) {\n      if (runningGets[url] !== undefined) {\n        return runningGets[url];\n      } else {\n        runningGets[url] = this.ajax('GET', url);\n        return runningGets[url];\n      }\n    };\n\n    this.post = function(url, data) {\n      return this.ajax('POST', url, data);\n    };\n\n    this.patch = function(url, data) {\n      return this.ajax('PATCH', url, data);\n    };\n\n    this.put = function(url, data) {\n      return this.ajax('PUT', url, data);\n    };\n\n    this.delete = function(url) {\n      return this.ajax('DELETE', url);\n    };\n\n    // Shorthand for handling backend errors\n    this.error = function(rejection) {\n      if (rejection.ban) {\n        _.showBannedPage(rejection.ban);\n        _.modal();\n      } else {\n        this.alert(rejection);\n      }\n    };\n\n    this.alert = function(rejection) {\n      var message = gettext(\"Unknown error has occured.\");\n\n      if (rejection.status === 0) {\n        message = gettext(\"Lost connection with application.\");\n      }\n\n      if (rejection.status === 400 && rejection.detail) {\n        message = rejection.detail;\n      }\n\n      if (rejection.status === 403) {\n        message = rejection.detail;\n        if (message === \"Permission denied\") {\n          message = gettext(\n            \"You don't have permission to perform this action.\");\n        }\n      }\n\n      if (rejection.status === 404) {\n        message = gettext(\"Action link is invalid.\");\n      }\n\n      _.alert.error(message);\n    };\n  };\n\n  Misago.addService('ajax', function(_) {\n    return new Ajax(_);\n  });\n}(Misago.prototype));\n\n(function (Misago) {\n  'use strict';\n\n  var ALERT_BASE_DISPLAY_TIME = 5 * 1000;\n  var ALERT_LENGTH_FACTOR = 70;\n  var ALERT_MAX_DISPLAY_TIME = 9 * 1000;\n  var ALERT_HIDE_ANIMATION_LENGTH = 300;\n\n  var Alert = function(_) {\n    var self = this;\n\n    this.type = '';\n    this.message = null;\n    this.isVisible = false;\n\n    var show = function(type, message) {\n      self.type = type;\n      self.message = message;\n      self.isVisible = true;\n\n      var displayTime = ALERT_BASE_DISPLAY_TIME;\n      displayTime += message.length * ALERT_LENGTH_FACTOR;\n      if (displayTime > ALERT_MAX_DISPLAY_TIME) {\n        displayTime = ALERT_MAX_DISPLAY_TIME;\n      }\n\n      _.runloop.runOnce(function () {\n        m.startComputation();\n        self.isVisible = false;\n        m.endComputation();\n      }, 'flash-message-hide', displayTime);\n    };\n\n    var set = function(type, message) {\n      _.runloop.stop('flash-message-hide');\n      _.runloop.stop('flash-message-show');\n\n      if (self.isVisible) {\n        self.isVisible = false;\n        _.runloop.runOnce(function () {\n          m.startComputation();\n          show(type, message);\n          m.endComputation();\n        }, 'flash-message-show', ALERT_HIDE_ANIMATION_LENGTH);\n      } else {\n        show(type, message);\n      }\n    };\n\n    this.info = function(message) {\n      set('info', message);\n    };\n\n    this.success = function(message) {\n      set('success', message);\n    };\n\n    this.warning = function(message) {\n      set('warning', message);\n    };\n\n    this.error = function(message) {\n      set('error', message);\n    };\n  };\n\n  Misago.addService('alert', {\n    factory: function(_) {\n      return new Alert(_);\n    }\n  });\n}(Misago.prototype));\n\n(function (Misago) {\n  'use strict';\n\n  var Auth = function(_) {\n    var self = this;\n\n    _.user = _.models.deserialize('user', _.context.user);\n\n    // Auth state synchronization across tabs\n    this.isDesynced = false; // becomes true if auth state between tabs differs\n    this.newUser = null; // becomes user obj to which we want to sync\n\n    var handleAuthChange = function(isAuthenticated) {\n      if (!self.isDesynced) {\n        m.startComputation();\n\n        // display annoying \"you were desynced\" message\n        self.isDesynced = true;\n\n        if (isAuthenticated) {\n          self.newUser = _.localstore.get('auth-user');\n        }\n\n        m.endComputation();\n      }\n    };\n\n    var handleUserChange = function(newUser) {\n      if (!self.isDesynced) {\n        m.startComputation();\n\n        if (_.user.id !== newUser.id) {\n          self.isDesynced = true;\n          self.newUser = newUser;\n        } else if (newUser) {\n          _.user = $.extend(_.user, newUser);\n        }\n\n        m.endComputation();\n      }\n    };\n\n    var syncSession = function() {\n      _.localstore.set('auth-user', _.user);\n      _.localstore.set('auth-is-authenticated', _.user.isAuthenticated);\n\n      _.localstore.watch('auth-is-authenticated', handleAuthChange);\n      _.localstore.watch('auth-user', handleUserChange);\n    };\n\n    syncSession();\n\n    // Shorthand for signing user components out\n    var switchMount = function(mountId) {\n      var mount = document.getElementById(mountId);\n      var component = null;\n\n      if (mount) {\n        component = mount.dataset.componentName;\n        m.mount(\n          mount, _.component(component.replace('user-nav', 'guest-nav')));\n      }\n    };\n\n    this.signOut = function() {\n      _.user.isAuthenticated = false;\n      _.user.isAnonymous = true;\n\n      syncSession();\n\n      switchMount('user-menu-mount');\n      switchMount('user-menu-compact-mount');\n    };\n  };\n\n  Misago.addService('auth',\n  function(_) {\n    return new Auth(_);\n  },\n  {\n    after: 'model:user'\n  });\n}(Misago.prototype));\n\n/* global grecaptcha */\n(function (Misago) {\n  'use strict';\n\n  var NoCaptcha = function() {\n    var deferred = m.deferred();\n    deferred.resolve();\n\n    this.load = function() {\n      return deferred.promise;\n    };\n\n    this.value = function() {\n      return null;\n    };\n  };\n\n  var QACaptcha = function(_) {\n    var self = this;\n\n    this.loading = false;\n    this.question = null;\n    this.value = m.prop('');\n\n    var deferred = m.deferred();\n    this.load = function() {\n      this.value('');\n\n      if (!this.question && !this.loading) {\n        this.loading = true;\n\n        _.api.endpoint('captcha-question').get().then(function(question) {\n          self.question = question;\n          deferred.resolve();\n        }, function() {\n          _.ajax.alert(gettext('Failed to load CAPTCHA.'));\n          deferred.reject();\n        }).then(function() {\n          self.loading = true;\n        });\n      }\n\n      return deferred.promise;\n    };\n\n    this.component = function(kwargs) {\n      return _.component('form-group', {\n        label: this.question.question,\n        labelClass: kwargs.labelClass || null,\n        controlClass: kwargs.controlClass || null,\n        control: _.input({\n          value: _.validate(kwargs.form, 'captcha'),\n          id: 'id_captcha',\n          disabled: kwargs.form.isBusy\n        }),\n        validation: kwargs.form.errors,\n        validationKey: 'captcha',\n        helpText: this.question.help_text\n      });\n    };\n\n    this.validator = function() {\n      return [];\n    };\n  };\n\n  var ReCaptcha = function(_) {\n    this.included = false;\n    this.question = null;\n\n    var deferred = m.deferred();\n\n    var wait = function(promise) {\n      if (typeof grecaptcha !== \"undefined\") {\n        promise.resolve();\n      } else {\n        _.runloop.runOnce(function() {\n          wait(promise);\n        }, 'loading-grecaptcha', 150);\n      }\n    };\n\n    this.load = function() {\n      if (typeof grecaptcha !== \"undefined\") {\n        grecaptcha.reset();\n      }\n\n      if (!this.included) {\n        _.include('https://www.google.com/recaptcha/api.js', true);\n        this.included = true;\n      }\n\n      wait(deferred);\n\n      return deferred.promise;\n    };\n\n    var controlConfig = function(el, isInit, context) {\n      context.retain = true;\n\n      if (!isInit) {\n        grecaptcha.render('recaptcha', {\n          'sitekey': _.settings.recaptcha_site_key\n        });\n      }\n    };\n\n    this.component = function(kwargs) {\n      var control = m('#recaptcha', {\n        config: controlConfig\n      });\n\n      return _.component('form-group', {\n        label: gettext(\"Security test\"),\n        labelClass: kwargs.labelClass || null,\n        controlClass: kwargs.controlClass || null,\n        control: control,\n        validation: kwargs.form.errors,\n        validationKey: 'captcha'\n      });\n    };\n\n    this.value = function() {\n      if (typeof grecaptcha !== \"undefined\") {\n        return grecaptcha.getResponse();\n      } else {\n        return '';\n      }\n    };\n\n    this.clean = function(form) {\n      if (!this.value()) {\n        form.errors.captcha = [\n          gettext('This field is required.')\n        ];\n      } else {\n        form.errors.captcha = true;\n      }\n    };\n\n    this.validator = function() {\n      return [];\n    };\n  };\n\n  var Captcha = function(_) {\n    var types = {\n      'no': NoCaptcha,\n      'qa': QACaptcha,\n      're': ReCaptcha\n    };\n\n    var captcha = new types[_.settings.captcha_type](_);\n\n    this.value = captcha.value;\n\n    this.load = function() {\n      return captcha.load();\n    };\n\n    this.component = function(kwargs) {\n      if (captcha.component) {\n        return captcha.component(kwargs);\n      } else {\n        return null;\n      }\n    };\n\n    this.validator = function() {\n      if (captcha.validator) {\n        return captcha.validator();\n      } else {\n        return null;\n      }\n    };\n\n    this.clean = function(form) {\n      if (captcha.clean) {\n        captcha.clean(form);\n      } else {\n        form.errors.captcha = true;\n      }\n    };\n  };\n\n  Misago.addService('captcha', function(_) {\n    return new Captcha(_);\n  },\n  {\n    after: 'include'\n  });\n}(Misago.prototype));\n\n(function (Misago) {\n  'use strict';\n\n  var component = function(name, component) {\n    if (this._components[name]) {\n      if (arguments.length > 1) {\n        var argumentsArray = [this._components[name]];\n        for (var i = 1; i < arguments.length; i += 1) {\n          argumentsArray.push(arguments[i]);\n        }\n        argumentsArray.push(this);\n        return m.component.apply(undefined, argumentsArray);\n      } else {\n        return m.component(this._components[name], this);\n      }\n    } else if (component) {\n      this._components[name] = component;\n    } else {\n      throw '\"' + name + '\" component is not registered and can\\'t be created';\n    }\n  };\n\n  Misago.addService('components', function(_) {\n    _._components = {};\n    _.component = component;\n  });\n}(Misago.prototype));\n\n(function (Misago) {\n  'use strict';\n\n  Misago.addService('conf', function(_) {\n    _.settings = Misago.get(_.context, 'SETTINGS', {});\n  });\n}(Misago.prototype));\n\n(function (Misago) {\n  'use strict';\n\n  var Dropdown = function(_) {\n    var slots = {};\n\n    this.toggle = function(elementId, component) {\n      var element = document.getElementById(elementId);\n\n      if (element.hasChildNodes() && slots[elementId] === component) {\n        slots[elementId] = null;\n        m.mount(element, null);\n        $(element).removeClass('open');\n      } else {\n        console.log(element.hasChildNodes());\n        slots[elementId] = component;\n        m.mount(element, _.component(component));\n        $(element).addClass('open');\n      }\n    };\n\n    this.destroy = function() {\n      var element = null;\n\n      for (var elementId in slots) {\n        if (slots.hasOwnProperty(elementId)) {\n          element = document.getElementById(elementId);\n          if (element && element.hasChildNodes()) {\n            m.mount(element, null);\n          }\n        }\n      }\n    };\n  };\n\n  Misago.addService('dropdown', {\n    factory: function(_) {\n      return new Dropdown(_);\n    },\n    destroy: function(_) {\n      _.dropdown.destroy();\n    }\n  },\n  {\n    before: 'components'\n  });\n}(Misago.prototype));\n\n(function (Misago) {\n  'use strict';\n\n  var boilerplate = function(form) {\n    var _submit = form.submit;\n    var _success = form.success;\n    var _error = form.error;\n\n    form.isBusy = false;\n\n    form.errors = null;\n\n    form.submit = function() {\n      if (form.isBusy) {\n        return false;\n      }\n\n      if (form.clean) {\n        if (form.clean()) {\n          form.isBusy = true;\n          _submit.apply(form);\n        }\n      } else {\n        form.isBusy = true;\n      }\n      return false;\n    };\n\n    form.success = function() {\n      m.startComputation();\n\n      _success.apply(form, arguments);\n      form.isBusy = false;\n\n      m.endComputation();\n    };\n\n    form.error = function() {\n      m.startComputation();\n\n      _error.apply(form, arguments);\n      form.isBusy = false;\n\n      m.endComputation();\n    };\n\n    form.hasErrors = function() {\n      if (form.errors === null) {\n        return false;\n      }\n\n      for (var key in form.validation) {\n        if (form.validation.hasOwnProperty(key)) {\n          if (form.errors[key] !== true) {\n            return true;\n          }\n        }\n      }\n\n      return false;\n    };\n\n    return form;\n  };\n\n  var form = function(name, constructor) {\n    if (this._forms[name]) {\n      if (constructor) {\n        return boilerplate(new this._forms[name](constructor, this));\n      } else {\n        return boilerplate(new this._forms[name](this));\n      }\n    } else {\n      this._forms[name] = constructor;\n    }\n  };\n\n  Misago.addService('forms', function(_) {\n    _._forms = {};\n    _.form = form;\n  });\n}(Misago.prototype));\n\n(function (Misago) {\n  'use strict';\n\n  var include = function(script, remote) {\n    if (!remote) {\n      script = this.context.STATIC_URL + script;\n    }\n\n    $.ajax({\n      url: script,\n      cache: true,\n      dataType: 'script'\n    });\n  };\n\n  Misago.addService('include', function(_) {\n    _.include = include;\n  },\n  {\n    after: 'conf'\n  });\n}(Misago.prototype));\n\n(function (Misago) {\n  'use strict';\n\n  var setLinks = function(_) {\n    _.baseUrl = $('base').attr('href');\n\n    var staticUrl = Misago.get(_.context, 'STATIC_URL', '/');\n    var mediaUrl = Misago.get(_.context, 'MEDIA_URL', '/');\n\n    // Media/Static urls\n    var prefixUrl = function(prefix) {\n      return function(url) {\n        return prefix + url;\n      };\n    };\n\n    _.staticUrl = prefixUrl(staticUrl);\n    _.mediaUrl = prefixUrl(mediaUrl);\n  };\n\n  Misago.addService('links', function(_) {\n    setLinks(_);\n  });\n}(Misago.prototype));\n\n(function (Misago) {\n  'use strict';\n\n  var LocalStore = function() {\n    var storage = window.localStorage;\n    var prefix = '_misago_';\n    var watchers = [];\n\n    var handleStorageEvent = function(e) {\n      var newValueJson = JSON.parse(e.newValue);\n      $.each(watchers, function(i, watcher) {\n        if (watcher.keyName === e.key && e.oldValue !== e.newValue) {\n          watcher.callback(newValueJson);\n        }\n      });\n    };\n\n    window.addEventListener('storage', handleStorageEvent);\n\n    var prefixKey = function(keyName) {\n      return prefix + keyName;\n    };\n\n    this.set = function(keyName, value) {\n      storage.setItem(prefixKey(keyName), JSON.stringify(value));\n    };\n\n    this.get = function(keyName) {\n      var itemString = storage.getItem(prefixKey(keyName));\n      if (itemString) {\n        return JSON.parse(itemString);\n      } else {\n        return null;\n      }\n    };\n\n    this.watch = function(keyName, callback) {\n      watchers.push({keyName: prefixKey(keyName), callback: callback});\n    };\n\n    this.destroy = function() {\n      this.watchers = [];\n    };\n  };\n\n  Misago.addService('localstore', {\n    factory: function() {\n      return new LocalStore();\n    },\n    destroy: function(_) {\n      _.localstore.destroy();\n    }\n  });\n}(Misago.prototype));\n\n(function (Misago) {\n  'use strict';\n\n  var Modal = function(_) {\n    var self = this;\n\n    var element = document.getElementById('modal-mount');\n\n    // Open/close modal\n    var modal = $(element).modal({show: false});\n    this.open = false;\n\n    modal.on('hidden.bs.modal', function () {\n      // m() object is stateful, so in tests we don't\n      // unmount the components, as this breaks it\n      if (self.open && !_.setup.test) {\n        m.mount(element, null);\n        this.open = false;\n      }\n    });\n\n    this.show = function(component) {\n      this.open = true;\n      m.mount(document.getElementById('modal-mount'), component);\n      $(element).modal('show');\n    };\n\n    this.hide = function() {\n      modal.modal('hide');\n    };\n  };\n\n  Misago.addService('_modal', function(_) {\n    return new Modal(_);\n  },\n  {\n    before: 'mount-components'\n  });\n}(Misago.prototype));\n\n(function (Misago) {\n  'use strict';\n\n  var modal = function(name, component) {\n    if (this._modals[name]) {\n      var argumentsArray = [this._modals[name]];\n      for (var i = 1; i < arguments.length; i += 1) {\n        argumentsArray.push(arguments[i]);\n      }\n      argumentsArray.push(this);\n      this._modal.show(m.component.apply(m, argumentsArray));\n    } else if (name) {\n      this._modals[name] = component;\n    } else {\n      this._modal.hide();\n    }\n  };\n\n  Misago.addService('modals', function(_) {\n    _._modals = {};\n    _.modal = modal;\n  },\n  {\n    after: '_modal'\n  });\n}(Misago.prototype));\n\n(function (Misago) {\n  'use strict';\n\n  var Models = function() {\n    this.classes = {};\n    this.deserializers = {};\n\n    this.add = function(name, kwargs) {\n      if (kwargs.class) {\n        this.classes[name] = kwargs.class;\n      }\n\n      if (kwargs.deserialize) {\n        this.deserializers[name] = kwargs.deserialize;\n      }\n    };\n\n    this.new = function(name, data) {\n      if (this.classes[name]) {\n        // Coerce ID to string\n        // This is done to avoid type comparisions gotchas\n        // later into app\n        data.id = data.id ? String(data.id) : null;\n\n        return new this.classes[name](data);\n      } else {\n        return data;\n      }\n    };\n\n    this.deserialize = function(name, json) {\n      if (this.deserializers[name]) {\n        return this.new(name, this.deserializers[name](json, this));\n      } else {\n        return this.new(name, json);\n      }\n    };\n  };\n\n  Misago.addService('models', function() {\n    return new Models();\n  });\n}(Misago.prototype));\n\n(function (Misago) {\n  'use strict';\n\n  Misago.addService('set-momentjs-locale', function() {\n    moment.locale($('html').attr('lang'));\n  });\n}(Misago.prototype));\n\n(function (Misago) {\n  'use strict';\n\n  Misago.addService('mount-page', function(_) {\n    _.mountPage = function(component) {\n      var mount = document.getElementById('page-mount');\n      m.mount(mount, component);\n    };\n  });\n}(Misago.prototype));\n\n(function (Misago) {\n  'use strict';\n\n  var addMount = function(name) {\n    if (this._mounts.indexOf(name) === -1) {\n      this._mounts.push(name);\n    }\n  };\n\n  Misago.addService('mounts', function(_) {\n    // Default monts\n    _._mounts = [\n      'alert-mount',\n      'auth-changed-message-mount',\n      'page-component',\n      'user-menu-mount',\n      'user-menu-compact-mount'\n    ];\n\n    // Function for including new mounts\n    _.addMount = addMount;\n\n    // List of active mounts, for debugging\n    _.activeMounts = {};\n  });\n\n  Misago.addService('mount-components', {\n    factory: function(_) {\n      _._mounts.forEach(function(elementId) {\n        var mount = document.getElementById(elementId);\n        if (mount) {\n          _.activeMounts[elementId] = mount.dataset.componentName;\n          m.mount(mount, _.component(mount.dataset.componentName));\n        }\n      });\n    },\n    destroy: function(_) {\n      _._mounts.forEach(function(elementId) {\n        var mount = document.getElementById(elementId);\n        if (mount && mount.hasChildNodes()) {\n          m.mount(mount, null);\n        }\n      });\n    }\n  },\n  {\n    before: '_end'\n  });\n}(Misago.prototype));\n\n(function (Misago) {\n  'use strict';\n\n  var RunLoop = function(_) {\n    var self = this;\n\n    this._intervals = {};\n\n    var stopInterval = function(name) {\n      if (self._intervals[name]) {\n        window.clearTimeout(self._intervals[name]);\n        self._intervals[name] = null;\n      }\n    };\n\n    this.run = function(callable, name, delay) {\n      this._intervals[name] = window.setTimeout(function() {\n        stopInterval(name);\n        var result = callable(_);\n        if (result !== false) {\n          self.run(callable, name, delay);\n        }\n      }, delay);\n    };\n\n    this.runOnce = function(callable, name, delay) {\n      this._intervals[name] = window.setTimeout(function() {\n        stopInterval(name);\n        callable(_);\n      }, delay);\n    };\n\n    this.stop = function(name) {\n      for (var loop in this._intervals) {\n        if (!name || name === loop) {\n          stopInterval(loop);\n        }\n      }\n    };\n  };\n\n  Misago.addService('runloop', {\n    factory: function(_) {\n      return new RunLoop(_);\n    },\n    destroy: function(_) {\n      _.runloop.stop();\n    }\n  });\n}(Misago.prototype));\n\n(function (Misago) {\n  'use strict';\n\n  Misago.addService('show-banned-page', function(_) {\n    _.showBannedPage = function(ban, changeState) {\n      var component = _.component(\n        'banned-page', _.models.deserialize('ban', ban));\n\n      if (typeof changeState === 'undefined' || !changeState) {\n        _.title.set(gettext(\"You are banned\"));\n        window.history.pushState({}, \"\", _.context.BANNED_URL);\n      }\n\n      _.mountPage(component);\n    };\n  });\n}(Misago.prototype));\n\n(function (Misago) {\n  'use strict';\n\n  Misago.addService('start-tick', function(_) {\n    var ticks = m.prop();\n\n    _.runloop.run(function() {\n      m.startComputation();\n      // just tick once a minute so stuff gets rerendered\n      ticks(ticks() + 1);\n      // syncing dynamic timestamps, etc ect\n      m.endComputation();\n    }, 'tick', 60000);\n  });\n}(Misago.prototype));\n\n(function (Misago) {\n  'use strict';\n\n  var PageTitle = function(forum_name) {\n    this.set = function(title) {\n      if (title) {\n        this._set_complex(title);\n      } else {\n        document.title = forum_name;\n      }\n    };\n\n    this._set_complex = function(title) {\n      if (typeof title === 'string') {\n        title = {title: title};\n      }\n\n      var completeTitle = title.title;\n\n      if (typeof title.page !== 'undefined' && title.page > 1) {\n        var page_label = interpolate(\n          gettext('page %(page)s'), { page:title.page }, true);\n        completeTitle += ' (' + page_label + ')';\n      }\n\n      if (typeof title.parent !== 'undefined') {\n        completeTitle += ' | ' + title.parent;\n      }\n\n      document.title = completeTitle + ' | ' + forum_name;\n    };\n  };\n\n  Misago.addService('page-title', function(_) {\n    _.title = new PageTitle(_.settings.forum_name);\n  });\n}(Misago.prototype));\n\n(function (Misago) {\n  'use strict';\n\n  var EMAIL = /^(([^<>()[\\]\\.,;:\\s@\\\"]+(\\.[^<>()[\\]\\.,;:\\s@\\\"]+)*)|(\\\".+\\\"))@(([^<>()[\\]\\.,;:\\s@\\\"]+\\.)+[^<>()[\\]\\.,;:\\s@\\\"]{2,})$/i;\n  var USERNAME = new RegExp('^[0-9a-z]+$', 'i');\n\n  // Validators namespace\n  Misago.validators = {\n    required: function() {\n      return function(value) {\n        if ($.trim(value).length === 0) {\n          return gettext(\"This field is required.\");\n        }\n      };\n    },\n    email: function(message) {\n      return function(value) {\n        if (!EMAIL.test(value)) {\n          return message || gettext(\"Enter a valid email address.\");\n        }\n      };\n    },\n    minLength: function(limit_value, message) {\n      return function(value) {\n        var returnMessage = '';\n        var length = $.trim(value).length;\n\n        if (length < limit_value) {\n          if (message) {\n            returnMessage = message(limit_value, length);\n          } else {\n            returnMessage = ngettext(\n              \"Ensure this value has at least %(limit_value)s character (it has %(show_value)s).\",\n              \"Ensure this value has at least %(limit_value)s characters (it has %(show_value)s).\",\n              limit_value);\n          }\n          return interpolate(returnMessage, {\n            limit_value: limit_value,\n            show_value: length\n          }, true);\n        }\n      };\n    },\n    maxLength: function(limit_value, message) {\n      return function(value) {\n        var returnMessage = '';\n        var length = $.trim(value).length;\n\n        if (length > limit_value) {\n          if (message) {\n            returnMessage = message(limit_value, length);\n          } else {\n            returnMessage = ngettext(\n              \"Ensure this value has at most %(limit_value)s character (it has %(show_value)s).\",\n              \"Ensure this value has at most %(limit_value)s characters (it has %(show_value)s).\",\n              limit_value);\n          }\n          return interpolate(returnMessage, {\n            limit_value: limit_value,\n            show_value: length\n          }, true);\n        }\n      };\n    },\n    usernameMinLength: function(settings) {\n      var message = function(limit_value) {\n        return ngettext(\n          \"Username must be at least %(limit_value)s character long.\",\n          \"Username must be at least %(limit_value)s characters long.\",\n          limit_value);\n      };\n      return this.minLength(settings.username_length_min, message);\n    },\n    usernameMaxLength: function(settings) {\n      var message = function(limit_value) {\n        return ngettext(\n          \"Username cannot be longer than %(limit_value)s character.\",\n          \"Username cannot be longer than %(limit_value)s characters.\",\n          limit_value);\n      };\n      return this.maxLength(settings.username_length_max, message);\n    },\n    usernameContent: function() {\n      return function(value) {\n        if (!USERNAME.test($.trim(value))) {\n          return gettext(\"Username can only contain latin alphabet letters and digits.\");\n        }\n      };\n    },\n    passwordMinLength: function(settings) {\n      var message = function(limit_value) {\n        return ngettext(\n          \"Valid password must be at least %(limit_value)s character long.\",\n          \"Valid password must be at least %(limit_value)s characters long.\",\n          limit_value);\n      };\n      return this.minLength(settings.password_length_min, message);\n    }\n  };\n\n  var validateField = function(value, validators) {\n    var result = Misago.validators.required()(value);\n    var errors = [];\n\n    if (result) {\n      return [result];\n    } else {\n      for (var i in validators) {\n        result = validators[i](value);\n\n        if (result) {\n          errors.push(result);\n        }\n      }\n    }\n\n    return errors.length ? errors : true;\n  };\n\n  var validateForm = function(form) {\n    var errors = {};\n    var value = null;\n\n    var isValid = true;\n\n    for (var key in form.validation) {\n      if (form.validation.hasOwnProperty(key)) {\n        value = form[key]();\n        errors[key] = validateField(form[key](), form.validation[key]);\n        if (errors[key] !== true) {\n          isValid = false;\n        }\n      }\n    }\n\n    form.errors = errors;\n    return isValid;\n  };\n\n  var validate = function(form, name) {\n    if (name) {\n      return function(value) {\n        var errors = null;\n        if (typeof value !== 'undefined') {\n          errors = validateField(value, form.validation[name]);\n          if (errors) {\n            if (!form.errors) {\n              form.errors = {};\n            }\n            form.errors[name] = errors;\n          }\n          form[name](value);\n          return form[name](value);\n        } else {\n          return form[name]();\n        }\n      };\n    } else {\n      return validateForm(form);\n    }\n  };\n\n  Misago.addService('validate', {\n    factory: function() {\n      return validate;\n    }\n  });\n}(Misago.prototype));\n\n/* global zxcvbn */\n(function (Misago) {\n  'use strict';\n\n  var Zxcvbn = function(_) {\n    this.included = false;\n\n    this.scorePassword = function(password, inputs) {\n      // 0-4 score, the more the stronger password\n      return zxcvbn(password, inputs).score;\n    };\n\n    // loading\n    this.include = function() {\n      _.include('misago/js/zxcvbn.js');\n      this.included = true;\n    };\n\n    var wait = function(promise) {\n      if (typeof zxcvbn !== \"undefined\") {\n        promise.resolve();\n      } else {\n        _.runloop.runOnce(function() {\n          wait(promise);\n        }, 'loading-zxcvbn', 150);\n      }\n    };\n\n    var deferred = m.deferred();\n    this.load = function() {\n      if (!this.included) {\n        this.include();\n      }\n      wait(deferred);\n      return deferred.promise;\n    };\n  };\n\n  Misago.addService('zxcvbn', function(_) {\n    return new Zxcvbn(_);\n  },\n  {\n    after: 'include'\n  });\n}(Misago.prototype));\n\n(function (Misago) {\n  'use strict';\n\n  var Ban = function(data) {\n    this.message = {\n      html: data.message.html,\n      plain: data.message.plain,\n    };\n\n    this.expires_on = data.expires_on;\n  };\n\n  var deserializeBan = function(data) {\n    data.expires_on = Misago.deserializeDatetime(data.expires_on);\n\n    return data;\n  };\n\n  Misago.addService('model:ban', function(_) {\n    _.models.add('ban', {\n      class: Ban,\n      deserialize: deserializeBan\n    });\n  },\n  {\n    after: 'models'\n  });\n} (Misago.prototype));\n\n(function (Misago) {\n  'use strict';\n\n  var LegalPage = function(data) {\n    this.title = data.title;\n    this.body = data.body;\n    this.link = data.link;\n  };\n\n  Misago.addService('model:legal-page', function(_) {\n    _.models.add('legal-page', {\n      class: LegalPage\n    });\n  },\n  {\n    after: 'models'\n  });\n} (Misago.prototype));\n\n(function (Misago) {\n  'use strict';\n\n  var Rank = function(data) {\n    this.id = data.id;\n\n    this.name = data.name;\n    this.slug = data.slug;\n\n    this.description = data.description;\n\n    this.title = data.title;\n    this.css_class = data.css_class;\n\n    this.is_tab = data.is_tab;\n  };\n\n  Misago.addService('model:rank', function(_) {\n    _.models.add('rank', {\n      class: Rank\n    });\n  },\n  {\n    after: 'models'\n  });\n} (Misago.prototype));\n\n(function (Misago) {\n  'use strict';\n\n  var User = function(data) {\n    this.id = data.id;\n\n    this.isAuthenticated = !!this.id;\n    this.isAnonymous = !this.isAuthenticated;\n\n    this.username = data.username;\n    this.slug = data.slug;\n\n    this.email = data.email;\n\n    this.full_title = data.full_title;\n    this.rank = data.rank;\n\n    this.avatar_hash = data.avatar_hash;\n\n    this.acl = data.acl;\n\n    this.absolute_url = data.absolute_url;\n  };\n\n  var deserializeUser = function(data, models) {\n    if (data.joined_on) {\n      data.joined_on = Misago.deserializeDatetime(data.joined_on);\n    }\n\n    if (data.rank) {\n      data.rank = models.deserialize('rank', data.rank);\n    }\n\n    return data;\n  };\n\n  Misago.addService('model:user', function(_) {\n    _.models.add('user', {\n      class: User,\n      deserialize: deserializeUser\n    });\n  },\n  {\n    after: 'model:rank'\n  });\n} (Misago.prototype));\n\n(function (Misago) {\n  'use strict';\n\n  function persistent(el, isInit, context) {\n    context.retain = true;\n  }\n\n  var alert = {\n    classes: {\n      'info': 'alert-info',\n      'success': 'alert-success',\n      'warning': 'alert-warning',\n      'error': 'alert-danger'\n    },\n    view: function(ctrl, _) {\n      var config = {\n        config: persistent,\n        class: _.alert.isVisible ? 'in' : 'out'\n      };\n\n      return m('.alerts', config,\n        m('p.alert', {class: this.classes[_.alert.type]},\n          _.alert.message\n        )\n      );\n    }\n  };\n\n  Misago.addService('component:alert', function(_) {\n    _.component('alert', alert);\n  },\n  {\n    after: 'components'\n  });\n}(Misago.prototype));\n\n(function (Misago) {\n  'use strict';\n\n  function persistent(el, isInit, context) {\n    context.retain = true;\n  }\n\n  var authChanged = {\n    refresh: function() {\n      window.location.reload();\n    },\n    view: function(ctrl, _) {\n      var message = '';\n\n      var options = {\n        config: persistent,\n        class: (_.auth.isDesynced ? 'show' : null)\n      };\n\n      if (_.auth.isDesynced) {\n        if (_.auth.newUser && _.auth.newUser.isAuthenticated) {\n          message = gettext(\"You have signed in as %(username)s. Please refresh this page before continuing.\");\n          message = interpolate(message, {username: _.auth.newUser.username}, true);\n        } else {\n          message = gettext(\"%(username)s, you have been signed out. Please refresh this page before continuing.\");\n          message = interpolate(message, {username: _.user.username}, true);\n        }\n      }\n\n      return m('.auth-changed-message', options,\n        m('',\n          m('.container', [\n            m('p',\n              message\n            ),\n            m('p', [\n              m('button.btn.btn-default[type=\"button\"]', {onclick: this.refresh},\n                gettext(\"Reload page\")\n              ),\n              m('span.hidden-xs.hidden-sm.text-muted',\n                gettext(\"or press F5 key.\")\n              )\n            ])\n          ])\n        )\n      );\n    }\n  };\n\n  Misago.addService('component:auth-changed-message', function(_) {\n    _.component('auth-changed-message', authChanged);\n  },\n  {\n    after: 'components'\n  });\n}(Misago.prototype));\n\n(function (Misago) {\n  'use strict';\n\n  var banExpirationMessage = {\n    controller: function(ban, container) {\n      var _ = container || ban;\n\n      if (container) {\n        return ban;\n      } else {\n        return _.models.deserialize('ban', _.context.ban);\n      }\n    },\n    view: function(ban) {\n      var expirationMessage = null;\n      if (ban.expires_on) {\n        if (ban.expires_on.isAfter(moment())) {\n          expirationMessage = interpolate(\n            gettext('This ban expires %(expires_on)s.'),\n            {'expires_on': ban.expires_on.fromNow()},\n            true);\n        } else {\n          expirationMessage = gettext('This ban has expired.');\n        }\n      } else {\n        expirationMessage = gettext('This ban is permanent.');\n      }\n\n      return m('p', expirationMessage);\n    }\n  };\n\n  Misago.addService('component:ban-expiration-message', function(_) {\n    _.component('ban-expiration-message', banExpirationMessage);\n  },\n  {\n    after: 'components'\n  });\n}(Misago.prototype));\n\n(function (Misago) {\n  'use strict';\n\n  var bannedPage = {\n    view: function(ctrl, ban, _) {\n      var error_message = [];\n\n      if (ban.message.html) {\n        error_message.push(m('.lead', m.trust(ban.message.html)));\n      } else {\n        error_message.push(m('p.lead', ban.message.plain));\n      }\n\n      error_message.push(_.component('ban-expiration-message', ban));\n\n      return m('.page.page-error.page-error-banned',\n        m('.container',\n          m('.message-panel', [\n            m('.message-icon',\n              m('span.material-icon', 'highlight_off')\n            ),\n            m('.message-body', error_message)\n          ])\n        )\n      );\n    }\n  };\n\n  Misago.addService('component:banned-page', function(_) {\n    _.component('banned-page', bannedPage);\n  },\n  {\n    after: 'components'\n  });\n}(Misago.prototype));\n\n(function (Misago) {\n  'use strict';\n\n  var button = {\n    view: function(ctrl, kwargs) {\n      var options = {\n        disabled: kwargs.disabled || kwargs.loading || false,\n        config: kwargs.config || null,\n        loading: kwargs.loading || false,\n        type: kwargs.submit ? 'submit' : 'button',\n        onclick: kwargs.onclick || null\n      };\n\n      var element = 'button[type=\"' + options.type + '\"].btn';\n      if (options.loading) {\n        element += '.btn-loading';\n      }\n\n      if (kwargs.id) {\n        element += '#' + kwargs.id;\n      }\n\n      element += (kwargs.class || '');\n\n      var label = kwargs.label;\n      if (options.loading) {\n        label = [\n          label,\n          m('.loader-compact', [\n            m('.bounce1'),\n            m('.bounce2'),\n            m('.bounce3')\n          ])\n        ];\n      }\n\n      return m(element, options, label);\n    },\n  };\n\n  Misago.addService('component:button', function(_) {\n    _.component('button', button);\n  },\n  {\n    after: 'components'\n  });\n} (Misago.prototype));\n\n(function (Misago) {\n  'use strict';\n\n  var textFields = ['text', 'password', 'email'];\n\n  var formGroup = {\n    view: function(ctrl, kwargs) {\n      var groupClass = '.form-group';\n      var errors = null;\n      var helpText = null;\n\n      var controlType = kwargs.control.attrs.type;\n      var controlId = kwargs.control.attrs.id;\n\n      var feedbackId = controlId + '_feedback';\n      var feedbackIcon = null;\n      var showFeedbackIcon = null;\n\n      var isValidated = kwargs.validationKey && kwargs.validation !== null;\n\n      kwargs.control.attrs['aria-describedby'] = '';\n\n      if (isValidated && kwargs.validation[kwargs.validationKey]) {\n        showFeedbackIcon = textFields.indexOf(controlType) >= 0;\n        kwargs.control.attrs['aria-describedby'] = feedbackId;\n\n        if (kwargs.validation[kwargs.validationKey] === true) {\n          groupClass += '.has-success';\n          feedbackIcon = [\n            m('span.material-icon.form-control-feedback',\n              {\n                'aria-hidden': 'true'\n              },\n              'check'\n            ),\n            m('span.sr-only#' + feedbackId, gettext(\"(success)\"))\n          ];\n        } else {\n          groupClass += '.has-error';\n          errors = kwargs.validation[kwargs.validationKey];\n          feedbackIcon = [\n            m('span.material-icon.form-control-feedback',\n              {\n                'aria-hidden': 'true'\n              },\n              'clear'\n            ),\n            m('span.sr-only#' + feedbackId, gettext(\"(error)\"))\n          ];\n        }\n      }\n\n      if (kwargs.helpText) {\n        if (typeof kwargs.helpText === 'string' ||\n            kwargs.helpText instanceof String) {\n          helpText = m('p.help-block', kwargs.helpText);\n        } else {\n          helpText = kwargs.helpText;\n        }\n      }\n\n      return m(groupClass, [\n        m('label.control-label' + (kwargs.labelClass || ''),\n          {\n            for: kwargs.labelFor || controlId\n          },\n          kwargs.label + ':'\n        ),\n        m(kwargs.controlClass || '', [\n          kwargs.control,\n          showFeedbackIcon ? feedbackIcon : null,\n          errors ? m('.help-block.errors', errors.map(function(item) {\n            return m('p', item);\n          })) : null,\n          helpText\n        ])\n      ]);\n    },\n  };\n\n  Misago.addService('component:form-group', function(_) {\n    _.component('form-group', formGroup);\n  },\n  {\n    after: 'components'\n  });\n} (Misago.prototype));\n\n(function (Misago) {\n  'use strict';\n\n  var loader = {\n    view: function() {\n      return m('.loader.sk-folding-cube', [\n        m('.sk-cube1.sk-cube'),\n        m('.sk-cube2.sk-cube'),\n        m('.sk-cube4.sk-cube'),\n        m('.sk-cube3.sk-cube')\n      ]);\n    }\n  };\n\n  Misago.addService('component:loader', function(_) {\n    _.component('loader', loader);\n  },\n  {\n    after: 'components'\n  });\n} (Misago.prototype));\n\n(function (Misago) {\n  'use strict';\n\n  var persistent = function(el, isInit, context) {\n    context.retain = true;\n  };\n\n  var markup = {\n    view: function(ctrl, content) {\n      return m('article.misago-markup', {config: persistent},\n        m.trust(content)\n      );\n    }\n  };\n\n  Misago.addService('component:markup', function(_) {\n    _.component('markup', markup);\n  },\n  {\n    after: 'components'\n  });\n}(Misago.prototype));\n\n(function (Misago) {\n  'use strict';\n\n  var header = {\n    view: function(ctrl, title) {\n      return m('.modal-header', [\n        m('button.close[type=\"button\"]',\n          {'data-dismiss': 'modal', 'aria-label': gettext('Close')},\n          m('span', {'aria-hidden': 'true'}, m.trust('&times;'))\n        ),\n        m('h4#misago-modal-label.modal-title', title)\n      ]);\n    }\n  };\n\n  Misago.addService('component:modal:header', function(_) {\n    _.component('modal:header', header);\n  },\n  {\n    after: 'components'\n  });\n}(Misago.prototype));\n\n(function (Misago) {\n  'use strict';\n\n  var header = {\n    view: function(ctrl, options) {\n      return m('.page-header',\n        m('.container', [\n          m('h1', options.title),\n        ])\n      );\n    }\n  };\n\n  Misago.addService('component:header', function(_) {\n    _.component('header', header);\n  },\n  {\n    after: 'components'\n  });\n}(Misago.prototype));\n\n(function (Misago) {\n  'use strict';\n\n  var persistent = function(el, isInit, context) {\n    context.retain = true;\n  };\n\n  var styles = [\n    'progress-bar-danger',\n    'progress-bar-warning',\n    'progress-bar-warning',\n    'progress-bar-primary',\n    'progress-bar-success'\n  ];\n\n  var labels = [\n    gettext('Entered password is very weak.'),\n    gettext('Entered password is weak.'),\n    gettext('Entered password is average.'),\n    gettext('Entered password is strong.'),\n    gettext('Entered password is very strong.')\n  ];\n\n  var passwordStrength = {\n    view: function(ctrl, kwargs, _) {\n      var score = _.zxcvbn.scorePassword(kwargs.password, kwargs.inputs);\n      var options = {\n        config: persistent,\n        class: styles[score],\n        style: \"width: \" + (20 + (20 * score)) + '%',\n        'role': \"progressbar\",\n        'aria-valuenow': score,\n        'aria-valuemin': \"0\",\n        'aria-valuemax': \"4\"\n      };\n\n      return m('.help-block.password-strength', {key: 'password-strength'}, [\n        m('.progress',\n          m('.progress-bar', options,\n            m('span.sr-only', labels[score])\n          )\n        ),\n        m('p.text-small', labels[score])\n      ]);\n    },\n  };\n\n  Misago.addService('component:password-strength', function(_) {\n    _.component('password-strength', passwordStrength);\n  },\n  {\n    after: 'components'\n  });\n} (Misago.prototype));\n\n(function (Misago) {\n  'use strict';\n\n  var avatar = {\n    defaultSize: 100,\n\n    src: function(user, size, _) {\n      var src = _.baseUrl + 'user-avatar/';\n\n      if (user && user.id) {\n        // just avatar hash, size and user id\n        src += user.avatar_hash + '/' + size + '/' + user.id + '.png';\n      } else {\n        // just append avatar size to file to produce no-avatar placeholder\n        src += size + '.png';\n      }\n\n      return src;\n    },\n    view: function(ctrl, user, size, _) {\n      var finalSize = size || this.defaultSize;\n      return m('img', {\n        alt: user && user.username ? user.username : gettext(\"Unregistered\"),\n        width: finalSize,\n        height: finalSize,\n        src: this.src(user, finalSize, _)\n      });\n    }\n  };\n\n  Misago.addService('component:user-avatar', function(_) {\n    _.component('user-avatar', avatar);\n  },\n  {\n    after: 'components'\n  });\n} (Misago.prototype));\n\n(function (Misago) {\n  'use strict';\n\n  var RequestLink = function(vm, _) {\n    var self = this;\n\n    this.email = m.prop('');\n\n    this.validation = {\n      'email': [\n        Misago.validators.email()\n      ]\n    };\n\n    this.clean = function() {\n      if (!_.validate(this)) {\n        _.alert.error(gettext(\"Enter a valid email address.\"));\n        return false;\n      } else {\n        return true;\n      }\n    };\n\n    this.submit = function() {\n      _.ajax.post(vm.api, {\n        email: self.email()\n      }).then(function(user) {\n        self.success(user);\n      }, function(error) {\n        self.error(error);\n      });\n    };\n\n    this.success = function(user) {\n      vm.success(user);\n    };\n\n    this.error = function(rejection) {\n      if (rejection.status === 400) {\n          vm.error(rejection, _);\n      } else {\n        _.ajax.error(rejection);\n      }\n    };\n\n    this.reset = function() {\n      this.email('');\n      vm.reset();\n    };\n  };\n\n  Misago.addService('form:request-link', function(_) {\n    _.form('request-link', RequestLink);\n  },\n  {\n    after: 'forms'\n  });\n} (Misago.prototype));\n\n(function (Misago) {\n  'use strict';\n\n  var style = '.well.well-form.well-form-request-activation-link';\n\n  var ViewModel = function(api) {\n    this.api = api;\n    this.user = null;\n\n    this.success = function(user) {\n      this.user = user;\n    };\n\n    this.error = function(rejection, _) {\n      if (rejection.code === 'already_active') {\n        _.alert.info(rejection.detail);\n        _.modal('sign-in');\n      } else if (rejection.code === 'inactive_admin') {\n        _.alert.info(rejection.detail);\n      } else {\n        _.alert.error(rejection.detail);\n      }\n    };\n\n    this.reset = function() {\n      this.user = null;\n    };\n  };\n\n  var form = {\n    controller: function(_) {\n      var vm = new ViewModel(_.context.SEND_ACTIVATION_API);\n\n      return {\n        vm: vm,\n        form: _.form('request-link', vm)\n      };\n    },\n    view: function(ctrl, _) {\n      if (ctrl.vm.user) {\n        return this.done(ctrl.vm, ctrl.form, _);\n      } else {\n        return this.form(ctrl.form, _);\n      }\n    },\n    done: function(vm, form, _) {\n      var message = gettext(\"Activation link sent to %(email)s.\");\n\n      return m(style + '.well-done',\n        m('.done-message', [\n          m('.message-icon',\n            m('span.material-icon', 'check')\n          ),\n          m('.message-body',\n            m('p',\n              interpolate(message, {\n                email: vm.user.email\n              }, true)\n            )\n          ),\n          _.component('button', {\n            class: '.btn-default.btn-block',\n            submit: false,\n            label: gettext(\"Request another link\"),\n            onclick: form.reset.bind(form)\n          })\n\n        ])\n      );\n    },\n    form: function(form, _) {\n      return m(style,\n        m('form', {onsubmit: form.submit}, [\n          m('.form-group',\n            m('.control-input',\n              Misago.input({\n                disabled: form.isBusy,\n                value: form.email,\n                placeholder: gettext(\"Your e-mail address\")\n              })\n            )\n          ),\n          _.component('button', {\n            class: '.btn-primary.btn-block',\n            submit: true,\n            loading: form.isBusy,\n            label: gettext(\"Send link\")\n          })\n        ])\n      );\n    }\n  };\n\n  Misago.addService('component:request-activation-link-form', function(_) {\n    _.component('request-activation-link-form', form);\n  },\n  {\n    after: 'components'\n  });\n}(Misago.prototype));\n\n(function (Misago) {\n  'use strict';\n\n  var donePage = {\n    controller: function(user, _) {\n      _.title.set(gettext(\"Your password has been changed.\"));\n\n      return {\n        showSignIn: function() {\n          _.modal('sign-in');\n        }\n      };\n    },\n    view: function(ctrl, user) {\n      var button = 'button.btn.btn-primary[type=\"button\"]';\n      var message = gettext(\"%(username)s, your password has been changed successfully.\");\n\n      return m('.page.page-message.page-message-success.page-forgotten-password-done',\n        m('.container',\n          m('.message-panel', [\n            m('.message-icon',\n              m('span.material-icon', 'check')\n            ),\n            m('.message-body', [\n              m('p.lead',\n                interpolate(message, {\n                  username: user.username\n                }, true)\n              ),\n              m('p',\n                gettext(\"You will have to sign in using new password before continuing.\")\n              ),\n              m('p',\n                m(button, {onclick: ctrl.showSignIn},\n                  gettext(\"Sign in\")\n                )\n              )\n            ])\n          ])\n        )\n      );\n    }\n  };\n\n  Misago.addService('component:forgotten-password:done-page', function(_) {\n    _.component('forgotten-password:done-page', donePage);\n  },\n  {\n    after: 'components'\n  });\n}(Misago.prototype));\n\n(function (Misago) {\n  'use strict';\n\n  var inactivePage = {\n    view: function(ctrl, activation, _) {\n      var activateButton = null;\n\n      if (activation.type === 'inactive_user') {\n        activateButton = m('p',\n          m('a', {href: _.context.REQUEST_ACTIVATION_URL},\n            gettext(\"Activate your account.\")\n          )\n        );\n      }\n\n      return m('.page.page-message.page-message-info.page-forgotten-password-inactive',\n        m('.container',\n          m('.message-panel', [\n            m('.message-icon',\n              m('span.material-icon', 'info_outline')\n            ),\n            m('.message-body', [\n              m('p.lead',\n                gettext(\"Your account is inactive.\")\n              ),\n              m('p',\n                activation.message\n              ),\n              activateButton\n            ])\n          ])\n        )\n      );\n    }\n  };\n\n  Misago.addService('component:forgotten-password:inactive-page', function(_) {\n    _.component('forgotten-password:inactive-page', inactivePage);\n  },\n  {\n    after: 'components'\n  });\n}(Misago.prototype));\n\n(function (Misago) {\n  'use strict';\n\n  var style = '.well.well-form.well-form-request-password-reset-link';\n\n  var ViewModel = function(api) {\n    this.api = api;\n    this.user = null;\n\n    this.activation = null;\n    this.activationMessage = null;\n\n    this.success = function(user) {\n      this.user = user;\n    };\n\n    this.error = function(rejection, _) {\n      if (['inactive_user', 'inactive_admin'].indexOf(rejection.code) > -1) {\n        var component = _.component('forgotten-password:inactive-page', {\n          'type': rejection.code,\n          'message': rejection.detail\n        });\n\n        _.mountPage(component);\n      } else {\n        _.alert.error(rejection.detail);\n      }\n    };\n\n    this.reset = function() {\n      this.user = null;\n      this.activation = null;\n      this.activationMessage = null;\n    };\n  };\n\n  var component = {\n    controller: function(_) {\n      var vm = new ViewModel(_.context.SEND_PASSWORD_RESET_API);\n\n      return {\n        vm: vm,\n        form: _.form('request-link', vm)\n      };\n    },\n    view: function(ctrl, _) {\n      if (ctrl.vm.user) {\n        return this.done(ctrl.vm, ctrl.form, _);\n      } else {\n        return this.form(ctrl.form, _);\n      }\n    },\n    done: function(vm, form, _) {\n      var message = gettext(\"Reset password link sent to %(email)s.\");\n\n      return m(style + '.well-done',\n        m('.done-message', [\n          m('.message-icon',\n            m('span.material-icon', 'check')\n          ),\n          m('.message-body',\n            m('p',\n              interpolate(message, {\n                email: vm.user.email\n              }, true)\n            )\n          ),\n          _.component('button', {\n            class: '.btn-default.btn-block',\n            submit: false,\n            label: gettext(\"Request another link\"),\n            onclick: form.reset.bind(form)\n          })\n\n        ])\n      );\n    },\n    form: function(form, _) {\n      return m(style,\n        m('form', {onsubmit: form.submit}, [\n          m('.form-group',\n            m('.control-input',\n              Misago.input({\n                disabled: form.isBusy,\n                value: form.email,\n                placeholder: gettext(\"Your e-mail address\")\n              })\n            )\n          ),\n          _.component('button', {\n            class: '.btn-primary.btn-block',\n            submit: true,\n            loading: form.isBusy,\n            label: gettext(\"Send link\")\n          })\n        ])\n      );\n    }\n  };\n\n  Misago.addService('component:forgotten-password:request-link', function(_) {\n    _.component('forgotten-password:request-link', component);\n  },\n  {\n    after: 'components'\n  });\n}(Misago.prototype));\n\n(function (Misago) {\n  'use strict';\n\n  var style = '.well.well-form.well-form-reset-password';\n\n  var ViewModel = function(api) {\n    this.api = api;\n\n    this.success = function(user, _) {\n      _.auth.signOut();\n      _.mountPage(_.component('forgotten-password:done-page', user));\n    };\n  };\n\n  var component = {\n    controller: function(_) {\n      var vm = new ViewModel(_.context.CHANGE_PASSWORD_API);\n\n      return {\n        form: _.form('reset-password', vm)\n      };\n    },\n    view: function(ctrl, _) {\n      return m(style,\n        m('form', {onsubmit: ctrl.form.submit}, [\n          m('.form-group',\n            m('.control-input',\n              Misago.input({\n                disabled: ctrl.form.isBusy,\n                value: ctrl.form.password,\n                type: 'password',\n                placeholder: gettext(\"Enter new password\")\n              })\n            )\n          ),\n          _.component('button', {\n            class: '.btn-primary.btn-block',\n            submit: true,\n            loading: ctrl.form.isBusy,\n            label: gettext(\"Change password\")\n          })\n        ])\n      );\n    }\n  };\n\n  Misago.addService('component:forgotten-password:reset-password', function(_) {\n    _.component('forgotten-password:reset-password', component);\n  },\n  {\n    after: 'components'\n  });\n}(Misago.prototype));\n\n(function (Misago) {\n  'use strict';\n\n  var ResetPassword = function(vm, _) {\n    var self = this;\n\n    this.password = m.prop('');\n\n    this.validation = {\n      'password': [\n        Misago.validators.passwordMinLength(_.settings)\n      ]\n    };\n\n    this.clean = function() {\n      if (!_.validate(this)) {\n        if ($.trim(this.password()).length) {\n          _.alert.error(this.errors.password);\n        } else {\n          _.alert.error(gettext(\"Enter new password.\"));\n        }\n        return false;\n      } else {\n        return true;\n      }\n    };\n\n    this.submit = function() {\n      _.ajax.post(vm.api, {\n        password: self.password()\n      }).then(function(user) {\n        self.success(user);\n      }, function(error) {\n        self.error(error);\n      });\n    };\n\n    this.success = function(user) {\n      vm.success(user, _);\n    };\n\n    this.error = function(rejection) {\n      _.ajax.error(rejection);\n    };\n  };\n\n  Misago.addService('form:reset-password', function(_) {\n    _.form('reset-password', ResetPassword);\n  },\n  {\n    after: 'forms'\n  });\n} (Misago.prototype));\n\n(function (Misago) {\n  'use strict';\n\n  var nav = {\n    controller: function(_) {\n      return {\n        openUserMenu: function() {\n          _.dropdown.toggle('navbar-dropdown', 'navbar:dropdown:guest');\n        }\n      };\n    },\n    view: function(ctrl, _) {\n      return m('button', {type: 'button', onclick: ctrl.openUserMenu},\n        _.component('user-avatar', null, 64)\n      );\n    }\n  };\n\n  Misago.addService('component:navbar:compact:guest-nav', function(_) {\n    _.component('navbar:compact:guest-nav', nav);\n  },\n  {\n    after: 'components'\n  });\n}(Misago.prototype));\n\n(function (Misago) {\n  'use strict';\n\n  var nav = {\n    controller: function(_) {\n      return {\n        openUserMenu: function() {\n          _.dropdown.toggle('navbar-dropdown', 'navbar:dropdown:user');\n          return false;\n        }\n      };\n    },\n    view: function(ctrl, _) {\n      var config = {\n        onclick: ctrl.openUserMenu,\n        href: _.user.absolute_url\n      };\n\n      return m('a', config,\n        _.component('user-avatar', _.user, 64)\n      );\n    }\n  };\n\n  Misago.addService('component:navbar:compact:user-nav', function(_) {\n    _.component('navbar:compact:user-nav', nav);\n  },\n  {\n    after: 'components'\n  });\n}(Misago.prototype));\n\n(function (Misago) {\n  'use strict';\n\n  var nav = {\n    controller: function(_) {\n      return {\n        showSignIn: function() {\n          _.modal('sign-in');\n        }\n      };\n    },\n    view: function(ctrl, _) {\n      return m('div.nav.nav-guest', [\n        _.component('button', {\n          class: '.navbar-btn.btn-default',\n          onclick: ctrl.showSignIn,\n          disabled: ctrl.isBusy,\n          label: gettext('Sign in')\n        }),\n        _.component('navbar:register-button', '.navbar-btn.btn-primary')\n      ]);\n    }\n  };\n\n  Misago.addService('component:navbar:desktop:guest-nav', function(_) {\n    _.component('navbar:desktop:guest-nav', nav);\n  },\n  {\n    after: 'components'\n  });\n}(Misago.prototype));\n\n(function (Misago) {\n  'use strict';\n\n  var nav = {\n    controller: function(_) {\n      return {\n        dropdownToggle: {\n          href: _.user.absolute_url,\n\n          'data-toggle': 'dropdown',\n          'data-misago-routed': 'false',\n\n          'aria-haspopup': 'true',\n          'aria-expanded': 'false'\n        }\n      };\n    },\n    view: function(ctrl, _) {\n      return m('ul.nav.navbar-nav.nav-user', [\n        m('li.dropdown', [\n          m('a.dropdown-toggle[role=\"button\"]', ctrl.dropdownToggle,\n            _.component('user-avatar', _.user, 64)\n          ),\n          _.component('navbar:dropdown:user')\n        ])\n      ]);\n    }\n  };\n\n  Misago.addService('component:navbar:desktop:user-nav', function(_) {\n    _.component('navbar:desktop:user-nav', nav);\n  },\n  {\n    after: 'components'\n  });\n}(Misago.prototype));\n\n(function (Misago) {\n  'use strict';\n\n  var dropdown = {\n    controller: function(_) {\n      return {\n        showSignIn: function() {\n          _.modal('sign-in');\n        }\n      };\n    },\n    view: function(ctrl, _) {\n      return m('ul.dropdown-menu.user-dropdown.dropdown-menu-right[role=\"menu\"]',\n        m('li.guest-preview', [\n          m('h4',\n            gettext(\"You are browsing as guest.\")\n          ),\n          m('p',\n            gettext('Sign in or register to start and participate in discussions.')\n          ),\n          m('.row', [\n            m('.col-xs-6',\n              _.component('button', {\n                class: '.btn.btn-default.btn-block',\n                onclick: ctrl.showSignIn,\n                disabled: ctrl.isBusy,\n                label: gettext('Sign in')\n              })\n            ),\n            m('.col-xs-6',\n              _.component(\n                'navbar:register-button', '.btn.btn-primary.btn-block')\n            )\n          ])\n        ])\n      );\n    }\n  };\n\n  Misago.addService('component:navbar:dropdown:guest', function(_) {\n    _.component('navbar:dropdown:guest', dropdown);\n  },\n  {\n    after: 'components'\n  });\n}(Misago.prototype));\n\n(function (Misago) {\n  'use strict';\n\n  var button = {\n    controller: function(style, _) {\n      return {\n        isBusy: false,\n\n        showRegister: function() {\n          if (_.settings.account_activation === 'closed') {\n            _.alert.info(gettext(\"New registrations are currently disabled.\"));\n          } else {\n            m.startComputation();\n            this.isBusy = true;\n            m.endComputation();\n\n            var self = this;\n            m.sync([\n              _.zxcvbn.load(),\n              _.captcha.load()\n            ]).then(function() {\n              _.modal('register');\n            }, function() {\n              _.alert.error(gettext('Registation is not available now due to an error.'));\n            }).then(function() {\n              m.startComputation();\n              self.isBusy = false;\n              m.endComputation();\n            });\n          }\n        }\n      };\n    },\n    view: function(ctrl, style, _) {\n      return _.component('button', {\n        class: style,\n        onclick: ctrl.showRegister.bind(ctrl),\n        loading: ctrl.isBusy,\n        label: gettext('Register')\n      });\n    }\n  };\n\n  Misago.addService('component:navbar:register-button', function(_) {\n    _.component('navbar:register-button', button);\n  },\n  {\n    after: 'components'\n  });\n}(Misago.prototype));\n\n(function (Misago) {\n  'use strict';\n\n  var dropdown = {\n    class: '.dropdown-menu.user-dropdown.dropdown-menu-right',\n\n    controller: function() {\n      return {\n        logout: function() {\n          var decision = confirm(gettext(\"Are you sure you want to sign out?\"));\n          if (decision) {\n            $('#hidden-logout-form').submit();\n          }\n        }\n      };\n    },\n    view: function(ctrl, _) {\n      return m('ul' + this.class + '[role=\"menu\"]', [\n        m('li.dropdown-header',\n          m('strong',\n            _.user.username\n          )\n        ),\n        m('li.divider'),\n        m('li',\n          m('a', {href: _.user.absolute_url}, [\n            m('span.material-icon',\n              'account_circle'\n            ),\n            gettext(\"See your profile\")\n          ])\n        ),\n        m('li',\n          m('a', {href: _.context.USERCP_URL}, [\n            m('span.material-icon',\n              'done_all'\n            ),\n            gettext(\"Change options\")\n          ])\n        ),\n        m('li',\n          m('button.btn-link[type=\"button\"]', [\n            m('span.material-icon',\n              'face'\n            ),\n            gettext(\"Change avatar\")\n          ])\n        ),\n        m('li.divider'),\n        m('li.dropdown-footer',\n          m('button.btn.btn-default.btn-block', {onclick: ctrl.logout},\n            gettext(\"Logout\")\n          )\n        )\n      ]);\n    }\n  };\n\n  Misago.addService('component:navbar:dropdown:user', function(_) {\n    _.component('navbar:dropdown:user', dropdown);\n  },\n  {\n    after: 'components'\n  });\n}(Misago.prototype));\n\n(function (Misago) {\n  'use strict';\n\n  var persistent = function(el, isInit, context) {\n    context.retain = true;\n  };\n\n  var refresh = function() {\n    document.location.reload();\n  };\n\n  var modal = {\n    controller: function(message, _) {\n      if (message.activation === 'active') {\n        _.runloop.runOnce(\n          refresh, 'refresh-after-registration', 10000);\n      }\n    },\n    view: function(ctrl, message, _) {\n      var messageHtml = null;\n\n      if (message.activation === 'active') {\n        messageHtml = this.active(message);\n      } else {\n        messageHtml = this.inactive(message);\n      }\n\n      return m('.modal-dialog.modal-message.modal-register[role=\"document\"]',\n        {config: persistent},\n        m('.modal-content', [\n          _.component('modal:header', gettext('Registration complete')),\n          m('.modal-body',\n            messageHtml\n          )\n        ])\n      );\n    },\n    active: function(message) {\n      var lead = gettext(\"%(username)s, your account has been created and you were signed in.\");\n      return [\n        m('.message-icon',\n          m('span.material-icon', 'check')\n        ),\n        m('.message-body', [\n          m('p.lead',\n            interpolate(lead, {'username': message.username}, true)\n          ),\n          m('p',\n            gettext('The page will refresh automatically in 10 seconds.')\n          ),\n          m('p',\n            m('button[type=\"button\"].btn.btn-default', {onclick: refresh},\n              gettext('Refresh page')\n            )\n          )\n        ])\n      ];\n    },\n    inactive: function(message) {\n      var lead = null;\n      var help = null;\n\n      if (message.activation === 'user') {\n        lead = gettext(\"%(username)s, your account has been created but you need to activate it before you will be able to sign in.\");\n        help = gettext(\"We have sent an e-mail to %(email)s with link that you have to click to activate your account.\");\n      } else if (message.activation === 'admin') {\n        lead = gettext(\"%(username)s, your account has been created but board administrator will have to activate it before you will be able to sign in.\");\n        help = gettext(\"We will send an e-mail to %(email)s when this takes place.\");\n      }\n\n      return [\n        m('.message-icon',\n          m('span.material-icon', 'info_outline')\n        ),\n        m('.message-body', [\n          m('p.lead',\n            interpolate(lead, {'username': message.username}, true)\n          ),\n          m('p',\n            interpolate(help, {'email': message.email}, true)\n          )\n        ])\n      ];\n    }\n  };\n\n  Misago.addService('modal:register:complete', function(_) {\n    _.modal('register:complete', modal);\n  },\n  {\n    after: 'modals'\n  });\n}(Misago.prototype));\n\n(function (Misago) {\n  'use strict';\n\n  var persistent = function(el, isInit, context) {\n    context.retain = true;\n  };\n\n  var modal = {\n    controller: function(_) {\n      return {\n        form: _.form('register')\n      };\n    },\n    view: function(ctrl, _) {\n      var captcha = _.captcha.component({\n        form: ctrl.form,\n\n        labelClass: '.col-md-4',\n        controlClass: '.col-md-8'\n      });\n\n      var footnote = null;\n\n      if (_.context.TERMS_OF_SERVICE_URL) {\n        footnote = m('a', {href: _.context.TERMS_OF_SERVICE_URL},\n          m.trust(interpolate(gettext(\"By registering you agree to site's %(terms)s.\"), {\n            terms: '<strong>' + gettext(\"terms and conditions\") + '</strong>'\n          }, true))\n        );\n      }\n\n      return m('.modal-dialog.modal-form.modal-register[role=\"document\"]',\n        {config: persistent},\n        m('.modal-content', [\n          _.component('modal:header', gettext('Register')),\n          m('form.form-horizontal',\n          {\n            onsubmit: ctrl.form.submit\n          },\n          [\n            m('input[type=\"text\"]', {\n              name:'_username',\n              style: 'display: none'\n            }),\n            m('input[type=\"password\"]', {\n              name:'_password',\n              style: 'display: none'\n            }),\n            m('.modal-body', [\n              _.component('form-group', {\n                label: gettext(\"Username\"),\n                labelClass: '.col-md-4',\n                controlClass: '.col-md-8',\n                control: _.input({\n                  value: _.validate(ctrl.form, 'username'),\n                  id: 'id_username',\n                  disabled: ctrl.form.isBusy\n                }),\n                validation: ctrl.form.errors,\n                validationKey: 'username'\n              }),\n              _.component('form-group', {\n                label: gettext(\"E-mail\"),\n                labelClass: '.col-md-4',\n                controlClass: '.col-md-8',\n                control: _.input({\n                  value: _.validate(ctrl.form, 'email'),\n                  id: 'id_email',\n                  disabled: ctrl.form.isBusy\n                }),\n                validation: ctrl.form.errors,\n                validationKey: 'email'\n              }),\n              _.component('form-group', {\n                label: gettext(\"Password\"),\n                labelClass: '.col-md-4',\n                controlClass: '.col-md-8',\n                control: _.input({\n                  value: _.validate(ctrl.form, 'password'),\n                  type: 'password',\n                  id: 'id_password',\n                  disabled: ctrl.form.isBusy\n                }),\n                validation: ctrl.form.errors,\n                validationKey: 'password',\n                helpText: _.component('password-strength', {\n                  inputs: [\n                    ctrl.form.username(),\n                    ctrl.form.email()\n                  ],\n                  password: ctrl.form.password()\n                })\n              }),\n              captcha\n            ]),\n            m('.modal-footer', [\n              footnote,\n              _.component('button', {\n                class: '.btn-primary',\n                submit: true,\n                loading: ctrl.form.isBusy,\n                label: gettext(\"Register account\")\n              })\n            ])\n          ])\n        ])\n      );\n    }\n  };\n\n  Misago.addService('modal:register', function(_) {\n    _.modal('register', modal);\n  },\n  {\n    after: 'modals'\n  });\n}(Misago.prototype));\n\n(function (Misago) {\n  'use strict';\n\n  var Register = function(_) {\n    var self = this;\n\n    this.showActivation = false;\n\n    this.username = m.prop('');\n    this.email = m.prop('');\n    this.password = m.prop('');\n\n    this.captcha = _.captcha.value;\n\n    this.errors = null;\n\n    this.validation = {\n      'username': [\n        Misago.validators.usernameContent(),\n        Misago.validators.usernameMinLength(_.settings),\n        Misago.validators.usernameMaxLength(_.settings)\n      ],\n      'email': [\n        Misago.validators.email()\n      ],\n      'password': [\n        Misago.validators.passwordMinLength(_.settings)\n      ],\n      'captcha': _.captcha.validator()\n    };\n\n    this.clean = function() {\n      if (this.errors === null) {\n        _.validate(this);\n      }\n\n      _.captcha.clean(this);\n\n      if (this.hasErrors()) {\n        _.alert.error(gettext(\"Form contains errors.\"));\n        return false;\n      } else {\n        return true;\n      }\n    };\n\n    this.submit = function() {\n      _.ajax.post(_.context.USERS_API, {\n        username: this.username(),\n        email: this.email(),\n        password: this.password(),\n        captcha: this.captcha()\n      }).then(this.success, this.error);\n    };\n\n    this.success = function(data) {\n      _.modal('register:complete', data);\n    };\n\n    this.error = function(rejection) {\n      if (rejection.status === 400) {\n        _.alert.error(gettext(\"Form contains errors.\"));\n        $.extend(self.errors, rejection);\n      } else {\n        _.ajax.error(rejection);\n      }\n    };\n  };\n\n  Misago.addService('form:register', function(_) {\n    _.form('register', Register);\n  },\n  {\n    after: 'forms'\n  });\n} (Misago.prototype));\n\n(function (Misago) {\n  'use strict';\n\n  var SignIn = function(_) {\n    var self = this;\n\n    this.showActivation = false;\n\n    this.username = m.prop('');\n    this.password = m.prop('');\n\n    this.validation = {\n      'username': [],\n      'password': []\n    };\n\n    this.clean = function() {\n      if (!_.validate(this)) {\n        _.alert.error(gettext(\"Fill out both fields.\"));\n        return false;\n      } else {\n        return true;\n      }\n    };\n\n    this.submit = function() {\n      _.ajax.post(_.context.AUTH_API, {\n        username: self.username(),\n        password: self.password()\n      }).then(function() {\n        self.success();\n      }, function(error) {\n        self.error(error);\n      });\n    };\n\n    this.success = function() {\n      _.modal();\n\n      var $form = $('#hidden-login-form');\n\n      // refresh CSRF token because api call to /auth/ changed it\n      _.ajax.refreshCsrfToken();\n\n      // fill out form with user credentials and submit it, this will tell\n      // misago to redirect user back to right page, and will trigger browser's\n      // key ring feature\n      $form.find('input[type=\"hidden\"]').val(_.ajax.csrfToken);\n      $form.find('input[name=\"redirect_to\"]').val(window.location.pathname);\n      $form.find('input[name=\"username\"]').val(this.username());\n      $form.find('input[name=\"password\"]').val(this.password());\n      $form.submit();\n    };\n\n    this.error = function(rejection) {\n      if (rejection.status === 400) {\n        if (rejection.code === 'inactive_admin') {\n          _.alert.info(rejection.detail);\n        } else if (rejection.code === 'inactive_user') {\n          _.alert.info(rejection.detail);\n          self.showActivation = true;\n        } else if (rejection.code === 'banned') {\n          _.showBannedPage(rejection.detail);\n          _.modal();\n        } else {\n          _.alert.error(rejection.detail);\n        }\n      } else {\n        _.ajax.error(rejection);\n      }\n    };\n  };\n\n  Misago.addService('form:sign-in', function(_) {\n    _.form('sign-in', SignIn);\n  },\n  {\n    after: 'forms'\n  });\n} (Misago.prototype));\n\n(function (Misago) {\n  'use strict';\n\n  function persistent(el, isInit, context) {\n    context.retain = true;\n  }\n\n  var modal = {\n    controller: function(_) {\n      return {\n        form: _.form('sign-in')\n      };\n    },\n    view: function(ctrl, _) {\n      var activateButton = null;\n\n      if (ctrl.form.showActivation) {\n        activateButton = m('a.btn.btn-block.btn-success',\n          {href: _.context.REQUEST_ACTIVATION_URL},\n          gettext(\"Activate account\")\n        );\n      }\n\n      return m('.modal-dialog.modal-sm.modal-signin[role=\"document\"]',\n        {config: persistent},\n        m('.modal-content', [\n          _.component('modal:header', gettext(\"Sign in\")),\n          m('form', {onsubmit: ctrl.form.submit}, [\n            m('.modal-body', [\n              m('.form-group',\n                m('.control-input',\n                  Misago.input({\n                    disabled: ctrl.form.isBusy,\n                    value: ctrl.form.username,\n                    placeholder: gettext(\"Username or e-mail\")\n                  })\n                )\n              ),\n              m('.form-group',\n                m('.control-input',\n                  Misago.input({\n                    type: 'password',\n                    disabled: ctrl.form.isBusy,\n                    value: ctrl.form.password,\n                    placeholder: gettext(\"Password\")\n                  })\n                )\n              )\n            ]),\n            m('.modal-footer', [\n              activateButton,\n              _.component('button', {\n                class: '.btn-primary.btn-block',\n                submit: true,\n                loading: ctrl.form.isBusy,\n                label: gettext(\"Sign in\")\n              }),\n              m('a.btn.btn-block.btn-default',\n                {href: _.context.FORGOTTEN_PASSWORD_URL},\n                gettext(\"Forgot password?\")\n              )\n            ])\n          ])\n        ])\n      );\n    }\n  };\n\n  Misago.addService('modal:sign-in', function(_) {\n    _.modal('sign-in', modal);\n  },\n  {\n    after: 'modals'\n  });\n}(Misago.prototype));\n"],"sourceRoot":"/source/"}

+ 0 - 1
misago/templates/misago/base.html

@@ -48,7 +48,6 @@
     {% include "misago/scripts.html" %}
     {% include "misago/scripts.html" %}
     <script type="text/javascript">
     <script type="text/javascript">
       var misago = new Misago();
       var misago = new Misago();
-      {% include "misago/extra.js" %}
       misago.init({
       misago.init({
         api: '{% url 'misago:api:api-root' %}'
         api: '{% url 'misago:api:api-root' %}'
       }, {{ frontend_context|as_json }});
       }, {{ frontend_context|as_json }});

+ 0 - 1
misago/templates/misago/extra.js

@@ -1 +0,0 @@
-/* extra.js file allows you to run custom js against "misago" object */

+ 1 - 2
misago/users/api/auth.py

@@ -71,8 +71,7 @@ def send_activation(request):
     if form.is_valid():
     if form.is_valid():
         requesting_user = form.user_cache
         requesting_user = form.user_cache
 
 
-        mail_subject = _("Activate %(user)s account "
-                         "on %(forum_title)s forums")
+        mail_subject = _("Activate %(user)s account on %(forum_title)s forums")
         subject_formats = {
         subject_formats = {
             'user': requesting_user.username,
             'user': requesting_user.username,
             'forum_title': settings.forum_name,
             'forum_title': settings.forum_name,

+ 3 - 0
misago/users/context_processors.py

@@ -14,6 +14,9 @@ def user_links(request):
 
 
         'USERCP_URL': reverse('misago:options'),
         'USERCP_URL': reverse('misago:options'),
         'USERS_LIST_URL': reverse('misago:users'),
         'USERS_LIST_URL': reverse('misago:users'),
+
+        'AUTH_API': reverse('misago:api:auth'),
+        'USERS_API': reverse('misago:api:user-list'),
     })
     })
 
 
     return {
     return {

+ 1 - 1
misago/users/urls/api.py

@@ -6,7 +6,7 @@ from misago.users.api.usernamechanges import UsernameChangesViewSet
 
 
 
 
 urlpatterns = patterns('misago.users.api.auth',
 urlpatterns = patterns('misago.users.api.auth',
-    url(r'^auth/$', 'gateway'),
+    url(r'^auth/$', 'gateway', name='auth'),
     url(r'^auth/send-activation/$', 'send_activation', name='send_activation'),
     url(r'^auth/send-activation/$', 'send_activation', name='send_activation'),
     url(r'^auth/send-password-form/$', 'send_password_form', name='send_password_form'),
     url(r'^auth/send-password-form/$', 'send_password_form', name='send_password_form'),
     url(r'^auth/change-password/(?P<user_id>\d+)/(?P<token>[a-zA-Z0-9]+)/$', 'change_forgotten_password', name='change_forgotten_password'),
     url(r'^auth/change-password/(?P<user_id>\d+)/(?P<token>[a-zA-Z0-9]+)/$', 'change_forgotten_password', name='change_forgotten_password'),

+ 1 - 1
misago/users/views/activation.py

@@ -24,7 +24,7 @@ def activation_view(f):
 @activation_view
 @activation_view
 def request_activation(request):
 def request_activation(request):
     request.frontend_context.update({
     request.frontend_context.update({
-        'SEND_ACTIVATION_API_URL': reverse('misago:api:send_activation')
+        'SEND_ACTIVATION_API': reverse('misago:api:send_activation')
     })
     })
     return render(request, 'misago/activation/request.html')
     return render(request, 'misago/activation/request.html')
 
 

+ 2 - 2
misago/users/views/forgottenpassword.py

@@ -20,7 +20,7 @@ def reset_view(f):
 @reset_view
 @reset_view
 def request_reset(request):
 def request_reset(request):
     request.frontend_context.update({
     request.frontend_context.update({
-        'SEND_PASSWORD_RESET_API_URL': reverse('misago:api:send_password_form'),
+        'SEND_PASSWORD_RESET_API': reverse('misago:api:send_password_form'),
     })
     })
     return render(request, 'misago/forgottenpassword/request.html')
     return render(request, 'misago/forgottenpassword/request.html')
 
 
@@ -61,5 +61,5 @@ def reset_password_form(request, user_id, token):
         'token': token,
         'token': token,
     })
     })
 
 
-    request.frontend_context['CHANGE_PASSWORD_API_URL'] = api_url
+    request.frontend_context['CHANGE_PASSWORD_API'] = api_url
     return render(request, 'misago/forgottenpassword/form.html')
     return render(request, 'misago/forgottenpassword/form.html')