NOTICE: By continued use of this site you understand and agree to the binding Terms of Service and Privacy Policy.
// ==UserScript== // @name GitHub Static Time // @version 1.1.0 // @description A userscript that replaces relative times with a static time formatted as you like it // @license MIT // @author Rob Garrison // @namespace https://github.com/Mottie // @match https://github.com/* // @run-at document-end // @grant GM_addStyle // @grant GM_getValue // @grant GM_setValue // @grant GM_registerMenuCommand // @require https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.29.4/moment-with-locales.min.js // @require https://greasyfork.org/scripts/28721-mutations/code/mutations.js?version=1108163 // @require https://greasyfork.org/scripts/398877-utils-js/code/utilsjs.js?version=1079637 // @icon https://github.githubassets.com/pinned-octocat.svg // @updateURL https://raw.githubusercontent.com/Mottie/Github-userscripts/master/github-static-time.user.js // @downloadURL https://raw.githubusercontent.com/Mottie/Github-userscripts/master/github-static-time.user.js // @supportURL https://github.com/Mottie/GitHub-userscripts/issues // ==/UserScript== (() => { /* global moment $ $$ on */ "use strict"; let busy = false; let timeFormat = GM_getValue("ghst-format", "LLL"); let locale = GM_getValue("ghst-locale", "en"); let useUTC = GM_getValue("ghst-utc", "false"); // list copied from // https://github.com/moment/momentjs.com/blob/master/data/locale.js const locales = [ { "abbr": "af", "name": "Afrikaans" }, { "abbr": "sq", "name": "Albanian" }, { "abbr": "ar", "name": "Arabic" }, { "abbr": "ar-dz", "name": "Arabic (Algeria)" }, { "abbr": "ar-kw", "name": "Arabic (Kuwait)" }, { "abbr": "ar-ly", "name": "Arabic (Libya)" }, { "abbr": "ar-ma", "name": "Arabic (Morocco)" }, { "abbr": "ar-sa", "name": "Arabic (Saudi Arabia)" }, { "abbr": "ar-tn", "name": "Arabic (Tunisia)" }, { "abbr": "hy-am", "name": "Armenian" }, { "abbr": "az", "name": "Azerbaijani" }, { "abbr": "bm", "name": "Bambara" }, { "abbr": "eu", "name": "Basque" }, { "abbr": "be", "name": "Belarusian" }, { "abbr": "bn", "name": "Bengali" }, { "abbr": "bn-bd", "name": "Bengali (Bangladesh)" }, { "abbr": "bs", "name": "Bosnian" }, { "abbr": "br", "name": "Breton" }, { "abbr": "bg", "name": "Bulgarian" }, { "abbr": "my", "name": "Burmese" }, { "abbr": "km", "name": "Cambodian" }, { "abbr": "ca", "name": "Catalan" }, { "abbr": "tzm", "name": "Central Atlas Tamazight" }, { "abbr": "tzm-latn", "name": "Central Atlas Tamazight Latin" }, { "abbr": "zh-cn", "name": "Chinese (China)" }, { "abbr": "zh-hk", "name": "Chinese (Hong Kong)" }, { "abbr": "zh-mo", "name": "Chinese (Macau)" }, { "abbr": "zh-tw", "name": "Chinese (Taiwan)" }, { "abbr": "cv", "name": "Chuvash" }, { "abbr": "hr", "name": "Croatian" }, { "abbr": "cs", "name": "Czech" }, { "abbr": "da", "name": "Danish" }, { "abbr": "nl", "name": "Dutch" }, { "abbr": "nl-be", "name": "Dutch (Belgium)" }, { "abbr": "en-au", "name": "English (Australia)" }, { "abbr": "en-ca", "name": "English (Canada)" }, { "abbr": "en-in", "name": "English (India)" }, { "abbr": "en-ie", "name": "English (Ireland)" }, { "abbr": "en-il", "name": "English (Israel)" }, { "abbr": "en-nz", "name": "English (New Zealand)" }, { "abbr": "en-sg", "name": "English (Singapore)" }, { "abbr": "en-gb", "name": "English (United Kingdom)" }, { "abbr": "en", "name": "English (United States)" }, { "abbr": "eo", "name": "Esperanto" }, { "abbr": "et", "name": "Estonian" }, { "abbr": "fo", "name": "Faroese" }, { "abbr": "fil", "name": "Filipino" }, { "abbr": "fi", "name": "Finnish" }, { "abbr": "fr", "name": "French" }, { "abbr": "fr-ca", "name": "French (Canada)" }, { "abbr": "fr-ch", "name": "French (Switzerland)" }, { "abbr": "fy", "name": "Frisian" }, { "abbr": "gl", "name": "Galician" }, { "abbr": "ka", "name": "Georgian" }, { "abbr": "de", "name": "German" }, { "abbr": "de-at", "name": "German (Austria)" }, { "abbr": "de-ch", "name": "German (Switzerland)" }, { "abbr": "el", "name": "Greek" }, { "abbr": "gu", "name": "Gujarati" }, { "abbr": "he", "name": "Hebrew" }, { "abbr": "hi", "name": "Hindi" }, { "abbr": "hu", "name": "Hungarian" }, { "abbr": "is", "name": "Icelandic" }, { "abbr": "id", "name": "Indonesian" }, { "abbr": "ga", "name": "Irish or Irish Gaelic" }, { "abbr": "it", "name": "Italian" }, { "abbr": "it-ch", "name": "Italian (Switzerland)" }, { "abbr": "ja", "name": "Japanese" }, { "abbr": "jv", "name": "Javanese" }, { "abbr": "kn", "name": "Kannada" }, { "abbr": "kk", "name": "Kazakh" }, { "abbr": "tlh", "name": "Klingon" }, { "abbr": "gom-deva", "name": "Konkani Devanagari script" }, { "abbr": "gom-latn", "name": "Konkani Latin script" }, { "abbr": "ko", "name": "Korean" }, { "abbr": "ku", "name": "Kurdish" }, { "abbr": "ky", "name": "Kyrgyz" }, { "abbr": "lo", "name": "Lao" }, { "abbr": "lv", "name": "Latvian" }, { "abbr": "lt", "name": "Lithuanian" }, { "abbr": "lb", "name": "Luxembourgish" }, { "abbr": "mk", "name": "Macedonian" }, { "abbr": "ms-my", "name": "Malay" }, { "abbr": "ms", "name": "Malay" }, { "abbr": "ml", "name": "Malayalam" }, { "abbr": "dv", "name": "Maldivian" }, { "abbr": "mt", "name": "Maltese (Malta)" }, { "abbr": "mi", "name": "Maori" }, { "abbr": "mr", "name": "Marathi" }, { "abbr": "mn", "name": "Mongolian" }, { "abbr": "me", "name": "Montenegrin" }, { "abbr": "ne", "name": "Nepalese" }, { "abbr": "se", "name": "Northern Sami" }, { "abbr": "nb", "name": "Norwegian Bokmål" }, { "abbr": "nn", "name": "Nynorsk" }, { "abbr": "oc-lnc", "name": "Occitan, lengadocian dialecte" }, { "abbr": "fa", "name": "Persian" }, { "abbr": "pl", "name": "Polish" }, { "abbr": "pt", "name": "Portuguese" }, { "abbr": "pt-br", "name": "Portuguese (Brazil)" }, { "abbr": "x-pseudo", "name": "Pseudo" }, { "abbr": "pa-in", "name": "Punjabi (India)" }, { "abbr": "ro", "name": "Romanian" }, { "abbr": "ru", "name": "Russian" }, { "abbr": "gd", "name": "Scottish Gaelic" }, { "abbr": "sr", "name": "Serbian" }, { "abbr": "sr-cyrl", "name": "Serbian Cyrillic" }, { "abbr": "sd", "name": "Sindhi" }, { "abbr": "si", "name": "Sinhalese" }, { "abbr": "sk", "name": "Slovak" }, { "abbr": "sl", "name": "Slovenian" }, { "abbr": "es", "name": "Spanish" }, { "abbr": "es-do", "name": "Spanish (Dominican Republic)" }, { "abbr": "es-mx", "name": "Spanish (Mexico)" }, { "abbr": "es-us", "name": "Spanish (United States)" }, { "abbr": "sw", "name": "Swahili" }, { "abbr": "sv", "name": "Swedish" }, { "abbr": "tl-ph", "name": "Tagalog (Philippines)" }, { "abbr": "tg", "name": "Tajik" }, { "abbr": "tzl", "name": "Talossan" }, { "abbr": "ta", "name": "Tamil" }, { "abbr": "te", "name": "Telugu" }, { "abbr": "tet", "name": "Tetun Dili (East Timor)" }, { "abbr": "th", "name": "Thai" }, { "abbr": "bo", "name": "Tibetan" }, { "abbr": "tr", "name": "Turkish" }, { "abbr": "tk", "name": "Turkmen" }, { "abbr": "uk", "name": "Ukrainian" }, { "abbr": "ur", "name": "Urdu" }, { "abbr": "ug-cn", "name": "Uyghur (China)" }, { "abbr": "uz", "name": "Uzbek" }, { "abbr": "uz-latn", "name": "Uzbek Latin" }, { "abbr": "vi", "name": "Vietnamese" }, { "abbr": "cy", "name": "Welsh" }, { "abbr": "yo", "name": "Yoruba Nigeria" }, { "abbr": "ss", "name": "siSwati" } ]; const block = document.createElement("span"); block.className = "ghst-time time"; function staticTime(tempFormat) { if (busy) { return; } busy = true; let selector = typeof tempFormat === "string" // update existing timestamps ? ".ghst-time" // process html elements : "relative-time, time-ago"; if ($(selector)) { let indx = 0; const els = $$(selector); const len = els.length; // loop with delay to allow user interaction const loop = () => { let el, time, node, formatted, // max number of DOM insertions per loop max = 0; while (max < 20 && indx < len) { if (indx >= len) { return; } el = els[indx]; time = moment(el.getAttribute("datetime") || ""); if (el && time.isValid()) { if (useUTC === "true") { time = time.utc(); } if (tempFormat) { formatted = time.format(tempFormat); el.textContent = formatted; el.title = formatted; } else { formatted = time.format(timeFormat); node = block.cloneNode(true); node.setAttribute("datetime", time); node.textContent = formatted; node.title = formatted; // el.parentElement may be null sometimes when using browser // back arrow if (el.parentElement) { // replace relative-time/time-ago element el.parentElement.replaceChild(node, el); } } max++; } indx++; } if (indx < len) { requestAnimationFrame(loop); } }; loop(); } busy = false; } function addPanel() { const div = document.createElement("div"); GM_addStyle(` #ghst-settings { opacity:0; visibility:hidden; } #ghst-settings.ghst-open { position:fixed; z-index:65535; top:0; bottom:0; left:0; right:0; opacity:1; visibility:visible; background:rgba(0, 0, 0, .5); } #ghst-settings-inner { position:fixed; left:50%; top:50%; width:25rem; transform:translate(-50%,-50%); box-shadow:0 .5rem 1rem #111; color:#c0c0c0 } #ghst-settings-inner .boxed-group-inner { height: 255px; } #ghst-footer { clear:both; border-top:1px solid rgba(68, 68, 68, .3); padding-top:5px; } `); div.id = "ghst-settings"; let options = ""; locales.forEach(loc => { let sel = loc.abbr === locale ? " selected" : ""; options += `<option value="${loc.abbr}"${sel}>${loc.name}</option>`; }); div.innerHTML = ` <div id="ghst-settings-inner" class="boxed-group"> <h3>GitHub Static Time Settings</h3> <div class="boxed-group-inner"> <dl class="form-group flattened"> <dt> <label for="ghst-locale">Select a locale</label> </dt> <dd> <select id="ghst-locale" class="form-select float-right" value="${locale}"> ${options} </select> <br> </dd> </dl> <dl class="form-group flattened"> <dt> <label for="ghst-utc">Show UTC time (use "z" in format below)</label> </dt> <dd> <div class="form-checkbox"> <input id="ghst-utc" type="checkbox" class="float-right"> </div> <br> </dd> </dl> <dl class="form-group flattened"> <dt> <label for="ghst-format"> Set <a href="https://momentjs.com/docs/#/displaying/format/" target="_blank"> MomentJS </a> format (e.g. "MMMM Do YYYY, h:mm A"): </label> </dt> <dd> <input id="ghst-format" type="text" class="form-control" value="${timeFormat}"/> </dd> </dl> <div id="ghst-footer"> <button type="button" id="ghst-cancel" class="btn btn-sm float-right">Cancel</button> <button type="button" id="ghst-save" class="btn btn-sm float-right">Save</button> </div> </div> </div>`; $("body").appendChild(div); $("#ghst-utc").checked = useUTC === "true"; on($("#ghst-settings"), "click", closePanel); on($("body"), "keyup", event => { if ( event.key === "Escape" && $("#ghst-settings").classList.contains("ghst-open") ) { closePanel(event); return false; } else if (event.key === "Enter" && event.shiftKey) { closePanel(); update("save"); } }); on($("#ghst-settings-inner"), "click", event => { event.stopPropagation(); // event.preventDefault(); }); on($("#ghst-save"), "click", () => { closePanel(); update("save"); }); on($("#ghst-locale"), "change", update); on($("#ghst-utc"), "change", update); on($("#ghst-format"), "change", update); on($("#ghst-cancel"), "click", closePanel); } function closePanel(event) { $("#ghst-settings").classList.remove("ghst-open"); if (event) { return update("revert"); } } function update(mode) { if (mode === "revert") { $("#ghst-locale").value = locale; $("#ghst-format").value = timeFormat; $("#ghst-utc").checked = useUTC === "true"; } let loc = $("#ghst-locale").value || "en"; let time = $("#ghst-format").value || "LLL"; let utc = $("#ghst-utc").checked ? "true" : "false"; if (mode === "save") { timeFormat = time; locale = loc; useUTC = utc; GM_setValue("ghst-format", timeFormat); GM_setValue("ghst-locale", locale); GM_setValue("ghst-utc", useUTC); } moment.locale(loc); staticTime(time); return false; } function init() { addPanel(); moment.locale(locale); staticTime(); } // Add GM options GM_registerMenuCommand("Set GitHub static time format", () => { $("#ghst-settings").classList.add("ghst-open"); }); // repo file list needs additional time to render document.addEventListener("ghmo:container", () => { setTimeout(() => { staticTime(); }, 100); }); document.addEventListener("ghmo:preview", staticTime); init(); })();