// ==UserScript== // @name Stack Overflow Enhancer // @namespace https://github.com/ChrisMBarr/UserScripts // @version 0.5 // @description Improve some UI/UX stuff on StackOverflow // @author Chris Barr // @homepageURL https://github.com/ChrisMBarr/UserScripts // @updateURL https://github.com/ChrisMBarr/UserScripts/raw/main/src/stack-overflow-enhancer.user.js // @match https://stackoverflow.com/* // @icon https://www.google.com/s2/favicons?sz=64&domain=stackoverflow.com // @grant unsafeWindow // @grant GM_addStyle // @noframes // ==/UserScript== (function () { "use strict"; var $ = unsafeWindow.jQuery; var StackExchange = unsafeWindow.StackExchange; //------------------------------------------------------------------------------------------------------------ // CONFIG //------------------------------------------------------------------------------------------------------------ //Add arrays of tags you want to have flagged/highlighted when they are ALL present on a question //The must be lower case const flagTagCombos = [ ["angular", "angularjs"], ["angular", "jquery"], ["bootstrap", "twitter-bootstrap"], ["bootstrap-4", "bootstrap-5"], ]; //Elements to show or hide const sidebarHideBlogs = true; const sidebarHideCollectives = true; const sidebarHideAds = true; //The number of spaces to use when indenting text in the question/answer editor (not the snippet editor) const editorIndentSpaces = 2; //Save some comments you may need to use commonly when moderating //The text may contain certain tokens to be replaced at runtime const commentSnippets = [ { name: "Welcome - How To Ask", text: "Welcome to StackOverflow! Please take a look at the article on {{LINK_HELP_HOW_TO_ASK}} as it will help you out in the future. Right now your question is missing some details that make it difficult for anyone to answer.", }, { name: "Minimal Reproducible Example - No Code", text: "Your question seems to be missing some detail. If you want help with your code, it's impossible for anyone to help you if you don't show the code you want help with. In order to help you we need a {{LINK_HELP_MRE}} directly in your question. Please {{LINK_EDIT_YOUR_QUESTION}} and add some code to your question.", }, { name: "Minimal Reproducible Example - Add More Code", text: "Your question seems to be missing some detail. In order to help you we need a {{LINK_HELP_MRE}} directly in your question. Please {{LINK_EDIT_YOUR_QUESTION}} and add some code to your question.", }, { name: "Minimal Reproducible Example - Add Snippet", text: "Your question seems to be missing some detail. In order to help you we need a {{LINK_HELP_MRE}} directly in your question. Please {{LINK_EDIT_YOUR_QUESTION}} and click the button in the toolbar that looks like `[<>]` to add a code snippet to your question. When you add it, please be sure that it properly demonstrates the issue you are having.", }, { name: "Minimal Reproducible Example - External Code", text: "In order to help you we need a {{LINK_HELP_MRE}} directly in your question. Please do not just point us to code linked to another website. Code on that site might change or disappear over time, leaving the question here without any context for future readers. Your question becomes much easier to answer when the code you want help with is actually in the question. Please {{LINK_EDIT_YOUR_QUESTION}} and click the button in the toolbar that looks like `[<>]` to add a code snippet to your question", }, { name: "Images of Code", text: "Please do not post images of your code and/or error messages. Post the actual text instead. This makes it easier for people to help you and for others to find this question in a search if they have similar issues in the future. Please read over the answers to this question to understand a bit more about this: [Why should I not upload images of code/data/errors?](//meta.stackoverflow.com/q/285551)", }, { name: "Converted to Snippet", text: "I've edited your question so that your code is now a snippet that can be run directly from your question.", }, ]; //------------------------------------------------------------------------------------------------------------ // CUSTOM STYLES //------------------------------------------------------------------------------------------------------------ const classNameTagComboFlag = "SOE--tag-combo-flag"; const classNameTagCurrentlyViewing = "SOE--tag-currently-viewing"; const classNameClosedQuestion = "SOE--closed-question"; GM_addStyle( //Note that the CSS rule order matters here for when certain things should be overridden by others [ `.${classNameClosedQuestion} {--_ps-state-fc:var(--black-300)!important;--_ps-stats-fc:var(--black-300)!important;--_ps-content-title-a-fc:var(--black-300)!important;}`, `.${classNameClosedQuestion} .post-tag{color:var(--black-500) !important;--theme-tag-background-color:var(--black-150)!important;}`, `.${classNameTagCurrentlyViewing} > .post-tag{background-color:var(--green-100)!important;color:var(--green-600)!important;border-color:var(--green-200)!important;}`, `.${classNameTagComboFlag} > .post-tag{background-color:var(--red-100)!important;color:var(--red-600)!important;border-color:var(--red-200)!important;}`, ].join("") ); //------------------------------------------------------------------------------------------------------------ // THE CODE //------------------------------------------------------------------------------------------------------------ const $sidebar = $("#sidebar"); const $mainContent = $("#mainbar"); const $sidebarItems = $sidebar.children(); const $questionTags = $mainContent.find(".js-post-tag-list-wrapper"); //------------------------------------------------------------------------------------------------------------ //Hide some elements if (sidebarHideBlogs) { $sidebarItems.find(".s-sidebarwidget--header:contains(The Overflow Blog)").parents(".s-sidebarwidget").hide(); } if (sidebarHideCollectives) { $sidebarItems.find(".s-sidebarwidget--header:contains(Collectives)").parents(".s-sidebarwidget").hide(); } if (sidebarHideAds) { $sidebarItems.filter(".js-sidebar-zone").remove(); } //------------------------------------------------------------------------------------------------------------ //Dim closed/duplicate questions $mainContent .find(".s-post-summary--content-title:contains([closed]), .s-post-summary--content-title:contains([duplicate])") .parents(".s-post-summary") .addClass(classNameClosedQuestion); //------------------------------------------------------------------------------------------------------------ //Highlight tag being currently viewed const tagPathPrefix = "/questions/tagged/"; if (location.pathname.includes(tagPathPrefix)) { const tags = location.pathname.replace(tagPathPrefix, "").split("+").map(decodeURIComponent); function highlightTags($el) { $el .find(`a.post-tag`) .filter((i, el) => { return tags.includes(el.innerText); }) .parent() .addClass(classNameTagCurrentlyViewing); } highlightTags($questionTags); highlightTags($sidebar.find(".js-tag")); } //------------------------------------------------------------------------------------------------------------ //Highlight question tag combinations if ($questionTags.length) { $questionTags.each((i, el) => { const $tags = $(el).children(); const tagArr = $tags.get().map((tag) => tag.innerText); flagTagCombos.forEach((tagCombo) => { const hasCombo = tagCombo.every((v) => tagArr.includes(v)); if (hasCombo) { tagCombo.forEach((tagText) => $tags.filter((i, tag) => tag.innerText.trim().toLowerCase() === tagText).addClass(classNameTagComboFlag) ); } }); }); } //------------------------------------------------------------------------------------------------------------ //Easy indent/unindent when editing a question or answer const indent = " ".repeat(editorIndentSpaces); const unIndentPattern = new RegExp(`^ {${editorIndentSpaces}}`); function initCustomizedTextarea($textArea) { $textArea.on("keydown", editorKeyListener); const $lastEditorButton = $textArea.parents(".wmd-container").find(".wmd-button-row .wmd-help-button"); $( `