From 280b50a166f1f3ed6e4bbf7ea66abc85ac62845a Mon Sep 17 00:00:00 2001
From: Vasili Gulevich <V.V.Gulevich@mail.ru>
Date: Sat, 20 Aug 2016 23:56:52 +0700
Subject: [PATCH] Implemented link to origin

Proof of concept
Issue #2
---
 TogglLibrary.js | 92 +++++++++++++++++++++++++++++++++++++++++--------
 1 file changed, 78 insertions(+), 14 deletions(-)

diff --git a/TogglLibrary.js b/TogglLibrary.js
index 48fc72b..cb7ab94 100644
--- a/TogglLibrary.js
+++ b/TogglLibrary.js
@@ -9,6 +9,10 @@
  */
 
 function TogglButtonGM(selector, renderer) {
+ 
+ function debug() {
+    console.debug.apply(console, prepend("TogglLibrary:", arguments));
+  }
 
   var
     $activeApiUrl = null,
@@ -168,26 +172,86 @@ function TogglButtonGM(selector, renderer) {
     }
   }
 
-  function getIdFromEntry(entry) {
-    return Number.parseInt(entry.querySelector("input[type=\"checkbox\"]").getAttribute("data-id"), 10);
+  function getIdFromEntry(entryNode) {
+    return Number.parseInt(entryNode.querySelector("input[type=\"checkbox\"]").getAttribute("data-id"), 10);
   }
   
-  this.tryDecorateTimer = () => {
-    if (!window.location.href.startsWith("https://toggl.com/app/timer"))
-      return;
-    const timerEntries = document.querySelectorAll("#time-entry-list ul li.entry");
-    console.log("Timer entries: " + timerEntries.length);
-    for (var entry of timerEntries) {
-      let id = getIdFromEntry(entry);
-      let originUrl = GM_getValue("_url["+id+"]", null);
+  function decorateEntry(entry) {
+      debug("Decorating entry", entry);
+      // Additional column for all entries, to keep column alignment
+      let renderTarget = entry.querySelector("div.paragon-decoration");
+      if (!renderTarget) {
+        renderTarget = document.createElement("div");
+        renderTarget.classList.add("paragon-decoration");
+        const duration = entry.querySelector("div.duration-container");
+        entry.insertBefore(renderTarget, duration);
+      }
+      
+      const id = getIdFromEntry(entry);
+      // URL will only be found within script that has it in its database
+      // Multiple scripts are not supposed to have URL's for same entries, so they won't be in conflict while rendering decoration
+      const originUrl = GM_getValue("_url["+id+"]", null);
       if (originUrl) {
-        console.log("Origin for entry: " + id + ": " + originUrl);
-        let backReferenceColumn = document.createElement("div");
-        renderer.renderOriginLink(backReferenceColumn, originUrl);
-        entry.appendChild(backReferenceColumn);
+        debug("Origin for entry: " + id + ": " + originUrl);
+        renderer.renderOriginLink(renderTarget, originUrl);
       }
+  }  
+  
+  function forEachNode(nodeList, consumer) {
+    for (var entry of nodeList) {
+      consumer(entry);
     }
+  }
+
+  // Decorates node if is an entry or its nested entries
+  function decorateAllEntriesIn(node) {
+    debug("Decorating all in:", node);
+    if (node.nodeName == "LI" && node.classList.contains("entry")) {
+      decorateEntry(node);
+    } else if (node.querySelectorAll) {
+      forEachNode(node.querySelectorAll("li.entry"), decorateEntry);
+    }
+  }
+    
+  function prepend(value, array) {
+    var newArray = Array.prototype.slice.call(array);
+    newArray.unshift(value);
+    return newArray;
+  }
+  
+  function waitForElement(element, query) {
+    return new Promise((resolve, reject) => {
+      const observer = new MutationObserver(function(mutations) {
+        const result = element.querySelector(query);
+        if (result) {
+          debug("Matched query:", query, result);
+          resolve(result);
+          observer.disconnect();
+        }
+      });
+      observer.observe(element, {childList: true, subtree:true});
+    }); 
+  }
+  
+  
+  this.tryDecorateTimer = () => {
+    if (!window.location.href.startsWith("https://toggl.com/app/timer"))
+      return;
+    GM_addStyle("li.entry div.paragon-decoration { width:3em; padding:16px; }");
+    waitForElement(document, "#time-entry-list").then(timeEntryList => {
+      const observer = new MutationObserver(mutations => {
+        mutations.forEach(mutation => {
+          debug("Mutation:", mutation.type, " target:", mutation.target, "added:", mutation.addedNodes, "removed:", mutation.removedNodes);
+          forEachNode(mutation.addedNodes,decorateAllEntriesIn);
+          if (mutation.removedNodes.length > 0) // Entry editing removes decoration
+            decorateAllEntriesIn(mutation.target);
+        })
+      });
+      observer.observe(timeEntryList, { subtree:true, childList: true });
+      decorateAllEntriesIn(timeEntryList);
+    });
   };
+  
   this.clickLinks = function() {
     for (i in $instances) {
       $instances[i].clickLink();
-- 
GitLab