/* -*- Mode: js2; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set et sw=2 ts=2: */ 'use strict'; function createId(boardid, nodeid) { return "b" + boardid + "_t" + nodeid; } function idFromStr(str) { return str.substring(str.search('t') + 1, str.length); } function subscriptionIdFromStr(str) { return str.substring(1, str.search('_')); } var nodes = new Array(); function findNode(id, subscribeid) { for(var i = 0; i < nodes.length; i++) { var node = nodes[i]; var child = node.findNode(id, subscribeid); if(child != null) { return child; } } return null; } function findNodeFromString(idstr) { return findNode(idFromStr(idstr), subscriptionIdFromStr(idstr)); } function Node(id, subscribeid) { this.id = id; this.subscribeid = subscribeid; this.childNodes = new Array(); this.attributes = {}; this.parent = null; } Node.prototype.dump = function() { alert(this.id); }; Node.prototype.dumpChildren = function(msg) { console.log("Node " + this.id + " children (" + this.childNodes.length + "):"); if(msg) { console.log(msg); } for(var i = 0; i < this.childNodes.length; i++) { console.log("[" + i + "]: " + this.childNodes[i].id); } } Node.prototype.findNode = function(id, subscribeid) { if(this.subscribeid != subscribeid) { return null; } if(this.id == id) { return this; } for(var i = 0; i < this.childNodes.length; i++) { var node = this.childNodes[i]; var child = node.findNode(id, subscribeid); if(child != null) { return child; } } return null; }; Node.prototype.addChild = function(node, insertBeforeId) { if(node.parent != null) { node.parent.removeChild(node); } node.parent = this; var inserted = false; for(var i = 0; i < this.childNodes.length; ++i) { if(this.childNodes[i].id == insertBeforeId) { this.childNodes.splice(i, 0, node); this.children_element.insertBefore(node.element, this.children_element.childNodes[i]); inserted = true; break; } } if(inserted == false) { this.childNodes.push(node); this.children_element.appendChild(node.element); } this.updateAnnotations(); }; Node.prototype.removeChild = function(node) { this.childNodes = this.childNodes.filter(function(e) { return e.id != node.id; }); node.parent = null; this.children_element.removeChild(node.element); this.updateAnnotations(); }; Node.prototype.create = function() { // Node root element this.element = document.createElement("div"); // Element for title, description, state, etc... this.data_element = document.createElement("div"); this.data_element.className = "data"; this.data_element.style.pointerEvents = "none"; this.element.appendChild(this.data_element); // Root element for child nodes. this.children_element = document.createElement("div"); this.children_element.className = "children"; this.element.appendChild(this.children_element); var annotation = document.createElement("div"); annotation.className = "annotation"; this.id_element = document.createElement("div"); this.id_element.className = "id"; var id_txt = document.createTextNode(this.id); this.id_element.appendChild(id_txt); annotation.appendChild(this.id_element); this.children_indicator_element = document.createElement("div"); this.children_indicator_element.className = "children_indicator"; annotation.appendChild(this.children_indicator_element); this.data_element.appendChild(annotation); this.collapse_button = document.createElement("div"); this.collapse_button.name = "add_button"; this.collapse_button.setAttribute("onclick", "collapse(event)"); this.collapse_button.setAttribute("nodeid", this.id); this.collapse_button.className = "button"; this.collapse_button.innerHTML = "▼"; this.data_element.appendChild(this.collapse_button); var add_child_button = document.createElement("div"); add_child_button.name = "add_button"; add_child_button.setAttribute("onclick", "addChild(event)"); add_child_button.setAttribute("nodeid", this.id); add_child_button.className = "button"; add_child_button.classList.add('add_child'); add_child_button.innerHTML = "➕"; this.data_element.appendChild(add_child_button); this.state_element = document.createElement("div"); this.state_element.name = "state"; this.state_element.setAttribute("onclick", "changeState(event)"); this.state_element.setAttribute("nodeid", this.id); this.state_element.className = "state"; this.setAttribute("state", "todo"); this.data_element.appendChild(this.state_element); this.title_element = document.createElement("div"); this.title_element.className = "title"; this.title_element.setAttribute("ondblclick", "editTitle(event)"); this.data_element.appendChild(this.title_element); this.description_element = document.createElement("div"); this.description_element.className = "description"; this.description_element.setAttribute("ondblclick", "editDescription(event)"); this.data_element.appendChild(this.description_element); var node = this.element; node.name = "node"; node.className = "node"; node.setAttribute("ondrop", "drop(event)"); node.setAttribute("ondragenter", "dragenter(event)"); node.setAttribute("ondragover", "dragover(event)"); node.setAttribute("ondragleave", "dragleave(event)"); node.setAttribute("draggable", true); node.setAttribute("ondragstart", "drag(event)"); node.setAttribute("ondragend", "dragEnd(event)"); // This is a hack to make it possible to identify the nodeid and // oberveid from the node id alone. node.id = createId(this.subscribeid, this.id); var collapsed = localStorage.getItem(node.id+"_collapsed") == "true"; this.setCollapsed(collapsed); }; Node.prototype.updateAnnotations = function() { var ann = ""; if(this.childNodes.length > 0) { ann += "⤷"; } if(this.description_element.innerHTML != "") { ann += "…"; } this.children_indicator_element.innerHTML = ann; }; Node.prototype.setCollapsed = function(collapsed) { if(collapsed) { this.children_indicator_element.classList.add('collapsed'); this.element.classList.add('collapsed'); this.collapse_button.innerHTML = "▼"; } else { this.children_indicator_element.classList.remove('collapsed'); this.element.classList.remove('collapsed'); this.collapse_button.innerHTML = "▲"; } }; //! Recursively set draggable property from node until root node. Node.prototype.setDraggable = function(value) { var node = this.element; node.draggable = value; if(this.parent != null) { this.parent.setDraggable(value); } }; function checkHTML(html) { var doc = document.createElement('p'); doc.innerHTML = html; // Check for correct and balanced HTML if(doc.innerHTML !== html) { return false; } // Check for valid tags var elements = doc.getElementsByTagName("*"); for(let element of elements) { if(element.tagName == "A") { // Accept 'href' attribute in A tag only. if(element.attributes.length > 1 || !element.hasAttribute("href")) { return false; } continue; } // Check for allowed tag-names if(element.tagName != "P" && element.tagName != "EM" && element.tagName != "STRONG" && element.tagName != "BR" && element.tagName != "UL" && element.tagName != "LI" && // Allow no attributes element.attributes.length != 0) { return false; } } return true; } function makeClickthrough(e) { var elements = e.getElementsByTagName("*"); for(let element of elements) { if(element.tagName == "A") { element.style.pointerEvents = "initial"; // enable links continue; } element.style.pointerEvents = "none"; // enable clickthrough to parent } } Node.prototype.setAttribute = function(name, value) { this.attributes[name] = value; if(name == "title") { if(this.title_element.firstChild != null) { this.title_element.removeChild(this.title_element.firstChild); } var title_txt = document.createTextNode(value); this.title_element.appendChild(title_txt); } if(name == "description") { if(this.description_element.firstChild != null) { this.description_element.removeChild(this.description_element.firstChild); } this.description_element.innerHTML = marked(value); makeClickthrough(this.description_element); this.updateAnnotations(); } if(name == "dragged") { if(value == "true") { // Make transparent and non-draggable this.element.style.opacity = "0.3"; // TODO: Apply recursively? this.element.setAttribute("draggable", false); } else { // Make opaque and draggable again this.element.style.opacity = "1.0"; // TODO: Apply recursively? this.element.setAttribute("draggable", true); } } if(name == "state") { var txt = this.state_element.firstChild; if(txt != null) { this.state_element.removeChild(txt); } txt = document.createTextNode(value); this.state_element.appendChild(txt); if(value == "done") { this.state_element.style.backgroundColor = "#36b37e"; } else if(value == "in-progress") { this.state_element.style.backgroundColor = "#ffd351"; } else if(value == "blocked") { this.state_element.style.backgroundColor = "#da4343"; } else { this.state_element.style.backgroundColor = "#ebedf0"; } } }; Node.prototype.getTitle = function() { if(this.attributes["title"]) { return this.attributes["title"]; } return ""; }; Node.prototype.getDescription = function() { if(this.attributes["description"]) { return this.attributes["description"]; } return ""; };