diff --git a/client/html/endless_pager.hbs b/client/html/endless_pager.hbs index a47cd96..6870f9a 100644 --- a/client/html/endless_pager.hbs +++ b/client/html/endless_pager.hbs @@ -1,5 +1,5 @@
-
+
diff --git a/client/js/controllers/page_controller.js b/client/js/controllers/page_controller.js index 8c75c83..7b589a3 100644 --- a/client/js/controllers/page_controller.js +++ b/client/js/controllers/page_controller.js @@ -9,6 +9,7 @@ class PageController { constructor() { events.listen(events.SettingsChange, () => { this.update(); + return true; }); this.update(); } diff --git a/client/js/controllers/tags_controller.js b/client/js/controllers/tags_controller.js index 931a32c..b49bffd 100644 --- a/client/js/controllers/tags_controller.js +++ b/client/js/controllers/tags_controller.js @@ -40,7 +40,7 @@ class TagsController { Promise.all(promises).then( () => { events.notify(events.TagsChange); - events.notify(events.Success, 'Changes saved successfully'); + events.notify(events.Success, 'Changes saved.'); }, response => { events.notify(events.Error, response.description); diff --git a/client/js/controllers/top_nav_controller.js b/client/js/controllers/top_nav_controller.js index 51d3002..cab1a4d 100644 --- a/client/js/controllers/top_nav_controller.js +++ b/client/js/controllers/top_nav_controller.js @@ -43,7 +43,9 @@ class TopNavController { this.topNavView.activate(this.activeItem); }; - events.listen(events.Authentication, rerender); + events.listen( + events.Authentication, + () => { rerender(); return true; }); rerender(); } diff --git a/client/js/controllers/users_controller.js b/client/js/controllers/users_controller.js index c824eb3..529f490 100644 --- a/client/js/controllers/users_controller.js +++ b/client/js/controllers/users_controller.js @@ -195,25 +195,23 @@ class UsersController { _delete(user) { const isLoggedIn = api.isLoggedIn(user); - return new Promise((resolve, reject) => { - api.delete('/user/' + user.name) - .then(response => { - if (isLoggedIn) { - api.forget(); - api.logout(); - } - resolve(); - if (api.hasPrivilege('users:list')) { - page('/users'); - } else { - page('/'); - } - events.notify(events.Success, 'Account deleted'); - }, response => { - reject(); - events.notify(events.Error, response.description); - }); - }); + return api.delete('/user/' + user.name) + .then(response => { + if (isLoggedIn) { + api.forget(); + api.logout(); + } + if (api.hasPrivilege('users:list')) { + page('/users'); + } else { + page('/'); + } + events.notify(events.Success, 'Account deleted.'); + return Promise.resolve(); + }, response => { + events.notify(events.Error, response.description); + return Promise.reject(); + }); } _show(user, section) { diff --git a/client/js/events.js b/client/js/events.js index 0493c3b..0dd010a 100644 --- a/client/js/events.js +++ b/client/js/events.js @@ -1,34 +1,48 @@ 'use strict'; -let listeners = []; +let pendingMessages = new Map(); +let listeners = new Map(); function unlisten(messageClass) { - listeners[messageClass] = []; + listeners.set(messageClass, []); } function listen(messageClass, handler) { - if (!(messageClass in listeners)) { - listeners[messageClass] = []; + if (pendingMessages.has(messageClass)) { + let newPendingMessages = []; + for (let message of pendingMessages.get(messageClass)) { + if (!handler(message)) { + newPendingMessages.push(message); + } + } + pendingMessages.set(messageClass, newPendingMessages); } - listeners[messageClass].push(handler); + if (!listeners.has(messageClass)) { + listeners.set(messageClass, []); + } + listeners.get(messageClass).push(handler); } function notify(messageClass, message) { - if (!(messageClass in listeners)) { + if (!listeners.has(messageClass) || !listeners.get(messageClass).length) { + if (!pendingMessages.has(messageClass)) { + pendingMessages.set(messageClass, []); + } + pendingMessages.get(messageClass).push(message); return; } - for (let handler of listeners[messageClass]) { + for (let handler of listeners.get(messageClass)) { handler(message); } } module.exports = { - Success: 1, - Error: 2, - Info: 3, - Authentication: 4, - SettingsChange: 5, - TagsChange: 6, + Success: 'success', + Error: 'error', + Info: 'info', + Authentication: 'auth', + SettingsChange: 'settings-change', + TagsChange: 'tags-change', notify: notify, listen: listen, diff --git a/client/js/main.js b/client/js/main.js index 6fb92c3..1636e96 100644 --- a/client/js/main.js +++ b/client/js/main.js @@ -29,10 +29,16 @@ controllers.push(require('./controllers/home_controller.js')); const tags = require('./tags.js'); const events = require('./events.js'); +const views = require('./util/views.js'); for (let controller of controllers) { controller.registerRoutes(); } +page.exit((ctx, next) => { + views.unlistenToMessages(); + next(); +}); + const api = require('./api.js'); Promise.all([tags.refreshExport(), api.loginFromCookies()]) .then(() => { diff --git a/client/js/tags.js b/client/js/tags.js index 5b1c3b0..3098104 100644 --- a/client/js/tags.js +++ b/client/js/tags.js @@ -60,7 +60,9 @@ function getExport() { return _export || {}; } -events.listen(events.TagsChange, refreshExport); +events.listen( + events.TagsChange, + () => { refreshExport(); return true; }); module.exports = { getExport: getExport, diff --git a/client/js/util/views.js b/client/js/util/views.js index 8dd4115..5f61a49 100644 --- a/client/js/util/views.js +++ b/client/js/util/views.js @@ -37,31 +37,31 @@ function makeThumbnail(url) { function makeRadio(options) { return makeVoidElement( - 'input', - { - id: options.id, - name: options.name, - value: options.value, - type: 'radio', - checked: options.selectedValue === options.value, - required: options.required, - }) + - _makeLabel(options, {class: 'radio'}); + 'input', + { + id: options.id, + name: options.name, + value: options.value, + type: 'radio', + checked: options.selectedValue === options.value, + required: options.required, + }) + + _makeLabel(options, {class: 'radio'}); } function makeCheckbox(options) { return makeVoidElement( - 'input', - { - id: options.id, - name: options.name, - value: options.value, - type: 'checkbox', - checked: options.checked !== undefined ? - options.checked : false, - required: options.required, - }) + - _makeLabel(options, {class: 'checkbox'}); + 'input', + { + id: options.id, + name: options.name, + value: options.value, + type: 'checkbox', + checked: options.checked !== undefined ? + options.checked : false, + required: options.required, + }) + + _makeLabel(options, {class: 'checkbox'}); } function makeSelect(options) { @@ -143,26 +143,6 @@ function makeFlexboxAlign(options) { .map(() => '
  • ').join(''); } -function _messageHandler(target, message, className) { - if (!message) { - message = 'Unknown message'; - } - const messagesHolder = target.querySelector('.messages'); - if (!messagesHolder) { - alert(message); - return; - } - /* TODO: animate this */ - const node = document.createElement('div'); - node.innerHTML = message.replace(/\n/g, '
    '); - node.classList.add('message'); - node.classList.add(className); - const wrapper = document.createElement('div'); - wrapper.classList.add('message-wrapper'); - wrapper.appendChild(node); - messagesHolder.appendChild(wrapper); -} - function _serializeElement(name, attributes) { return [name] .concat(Object.keys(attributes).map(key => { @@ -186,16 +166,44 @@ function makeVoidElement(name, attributes) { return '<{0}/>'.format(_serializeElement(name, attributes)); } -function listenToMessages(target) { +function _messageHandler(target, message, className) { + if (!message) { + message = 'Unknown message'; + } + const messagesHolder = target.querySelector('.messages'); + if (!messagesHolder) { + return false; + } + /* TODO: animate this */ + const node = document.createElement('div'); + node.innerHTML = message.replace(/\n/g, '
    '); + node.classList.add('message'); + node.classList.add(className); + const wrapper = document.createElement('div'); + wrapper.classList.add('message-wrapper'); + wrapper.appendChild(node); + messagesHolder.appendChild(wrapper); + return true; +} + +function unlistenToMessages() { events.unlisten(events.Success); events.unlisten(events.Error); events.unlisten(events.Info); - events.listen( - events.Success, msg => { _messageHandler(target, msg, 'success'); }); - events.listen( - events.Error, msg => { _messageHandler(target, msg, 'error'); }); - events.listen( - events.Info, msg => { _messageHandler(target, msg, 'info'); }); +} + +function listenToMessages(target) { + unlistenToMessages(); + const listen = (eventType, className) => { + events.listen( + eventType, + msg => { + return _messageHandler(target, msg, className); + }); + }; + listen(events.Success, 'success'); + listen(events.Error, 'error'); + listen(events.Info, 'info'); } function clearMessages(target) { @@ -314,6 +322,7 @@ module.exports = { enableForm: enableForm, disableForm: disableForm, listenToMessages: listenToMessages, + unlistenToMessages: unlistenToMessages, clearMessages: clearMessages, decorateValidator: decorateValidator, makeVoidElement: makeVoidElement, diff --git a/client/js/views/empty_view.js b/client/js/views/empty_view.js index d4e36b6..2142fb8 100644 --- a/client/js/views/empty_view.js +++ b/client/js/views/empty_view.js @@ -10,7 +10,7 @@ class EmptyView { render(ctx) { const target = document.getElementById('content-holder'); const source = this.template; - views.listenToMessages(target); + views.listenToMessages(source); views.showView(target, source); } } diff --git a/client/js/views/endless_page_view.js b/client/js/views/endless_page_view.js index d961a0f..784d58c 100644 --- a/client/js/views/endless_page_view.js +++ b/client/js/views/endless_page_view.js @@ -15,7 +15,7 @@ class EndlessPageView { const source = this.holderTemplate(); const pageHeaderHolder = source.querySelector('.page-header-holder'); const pagesHolder = source.querySelector('.pages-holder'); - views.listenToMessages(target); + views.listenToMessages(source); views.showView(target, source); this.active = true; this.working = 0; diff --git a/client/js/views/help_view.js b/client/js/views/help_view.js index f43a1a6..b4ff278 100644 --- a/client/js/views/help_view.js +++ b/client/js/views/help_view.js @@ -61,7 +61,7 @@ class HelpView { } } - views.listenToMessages(target); + views.listenToMessages(source); views.showView(target, source); views.scrollToHash(); diff --git a/client/js/views/home_view.js b/client/js/views/home_view.js index b27fb14..c0d5df1 100644 --- a/client/js/views/home_view.js +++ b/client/js/views/home_view.js @@ -16,7 +16,7 @@ class HomeView { buildDate: config.meta.buildDate, }); - views.listenToMessages(target); + views.listenToMessages(source); views.showView(target, source); } } diff --git a/client/js/views/login_view.js b/client/js/views/login_view.js index 10ba67a..112cdab 100644 --- a/client/js/views/login_view.js +++ b/client/js/views/login_view.js @@ -36,7 +36,7 @@ class LoginView { .always(() => { views.enableForm(form); }); }); - views.listenToMessages(target); + views.listenToMessages(source); views.showView(target, source); } } diff --git a/client/js/views/manual_page_view.js b/client/js/views/manual_page_view.js index a74729c..53f0c4b 100644 --- a/client/js/views/manual_page_view.js +++ b/client/js/views/manual_page_view.js @@ -99,13 +99,13 @@ class ManualPageView { })); } - views.listenToMessages(target); + views.listenToMessages(source); views.showView(target, source); if (response.total <= (currentPage - 1) * response.pageSize) { events.notify(events.Info, 'No data to show'); } }, response => { - views.listenToMessages(target); + views.listenToMessages(source); views.showView(target, source); events.notify(events.Error, response.description); }); diff --git a/client/js/views/password_reset_view.js b/client/js/views/password_reset_view.js index 14f0737..67043c5 100644 --- a/client/js/views/password_reset_view.js +++ b/client/js/views/password_reset_view.js @@ -24,7 +24,7 @@ class PasswordResetView { .catch(() => { views.enableForm(form); }); }); - views.listenToMessages(target); + views.listenToMessages(source); views.showView(target, source); } } diff --git a/client/js/views/registration_view.js b/client/js/views/registration_view.js index 4a3178f..bcd8c74 100644 --- a/client/js/views/registration_view.js +++ b/client/js/views/registration_view.js @@ -33,7 +33,7 @@ class RegistrationView { .always(() => { views.enableForm(form); }); }); - views.listenToMessages(target); + views.listenToMessages(source); views.showView(target, source); } } diff --git a/client/js/views/settings_view.js b/client/js/views/settings_view.js index 19bf591..6809ee8 100644 --- a/client/js/views/settings_view.js +++ b/client/js/views/settings_view.js @@ -25,7 +25,7 @@ class SettingsView { }); }); - views.listenToMessages(target); + views.listenToMessages(source); views.showView(target, source); } } diff --git a/client/js/views/tag_categories_view.js b/client/js/views/tag_categories_view.js index f00c86f..7dd7115 100644 --- a/client/js/views/tag_categories_view.js +++ b/client/js/views/tag_categories_view.js @@ -101,7 +101,7 @@ class TagListHeaderView { this._saveButtonClickHandler(e, ctx, target); }); - views.listenToMessages(target); + views.listenToMessages(source); views.showView(target, source); } } diff --git a/client/js/views/user_delete_view.js b/client/js/views/user_delete_view.js index 740c205..c9e2be7 100644 --- a/client/js/views/user_delete_view.js +++ b/client/js/views/user_delete_view.js @@ -19,10 +19,11 @@ class UserDeleteView { e.preventDefault(); views.clearMessages(target); views.disableForm(form); - ctx.delete(); + ctx.delete() + .catch(() => { views.enableForm(form); }); }); - views.listenToMessages(target); + views.listenToMessages(source); views.showView(target, source); } } diff --git a/client/js/views/user_edit_view.js b/client/js/views/user_edit_view.js index bb5eb9c..0f9d858 100644 --- a/client/js/views/user_edit_view.js +++ b/client/js/views/user_edit_view.js @@ -54,7 +54,7 @@ class UserEditView { .always(() => { views.enableForm(form); }); }); - views.listenToMessages(target); + views.listenToMessages(source); views.showView(target, source); } } diff --git a/client/js/views/user_summary_view.js b/client/js/views/user_summary_view.js index 9369376..c740cce 100644 --- a/client/js/views/user_summary_view.js +++ b/client/js/views/user_summary_view.js @@ -10,7 +10,7 @@ class UserSummaryView { render(ctx) { const target = ctx.target; const source = this.template(ctx); - views.listenToMessages(target); + views.listenToMessages(source); views.showView(target, source); } } diff --git a/client/js/views/user_view.js b/client/js/views/user_view.js index 7cc8533..aac2e08 100644 --- a/client/js/views/user_view.js +++ b/client/js/views/user_view.js @@ -38,7 +38,7 @@ class UserView { ctx.target = source.querySelector('#user-content-holder'); view.render(ctx); - views.listenToMessages(target); + views.listenToMessages(source); views.showView(target, source); } }