From 1b62daed9a04ed3af8e5b92d2ea2e514f4bdf1c1 Mon Sep 17 00:00:00 2001
From: rr- <rr-@sakuya.pl>
Date: Fri, 5 Aug 2016 20:09:11 +0200
Subject: [PATCH] client/general: clean up, refactor

---
 client/html/login.tpl                        | 19 ++++-
 client/html/password_reset.tpl               |  6 +-
 client/html/settings.tpl                     | 82 ++++++++++++------
 client/html/tag_delete.tpl                   |  6 +-
 client/html/user_delete.tpl                  |  6 +-
 client/html/user_edit.tpl                    | 44 ++++++++--
 client/html/user_registration.tpl            | 27 +++++-
 client/js/controllers/settings_controller.js |  2 +-
 client/js/tags.js                            | 22 ++---
 client/js/util/misc.js                       | 50 +++++++----
 client/js/util/views.js                      | 90 ++++++++++----------
 client/js/views/login_view.js                | 22 ++---
 client/js/views/password_reset_view.js       |  2 +-
 client/js/views/registration_view.js         |  6 +-
 client/js/views/settings_view.js             | 24 +++---
 client/js/views/user_edit_view.js            | 44 +++++-----
 16 files changed, 283 insertions(+), 169 deletions(-)

diff --git a/client/html/login.tpl b/client/html/login.tpl
index d9e0735..ec63e6c 100644
--- a/client/html/login.tpl
+++ b/client/html/login.tpl
@@ -4,13 +4,26 @@
         <div class='input'>
             <ul>
                 <li>
-                    <%= ctx.makeTextInput({text: 'User name', id: 'user-name', name: 'name', required: true, pattern: ctx.userNamePattern}) %>
+                    <%= ctx.makeTextInput({
+                        text: 'User name',
+                        name: 'name',
+                        required: true,
+                        pattern: ctx.userNamePattern,
+                    }) %>
                 </li>
                 <li>
-                    <%= ctx.makePasswordInput({text: 'Password', id: 'user-password', name: 'password', required: true, pattern: ctx.passwordPattern}) %>
+                    <%= ctx.makePasswordInput({
+                        text: 'Password',
+                        name: 'password',
+                        required: true,
+                        pattern: ctx.passwordPattern,
+                    }) %>
                 </li>
                 <li>
-                    <%= ctx.makeCheckbox({text: 'Remember me', id: 'remember-user', name: 'remember-user'}) %>
+                    <%= ctx.makeCheckbox({
+                        text: 'Remember me',
+                        name: 'remember-user',
+                    }) %>
                 </li>
             </ul>
         </div>
diff --git a/client/html/password_reset.tpl b/client/html/password_reset.tpl
index 8169a2b..a4ccc7e 100644
--- a/client/html/password_reset.tpl
+++ b/client/html/password_reset.tpl
@@ -4,7 +4,11 @@
         <div class='input'>
             <ul>
                 <li>
-                    <%= ctx.makeTextInput({text: 'User name or e-mail address', id: 'user-name', name: 'user-name', required: true}) %>
+                    <%= ctx.makeTextInput({
+                        text: 'User name or e-mail address',
+                        name: 'user-name',
+                        required: true,
+                    }) %>
                 </li>
             </ul>
         </div>
diff --git a/client/html/settings.tpl b/client/html/settings.tpl
index 743d6cd..1f52489 100644
--- a/client/html/settings.tpl
+++ b/client/html/settings.tpl
@@ -2,32 +2,62 @@
     <form>
         <strong>Browsing settings</strong>
         <p>These settings are saved to the browser's local storage and are not coupled to the user account, so they don't apply to other devices or browsers alike.</p>
-        <div class='input'>
-            <ul>
-                <li>
-                    <%= ctx.makeCheckbox({text: 'Enable keyboard shortcuts', id: 'keyboard-shortcuts', name: 'keyboard-shortcuts', checked: ctx.browsingSettings.keyboardShortcuts}) %>
-                    <a class='append icon' href='/help/keyboard'><i class='fa fa-question-circle-o'></i></a>
-                </li>
-                <li>
-                    <%= ctx.makeNumericInput({text: 'Number of posts per page', id: 'posts-per-page', name: 'posts-per-page', checked: ctx.browsingSettings.postCount, min: 10, max: 100, value: ctx.browsingSettings.postsPerPage}) %>
-                </li>
-                <li>
-                    <%= ctx.makeCheckbox({text: 'Upscale small posts', id: 'upscale-small-posts', name: 'upscale-small-posts', checked: ctx.browsingSettings.upscaleSmallPosts}) %>
-                </li>
-                <li>
-                    <%= ctx.makeCheckbox({text: 'Endless scroll', id: 'endless-scroll', name: 'endless-scroll', checked: ctx.browsingSettings.endlessScroll}) %>
-                    <p class='hint'>Rather than using a paged navigation, smoothly scrolls through the content.</p>
-                </li>
-                <li>
-                    <%= ctx.makeCheckbox({text: 'Enable transparency grid', id: 'transparency-grid', name: 'transparency-grid', checked: ctx.browsingSettings.transparencyGrid}) %>
-                    <p class='hint'>Renders a checkered pattern behind posts with transparent background.</p>
-                </li>
-                <li>
-                    <%= ctx.makeCheckbox({text: 'Show tag suggestions', id: 'tag-suggestions', name: 'tag-suggestions', checked: ctx.browsingSettings.tagSuggestions}) %>
-                    <p class='hint'>Shows a popup with suggested tags in edit forms.</p>
-                </li>
-            </ul>
-        </div>
+        <ul class='input'>
+            <li>
+                <%= ctx.makeCheckbox({
+                    text: 'Enable keyboard shortcuts',
+                    name: 'keyboard-shortcuts',
+                    checked: ctx.browsingSettings.keyboardShortcuts,
+                }) %>
+                <a class='append icon' href='/help/keyboard'><i class='fa fa-question-circle-o'></i></a>
+            </li>
+
+            <li>
+                <%= ctx.makeNumericInput({
+                    text: 'Number of posts per page',
+                    name: 'posts-per-page',
+                    checked: ctx.browsingSettings.postCount,
+                    value: ctx.browsingSettings.postsPerPage,
+                    min: 10,
+                    max: 100,
+                }) %>
+            </li>
+
+            <li>
+                <%= ctx.makeCheckbox({
+                    text: 'Upscale small posts',
+                    name: 'upscale-small-posts',
+                    checked: ctx.browsingSettings.upscaleSmallPosts}) %>
+            </li>
+
+            <li>
+                <%= ctx.makeCheckbox({
+                    text: 'Endless scroll',
+                    name: 'endless-scroll',
+                    checked: ctx.browsingSettings.endlessScroll,
+                }) %>
+                <p class='hint'>Rather than using a paged navigation, smoothly scrolls through the content.</p>
+            </li>
+
+            <li>
+                <%= ctx.makeCheckbox({
+                    text: 'Enable transparency grid',
+                    name: 'transparency-grid',
+                    checked: ctx.browsingSettings.transparencyGrid,
+                }) %>
+                <p class='hint'>Renders a checkered pattern behind posts with transparent background.</p>
+            </li>
+
+            <li>
+                <%= ctx.makeCheckbox({
+                    text: 'Show tag suggestions',
+                    name: 'tag-suggestions',
+                    checked: ctx.browsingSettings.tagSuggestions,
+                }) %>
+                <p class='hint'>Shows a popup with suggested tags in edit forms.</p>
+            </li>
+        </ul>
+
         <div class='messages'></div>
         <div class='buttons'>
             <input type='submit' value='Save settings'/>
diff --git a/client/html/tag_delete.tpl b/client/html/tag_delete.tpl
index 5b63aa0..82736fc 100644
--- a/client/html/tag_delete.tpl
+++ b/client/html/tag_delete.tpl
@@ -4,7 +4,11 @@
 
         <ul>
             <li>
-                <%= ctx.makeCheckbox({id: 'confirm-deletion', name: 'confirm-deletion', required: true, text: 'I confirm that I want to delete this tag.'}) %>
+                <%= ctx.makeCheckbox({
+                    name: 'confirm-deletion',
+                    text: 'I confirm that I want to delete this tag.',
+                    required: true,
+                }) %>
             </li>
         </ul>
 
diff --git a/client/html/user_delete.tpl b/client/html/user_delete.tpl
index c7c8433..9704492 100644
--- a/client/html/user_delete.tpl
+++ b/client/html/user_delete.tpl
@@ -3,7 +3,11 @@
         <div class='input'>
             <ul>
                 <li>
-                    <%= ctx.makeCheckbox({id: 'confirm-deletion', name: 'confirm-deletion', required: true, text: 'I confirm that I want to delete this account.'}) %>
+                    <%= ctx.makeCheckbox({
+                        name: 'confirm-deletion',
+                        text: 'I confirm that I want to delete this account.',
+                        required: true,
+                    }) %>
                 </li>
             </ul>
         </div>
diff --git a/client/html/user_edit.tpl b/client/html/user_edit.tpl
index 3805c14..88d6037 100644
--- a/client/html/user_edit.tpl
+++ b/client/html/user_edit.tpl
@@ -1,27 +1,46 @@
 <div id='user-edit'>
     <form>
-        <ul>
+        <ul class='input'>
             <% if (ctx.canEditName) { %>
                 <li>
-                    <%= ctx.makeTextInput({text: 'User name', id: 'user-name', name: 'name', value: ctx.user.name, pattern: ctx.userNamePattern}) %>
+                    <%= ctx.makeTextInput({
+                        text: 'User name',
+                        name: 'name',
+                        value: ctx.user.name,
+                        pattern: ctx.userNamePattern,
+                    }) %>
                 </li>
             <% } %>
 
             <% if (ctx.canEditPassword) { %>
                 <li>
-                    <%= ctx.makePasswordInput({text: 'Password', id: 'user-password', name: 'password', placeholder: 'leave blank if not changing', pattern: ctx.passwordPattern}) %>
+                    <%= ctx.makePasswordInput({
+                        text: 'Password',
+                        name: 'password',
+                        placeholder: 'leave blank if not changing',
+                        pattern: ctx.passwordPattern,
+                    }) %>
                 </li>
             <% } %>
 
             <% if (ctx.canEditEmail) { %>
                 <li>
-                    <%= ctx.makeEmailInput({text: 'Email', id: 'user-email', name: 'email', value: ctx.user.email}) %>
+                    <%= ctx.makeEmailInput({
+                        text: 'Email',
+                        name: 'email',
+                        value: ctx.user.email,
+                    }) %>
                 </li>
             <% } %>
 
             <% if (ctx.canEditRank) { %>
                 <li>
-                    <%= ctx.makeSelect({text: 'Rank', id: 'user-rank', name: 'rank', keyValues: ctx.ranks, selectedKey: ctx.user.rank}) %>
+                    <%= ctx.makeSelect({
+                        text: 'Rank',
+                        name: 'rank',
+                        keyValues: ctx.ranks,
+                        selectedKey: ctx.user.rank,
+                    }) %>
                 </li>
             <% } %>
 
@@ -30,8 +49,19 @@
                     <label>Avatar</label>
                     <div id='avatar-content'></div>
                     <div id='avatar-radio'>
-                        <%= ctx.makeRadio({text: 'Gravatar', id: 'gravatar-radio', name: 'avatar-style', value: 'gravatar', selectedValue: ctx.user.avatarStyle}) %>
-                        <%= ctx.makeRadio({text: 'Manual avatar', id: 'avatar-radio', name: 'avatar-style', value: 'manual', selectedValue: ctx.user.avatarStyle}) %>
+                        <%= ctx.makeRadio({
+                            text: 'Gravatar',
+                            name: 'avatar-style',
+                            value: 'gravatar',
+                            selectedValue: ctx.user.avatarStyle,
+                        }) %>
+
+                        <%= ctx.makeRadio({
+                            text: 'Manual avatar',
+                            name: 'avatar-style',
+                            value: 'manual',
+                            selectedValue: ctx.user.avatarStyle,
+                        }) %>
                     </div>
                 </li>
             <% } %>
diff --git a/client/html/user_registration.tpl b/client/html/user_registration.tpl
index 8a6d3e4..9db35ce 100644
--- a/client/html/user_registration.tpl
+++ b/client/html/user_registration.tpl
@@ -4,14 +4,33 @@
         <div class='input'>
             <ul>
                 <li>
-                    <%= ctx.makeTextInput({text: 'User name', id: 'user-name', name: 'user-name', placeholder: 'letters, digits, _, -', required: true, pattern: ctx.userNamePattern}) %>
+                    <%= ctx.makeTextInput({
+                        text: 'User name',
+                        name: 'name',
+                        placeholder: 'letters, digits, _, -',
+                        required: true,
+                        pattern: ctx.userNamePattern,
+                    }) %>
                 </li>
                 <li>
-                    <%= ctx.makePasswordInput({text: 'Password', id: 'user-password', name: 'user-password', placeholder: '5+ characters', required: true, pattern: ctx.passwordPattern}) %>
+                    <%= ctx.makePasswordInput({
+                        text: 'Password',
+                        name: 'password',
+                        placeholder: '5+ characters',
+                        required: true,
+                        pattern: ctx.passwordPattern,
+                    }) %>
                 </li>
                 <li>
-                    <%= ctx.makeEmailInput({text: 'Email', id: 'user-email', name: 'user-email', placeholder: 'optional'}) %>
-                    <p class='hint'>Used for password reminder and to show a <a href='http://gravatar.com/'>Gravatar</a>. Leave blank for random Gravatar.</p>
+                    <%= ctx.makeEmailInput({
+                        text: 'Email',
+                        name: 'email',
+                        placeholder: 'optional',
+                    }) %>
+                    <p class='hint'>
+                        Used for password reminder and to show a <a href='http://gravatar.com/'>Gravatar</a>.
+                        Leave blank for random Gravatar.
+                    </p>
                 </li>
             </ul>
         </div>
diff --git a/client/js/controllers/settings_controller.js b/client/js/controllers/settings_controller.js
index 237ad52..a4fb183 100644
--- a/client/js/controllers/settings_controller.js
+++ b/client/js/controllers/settings_controller.js
@@ -16,7 +16,7 @@ class SettingsController {
 
     _evtChange(e) {
         this._view.clearMessages();
-        settings.save(e.detail.settings);
+        settings.save(e.detail);
         this._view.showSuccess('Settings saved.');
     }
 };
diff --git a/client/js/tags.js b/client/js/tags.js
index 3d62640..9ed3a68 100644
--- a/client/js/tags.js
+++ b/client/js/tags.js
@@ -111,14 +111,14 @@ function getSuggestions(tagName) {
     return actualTag.suggestions || [];
 }
 
-module.exports = {
-    getAllCategories: getAllCategories,
-    getAllTags: getAllTags,
-    getTagByName: getTagByName,
-    getCategoryByName: getCategoryByName,
-    getNameToTagMap: getNameToTagMap,
-    getOriginalTagName: getOriginalTagName,
-    refreshExport: refreshExport,
-    getAllImplications: getAllImplications,
-    getSuggestions: getSuggestions,
-};
+module.exports = misc.arrayToObject([
+    getAllCategories,
+    getAllTags,
+    getTagByName,
+    getCategoryByName,
+    getNameToTagMap,
+    getOriginalTagName,
+    refreshExport,
+    getAllImplications,
+    getSuggestions,
+], func => func.name);
diff --git a/client/js/util/misc.js b/client/js/util/misc.js
index 5c05d73..629db90 100644
--- a/client/js/util/misc.js
+++ b/client/js/util/misc.js
@@ -260,21 +260,35 @@ function arraysDiffer(source1, source2) {
         source2.filter(value => !source1.includes(value)).length > 0);
 }
 
-module.exports = {
-    range: range,
-    formatUrlParameters: formatUrlParameters,
-    parseUrlParameters: parseUrlParameters,
-    parseUrlParametersRoute: parseUrlParametersRoute,
-    formatRelativeTime: formatRelativeTime,
-    formatFileSize: formatFileSize,
-    formatMarkdown: formatMarkdown,
-    unindent: unindent,
-    enableExitConfirmation: enableExitConfirmation,
-    disableExitConfirmation: disableExitConfirmation,
-    confirmPageExit: confirmPageExit,
-    escapeHtml: escapeHtml,
-    makeCssName: makeCssName,
-    splitByWhitespace: splitByWhitespace,
-    arraysDiffer: arraysDiffer,
-    decamelize: decamelize,
-};
+function arrayToObject(array, keySelector, valueSelector) {
+    if (keySelector === undefined) {
+        keySelector = item => item;
+    }
+    if (valueSelector === undefined) {
+        valueSelector = item => item;
+    }
+    return array.reduce((obj, item) => {
+        obj[keySelector(item)] = valueSelector(item);
+        return obj;
+    }, {});
+}
+
+module.exports = arrayToObject([
+    range,
+    formatUrlParameters,
+    parseUrlParameters,
+    parseUrlParametersRoute,
+    formatRelativeTime,
+    formatFileSize,
+    formatMarkdown,
+    unindent,
+    enableExitConfirmation,
+    disableExitConfirmation,
+    confirmPageExit,
+    escapeHtml,
+    makeCssName,
+    splitByWhitespace,
+    arraysDiffer,
+    decamelize,
+    arrayToObject,
+], func => func.name);
diff --git a/client/js/util/views.js b/client/js/util/views.js
index 49a62c7..a98e4c0 100644
--- a/client/js/util/views.js
+++ b/client/js/util/views.js
@@ -204,7 +204,7 @@ function makeUserLink(user) {
 }
 
 function makeFlexboxAlign(options) {
-    return Array.from(misc.range(20))
+    return [...misc.range(20)]
         .map(() => '<li class="flexbox-dummy"></li>').join('');
 }
 
@@ -326,31 +326,31 @@ function getTemplate(templatePath) {
         if (!ctx) {
             ctx = {};
         }
-        Object.assign(ctx, {
-            getPostUrl: getPostUrl,
-            getPostEditUrl: getPostEditUrl,
-            makeRelativeTime: makeRelativeTime,
-            makeFileSize: makeFileSize,
-            makeMarkdown: makeMarkdown,
-            makeThumbnail: makeThumbnail,
-            makeRadio: makeRadio,
-            makeCheckbox: makeCheckbox,
-            makeSelect: makeSelect,
-            makeInput: makeInput,
-            makeButton: makeButton,
-            makeTextarea: makeTextarea,
-            makeTextInput: makeTextInput,
-            makePasswordInput: makePasswordInput,
-            makeEmailInput: makeEmailInput,
-            makeColorInput: makeColorInput,
-            makePostLink: makePostLink,
-            makeTagLink: makeTagLink,
-            makeUserLink: makeUserLink,
-            makeFlexboxAlign: makeFlexboxAlign,
-            makeAccessKey: makeAccessKey,
-            makeCssName: misc.makeCssName,
-            makeNumericInput: makeNumericInput,
-        });
+        Object.assign(ctx, misc.arrayToObject([
+            getPostUrl,
+            getPostEditUrl,
+            makeRelativeTime,
+            makeFileSize,
+            makeMarkdown,
+            makeThumbnail,
+            makeRadio,
+            makeCheckbox,
+            makeSelect,
+            makeInput,
+            makeButton,
+            makeTextarea,
+            makeTextInput,
+            makePasswordInput,
+            makeEmailInput,
+            makeColorInput,
+            makePostLink,
+            makeTagLink,
+            makeUserLink,
+            makeFlexboxAlign,
+            makeAccessKey,
+            misc.makeCssName,
+            makeNumericInput,
+        ], func => func.name));
         return htmlToDom(templateFactory(ctx));
     };
 }
@@ -389,7 +389,7 @@ function replaceContent(target, source) {
         target.removeChild(target.lastChild);
     }
     if (source instanceof NodeList) {
-        for (let child of Array.from(source)) {
+        for (let child of [...source]) {
             target.appendChild(child);
         }
     } else if (source instanceof Node) {
@@ -468,21 +468,21 @@ document.addEventListener('input', e => {
     }
 });
 
-module.exports = {
-    htmlToDom: htmlToDom,
-    getTemplate: getTemplate,
-    replaceContent: replaceContent,
-    enableForm: enableForm,
-    disableForm: disableForm,
-    decorateValidator: decorateValidator,
-    makeVoidElement: makeVoidElement,
-    makeNonVoidElement: makeNonVoidElement,
-    syncScrollPosition: syncScrollPosition,
-    slideDown: slideDown,
-    slideUp: slideUp,
-    monitorNodeRemoval: monitorNodeRemoval,
-    clearMessages: clearMessages,
-    showError: showError,
-    showSuccess: showSuccess,
-    showInfo: showInfo,
-};
+module.exports = misc.arrayToObject([
+    htmlToDom,
+    getTemplate,
+    replaceContent,
+    enableForm,
+    disableForm,
+    decorateValidator,
+    makeVoidElement,
+    makeNonVoidElement,
+    syncScrollPosition,
+    slideDown,
+    slideUp,
+    monitorNodeRemoval,
+    clearMessages,
+    showError,
+    showSuccess,
+    showInfo,
+], func => func.name);
diff --git a/client/js/views/login_view.js b/client/js/views/login_view.js
index 96d6d7d..7d97982 100644
--- a/client/js/views/login_view.js
+++ b/client/js/views/login_view.js
@@ -19,15 +19,15 @@ class LoginView extends events.EventTarget {
         views.syncScrollPosition();
 
         views.decorateValidator(this._formNode);
-        this._userNameFieldNode.setAttribute('pattern', config.userNameRegex);
-        this._passwordFieldNode.setAttribute('pattern', config.passwordRegex);
+        this._userNameInputNode.setAttribute('pattern', config.userNameRegex);
+        this._passwordInputNode.setAttribute('pattern', config.passwordRegex);
         this._formNode.addEventListener('submit', e => {
             e.preventDefault();
             this.dispatchEvent(new CustomEvent('submit', {
                 detail: {
-                    name: this._userNameFieldNode.value,
-                    password: this._passwordFieldNode.value,
-                    remember: this._rememberFieldNode.checked,
+                    name: this._userNameInputNode.value,
+                    password: this._passwordInputNode.value,
+                    remember: this._rememberInputNode.checked,
                 },
             }));
         });
@@ -37,16 +37,16 @@ class LoginView extends events.EventTarget {
         return this._hostNode.querySelector('form');
     }
 
-    get _userNameFieldNode() {
-        return this._formNode.querySelector('#user-name');
+    get _userNameInputNode() {
+        return this._formNode.querySelector('[name=name]');
     }
 
-    get _passwordFieldNode() {
-        return this._formNode.querySelector('#user-password');
+    get _passwordInputNode() {
+        return this._formNode.querySelector('[name=password]');
     }
 
-    get _rememberFieldNode() {
-        return this._formNode.querySelector('#remember-user');
+    get _rememberInputNode() {
+        return this._formNode.querySelector('[name=remember-user]');
     }
 
     disableForm() {
diff --git a/client/js/views/password_reset_view.js b/client/js/views/password_reset_view.js
index ddb9785..d817a04 100644
--- a/client/js/views/password_reset_view.js
+++ b/client/js/views/password_reset_view.js
@@ -49,7 +49,7 @@ class PasswordResetView extends events.EventTarget {
     }
 
     get _userNameOrEmailFieldNode() {
-        return this._formNode.querySelector('#user-name');
+        return this._formNode.querySelector('[name=user-name]');
     }
 }
 
diff --git a/client/js/views/registration_view.js b/client/js/views/registration_view.js
index d740514..fb924c2 100644
--- a/client/js/views/registration_view.js
+++ b/client/js/views/registration_view.js
@@ -51,15 +51,15 @@ class RegistrationView extends events.EventTarget {
     }
 
     get _userNameFieldNode() {
-        return this._formNode.querySelector('#user-name');
+        return this._formNode.querySelector('[name=name]');
     }
 
     get _passwordFieldNode() {
-        return this._formNode.querySelector('#user-password');
+        return this._formNode.querySelector('[name=password]');
     }
 
     get _emailFieldNode() {
-        return this._formNode.querySelector('#user-email');
+        return this._formNode.querySelector('[name=email]');
     }
 }
 
diff --git a/client/js/views/settings_view.js b/client/js/views/settings_view.js
index 6b534c1..f8c83a0 100644
--- a/client/js/views/settings_view.js
+++ b/client/js/views/settings_view.js
@@ -30,20 +30,12 @@ class SettingsView extends events.EventTarget {
         e.preventDefault();
         this.dispatchEvent(new CustomEvent('change', {
             detail: {
-                settings: {
-                    upscaleSmallPosts: this._formNode.querySelector(
-                        '#upscale-small-posts').checked,
-                    endlessScroll: this._formNode.querySelector(
-                        '#endless-scroll').checked,
-                    keyboardShortcuts: this._formNode.querySelector(
-                        '#keyboard-shortcuts').checked,
-                    transparencyGrid: this._formNode.querySelector(
-                        '#transparency-grid').checked,
-                    tagSuggestions: this._formNode.querySelector(
-                        '#tag-suggestions').checked,
-                    postsPerPage: this._formNode.querySelector(
-                        '#posts-per-page').value,
-                },
+                upscaleSmallPosts: this._find('upscale-small-posts').checked,
+                endlessScroll: this._find('endless-scroll').checked,
+                keyboardShortcuts: this._find('keyboard-shortcuts').checked,
+                transparencyGrid: this._find('transparency-grid').checked,
+                tagSuggestions: this._find('tag-suggestions').checked,
+                postsPerPage: this._find('posts-per-page').value,
             },
         }));
     }
@@ -51,6 +43,10 @@ class SettingsView extends events.EventTarget {
     get _formNode() {
         return this._hostNode.querySelector('form');
     }
+
+    _find(nodeName) {
+        return this._formNode.querySelector('[name=' + nodeName + ']');
+    }
 }
 
 module.exports = SettingsView;
diff --git a/client/js/views/user_edit_view.js b/client/js/views/user_edit_view.js
index 77f2ed9..45dc355 100644
--- a/client/js/views/user_edit_view.js
+++ b/client/js/views/user_edit_view.js
@@ -20,9 +20,9 @@ class UserEditView extends events.EventTarget {
         views.decorateValidator(this._formNode);
 
         this._avatarContent = null;
-        if (this._avatarContentFieldNode) {
+        if (this._avatarContentInputNode) {
             new FileDropperControl(
-                this._avatarContentFieldNode,
+                this._avatarContentInputNode,
                 {
                     lock: true,
                     resolve: files => {
@@ -62,24 +62,24 @@ class UserEditView extends events.EventTarget {
             detail: {
                 user: this._user,
 
-                name: this._userNameFieldNode ?
-                    this._userNameFieldNode.value :
+                name: this._userNameInputNode ?
+                    this._userNameInputNode.value :
                     undefined,
 
-                email: this._emailFieldNode ?
-                    this._emailFieldNode.value :
+                email: this._emailInputNode ?
+                    this._emailInputNode.value :
                     undefined,
 
-                rank: this._rankFieldNode ?
-                    this._rankFieldNode.value :
+                rank: this._rankInputNode ?
+                    this._rankInputNode.value :
                     undefined,
 
-                avatarStyle: this._avatarStyleFieldNode ?
-                    this._avatarStyleFieldNode.value :
+                avatarStyle: this._avatarStyleInputNode ?
+                    this._avatarStyleInputNode.value :
                     undefined,
 
-                password: this._passwordFieldNode ?
-                    this._passwordFieldNode.value :
+                password: this._passwordInputNode ?
+                    this._passwordInputNode.value :
                     undefined,
 
                 avatarContent: this._avatarContent,
@@ -91,27 +91,27 @@ class UserEditView extends events.EventTarget {
         return this._hostNode.querySelector('form');
     }
 
-    get _rankFieldNode() {
-        return this._formNode.querySelector('#user-rank');
+    get _rankInputNode() {
+        return this._formNode.querySelector('[name=rank]');
     }
 
-    get _emailFieldNode() {
-        return this._formNode.querySelector('#user-email');
+    get _emailInputNode() {
+        return this._formNode.querySelector('[name=email]');
     }
 
-    get _userNameFieldNode() {
-        return this._formNode.querySelector('#user-name');
+    get _userNameInputNode() {
+        return this._formNode.querySelector('[name=name]');
     }
 
-    get _passwordFieldNode() {
-        return this._formNode.querySelector('#user-password');
+    get _passwordInputNode() {
+        return this._formNode.querySelector('[name=password]');
     }
 
-    get _avatarContentFieldNode() {
+    get _avatarContentInputNode() {
         return this._formNode.querySelector('#avatar-content');
     }
 
-    get _avatarStyleFieldNode() {
+    get _avatarStyleInputNode() {
         return this._formNode.querySelector('[name=avatar-style]:checked');
     }
 }