refactor admin.js into more modular functions
[newtab.git] / admin.js
index 0c9e0d44660a6ecc8191f41aadf1e1182b52a13b..1a2b0db2d8403e3ad472a1e034b1ce59eaa9ec4f 100644 (file)
--- a/admin.js
+++ b/admin.js
-var mainlist;
-
-//chrome.tabs.create({ url: chrome.extension.getURL("newtab.html") });
-//window.close();
-
-document.addEventListener("DOMContentLoaded", function() {
-       chrome.storage.sync.get(
-{"lists": [
-    [
+var rmspan = ["<span class='remove' id='delete-", "'>–</span>"]
+var tick = "✔";
+var addText = "+";
+var rmText = "−";
+var removebg = "#bf616a";
+var hovergrn = "#a3be8c";
+var hoverbg = "#434c5e";
+var hoverbg2 = "#848ead";
+var editMode = false;
+var dragSrcEl = null;
+var defaultColumns= [
+      ["General",
         ["Github", "https://github.com"],
         ["Wikipedia", "https://en.wikipedia.org"],
         ["Gmail", "https://mail.google.com"]
-    ],
-    [
+      ],
+      ["Productivity",
         ["Desmos", "https://www.desmos.com/calculator"],
         ["Wolfram", "https://wolframalpha.com"],
         ["Hacker News", "https://news.ycombinator.com"]
-    ],
-    [
+      ],
+      ["Social",
         ["Reddit", "https://www.reddit.com"],
         ["YouTube", "https://youtube.com"],
         ["Instagram", "https://instagram.com"]
-    ]
-],
-"l1name": "General",
-"l2name": "Productivity",
-"l3name": "Social"},
-       userListsCallback);
-});
-
-function listToArray(list) {
-       var l = [];
-       var elem = list.getElementsByTagName("li");
-       for (var i = 0; i < elem.length; ++i) {
-         l[i] = [elem[i].getElementsByTagName("a")[0].innerText, elem[i].getElementsByTagName("a")[0].getAttribute("href")];
-       }
-       return l;
-}
-
-var userListsCallback = function(lists) {
-       mainlist = [lists["l1name"],lists["l2name"],lists["l3name"]]
-       for(var i=0;i<lists["lists"].length;i++) {
-               var div = document.createElement("div");
-               div.setAttribute("class", "favorites-list users-list");
-               document.getElementById("links").appendChild(div);
-
-               var ul = document.createElement("ul");
-               ul.setAttribute("id", mainlist[i]);
-               div.appendChild(ul);
-
-               var title = document.createElement("div");
-               title.setAttribute("class", "title");
-               ul.appendChild(title);
-
-               var p = document.createElement("p");
-               p.innerText = mainlist[i];
-               title.appendChild(p);
-
-               title.insertAdjacentHTML("beforeend", "<span id='add-"+mainlist[i]+"'>+</span>");
-
-               var list = lists["lists"][i];
-               for(var j=0;j<lists["lists"][i].length;j++) {
-                       var li = document.createElement("li");
-                       li.setAttribute("id", mainlist[i]+"-"+j);
-                       var siteurl = list[j][1];
-                       var name = list[j][0];
-                       // ronmurphy change start
-                       var img = document.createElement("img");
-                                               img.className = "icon";
-                                               img.src = "http://www.google.com/s2/favicons?domain="+siteurl+"";
-                       li.insertAdjacentHTML("beforeend", "<a href="+siteurl+"> <img src="+img.src+" alt="+extractDomain(siteurl,1)+"/> "+name+"</a>");
-                       // ronmurphy end
-                       //li.insertAdjacentHTML("beforeend", "<a href="+siteurl+">"+name+"</a>");
-                       li.insertAdjacentHTML("beforeend", "<span id='delete-"+j+"-"+mainlist[i]+"'>&#10799;</span>");
-                       ul.appendChild(li);
-               }
-
-               var sortable = Sortable.create(ul, {
-                       group: "userlists",
-                       onUpdate: function (evt) {
-                               save();
-                       }
-               });
-       }
-
-       menu();
-
-};
-
-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 {
-                       var ul = document.getElementById(r[1]);
-                       ul.insertAdjacentHTML("beforeend", "<div class='inp' id='div-"+listToArray(ul).length.toString()+"'><input style='width:35%' type='text' class='name' value='' placeholder='name'><input style='width:47%' type='text' class='url' value='' placeholder='url' id='form-"+listToArray(ul).length.toString()+"'><a id='input-"+listToArray(ul).length.toString()+"'>&#x2714;</a></div>");
-                       var inp = document.getElementById("div-"+listToArray(ul).length.toString());
-                       var span = document.getElementById("input-"+listToArray(ul).length.toString());
-                       var form = document.getElementById("form-"+listToArray(ul).length.toString());
-
-                       form.addEventListener('keyup', function(event){
-                               if (event.keyCode == 13) {
-                                       span.click();
-                               }
-                       });
-
-                       span.addEventListener('click', function(event){
-                               inp.outerHTML = "";
-                               delete inp;
-
-                               if (inp.getElementsByClassName("name")[0].value != "" || inp.getElementsByClassName("url")[0].value != "") {
-                                       var li = document.createElement("li");
-                                       li.setAttribute("id",r[1]+"-"+listToArray(ul).length.toString());
-                                       //var splitted = splitHostname(inp.getElementsByClassName("url")[0].value); fix later
-                                       var siteurl = addhttp(inp.getElementsByClassName("url")[0].value);
-                                       var name = inp.getElementsByClassName("name")[0].value;
-
-                                       li.insertAdjacentHTML("beforeend", "<a href="+siteurl+">"+name+"</a>");
-                                       li.insertAdjacentHTML("beforeend", "<span id='delete-"+listToArray(ul).length.toString()+"-"+r[1]+"'>-</span>");
-                                       document.getElementById(r[1]).appendChild(li);
-                                       save();
-                                       listen(li);
-                               }
-                       });
-               }
-       }, false);
-}
-
-function menu() {
-       var allUserLi = document.querySelectorAll('.users-list > ul > li > span, .users-list > ul > .title > span');
-
-       allUserLi.forEach(function(li, p_index){
-       listen(li);
-    });
-}
-
-function save(l) {
-       var set = l || JSON.parse(JSON.stringify(mainlist));
-       d = []
-       d = set;
-       for (var i = 0; i < set.length; ++i) {
-               e = document.getElementById(set[i]);
-               d[i] = listToArray(e);
-       }
-       chrome.storage.sync.set( {"lists": d} );
+      ]
+]
+
+
+// --------------------------------
+//
+// 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 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);
+
+  }
+}
+
+function genColumn(title) {
+
+    // Generate HTML elements for a column (without items)
+
+    var ul = document.createElement("ul");
+    ul.setAttribute("id", title);
+    ul.setAttribute('draggable', 'false');
+
+    var grip = document.createElement("span");
+    grip.setAttribute("class", "grip");
+    grip.addEventListener('mousedown', enableDrag);
+    grip.addEventListener('mouseup', disableDrag);
+    ul.appendChild(grip)
+
+    var titleDiv = document.createElement("div");
+    titleDiv.setAttribute("class", "title");
+
+    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);
+
+    ul.appendChild(titleDiv);
+
+    return ul;
+
+}
+
+function genItem(ul, nme, url) {
+
+  // Generate HTML elements for an item
+
+  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;
+
+}
+
+
+// --------------------------------
+//
+// Edit mode
+//
+// --------------------------------
+
+
+function edit(event) {
+  
+  // Enter/exit edit mode
+  
+  if (editMode == true) {
+    console.log("Exited edit mode");
+    closeEdit(this);
+    return false;
+  }
+
+  console.log("Entered edit mode");
+
+  this.style.background = hovergrn;
+  this.innerText = tick;
+  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";
+    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) {
+
+  // Exit edit mode and clean up elements
+
+  editMode = false;
+  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";
+    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);
+
+  }
+
+  saveConfig();
+
+}
+
+function addColumn(event) {
+
+  // Create a new column from Edit mode
+
+  var ul = document.createElement("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);
+
+  document.getElementById("links").appendChild(ul);
+  saveConfig();
+  updateListeners();
+  titleInput.focus();
+
+}
+
+
+function removeColumn(event) {
+
+  // Delete column in edit mode
+  
+  this.parentNode.parentNode.remove();
+  saveConfig();
+
+}
+
+
+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");
+    }
+
+  }
+
+}
+
+
+// --------------------------------
+//
+// 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 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 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");
+  data = []
+  for (let ul of document.getElementsByTagName("ul")) {
+    data.push(columnToArray(ul, true));
+  }
+  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;
+
 }