TogglLibrary

Library for Toggl-Button scripts used on platforms like drupal, github, youtrack and others.

Этот скрипт недоступен для установки пользователем. Он является библиотекой, которая подключается к другим скриптам мета-ключом // @require https://update.greasyfork.org/scripts/2670/247800/TogglLibrary.js

Чтобы установить этот скрипт, вы сначала должны установить расширение браузера, например Tampermonkey, Greasemonkey или Violentmonkey.

Для установки этого скрипта вам необходимо установить расширение, такое как Tampermonkey.

Чтобы установить этот скрипт, вы сначала должны установить расширение браузера, например Tampermonkey или Violentmonkey.

Чтобы установить этот скрипт, вы сначала должны установить расширение браузера, например Tampermonkey или Userscripts.

Чтобы установить этот скрипт, сначала вы должны установить расширение браузера, например Tampermonkey.

Чтобы установить этот скрипт, вы должны установить расширение — менеджер скриптов.

(у меня уже есть менеджер скриптов, дайте мне установить скрипт!)

Чтобы установить этот стиль, сначала вы должны установить расширение браузера, например Stylus.

Чтобы установить этот стиль, сначала вы должны установить расширение браузера, например Stylus.

Чтобы установить этот стиль, сначала вы должны установить расширение браузера, например Stylus.

Чтобы установить этот стиль, сначала вы должны установить расширение — менеджер стилей.

Чтобы установить этот стиль, сначала вы должны установить расширение — менеджер стилей.

Чтобы установить этот стиль, сначала вы должны установить расширение — менеджер стилей.

(у меня уже есть менеджер стилей, дайте мне установить скрипт!)

/*------------------------------------------------------------------------
 * JavaScript Library for Toggl-Button for GreaseMonkey
 *
 * (c) Jürgen Haas, PARAGON Executive Services GmbH
 * Version: 1.9
 *
 * @see https://gitlab.paragon-es.de/toggl-button/core
 *------------------------------------------------------------------------
 */

function TogglButtonGM(selector, renderer) {

  var
    $activeApiUrl = null,
    $apiUrl = "https://www.toggl.com/api/v7",
    $newApiUrl = "https://www.toggl.com/api/v8",
    $legacyApiUrl = "https://new.toggl.com/api/v8",
    $triedAlternative = false,
    $addedDynamicListener = false,
    $api_token = null,
    $default_wid = null,
    $clientMap = {},
    $projectMap = {},
    $instances = {};

  init(selector, renderer);

  function init(selector, renderer, apiUrl) {
    var timeNow = new Date().getTime(),
      timeAuth = GM_getValue('_authenticated', 0);
    apiUrl = apiUrl || $newApiUrl;
    $api_token = GM_getValue('_api_token', false);
    if ($api_token && (timeNow - timeAuth) < (6*60*60*1000)) {
      $activeApiUrl = GM_getValue('_api_url', $newApiUrl);
      $default_wid = GM_getValue('_default_wid', 0);
      $clientMap   = JSON.parse(GM_getValue('_clientMap', {}));
      $projectMap  = JSON.parse(GM_getValue('_projectMap', {}));
      if ($activeApiUrl == $legacyApiUrl) {
        // See issue #22.
        $activeApiUrl = $newApiUrl;
        GM_setValue('_api_url', $activeApiUrl);
      }
      render(selector, renderer);
      return;
    }

    var headers = {};
    if ($api_token) {
      headers = {
        "Authorization": "Basic " + btoa($api_token + ':api_token')
      };
    }
    $activeApiUrl = apiUrl;
    GM_xmlhttpRequest({
      method: "GET",
      url: apiUrl + "/me?with_related_data=true",
      headers: headers,
      onload: function(result) {
        if (result.status === 200) {
          var resp = JSON.parse(result.responseText);
          $clientMap[0] = 'No Client';
          if (resp.data.clients) {
            resp.data.clients.forEach(function (client) {
              $clientMap[client.id] = client.name;
            });
          }
          if (resp.data.projects) {
            resp.data.projects.forEach(function (project) {
              if ($clientMap[project.cid] == undefined) {
                project.cid = 0;
              }
              if (project.active) {
                $projectMap[project.id] = {
                  id: project.id,
                  cid: project.cid,
                  name: project.name,
                  billable: project.billable
                };
              }
            });
          }
          GM_setValue('_authenticated', new Date().getTime());
          GM_setValue('_api_token', resp.data.api_token);
          GM_setValue('_api_url', $activeApiUrl);
          GM_setValue('_default_wid', resp.data.default_wid);
          GM_setValue('_clientMap', JSON.stringify($clientMap));
          GM_setValue('_projectMap', JSON.stringify($projectMap));
          $api_token = resp.data.api_token;
          $default_wid = resp.data.default_wid;
          render(selector, renderer);
        } else if (!$triedAlternative) {
          $triedAlternative = true;
          if (apiUrl === $apiUrl) {
            init(selector, renderer, $newApiUrl);
          } else if (apiUrl === $newApiUrl) {
            init(selector, renderer, $apiUrl);
          }
        } else if ($api_token) {
          // Delete the API token and try again
          GM_setValue('_api_token', false);
          $triedAlternative = false;
          init(selector, renderer, $newApiUrl);
        } else {
          var wrapper = document.createElement('div'),
            content = createTag('div', 'content'),
            link = createLink('login', 'a', 'https://new.toggl.com/', 'Login');
          GM_addStyle(GM_getResourceText('togglStyle'));
          link.setAttribute('target', '_blank');
          wrapper.setAttribute('id', 'toggl-button-auth-failed');
          content.appendChild(document.createTextNode('Authorization to your Toggl account failed!'));
          content.appendChild(link);
          wrapper.appendChild(content);
          document.querySelector('body').appendChild(wrapper);
        }
      }
    });
  }

  function render(selector, renderer) {
    if (selector == null) {
      return;
    }
    var i, len, elems = document.querySelectorAll(selector + ':not(.toggl)');
    for (i = 0, len = elems.length; i < len; i += 1) {
      elems[i].classList.add('toggl');
      $instances[i] = new TogglButtonGMInstance(renderer(elems[i]));
    }

    if (!$addedDynamicListener) {
      $addedDynamicListener = true;

      document.addEventListener('TogglButtonGMUpdateStatus', function() {
        GM_xmlhttpRequest({
          method: "GET",
          url: $activeApiUrl + "/time_entries/current",
          headers: {
            "Authorization": "Basic " + btoa($api_token + ':api_token')
          },
          onload: function (result) {
            if (result.status === 200) {
              var resp = JSON.parse(result.responseText),
                data = resp.data || false;
              for (i in $instances) {
                $instances[i].checkCurrentLinkStatus(data);
              }
            }
          }
        });
      });

      window.addEventListener('focus', function() {
        document.dispatchEvent(new CustomEvent('TogglButtonGMUpdateStatus'));
      });

      if (selector !== 'body') {
        document.body.addEventListener('DOMSubtreeModified', function () {
          setTimeout(function () {
            render(selector, renderer);
          }, 1000);
        });
      }
    }
  }

  this.clickLinks = function() {
    for (i in $instances) {
      $instances[i].clickLink();
    }
  };

  this.getCurrentTimeEntry = function(callback) {
    GM_xmlhttpRequest({
      method: "GET",
      url: $activeApiUrl + "/time_entries/current",
      headers: {
        "Authorization": "Basic " + btoa($api_token + ':api_token')
      },
      onload: function (result) {
        if (result.status === 200) {
          var resp = JSON.parse(result.responseText),
            data = resp.data || false;
          if (data) {
            callback(data.id, true);
          }
        }
      }
    });
  };

  this.stopTimeEntry = function(entryId, asCallback) {
    if (entryId == null) {
      if (asCallback) {
        return;
      }
      this.getCurrentTimeEntry(this.stopTimeEntry);
      return;
    }
    GM_xmlhttpRequest({
      method: "PUT",
      url: $activeApiUrl + "/time_entries/" + entryId + "/stop",
      headers: {
        "Authorization": "Basic " + btoa($api_token + ':api_token')
      },
      onload: function () {
        document.dispatchEvent(new CustomEvent('TogglButtonGMUpdateStatus'));
      }
    });
  };

  function TogglButtonGMInstance(params) {

    var
      $curEntryId = null,
      $isStarted = false,
      $link = null,
      $generalInfo = null,
      $buttonTypeMinimal = false,
      $projectSelector = window.location.host,
      $projectId = null,
      $projectSelected = false,
      $projectSelectElem = null,
      $stopCallback = null,
      $tags = params.tags || [];

    this.checkCurrentLinkStatus = function (data) {
      var started, updateRequired = false;
      if (!data) {
        if ($isStarted) {
          updateRequired = true;
          started = false;
        }
      } else {
        if ($generalInfo != null) {
          if (!$isStarted || ($curEntryId != null && $curEntryId != data.id)) {
            $curEntryId = data.id;
            $isStarted = false;
          }
        }
        if ($curEntryId == data.id) {
          if (!$isStarted) {
            updateRequired = true;
            started = true;
          }
        } else {
          if ($isStarted) {
            updateRequired = true;
            started = false;
          }
        }
      }
      if (updateRequired) {
        if (!started) {
          $curEntryId = null;
        }
        if ($link != null) {
          updateLink(started);
        }
        if ($generalInfo != null) {
          if (data) {
            var projectName = 'No project',
              clientName = 'No client';
            if (data.pid !== undefined) {
              if ($projectMap[data.pid] == undefined) {
                GM_setValue('_authenticated', 0);
                window.location.reload();
                return;
              }
              projectName = $projectMap[data.pid].name;
              clientName = $clientMap[$projectMap[data.pid].cid];
            }
            var content = createTag('div', 'content'),
              contentClient = createTag('div', 'client'),
              contentProject = createTag('div', 'project'),
              contentDescription = createTag('div', 'description');
            contentClient.innerHTML = clientName;
            contentProject.innerHTML = projectName;
            contentDescription.innerHTML = data.description;
            content.appendChild(contentClient);
            content.appendChild(contentProject);
            content.appendChild(contentDescription);
            while ($generalInfo.firstChild) {
              $generalInfo.removeChild($generalInfo.firstChild);
            }
            $generalInfo.appendChild(content);
          }
          updateGeneralInfo(started);
        }
      }
    };

    this.clickLink = function (data) {
      $link.dispatchEvent(new CustomEvent('click'));
    };

    createTimerLink(params);

    function createTimerLink(params) {
      GM_addStyle(GM_getResourceText('togglStyle'));
      if (params.generalMode !== undefined && params.generalMode) {
        $generalInfo = document.createElement('div');
        $generalInfo.id = 'toggl-button-gi-wrapper';
        $generalInfo.addEventListener('click', function (e) {
          e.preventDefault();
          $generalInfo.classList.toggle('collapsed');
        });
        document.querySelector('body').appendChild($generalInfo);
        document.dispatchEvent(new CustomEvent('TogglButtonGMUpdateStatus'));
        return;
      }
      if (params.projectIds !== undefined) {
        $projectSelector += '-' + params.projectIds.join('-');
      }
      if (params.stopCallback !== undefined) {
        $stopCallback = params.stopCallback;
      }
      updateProjectId();
      $link = createLink('toggl-button');
      $link.classList.add(params.className);

      if (params.buttonType === 'minimal') {
        $link.classList.add('min');
        $link.removeChild($link.firstChild);
        $buttonTypeMinimal = true;
      }

      $link.addEventListener('click', function (e) {
        var opts = '';
        e.preventDefault();
        if ($isStarted) {
          stopTimeEntry();
        } else {
          var billable = false;
          if ($projectId != undefined && $projectId > 0) {
            billable = $projectMap[$projectId].billable;
          }
          opts = {
            $projectId: $projectId || null,
            billable: billable,
            description: invokeIfFunction(params.description),
            createdWith: 'TogglButtonGM - ' + params.className
          };
          createTimeEntry(opts);
        }
        return false;
      });

      // new button created - reset state
      $isStarted = false;

      // check if our link is the current time entry and set the state if it is
      checkCurrentTimeEntry({
        $projectId: $projectId,
        description: invokeIfFunction(params.description)
      });

      document.querySelector('body').classList.add('toggl-button-available');
      if (params.targetSelectors == undefined) {
        var wrapper,
          existingWrapper = document.querySelectorAll('#toggl-button-wrapper'),
          content = createTag('div', 'content');
        content.appendChild($link);
        content.appendChild(createProjectSelect());
        if (existingWrapper.length > 0) {
          wrapper = existingWrapper[0];
          while (wrapper.firstChild) {
            wrapper.removeChild(wrapper.firstChild);
          }
          wrapper.appendChild(content);
        }
        else {
          wrapper = document.createElement('div');
          wrapper.id = 'toggl-button-wrapper';
          wrapper.appendChild(content);
          document.querySelector('body').appendChild(wrapper);
        }
      } else {
        var elem = params.targetSelectors.context || document;
        if (params.targetSelectors.link != undefined) {
          elem.querySelector(params.targetSelectors.link).appendChild($link);
        }
        if (params.targetSelectors.projectSelect != undefined) {
          elem.querySelector(params.targetSelectors.projectSelect).appendChild(createProjectSelect());
        }
      }

      return $link;
    }

    function createTimeEntry(timeEntry) {
      var start = new Date();
      GM_xmlhttpRequest({
        method: "POST",
        url: $activeApiUrl + "/time_entries",
        headers: {
          "Authorization": "Basic " + btoa($api_token + ':api_token')
        },
        data: JSON.stringify({
          time_entry: {
            start: start.toISOString(),
            description: timeEntry.description,
            wid: $default_wid,
            pid: timeEntry.$projectId || null,
            billable: timeEntry.billable || false,
            duration: -(start.getTime() / 1000),
            tags: $tags,
            created_with: timeEntry.createdWith || 'TogglButtonGM'
          }
        }),
        onload: function (res) {
          var responseData, entryId;
          responseData = JSON.parse(res.responseText);
          entryId = responseData && responseData.data && responseData.data.id;
          $curEntryId = entryId;
          document.dispatchEvent(new CustomEvent('TogglButtonGMUpdateStatus'));
        }
      });
    }

    function checkCurrentTimeEntry(params) {
      GM_xmlhttpRequest({
        method: "GET",
        url: $activeApiUrl + "/time_entries/current",
        headers: {
          "Authorization": "Basic " + btoa($api_token + ':api_token')
        },
        onload: function (result) {
          if (result.status === 200) {
            var resp = JSON.parse(result.responseText);
            if (resp == null) {
              return;
            }
            if (params.description === resp.data.description) {
              $curEntryId = resp.data.id;
              updateLink(true);
            }
          }
        }
      });
    }

    function stopTimeEntry(entryId) {
      entryId = entryId || $curEntryId;
      if (!entryId) {
        return;
      }
      GM_xmlhttpRequest({
        method: "PUT",
        url: $activeApiUrl + "/time_entries/" + entryId + "/stop",
        headers: {
          "Authorization": "Basic " + btoa($api_token + ':api_token')
        },
        onload: function (result) {
          $curEntryId = null;
          document.dispatchEvent(new CustomEvent('TogglButtonGMUpdateStatus'));
          if (result.status === 200) {
            var resp = JSON.parse(result.responseText),
              data = resp.data || false;
            if (data) {
              if ($stopCallback !== undefined && $stopCallback !== null) {
                var currentdate = new Date();
                $stopCallback((currentdate.getTime() - (data.duration * 1000)), data.duration);
              }
            }
          }
        }
      });
    }

    function createTag(name, className, innerHTML) {
      var tag = document.createElement(name);
      tag.className = className;

      if (innerHTML) {
        tag.innerHTML = innerHTML;
      }

      return tag;
    }

    function createLink(className, tagName, linkHref, linkText) {
      // Param defaults
      tagName = tagName || 'a';
      linkHref = linkHref || '#';
      linkText = linkText || 'Start timer';

      var link = createTag(tagName, className);

      if (tagName === 'a') {
        link.setAttribute('href', linkHref);
      }

      link.appendChild(document.createTextNode(linkText));
      return link;
    }

    function updateGeneralInfo(started) {
      if (started) {
        $generalInfo.classList.add('active');
      } else {
        $generalInfo.classList.remove('active');
      }
      $isStarted = started;
    }

    function updateLink(started) {
      var linkText, color = '';

      if (started) {
        document.querySelector('body').classList.add('toggl-button-active');
        $link.classList.add('active');
        color = '#1ab351';
        linkText = 'Stop timer';
      } else {
        document.querySelector('body').classList.remove('toggl-button-active');
        $link.classList.remove('active');
        linkText = 'Start timer';
      }
      $isStarted = started;

      $link.setAttribute('style', 'color:'+color+';');
      if (!$buttonTypeMinimal) {
        $link.innerHTML = linkText;
      }

      $projectSelectElem.disabled = $isStarted;
    }

    function updateProjectId(id) {
      id = id || GM_getValue($projectSelector, 0);

      $projectSelected = (id != 0);

      if (id <= 0) {
        $projectId = null;
      }
      else {
        $projectId = id;
      }

      if ($projectSelectElem != undefined) {
        $projectSelectElem.value = id;
        $projectSelectElem.disabled = $isStarted;
      }

      GM_setValue($projectSelector, id);

      if ($link != undefined) {
        if ($projectSelected) {
          $link.classList.remove('hidden');
        }
        else {
          $link.classList.add('hidden');
        }
      }
    }

    function invokeIfFunction(trial) {
      if (trial instanceof Function) {
        return trial();
      }
      return trial;
    }

    function createProjectSelect() {
      var pid,
        wrapper = createTag('div', 'toggl-button-project-select'),
        noneOptionAdded = false,
        noneOption = document.createElement('option'),
        emptyOption = document.createElement('option'),
        resetOption = document.createElement('option');

      $projectSelectElem = createTag('select');

      // None Option to indicate that a project should be selected first
      if (!$projectSelected) {
        noneOption.setAttribute('value', '0');
        noneOption.text = '- First select a project -';
        $projectSelectElem.appendChild(noneOption);
        noneOptionAdded = true;
      }

      // Empty Option for tasks with no project
      emptyOption.setAttribute('value', '-1');
      emptyOption.text = 'No Project';
      $projectSelectElem.appendChild(emptyOption);

      var optgroup, project, clientMap = [];
      for (pid in $projectMap) {
        //noinspection JSUnfilteredForInLoop
        project = $projectMap[pid];
        if (clientMap[project.cid] == undefined) {
          optgroup = createTag('optgroup');
          optgroup.label = $clientMap[project.cid];
          clientMap[project.cid] = optgroup;
          $projectSelectElem.appendChild(optgroup);
        } else {
          optgroup = clientMap[project.cid];
        }
        var option = document.createElement('option');
        option.setAttribute('value', project.id);
        option.text = project.name;
        optgroup.appendChild(option);
      }

      // Reset Option to reload settings and projects from Toggl
      resetOption.setAttribute('value', 'RESET');
      resetOption.text = 'Reload settings';
      $projectSelectElem.appendChild(resetOption);

      $projectSelectElem.addEventListener('change', function () {
        if ($projectSelectElem.value == 'RESET') {
          GM_setValue('_authenticated', 0);
          window.location.reload();
          return;
        }

        if (noneOptionAdded) {
          $projectSelectElem.removeChild(noneOption);
          noneOptionAdded = false;
        }

        updateProjectId($projectSelectElem.value);

      });

      updateProjectId($projectId);

      wrapper.appendChild($projectSelectElem);
      return wrapper;
    }
  }

}