-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 thumbServer = "https://www.google.com/s2/favicons?domain=";
-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"]
- ]
-]
-
-
-// --------------------------------
-//
-// 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);
-
- // Set colours from preferences
-
- var bgCallback = function(colourPref) { document.body.style.background = colourPref["bgvalue"]; };
- var fgCallback = function(colourPref) { document.body.style.color = colourPref["fgvalue"]; };
- var hrCallback = function(colourPref) {
- document.documentElement.style.setProperty('--hover-bg', colourPref["hrvalue"]);
- hoverbg = colourPref["hrvalue"];
- };
-
- chrome.storage.sync.get({"bgvalue": "#2e3440"}, bgCallback);
- chrome.storage.sync.get({"fgvalue": "#d8dee9"}, fgCallback);
- chrome.storage.sync.get({"hrvalue": "#434c5e"}, hrCallback);
-
-}
-
-
-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 = requestThumbnail(url);
-
- 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;
-
-}
-
-
-function requestThumbnail(imageUrl) {
- // Get thumbnail from Google's favicon server
- var img = document.createElement("img");
- var xhr = new XMLHttpRequest();
- xhr.open('GET', thumbServer + imageUrl);
- xhr.responseType = "blob";
- xhr.onload = function() {
- img.setAttribute("data-src", thumbServer + imageUrl);
- img.className = "icon";
- var objUrl = URL.createObjectURL(xhr.response);
- img.setAttribute("src", objUrl);
- }.bind(this);
- xhr.send();
- return img;
-}
-
-
-// --------------------------------
-//
-// 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;
-
-}