您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Determines ideal goal value based on Created on and Due dates
// ==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(); } } }