- commit
- bfeeea2de1e6c80f442cb81e7950160ff7e560b3
- parent
- 51348885d0468bec263b1bf994e85641a78ec398
- Author
- Tobias Bengfort <tobias.bengfort@posteo.de>
- Date
- 2020-10-17 20:49
split into separate files
Diffstat
| M | Makefile | 4 | ++-- |
| C | static/src/voterunner.js -> static/src/template.js | 196 | ++----------------------------------------------------------- |
| A | static/src/utils.js | 69 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| M | static/src/voterunner.js | 187 | +++++-------------------------------------------------------- |
4 files changed, 89 insertions, 367 deletions
diff --git a/Makefile b/Makefile
@@ -1,7 +1,7 @@ 1 1 all: static/voterunner.js static/style.css 2 23 -1 static/voterunner.js: static/src/voterunner.js4 -1 browserify $< -o $@-1 3 static/voterunner.js: static/src/*.js -1 4 browserify static/src/voterunner.js -o $@ 5 5 6 6 static/style.css: static/scss/*.scss 7 7 sassc static/scss/style.scss $@
diff --git a/static/src/voterunner.js b/static/src/template.js
@@ -1,43 +1,8 @@1 -1 var preact = require('preact');2 1 var h = require('preact').h; 3 2 var MarkdownIt = require('markdown-it');4 -1 var io = require('socket.io-client');5 3 6 4 var md = new MarkdownIt(); 7 58 -1 var throttle = function(fn, timeout) {9 -1 var called, blocked;10 -111 -1 var result = function() {12 -1 if (blocked) {13 -1 called = true;14 -1 } else {15 -1 fn();16 -1 blocked = true;17 -1 called = false;18 -119 -1 setTimeout(function() {20 -1 blocked = false;21 -122 -1 if (called) {23 -1 result();24 -1 }25 -1 }, timeout);26 -1 }27 -1 };28 -129 -1 return result;30 -1 };31 -132 -1 var on = function(element, eventType, selector, fn) {33 -1 element.addEventListener(eventType, function(event) {34 -1 var target = event.target.closest(selector);35 -1 if (target && element.contains(target)) {36 -1 return fn.call(target, event);37 -1 }38 -1 });39 -1 };40 -141 6 var getVotes = function(nodes, node) { 42 7 if (!node.votes) { 43 8 node.votes = 1 + nodes @@ -135,161 +100,8 @@ var template = function(nodes, ID) { 135 100 }, tplFollowers(nodes, null, ID)); 136 101 }; 137 102138 -1 var initVDom = function(wrapper, nodes, ID, afterRender) {139 -1 wrapper.innerHTML = '';140 -1 var tree = template(nodes, ID);141 -1 var element = preact.render(tree, wrapper);142 -1 afterRender();143 -1144 -1 return function(newState) {145 -1 var newTree = template(newState, ID);146 -1 preact.render(newTree, wrapper, element);147 -1 afterRender();148 -1 };149 -1 };150 -1151 -1 var uid = function() {152 -1 // just enough uniqueness153 -1 var a = Math.random() * Date.now() * 0x1000;154 -1 return Math.floor(a).toString(36);155 -1 };156 -1 var setCookie = function(key, value, days) {157 -1 localStorage[key] = value;158 -1 };159 -1160 -1 var getCookie = function(key) {161 -1 return localStorage[key];-1 103 module.exports = { -1 104 getVotes: getVotes, -1 105 getName: getName, -1 106 template: template, 162 107 };163 -1164 -1 document.addEventListener('DOMContentLoaded', function() {165 -1 var TOPIC = document.URL.split('/')[3];166 -1 var ID = document.URL.split('/')[4];167 -1 if (!ID) ID = getCookie('id');168 -1 if (!ID) ID = uid();169 -1 setCookie('id', ID, 100);170 -1171 -1 var socket = io.connect('/');172 -1 window.socket = socket; // make available for tests173 -1 socket.emit('register', TOPIC, ID);174 -1175 -1 var nodes = JSON.parse(document.querySelector('#json-nodes').dataset.value);176 -1177 -1 var getNode = function(id) {178 -1 var node = nodes.find(n => n.id === id);179 -1 if (!node) {180 -1 node = {181 -1 id: id,182 -1 delegate: null,183 -1 };184 -1 nodes.push(node);185 -1 }186 -1 return node;187 -1 };188 -1189 -1 var invalidateVotes = function() {190 -1 nodes.forEach(function(node) {191 -1 node.votes = null;192 -1 node.delegationChain = null;193 -1 });194 -1 };195 -1196 -1 var ensureVisible = function(node) {197 -1 if (node && node.delegate) {198 -1 var delegatee = getNode(node.delegate);199 -1 delegatee.expanded = true;200 -1 ensureVisible(delegatee);201 -1 }202 -1 };203 -1204 -1 var user = nodes.find(n => n.id === ID);205 -1 if (user) {206 -1 document.querySelector('.user__name input').value = user.name;207 -1 document.querySelector('.user__comment textarea').value = user.comment;208 -1 ensureVisible(user);209 -1 }210 -1211 -1 var updateUser = function() {212 -1 document.querySelector('.user__votes').textContent = getVotes(nodes, user || {});213 -1214 -1 if (user && user.delegate) {215 -1 var delegatee = getNode(user.delegate);216 -1 document.querySelector('.user__delegation').textContent = 'delegated to: ' + getName(delegatee);217 -1 } else {218 -1 document.querySelector('.user__delegation').textContent = '(no delegation)';219 -1 }220 -1 };221 -1222 -1 var update = initVDom(document.querySelector('#tree'), nodes, ID, function() {223 -1 updateUser();224 -1 });225 -1226 -1 on(document, 'click', '.node__expand', function() {227 -1 var nodeElement = this.parentElement.parentElement.parentElement;228 -1 var id = nodeElement.id.substr(5);229 -1 var node = getNode(id);230 -1 node.expanded = !node.expanded;231 -1 update(nodes);232 -1 });233 -1234 -1 on(document, 'click', '.node__delegate', function() {235 -1 var nodeElement = this.parentElement.parentElement.parentElement;236 -1 var id = nodeElement.id.substr(5);237 -1 socket.emit('setDelegate', id);238 -1 });239 -1240 -1 on(document, 'click', '.user__rm', function() {241 -1 if (confirm('Do you really want to delete this opinion?')) {242 -1 socket.emit('rmNode');243 -1 document.querySelector('.user__name input').value = '';244 -1 document.querySelector('.user__comment textarea').value = '';245 -1 }246 -1 });247 -1248 -1 on(document, 'change', '.user__name input', function() {249 -1 socket.emit('setNodeName', this.value);250 -1 });251 -1252 -1 on(document, 'click', '.user__undelegate', function() {253 -1 socket.emit('rmDelegate');254 -1 });255 -1256 -1 on(document, 'input', '.user__comment textarea', throttle(function() {257 -1 var comment = document.querySelector('.user__comment textarea').value;258 -1 var node = nodes.find(n => n.id === ID);259 -1 // Do not create a new node if the comment is empty.260 -1 // This can happen e.g. on a keydown event from the ctrl or shift keys.261 -1 if (node || comment) {262 -1 socket.emit('setNodeComment', comment);263 -1 }264 -1 }, 1000));265 -1266 -1 socket.on('rmNode', function(id) {267 -1 nodes = nodes.filter(function(node) {268 -1 if (node.delegate === id) {269 -1 node.delegate = null;270 -1 }271 -1 return node.id !== id;272 -1 });273 -1 invalidateVotes();274 -1 update(nodes);275 -1 });276 -1 socket.on('setNodeName', function(id, name) {277 -1 getNode(id).name = name;278 -1 update(nodes);279 -1 });280 -1 socket.on('setNodeComment', function(id, comment) {281 -1 getNode(id).comment = comment;282 -1 update(nodes);283 -1 });284 -1 socket.on('setDelegate', function(id, delegate) {285 -1 getNode(id).delegate = delegate;286 -1 invalidateVotes();287 -1 ensureVisible(user);288 -1 update(nodes);289 -1 });290 -1 socket.on('rmDelegate', function(id) {291 -1 getNode(id).delegate = null;292 -1 invalidateVotes();293 -1 update(nodes);294 -1 });295 -1 });
diff --git a/static/src/utils.js b/static/src/utils.js
@@ -0,0 +1,69 @@
-1 1 var preact = require('preact');
-1 2
-1 3 var throttle = function(fn, timeout) {
-1 4 var called, blocked;
-1 5
-1 6 var result = function() {
-1 7 if (blocked) {
-1 8 called = true;
-1 9 } else {
-1 10 fn();
-1 11 blocked = true;
-1 12 called = false;
-1 13
-1 14 setTimeout(function() {
-1 15 blocked = false;
-1 16
-1 17 if (called) {
-1 18 result();
-1 19 }
-1 20 }, timeout);
-1 21 }
-1 22 };
-1 23
-1 24 return result;
-1 25 };
-1 26
-1 27 var on = function(element, eventType, selector, fn) {
-1 28 element.addEventListener(eventType, function(event) {
-1 29 var target = event.target.closest(selector);
-1 30 if (target && element.contains(target)) {
-1 31 return fn.call(target, event);
-1 32 }
-1 33 });
-1 34 };
-1 35
-1 36 var initVDom = function(wrapper, template, nodes, ID, afterRender) {
-1 37 wrapper.innerHTML = '';
-1 38 var tree = template(nodes, ID);
-1 39 var element = preact.render(tree, wrapper);
-1 40 afterRender();
-1 41
-1 42 return function(newState) {
-1 43 var newTree = template(newState, ID);
-1 44 preact.render(newTree, wrapper, element);
-1 45 afterRender();
-1 46 };
-1 47 };
-1 48
-1 49 var randomString = function() {
-1 50 var a = Math.random() * Date.now() * 0x1000;
-1 51 return Math.floor(a).toString(36);
-1 52 };
-1 53
-1 54 var setCookie = function(key, value, days) {
-1 55 localStorage[key] = value;
-1 56 };
-1 57
-1 58 var getCookie = function(key) {
-1 59 return localStorage[key];
-1 60 };
-1 61
-1 62 module.exports = {
-1 63 throttle: throttle,
-1 64 on: on,
-1 65 initVDom: initVDom,
-1 66 randomString: randomString,
-1 67 setCookie: setCookie,
-1 68 getCookie: getCookie,
-1 69 };
diff --git a/static/src/voterunner.js b/static/src/voterunner.js
@@ -1,172 +1,13 @@1 -1 var preact = require('preact');2 -1 var h = require('preact').h;3 -1 var MarkdownIt = require('markdown-it');4 1 var io = require('socket.io-client');5 -16 -1 var md = new MarkdownIt();7 -18 -1 var throttle = function(fn, timeout) {9 -1 var called, blocked;10 -111 -1 var result = function() {12 -1 if (blocked) {13 -1 called = true;14 -1 } else {15 -1 fn();16 -1 blocked = true;17 -1 called = false;18 -119 -1 setTimeout(function() {20 -1 blocked = false;21 -122 -1 if (called) {23 -1 result();24 -1 }25 -1 }, timeout);26 -1 }27 -1 };28 -129 -1 return result;30 -1 };31 -132 -1 var on = function(element, eventType, selector, fn) {33 -1 element.addEventListener(eventType, function(event) {34 -1 var target = event.target.closest(selector);35 -1 if (target && element.contains(target)) {36 -1 return fn.call(target, event);37 -1 }38 -1 });39 -1 };40 -141 -1 var getVotes = function(nodes, node) {42 -1 if (!node.votes) {43 -1 node.votes = 1 + nodes44 -1 .filter(n => n.delegate === node.id)45 -1 .map(n => getVotes(nodes, n))46 -1 .reduce((sum, n) => sum + n, 0);47 -1 }48 -149 -1 return node.votes;50 -1 };51 -152 -1 var getDelegationChain = function(nodes, node) {53 -1 if (!node.delegationChain) {54 -1 if (node.delegate) {55 -1 var delegate = nodes.find(n => n.id === node.delegate);56 -1 var delegationChain = getDelegationChain(nodes, delegate);57 -1 node.delegationChain = [node.delegate].concat(delegationChain);58 -1 } else {59 -1 node.delegationChain = [];60 -1 }61 -1 }62 -163 -1 return node.delegationChain;64 -1 };65 -166 -1 var getName = function(node) {67 -1 return node.name || 'anonymous';68 -1 };69 -170 -1 var tplFollowers = function(nodes, id, ID) {71 -1 return nodes72 -1 .filter(n => n.delegate === id)73 -1 .sort((a, b) => getVotes(nodes, b) - getVotes(nodes, a))74 -1 .map(n => tplNode(nodes, n, ID));75 -1 };76 -177 -1 var tplNode = function(nodes, node, ID) {78 -1 var classList = [];79 -1 if (node.expanded) {80 -1 classList.push('is-expanded');81 -1 }82 -1 if (node.id === ID) {83 -1 classList.push('node--self');84 -1 }85 -186 -1 var delegateAttrs = {};87 -1 if (node.id === ID || getDelegationChain(nodes, node).includes(ID)) {88 -1 delegateAttrs.disabled = true;89 -1 }90 -191 -1 return h('li', {92 -1 key: 'node-' + node.id,93 -1 id: 'node-' + node.id,94 -1 className: 'node ' + classList.join(' '),95 -1 role: 'treeitem',96 -1 'aria-expanded': '' + !!node.expanded,97 -1 }, [98 -1 h('article', {99 -1 className: 'node__body',100 -1 }, [101 -1 h('header', {102 -1 className: 'node__header bar',103 -1 }, [104 -1 h('button', {105 -1 className: 'node__expand bar__item bar__item--button bar__item--left',106 -1 title: node.expanded ? 'collapse' : 'expand',107 -1 }, node.expanded ? '\u25BC' : '\u25B6'),108 -1 h('button', {109 -1 className: 'node__delegate bar__item bar__item--button bar__item--right',110 -1 title: 'delegate to ' + getName(node),111 -1 attributes: delegateAttrs,112 -1 }, '\u2795'),113 -1 h('div', {className: 'node__votes bar__item bar__item--right'}, '' + getVotes(nodes, node)),114 -1 h('div', {className: 'node__name bar__item' + (!node.expanded && node.comment ? '' : ' bar__item--grow')}, getName(node)),115 -1 !node.expanded && node.comment && h('div', {className: 'node__preview bar__item bar__item--grow'}, node.comment.substr(0, 100)),116 -1 ]),117 -1 node.expanded && h('div', {118 -1 className: 'node__comment',119 -1 dangerouslySetInnerHTML: {120 -1 __html: md.render(node.comment || ''),121 -1 },122 -1 }),123 -1 ]),124 -1 h('ul', {125 -1 className: 'tree',126 -1 role: 'group',127 -1 }, tplFollowers(nodes, node.id, ID)),128 -1 ]);129 -1 };130 -1131 -1 var template = function(nodes, ID) {132 -1 return h('ul', {133 -1 className: 'tree',134 -1 role: 'tree',135 -1 }, tplFollowers(nodes, null, ID));136 -1 };137 -1138 -1 var initVDom = function(wrapper, nodes, ID, afterRender) {139 -1 wrapper.innerHTML = '';140 -1 var tree = template(nodes, ID);141 -1 var element = preact.render(tree, wrapper);142 -1 afterRender();143 -1144 -1 return function(newState) {145 -1 var newTree = template(newState, ID);146 -1 preact.render(newTree, wrapper, element);147 -1 afterRender();148 -1 };149 -1 };150 -1151 -1 var uid = function() {152 -1 // just enough uniqueness153 -1 var a = Math.random() * Date.now() * 0x1000;154 -1 return Math.floor(a).toString(36);155 -1 };156 -1 var setCookie = function(key, value, days) {157 -1 localStorage[key] = value;158 -1 };159 -1160 -1 var getCookie = function(key) {161 -1 return localStorage[key];162 -1 };-1 2 var template = require('./template'); -1 3 var utils = require('./utils'); 163 4 164 5 document.addEventListener('DOMContentLoaded', function() { 165 6 var TOPIC = document.URL.split('/')[3]; 166 7 var ID = document.URL.split('/')[4];167 -1 if (!ID) ID = getCookie('id');168 -1 if (!ID) ID = uid();169 -1 setCookie('id', ID, 100);-1 8 if (!ID) ID = utils.getCookie('id'); -1 9 if (!ID) ID = utils.randomString(); -1 10 utils.setCookie('id', ID, 100); 170 11 171 12 var socket = io.connect('/'); 172 13 window.socket = socket; // make available for tests @@ -209,21 +50,21 @@ document.addEventListener('DOMContentLoaded', function() { 209 50 } 210 51 211 52 var updateUser = function() {212 -1 document.querySelector('.user__votes').textContent = getVotes(nodes, user || {});-1 53 document.querySelector('.user__votes').textContent = template.getVotes(nodes, user || {}); 213 54 214 55 if (user && user.delegate) { 215 56 var delegatee = getNode(user.delegate);216 -1 document.querySelector('.user__delegation').textContent = 'delegated to: ' + getName(delegatee);-1 57 document.querySelector('.user__delegation').textContent = 'delegated to: ' + template.getName(delegatee); 217 58 } else { 218 59 document.querySelector('.user__delegation').textContent = '(no delegation)'; 219 60 } 220 61 }; 221 62222 -1 var update = initVDom(document.querySelector('#tree'), nodes, ID, function() {-1 63 var update = utils.initVDom(document.querySelector('#tree'), template.template, nodes, ID, function() { 223 64 updateUser(); 224 65 }); 225 66226 -1 on(document, 'click', '.node__expand', function() {-1 67 utils.on(document, 'click', '.node__expand', function() { 227 68 var nodeElement = this.parentElement.parentElement.parentElement; 228 69 var id = nodeElement.id.substr(5); 229 70 var node = getNode(id); @@ -231,13 +72,13 @@ document.addEventListener('DOMContentLoaded', function() { 231 72 update(nodes); 232 73 }); 233 74234 -1 on(document, 'click', '.node__delegate', function() {-1 75 utils.on(document, 'click', '.node__delegate', function() { 235 76 var nodeElement = this.parentElement.parentElement.parentElement; 236 77 var id = nodeElement.id.substr(5); 237 78 socket.emit('setDelegate', id); 238 79 }); 239 80240 -1 on(document, 'click', '.user__rm', function() {-1 81 utils.on(document, 'click', '.user__rm', function() { 241 82 if (confirm('Do you really want to delete this opinion?')) { 242 83 socket.emit('rmNode'); 243 84 document.querySelector('.user__name input').value = ''; @@ -245,15 +86,15 @@ document.addEventListener('DOMContentLoaded', function() { 245 86 } 246 87 }); 247 88248 -1 on(document, 'change', '.user__name input', function() {-1 89 utils.on(document, 'change', '.user__name input', function() { 249 90 socket.emit('setNodeName', this.value); 250 91 }); 251 92252 -1 on(document, 'click', '.user__undelegate', function() {-1 93 utils.on(document, 'click', '.user__undelegate', function() { 253 94 socket.emit('rmDelegate'); 254 95 }); 255 96256 -1 on(document, 'input', '.user__comment textarea', throttle(function() {-1 97 utils.on(document, 'input', '.user__comment textarea', utils.throttle(function() { 257 98 var comment = document.querySelector('.user__comment textarea').value; 258 99 var node = nodes.find(n => n.id === ID); 259 100 // Do not create a new node if the comment is empty.