From: Andrew Lorimer Date: Sun, 18 Aug 2019 10:43:34 +0000 (+1000) Subject: refactor admin.js into more modular functions X-Git-Url: https://git.lorimer.id.au/newtab.git/diff_plain/b83548606e93e42409622e687b629bf719f9faca refactor admin.js into more modular functions --- diff --git a/admin.js b/admin.js index ea20d86..1a2b0db 100644 --- a/admin.js +++ b/admin.js @@ -1,13 +1,14 @@ -var mainlist; var rmspan = ["–"] var tick = "✔"; +var addText = "+"; +var rmText = "−"; var removebg = "#bf616a"; var hovergrn = "#a3be8c"; var hoverbg = "#434c5e"; var hoverbg2 = "#848ead"; -document.addEventListener("DOMContentLoaded", function() { - chrome.storage.sync.get( - {"lists": [ +var editMode = false; +var dragSrcEl = null; +var defaultColumns= [ ["General", ["Github", "https://github.com"], ["Wikipedia", "https://en.wikipedia.org"], @@ -23,387 +24,556 @@ document.addEventListener("DOMContentLoaded", function() { ["YouTube", "https://youtube.com"], ["Instagram", "https://instagram.com"] ] - ] -}, - userListsCallback); -}); - -function columnToArray(list) { - var l = []; - var elem = list.getElementsByClassName("item"); - for (var i = 0; i < elem.length; ++i) { - l[i] = [elem[i].innerText, elem[i].getAttribute("href")]; - } - return l; +] + + +// -------------------------------- +// +// Initialisation +// +// -------------------------------- + + +document.addEventListener("DOMContentLoaded", loadLists); + +function loadLists() { + + // Retrieve lists from storage and trigger callback to generate HTML + + console.log("Getting lists from storage"); + chrome.storage.sync.get({"lists": defaultColumns}, parseColumns); + + document.getElementById('edit').addEventListener('click', edit, false); + document.getElementById('addcol').addEventListener('click', addColumn, false); + } -function listToArray(list) { - var l = []; - console.log("looking at list:"); - console.log(list); - if (editMode == true) { - l[0] = list.getElementsByClassName('title')[0].getElementsByTagName("input")[0].value; - } - else { - l[0] = list.getElementsByClassName('title')[0].getElementsByTagName("p")[0].textContent; - } - var elem = list.getElementsByTagName("li"); - for (var i = 0; i < elem.length; ++i) { - if (elem[i].class == "new") { - continue; - } - else { - l[i+1] = [elem[i].getElementsByTagName("a")[0].innerText, elem[i].getElementsByTagName("a")[0].getAttribute("href")]; + +function parseColumns(config) { + + var columns = config["lists"] + + // Generate elements for each column + for (let col of columns) { + + var ul = genColumn(col[0]); + document.getElementById("links").appendChild(ul); + + // Iterate through links + for(let item of col.slice(1)) { + li = genItem(ul, item[0], item[1]); + ul.appendChild(li); } + + var sortableProperties = { group: "usercolumns", animation: 150, onSort: function (evt) {saveConfig();} }; + new Sortable(ul, sortableProperties); + } - return l; } -var userListsCallback = function(lists) { - mainlist = [] - for (let x of lists['lists']) { - mainlist.push(x[0]); - } - for(var i=0;i+"); - - var list = lists["lists"][i]; - for(var j=1;j+extractDomain(siteurl,1)+ "+nme+""); - li.insertAdjacentHTML("beforeend", rmspan[0] + j + "-" + mainlist[i] + rmspan[1]); - ul.appendChild(li); - } - new Sortable(ul, { - group: "userlists", - animation: 150, - onUpdate: function (evt) { - save(); - } - }); - } + var titleDiv = document.createElement("div"); + titleDiv.setAttribute("class", "title"); - menu(); + var titleText = document.createElement("p"); + titleText.innerText = title; + titleDiv.appendChild(titleText); -}; + var addBtn = document.createElement("span"); + addBtn.innerText = addText; + addBtn.setAttribute("class", "add"); + addBtn.setAttribute("id", "add-" + title); + addBtn.addEventListener("click", addItem); + titleDiv.appendChild(addBtn); -function dragStart(e) { - dragSrcEl = this; - e.dataTransfer.effectAllowed = 'move'; - e.dataTransfer.setData('text/html', this.innerHTML); -} + ul.appendChild(titleDiv); -function dragOver(e) { - if (e.preventDefault) { - e.preventDefault(); - } - e.dataTransfer.dropEffect = 'move'; - return false; -} + return ul; -function dragEnter(e) { - this.classList.add('over'); } -function dragLeave(e) { - this.classList.remove('over'); -} +function genItem(ul, nme, url) { -function drop(e) { - if (e.stopPropagation); { - e.stopPropagation(); - } - if (dragSrcEl != this) { - dragSrcEl.innerHTML = this.innerHTML; - this.innerHTML = e.dataTransfer.getData('text/html'); - save(); - menu(); - } - return false; -} + // Generate HTML elements for an item -var dragSrcEl = null; + var li = document.createElement("li"); + li.setAttribute("class", ul.id + "-" + (ul.getElementsByTagName("li").length + 1).toString() ); + li.addEventListener("mouseup", saveConfig); + + var img = document.createElement("img"); + img.className = "icon"; + img.src = "https://www.google.com/s2/favicons?domain=" + url; + img.alt = extractDomain(url, 1); + + var link = document.createElement("a"); + link.className = "item"; + link.href = url; + + var rmBtn = document.createElement("span"); + rmBtn.className = "remove"; + rmBtn.id = "delete-" + li.id; + rmBtn.innerText = "-"; + rmBtn.addEventListener("click", removeItem); + + link.appendChild(img); + link.insertAdjacentHTML("beforeend", nme); + li.appendChild(link); + li.appendChild(rmBtn); + + return li; -document.onkeyup=function(e){ - var e = e || window.event; // for IE to cover IEs window event-object - fields = document.getElementsByClassName("new"); - if(e.which == 27 && fields != null) { - fields[fields.length-1].remove(); - return false; - } - if(e.which == 13 && fields.length > 0) { - fields[fields.length-1].getElementsByClassName("save")[0].click(); - return false; - } } -var editMode = false; -function edit() { +// -------------------------------- +// +// Edit mode +// +// -------------------------------- + + +function edit(event) { + + // Enter/exit edit mode + if (editMode == true) { console.log("Exited edit mode"); closeEdit(this); - return 0; + return false; } + console.log("Entered edit mode"); + this.style.background = hovergrn; this.innerText = tick; - addbtn = document.getElementById("addcol"); - addbtn.style.display = "flex"; + addBtn= document.getElementById("addcol"); + addBtn.style.display = "flex"; var cols = document.getElementsByTagName("ul"); + for (let col of cols) { + + var titleDiv = col.getElementsByClassName("title")[0]; + titleDiv.getElementsByClassName("add")[0].style.display = "flex"; + col.style.bottom = "26px"; - title = col.getElementsByClassName("title")[0]; - rmbutton = document.createElement("span"); - rmbutton.setAttribute("class", "rmcol"); - rmbutton.setAttribute("id", "rmcol-"+col.id); - rmbutton.innerText = "-"; - rmbutton.addEventListener('click', function(event){ - console.log("Deleting column " + this.parentNode.parentNode); - this.parentNode.parentNode.remove(); - var index = mainlist.indexOf(this.parentNode.parentNode.id); - console.log("Found deleted node " + this.parentNode.parentNode.id + " at index " + index); - if (index > -1) { - mainlist.splice(index, 1); - } - save(); - }); - title.appendChild(rmbutton); - title.getElementsByClassName("add")[0].style.display = "flex"; - titlep = title.getElementsByTagName("p")[0]; - editTitle = document.createElement("input"); - titlep.insertAdjacentHTML("afterend", ""); - titlep.remove(); col.getElementsByClassName("grip")[0].style.display = "inline-block"; + + var rmColBtn = document.createElement("span"); + rmColBtn.className = "rmcol"; + rmColBtn.id = "rmcol-" + col.id; + rmColBtn.innerText = rmText; + titleDiv.appendChild(rmColBtn); + + var titleStatic = titleDiv.getElementsByTagName("p")[0]; + var titleInput = document.createElement("input"); + titleInput.type = "text"; + titleInput.className = "colname"; + titleInput.placeholder = titleStatic.innerText; + titleInput.value = titleStatic.innerText; + titleDiv.insertBefore(titleInput, titleStatic); + titleStatic.remove(); + } + + updateListeners(); editMode = true; } -function closeEdit(editbtn) { +function closeEdit(editBtn) { + + // Exit edit mode and clean up elements + editMode = false; - editbtn.style.background = ""; - editbtn.innerText = "e"; - addbtn = document.getElementById("addcol"); - addbtn.style.display = "none"; + editBtn.style.background = ""; + editBtn.innerText = "e"; + + var addBtn = document.getElementById("addcol"); + addBtn.style.display = "none"; + var cols = document.getElementsByTagName("ul"); + for (let col of cols) { + col.style.bottom = "0"; - rmbutton = col.getElementsByClassName("title")[0].getElementsByClassName("rmcol")[0]; - rmbutton.remove(); - title = col.getElementsByClassName("title")[0]; - title.getElementsByClassName("add")[0].style.display = ""; - editTitle = title.getElementsByClassName("colname")[0]; - titlep = document.createElement("p"); - titlep.innerText = editTitle.value; - editTitle.remove(); - title.appendChild(titlep); col.getElementsByClassName("grip")[0].style.display = "none"; + + var rmColBtn = col.getElementsByClassName("title")[0].getElementsByClassName("rmcol")[0]; + rmColBtn.remove(); + + var titleDiv = col.getElementsByClassName("title")[0]; + titleDiv.getElementsByClassName("add")[0].style.display = ""; + + titleInput = titleDiv.getElementsByClassName("colname")[0]; + titleStatic = document.createElement("p"); + titleStatic.innerText = titleInput.value; + if (titleStatic.innerText == "") { + titleStatic.innerText = titleInput.placeholder; + } + titleInput.remove(); + titleDiv.appendChild(titleStatic); + } - save(); + + saveConfig(); + } -function addCol() { +function addColumn(event) { + + // Create a new column from Edit mode + var ul = document.createElement("ul"); - ul.setAttribute("id", "new"); - ul.setAttribute('draggable', 'true'); - ul.addEventListener('dragstart', dragStart, false); - ul.addEventListener('dragenter', dragEnter, false); - ul.addEventListener('dragover', dragOver, false); - ul.addEventListener('dragleave', dragLeave, false); - ul.addEventListener('drop', drop, false); - document.getElementById("links").appendChild(ul); + + // Make sure columns do not share an id + var ex = document.querySelectorAll('[id^="new "]'); // existing "new" columns + if (ex.length > 0) { + indices = [] + for (let i of ex) { + indices.push(Number(i.id.split(" ")[1])); + } + ul.setAttribute("id", "new " + (Math.max.apply(Math, indices)+1).toString()); + } + else { + ul.setAttribute("id", "new 1"); + } + ul.setAttribute('draggable', 'false'); var grip = document.createElement("span"); grip.setAttribute("class", "grip"); + grip.addEventListener("mousedown", enableDrag); + grip.addEventListener("mouseup", disableDrag); + grip.style.display = "inline-block"; + ul.style.bottom = "26px"; + ul.appendChild(grip) + + var titleDiv = document.createElement("div"); + titleDiv.setAttribute("class", "title"); + + var titleInput = document.createElement("input"); + titleInput.type = "text"; + titleInput.className = "colname"; + titleInput.placeholder = ul.id; + titleDiv.appendChild(titleInput); + + var addBtn = document.createElement("span"); + addBtn.style.display = "flex"; + addBtn.innerText = addText; + addBtn.setAttribute("class", "add"); + addBtn.setAttribute("id", "add-" + ul.id); + addBtn.addEventListener("click", addItem); + titleDiv.appendChild(addBtn); + + var rmColBtn = document.createElement("span"); + rmColBtn.className = "rmcol"; + rmColBtn.id = "rmcol-" + ul.id; + rmColBtn.innerText = rmText; + titleDiv.appendChild(rmColBtn); + + ul.appendChild(titleDiv); - var title = document.createElement("div"); - title.setAttribute("class", "title"); - title.appendChild(grip) - ul.appendChild(title); - - var p = document.createElement("p"); - p.innerText = "new"; - title.appendChild(p); - - title.insertAdjacentHTML("beforeend", "+"); - - rmbutton = document.createElement("span"); - rmbutton.setAttribute("class", "rmcol"); - rmbutton.setAttribute("id", "rmcol-new"); - rmbutton.innerText = "-"; - rmbutton.addEventListener('click', function(event){ - console.log("Deleting column " + this.parentNode.parentNode); - this.parentNode.parentNode.remove(); - var index = mainlist.indexOf(this.parentNode.parentNode.id); - console.log("Found deleted node " + this.parentNode.parentNode.id + " at index " + index); - if (index > -1) { - mainlist.splice(index, 1); - } - save(); - }); - title.appendChild(rmbutton); - title.getElementsByClassName("add")[0].style.display = "flex"; - titlep = title.getElementsByTagName("p")[0]; - editTitle = document.createElement("input"); - titlep.insertAdjacentHTML("afterend", ""); - titlep.remove(); + document.getElementById("links").appendChild(ul); + saveConfig(); + updateListeners(); + titleInput.focus(); + +} + + +function removeColumn(event) { + + // Delete column in edit mode - mainlist.push(["new"]); - save(); - listen(title.getElementsByClassName("add")[0]); + this.parentNode.parentNode.remove(); + saveConfig(); + } -function listen(li) { - li.addEventListener('click', function(event){ - var r = event.target.id.split("-"); - if (r[0] == "delete") { - var el = document.getElementById(r[2]+"-"+r[1]); - el.outerHTML = ""; - delete el; - save(); - } else { - addItem(r, li); + +function removeItem(event) { + + // Remove link in edit or normal mode + + this.parentNode.outerHTML = ""; + delete this.parentNode; + saveConfig(); + +} + + +function addItem(event) { + + // Interface for adding a new list item to an existing category + + var ul = this.parentNode.parentNode; + var id = ul.id; + + // Check if a form has already been generated for this column + existing = ul.getElementsByClassName("new"); + if (existing.length > 0) { + existing[0].getElementsByClassName("name")[0].focus(); + return false; + } + + var li = document.createElement("li"); + li.setAttribute("class", "new"); + li.addEventListener("keyup", formKeys); + + var saveBtn = document.createElement("span"); + saveBtn.className = "save"; + saveBtn.tabIndex = "3"; + saveBtn.id = "save-" + ul.id; + saveBtn.innerText = tick; + saveBtn.addEventListener("click", saveItem); + saveBtn.addEventListener("mouseover", saveMouseOver); + saveBtn.addEventListener("mouseout", saveMouseOut); + li.appendChild(saveBtn); + + var nameInput = document.createElement("input"); + nameInput.type = "text"; + nameInput.className = "name"; + nameInput.placeholder = "name"; + nameInput.tabIndex = "1"; + li.appendChild(nameInput); + + var urlInput = document.createElement("input"); + urlInput.type = "url"; + urlInput.className = "url"; + urlInput.placeholder = "url"; + urlInput.tabIndex = "2"; + urlInput.spellcheck = "false"; + li.appendChild(urlInput); + + ul.appendChild(li); + updateListeners(); + nameInput.focus(); + +} + + +function saveItem(event) { + + // Add new item to a column after pressing the save button in form + + var li = this.parentNode; + var ul = this.parentNode.parentNode; + var nameField = li.getElementsByClassName("name")[0]; + var urlField = li.getElementsByClassName("url")[0]; + + if (nameField.value != "" && urlField.value != "" && urlField.validity.typeMismatch== false) { + + var newli = genItem(ul, nameField.value, urlField.value); + li.remove(); + delete li; + + ul.appendChild(newli); + saveConfig(); + + } + else { + + if (nameField.value == "" && urlField.value == "") { + console.log("No data supplied, deleting form"); + li.remove(); + } + else { + console.log("Missing data, press Esc to delete form"); } - }, true); + + } + } -function addItem(caller, li) { - if (ul == null) { - var ul = li.parentNode.parentNode; - var id = ul.id; - } - else { - var ul = document.getElementById(caller[1]); - var id = caller[1]; - } - - if (document.querySelectorAll("#"+id+" .new").length > 0) { - fields = document.querySelector("#"+id+" .new .name"); - fields.focus(); - return false; - } - var li = document.createElement("li"); - li.setAttribute("class", "new"); - li.insertAdjacentHTML("beforeend", ""+tick+"
"); - ul.appendChild(li); - var span = document.getElementById("input-"+columnToArray(li).length.toString()); - var form = document.getElementById("form-"+columnToArray(li).length.toString()); - span.addEventListener('click', function(event){ - console.log("Adding item to:"); - console.log(this); - var li = document.getElementsByClassName("new")[0] - var ul = li.parentNode; - if (li.getElementsByClassName("name")[0].value != "" && li.getElementsByClassName("url")[0].value != "" && li.getElementsByClassName("url")[0].validity.typeMismatch== false) { - var newli = document.createElement("li"); - newli.setAttribute("id",caller[1]+"-"+columnToArray(ul).length.toString()); - var siteurl = addhttp(li.getElementsByClassName("url")[0].value); - var nme = li.getElementsByClassName("name")[0].value; - li.remove() - delete li; - - newli.insertAdjacentHTML("beforeend", ""+nme+""); - newli.insertAdjacentHTML("beforeend", rmspan[0]+columnToArray(ul).length.toString()+"-"+caller[1]+rmspan[1]); - document.getElementById(id).appendChild(newli); - save(); - listen(newli); - } - else { - if (li.getElementsByClassName("name")[0].value == "" && li.getElementsByClassName("url")[0].value == "") { - console.log("No data supplied, deleting form"); - this.parentNode.remove(); - } - else { - console.log("Missing data, press Esc to delete form"); - } - } - }); - span.onmouseover = function() { - nme = document.getElementsByClassName("name")[0]; - url = document.getElementsByClassName("url")[0]; - if (nme.value === '' || url.value === '' || url.validity.typeMismatch == true) { - this.style.background = removebg; - } - else { - this.style.background = hovergrn; - } - }; - span.onmouseout = function() { - this.style.background = hoverbg2; - }; - menu(); // TODO fix bug where form save button loses its eventlistener after generating form for another column twice - fields = document.querySelector("#"+id+" .new .name"); - fields.focus(); +// -------------------------------- +// +// UI event listeners +// +// -------------------------------- + + +function updateListeners() { + + // Update event listeners for interface elements, since listeners are tied DOM objects which are lost on dataTransfer operations + + var addBtns = document.getElementsByClassName("add"); + for (let addBtn of addBtns) { + addBtn.addEventListener("click", addItem); + } + + var rmBtns = document.getElementsByClassName("rm"); + for (let rmBtn of rmBtns) { + rmBtn.addEventListener("click", removeItem); + } + + var rmColBtns = document.getElementsByClassName("rmcol"); + for (let rmColBtn of rmColBtns) { + rmColBtn.addEventListener("click", removeColumn); + } + + var saveBtns = document.getElementsByClassName("save"); + for (let saveBtn of saveBtns) { + saveBtn.addEventListener("click", saveItem); + saveBtn.addEventListener("mouseover", saveMouseOver); + saveBtn.addEventListener("mouseout", saveMouseOut); + } + +} + + +function saveMouseOver(event) { + nameField = this.parentNode.getElementsByClassName("name")[0]; + urlField = this.parentNode.getElementsByClassName("url")[0]; + if (nameField.value === '' || urlField.value === '' || urlField.validity.typeMismatch == true) { + this.style.background = removebg; + } + else { + this.style.background = hovergrn; + } +} + + +function saveMouseOut(event) { + this.style.background = hoverbg2; } -function menu() { - // Update event listeners for all items, their remove buttons, and the add button for each column - var allUserLi = document.querySelectorAll('.remove, .add'); - allUserLi.forEach(function(li, p_index){ - listen(li); - }); +function enableDrag() { + // Enable drag & drop when grip is grabbed (otherwise any mouse click triggers dragStart) + console.log("Drag started"); + uls = document.getElementsByTagName("ul"); + for (let ul of uls) { + ul.setAttribute("draggable", "true"); + ul.addEventListener('dragstart', dragStart, false); + ul.addEventListener('dragover', dragOver, false); + ul.addEventListener('drop', drop, false); + } } -function save(l) { - console.log(mainlist); - var set = l || JSON.parse(JSON.stringify(mainlist)); - d = [] - d = set; + +function disableDrag() { + // Disable drag after grip has been released + console.log("Drag ended"); + uls = document.getElementsByTagName("ul"); + for (let ul of uls) { + ul.setAttribute("draggable", "false"); + ul.removeEventListener('dragstart'); + ul.removeEventListener('dragover'); + ul.removeEventListener('drop'); + } +} + + +function dragStart(e) { + dragSrcEl = this; + e.dataTransfer.effectAllowed = 'move'; + e.dataTransfer.setData('text/html', this.innerHTML); +} + + +function dragOver(e) { + if (e.preventDefault) { + e.preventDefault(); + } + e.dataTransfer.dropEffect = 'move'; + return false; +} + + +function drop(e) { + if (e.stopPropagation); { + e.stopPropagation(); + } + if (dragSrcEl != this) { + var srcTitleInput = dragSrcEl.getElementsByClassName("colname")[0].value; + var destTitleInput = this.getElementsByClassName("colname")[0].value; + dragSrcEl.innerHTML = this.innerHTML; + dragSrcEl.getElementsByClassName("colname")[0].value = destTitleInput; + this.innerHTML = e.dataTransfer.getData('text/html'); + this.getElementsByClassName("colname")[0].value = srcTitleInput; + saveConfig(); + updateListeners(); + } + return false; +} + + +function formKeys(e) { + var focus = document.activeElement; + if (focus.parentNode.className != "new") { + return false; + } + switch (e.which) { + case 27: // escape + focus.parentNode.remove(); + break; + case 13: // enter + focus.parentNode.getElementsByClassName("save")[0].click(); + break; + } +} + + +// -------------------------------- +// +// Configuration management +// +// -------------------------------- + + +function saveConfig() { + + // Save current DOM structure as JSON data + console.log("Saving settings"); - for (var i = 0; i < set.length; ++i) { - d[i] = listToArray(document.getElementById(set[i])); + data = [] + for (let ul of document.getElementsByTagName("ul")) { + data.push(columnToArray(ul, true)); } - chrome.storage.sync.set( {"lists": d} ); + chrome.storage.sync.set( {"lists": data} ); + +} + + +function columnToArray(ul, title = false) { + + // Convert a column of data (ul) to a 2D array, optionally including the title + + var data = []; + var items = ul.getElementsByClassName("item"); + + if (title == true && editMode == true) { + data[0] = ul.getElementsByClassName("title")[0].getElementsByTagName("input")[0].value; + if (data[0] == "" || data[0] == null) { + console.log("Using default category name since input was empty"); + data[0] = ul.getElementsByClassName("title")[0].getElementsByTagName("input")[0].placeholder; + } + } + else if (title == true) { + data[0] = ul.getElementsByClassName('title')[0].getElementsByTagName("p")[0].textContent; + } + + for (let li of items) { + if (li.class == "new" || li.class == "title") { + continue; // Ignore input forms and titles (already handled) + } + else { + data.push([li.innerText, li.getAttribute("href")]); + } + } + + return data; + }