Lattice Goals - Add Ideal

Determines ideal goal value based on Created on and Due dates

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name        Lattice Goals - Add Ideal
// @namespace   Violentmonkey Scripts
// @match       http://*.latticehq.com/goals/*
// @match       https://*.latticehq.com/goals/*
// @grant       none
// @version     1.3
// @author      pedro-mass
// @description Determines ideal goal value based on Created on and Due dates
// @run-at      document-idle
// @require     http://code.jquery.com/jquery-3.5.0.min.js
// ==/UserScript==

waitUntilTrue(shouldRun, run);

function isPercentageBasedGoal() {
  // todo: make this actually check if there is a percentage based check?
  return true;
}

function run() {
  console.log("Starting Lattice Goal Percentage calculations...");
  if (!isPercentageBasedGoal()) return;

  const Dates = getDates();
  const GoalStats = getGoalStats();
  const percentageByDate = getRelativePercentage(
    Dates.start,
    Dates.end,
    Dates.current
  );

  const idealValue = Math.round(
    getRelativeValue(GoalStats.start, GoalStats.goal, percentageByDate)
  );

  const goalDirection = GoalStats.start <= GoalStats.end ? 1 : -1;

  return insertIdeal(
    idealValue,
    GoalStats.unit,
    GoalStats.current,
    goalDirection
  );
}

function getRelativeValue(start, end, percentage) {
  const offset = start;
  return (end - offset) * percentage + offset;
}

function getDates() {
  const start = getDate(/^created\n\n/i);
  const end = getDate(/^due\n\n/i);
  const current = Date.now();

  return {
    start,
    end,
    current,
  };
}

function getGoalStats() {
  const unit = getValue(/^start: /i, "span").replace(/\d+/, "");
  const getNumber = (regex) =>
    Number(getValue(regex, "span").replace(unit, ""));
  const start = getNumber(/^start: /i);
  const current = getNumber(/^current: /i);
  const goal = getNumber(/^goal: /i);

  return {
    start,
    goal,
    current,
    unit,
  };
}

function getProgressIndicator(ideal, current) {
  if (current == null) return "";

  if (ideal <= current) {
    return "🎉";
  }

  return "😢";
}

function insertIdeal(ideal, unit, current, isAscendingGoal) {
  if (contains("span", /^ideally: /i).length > 0) {
    return;
  }

  const progressIndicator = isAscendingGoal
    ? getProgressIndicator(ideal, current)
    : getProgressIndicator(current, ideal);

  const idealElem = `<span class="css-1mddpa2">Ideally: <span>${ideal}${unit}</span> <span>${progressIndicator}</span></span>`;
  const goalsContainer = contains("div", /^start:/i);

  return $(goalsContainer).find("span").first().after(idealElem);
}

function shouldRun() {
  const pageCheck = contains("span", /^start: /i);
  return pageCheck != null && pageCheck.length > 0;
}

function getRelativePercentage(startDate, endDate, currentDate) {
  const offset = startDate;
  endDate = endDate - offset;
  currentDate = currentDate - offset;
  return currentDate / endDate;
}

function getDate(regex, selector = "div") {
  return new Date(getValue(regex, selector)).getTime();
}

function getValue(regex, selector) {
  return replaceString(getText(first(contains(selector, regex))), regex, "");
}

function replaceString(string, searchString, newString) {
  if (!string) {
    console.warn("Received bad params", { string, searchString, newString });
    return string;
  }

  return string.replace(searchString, newString);
}

function contains(selector, text) {
  var elements = document.querySelectorAll(selector);
  return Array.prototype.filter.call(elements, function (element) {
    return RegExp(text).test(getText(element));
  });
}

function getText(element) {
  return element.innerText;
}

function first(arr) {
  return arr[0];
}

function waitUntilTrue(checkFn, cb = (x) => x, timeout = 250) {
  const intervalId = setInterval(checkInterval, timeout);
  function checkInterval() {
    if (checkFn()) {
      clearInterval(intervalId);
      cb();
    }
  }
}