├── README.md ├── chrome ├── README.md ├── content.js ├── images │ ├── check.png │ ├── icon.png │ ├── people.png │ └── peoplecheck.png ├── manifest.json └── style.css └── firefox ├── README.md ├── data ├── content.js ├── images │ ├── check.png │ ├── icon.png │ ├── people.png │ └── peoplecheck.png └── style.css ├── index.js ├── package.json └── test └── test-index.js /README.md: -------------------------------------------------------------------------------- 1 | # SortedStackPlugin 2 | 3 | ## Description 4 | A Google Chrome and Firefox extension that supplements stackoverflow.com by marking the highest voted answer by the community with a unique icon, as well as moving this answer to the first place position in the list. 5 | 6 | Normally the person asking the question can select a single answer as accepted, and this acceptted answer will always be the first answer listed even if it doesn't have the most amount of votes. Often times other answers arise that were too late to the game or the asker didn't like. This extension always places the answers in sorted order by the number of votes the answer received, no matter if the asker selected an answer as accepted or not. 7 | 8 | ## Install 9 | **Chrome:** [Download](https://chrome.google.com/webstore/detail/sorted-stack/fpbbollnpcfogjaccdegemhcmpbeglkn) 10 | 11 | **Firefox:** [Download](https://addons.mozilla.org/en-US/firefox/addon/sorted-stack/) 12 | 13 | ## Building From Source 14 | #### Chrome 15 | 1. Download all the files in the chrome directory. 16 | 2. Open chrome://extensions/ 17 | 3. Click on "Load unpacked extension...". 18 | 4. Navigate to the directory containing all the downloaded files. 19 | 5. Make sure the "Enabled" box is checked. 20 | 6. You're all set to go! 21 | 22 | #### Firefox 23 | 1. Download all the files in the firefox directory. 24 | 2. Install jpm using npm (or through any other means of installation). 25 | 3. To run the extension, navigate to the directory with the files and run the command 'jpm run'. 26 | 4. To build the xpi file for installation run the command 'jpm xpi'. 27 | 28 | Icons made by Freepik from www.flaticon.com. 29 | -------------------------------------------------------------------------------- /chrome/README.md: -------------------------------------------------------------------------------- 1 | # SortedStackPlugin 2 | 3 | ## Description 4 | A Google Chrome extension that suppliments stackoverflow.com by marking the highest voted answer by the community with a unique icon, as well as moving this answer to the first place position in the list. With SortedStack, gone are the days of a question owners selected answer with 5 upvotes beating the answer 1264 upvotes for the first answer onscreen. 5 | 6 | Additonally, if the question owner's selected answer is also the community's highest voted answer, a unique icon is assigned to represent this. 7 | 8 | ## Building From Source 9 | 1. Download all the files. 10 | 2. Open chrome://extensions/ 11 | 3. Click on "Load unpacked extension...". 12 | 4. Navigate to the directory containing all the downloaded files. 13 | 5. Make sure the "Enabled" box is checked. 14 | 6. You're all set to go! 15 | 16 | 17 | Icons made by Freepik from www.flaticon.com. 18 | -------------------------------------------------------------------------------- /chrome/content.js: -------------------------------------------------------------------------------- 1 | classes = { 2 | answer: "answer", 3 | acceptedAnswer: "accepted-answer", 4 | me_icon: "customIcon", 5 | me_owner: "ownerSelect", 6 | me_community: "communitySelect", 7 | me_ownercommunity: "communityownerSelect" 8 | }; 9 | 10 | selectors = { 11 | customIcon: ".votecell > .vote > .customIcon", 12 | voteDiv: ".votecell > .vote", 13 | oldIcon: ".votecell > .vote > .vote-accepted-on", 14 | answerScore: ".votecell .vote [itemprop='upvoteCount'" 15 | }; 16 | 17 | titles = { 18 | owner: "This answer was selected as the best by the asker of the question.", 19 | community: "This answer was voted as the best by the community.", 20 | ownercommunity: "This answer was selected as the best by the asker of the question, as well as being voted the best answer by the community." 21 | }; 22 | 23 | var answers = document.getElementsByClassName(classes.answer); 24 | 25 | if (answers.length > 0) { 26 | var ownerSelectedAnswer = document.getElementsByClassName(classes.answer + " " + classes.acceptedAnswer); 27 | var hasOwnerSelectedAnswer = ownerSelectedAnswer.length > 0; 28 | 29 | if (hasOwnerSelectedAnswer){ 30 | HasOwnerSelected(answers, ownerSelectedAnswer[0]); 31 | } else { 32 | NoOwnerSelected(answers[0]); 33 | } 34 | } 35 | 36 | function HasOwnerSelected (answerElems, ownerAnswerElem) { 37 | var answers = []; 38 | 39 | for (var i = 0; i < answerElems.length; i++){ 40 | var answer = answerElems[i]; 41 | var selected = answer.className.indexOf(classes.acceptedAnswer) >= 0; 42 | answers.push({ 43 | score: GetVoteCount(answer), 44 | answer: answer, 45 | ownerSelected: selected 46 | }); 47 | } 48 | 49 | answers.sort(function (a, b) { 50 | return b.score - a.score; 51 | }); 52 | 53 | 54 | //we know the default checkmark exists so replace it 55 | ReplaceDefaultCheckmark(ownerAnswerElem); 56 | 57 | // Check to see if the overall highest score is the owner selected answers 58 | if (answers[0].answer.className.indexOf(classes.acceptedAnswer) >= 0) { 59 | //change the check to a person/check 60 | 61 | var iconElem = answers[0].answer.querySelector(selectors.customIcon); 62 | iconElem.className = classes.me_icon + " " + classes.me_ownercommunity; 63 | iconElem.title = titles.ownercommunity; 64 | } else { 65 | var topAnswer = answers[0].answer; 66 | var topAnswerVote = topAnswer.querySelector(selectors.voteDiv); 67 | 68 | var iconSpan = document.createElement("span"); 69 | iconSpan.className = classes.me_icon + " " + classes.me_community; 70 | iconSpan.title = titles.community; 71 | 72 | topAnswerVote.appendChild(iconSpan); 73 | 74 | var insertLocation = -1; 75 | for (var i = 0; i < answers.length; i++) { 76 | if (answers[i].ownerSelected === true){ 77 | insertLocation = i; 78 | break; 79 | } 80 | } 81 | 82 | var answerDiv = answers[insertLocation].answer; 83 | var answerLink = answerDiv.previousSibling; 84 | 85 | var insertAfterElem = answers[insertLocation - 1].answer; 86 | 87 | insertAfterElem.parentNode.insertBefore(answerDiv, insertAfterElem.nextSibling); 88 | insertAfterElem.parentNode.insertBefore(answerLink, insertAfterElem.nextSibling); 89 | } 90 | } 91 | 92 | function NoOwnerSelected (answer) { 93 | // then they should be sorted already so grab the first and apply icon 94 | var topAnswerVote = answer.querySelector(selectors.voteDiv); 95 | 96 | var iconSpan = document.createElement("span"); 97 | iconSpan.className = classes.me_icon + " " + classes.me_community; 98 | iconSpan.title = titles.community; 99 | 100 | topAnswerVote.appendChild(iconSpan); 101 | } 102 | 103 | function ReplaceDefaultCheckmark(ownerAnswerElem) { 104 | var oldCheck = ownerAnswerElem.querySelector(selectors.oldIcon); 105 | var title = oldCheck.title; 106 | 107 | var oldCheckParent = oldCheck.parentNode; 108 | oldCheckParent.removeChild(oldCheck); 109 | 110 | var voteElem = ownerAnswerElem.querySelector(selectors.voteDiv) 111 | 112 | var newCheck = document.createElement("span"); 113 | newCheck.title = title; 114 | newCheck.className = classes.me_icon + " " + classes.me_owner; 115 | 116 | voteElem.appendChild(newCheck); 117 | } 118 | 119 | function GetVoteCount (answer){ 120 | var stringNum = answer.querySelector(selectors.answerScore).textContent; 121 | 122 | return parseInt(stringNum, 10); 123 | } 124 | -------------------------------------------------------------------------------- /chrome/images/check.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shaunwickett13/SortedStackPlugin/2ce30ba0472fd3b44463972671a331042806c0a1/chrome/images/check.png -------------------------------------------------------------------------------- /chrome/images/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shaunwickett13/SortedStackPlugin/2ce30ba0472fd3b44463972671a331042806c0a1/chrome/images/icon.png -------------------------------------------------------------------------------- /chrome/images/people.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shaunwickett13/SortedStackPlugin/2ce30ba0472fd3b44463972671a331042806c0a1/chrome/images/people.png -------------------------------------------------------------------------------- /chrome/images/peoplecheck.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shaunwickett13/SortedStackPlugin/2ce30ba0472fd3b44463972671a331042806c0a1/chrome/images/peoplecheck.png -------------------------------------------------------------------------------- /chrome/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifest_version": 2, 3 | "name": "Sorted Stack", 4 | "version": "0.1", 5 | "content_scripts": [ 6 | { 7 | "matches": [ 8 | "http://stackoverflow.com/questions/*", 9 | "https://stackoverflow.com/questions/*" 10 | ], 11 | "js": ["content.js"], 12 | "css": ["style.css"] 13 | } 14 | ], 15 | "web_accessible_resources": [ 16 | "images/*.png" 17 | ], 18 | "icons": { 19 | "48": "images/icon.png" 20 | } 21 | } -------------------------------------------------------------------------------- /chrome/style.css: -------------------------------------------------------------------------------- 1 | .customIcon { 2 | width: 35px; 3 | height: 35px; 4 | background-size: cover; 5 | margin: 0 auto 10px auto; 6 | } 7 | 8 | .communitySelect { 9 | background-image: url("chrome-extension://__MSG_@@extension_id__/images/people.png"); 10 | } 11 | 12 | .communityownerSelect { 13 | background-image: url("chrome-extension://__MSG_@@extension_id__/images/peoplecheck.png"); 14 | height: 70px; 15 | } 16 | 17 | .ownerSelect { 18 | background-image: url("chrome-extension://__MSG_@@extension_id__/images/check.png"); 19 | } -------------------------------------------------------------------------------- /firefox/README.md: -------------------------------------------------------------------------------- 1 | #Sorted Stack 2 | Sorts stackoverflow.com answers and puts the highest scored answer first, removing the special treatment the askers selected answer usually gets. -------------------------------------------------------------------------------- /firefox/data/content.js: -------------------------------------------------------------------------------- 1 | classes = { 2 | answer: "answer", 3 | acceptedAnswer: "accepted-answer", 4 | me_icon: "customIcon", 5 | me_owner: "ownerSelect", 6 | me_community: "communitySelect", 7 | me_ownercommunity: "communityownerSelect" 8 | }; 9 | 10 | selectors = { 11 | customIcon: ".votecell > .vote > .customIcon", 12 | voteDiv: ".votecell > .vote", 13 | oldIcon: ".votecell > .vote > .vote-accepted-on", 14 | answerScore: ".votecell .vote [itemprop='upvoteCount'" 15 | }; 16 | 17 | titles = { 18 | owner: "This answer was selected as the best by the asker of the question.", 19 | community: "This answer was voted as the best by the community.", 20 | ownercommunity: "This answer was selected as the best by the asker of the question, as well as being voted the best answer by the community." 21 | }; 22 | 23 | var answers = document.getElementsByClassName(classes.answer); 24 | 25 | if (answers.length > 0) { 26 | var ownerSelectedAnswer = document.getElementsByClassName(classes.answer + " " + classes.acceptedAnswer); 27 | var hasOwnerSelectedAnswer = ownerSelectedAnswer.length > 0; 28 | 29 | if (hasOwnerSelectedAnswer){ 30 | HasOwnerSelected(answers, ownerSelectedAnswer[0]); 31 | } else { 32 | NoOwnerSelected(answers[0]); 33 | } 34 | } 35 | 36 | function HasOwnerSelected (answerElems, ownerAnswerElem) { 37 | var answers = []; 38 | 39 | for (var i = 0; i < answerElems.length; i++){ 40 | var answer = answerElems[i]; 41 | var selected = answer.className.indexOf(classes.acceptedAnswer) >= 0; 42 | answers.push({ 43 | score: GetVoteCount(answer), 44 | answer: answer, 45 | ownerSelected: selected 46 | }); 47 | } 48 | 49 | answers.sort(function (a, b) { 50 | return b.score - a.score; 51 | }); 52 | 53 | 54 | //we know the default checkmark exists so replace it 55 | ReplaceDefaultCheckmark(ownerAnswerElem); 56 | 57 | // Check to see if the overall highest score is the owner selected answers 58 | if (answers[0].answer.className.indexOf(classes.acceptedAnswer) >= 0) { 59 | //change the check to a person/check 60 | 61 | var icon = answers[0].answer.querySelector(selectors.customIcon); 62 | icon.src = self.options.peoplecheck; 63 | icon.className = classes.me_icon + " " + classes.me_ownercommunity; 64 | icon.title = titles.ownercommunity; 65 | } else { 66 | var topAnswer = answers[0].answer; 67 | var topAnswerVote = topAnswer.querySelector(selectors.voteDiv); 68 | 69 | var icon = document.createElement("img"); 70 | icon.src = self.options.people; 71 | icon.className = classes.me_icon + " " + classes.me_community; 72 | icon.title = titles.community; 73 | 74 | topAnswerVote.appendChild(icon); 75 | 76 | var insertLocation = -1; 77 | for (var i = 0; i < answers.length; i++) { 78 | if (answers[i].ownerSelected === true){ 79 | insertLocation = i; 80 | break; 81 | } 82 | } 83 | 84 | var answerDiv = answers[insertLocation].answer; 85 | var answerLink = answerDiv.previousSibling; 86 | 87 | var insertAfterElem = answers[insertLocation - 1].answer; 88 | 89 | insertAfterElem.parentNode.insertBefore(answerDiv, insertAfterElem.nextSibling); 90 | insertAfterElem.parentNode.insertBefore(answerLink, insertAfterElem.nextSibling); 91 | } 92 | } 93 | 94 | function NoOwnerSelected (answer) { 95 | // then they should be sorted already so grab the first and apply icon 96 | var topAnswerVote = answer.querySelector(selectors.voteDiv); 97 | 98 | var icon = document.createElement("img"); 99 | icon.src = self.options.people; 100 | icon.className = classes.me_icon + " " + classes.me_community; 101 | icon.title = titles.community; 102 | 103 | topAnswerVote.appendChild(icon); 104 | } 105 | 106 | function ReplaceDefaultCheckmark(ownerAnswerElem) { 107 | var oldCheck = ownerAnswerElem.querySelector(selectors.oldIcon); 108 | var title = oldCheck.title; 109 | 110 | var oldCheckParent = oldCheck.parentNode; 111 | oldCheckParent.removeChild(oldCheck); 112 | 113 | var voteElem = ownerAnswerElem.querySelector(selectors.voteDiv) 114 | 115 | var icon = document.createElement("img"); 116 | icon.src = self.options.check; 117 | icon.title = title; 118 | icon.className = classes.me_icon + " " + classes.me_owner; 119 | 120 | voteElem.appendChild(icon); 121 | } 122 | 123 | function GetVoteCount (answer){ 124 | var stringNum = answer.querySelector(selectors.answerScore).textContent; 125 | 126 | return parseInt(stringNum, 10); 127 | } 128 | -------------------------------------------------------------------------------- /firefox/data/images/check.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shaunwickett13/SortedStackPlugin/2ce30ba0472fd3b44463972671a331042806c0a1/firefox/data/images/check.png -------------------------------------------------------------------------------- /firefox/data/images/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shaunwickett13/SortedStackPlugin/2ce30ba0472fd3b44463972671a331042806c0a1/firefox/data/images/icon.png -------------------------------------------------------------------------------- /firefox/data/images/people.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shaunwickett13/SortedStackPlugin/2ce30ba0472fd3b44463972671a331042806c0a1/firefox/data/images/people.png -------------------------------------------------------------------------------- /firefox/data/images/peoplecheck.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shaunwickett13/SortedStackPlugin/2ce30ba0472fd3b44463972671a331042806c0a1/firefox/data/images/peoplecheck.png -------------------------------------------------------------------------------- /firefox/data/style.css: -------------------------------------------------------------------------------- 1 | .customIcon { 2 | width: 35px; 3 | height: 35px; 4 | background-size: cover; 5 | margin: 0 auto 10px auto; 6 | } 7 | 8 | .communitySelect { 9 | background-image: url("chrome-extension://__MSG_@@extension_id__/images/people.png"); 10 | } 11 | 12 | .communityownerSelect { 13 | background-image: url("chrome-extension://__MSG_@@extension_id__/images/peoplecheck.png"); 14 | height: 70px; 15 | } 16 | 17 | .ownerSelect { 18 | background-image: url("chrome-extension://__MSG_@@extension_id__/images/check.png"); 19 | } -------------------------------------------------------------------------------- /firefox/index.js: -------------------------------------------------------------------------------- 1 | var data = require("sdk/self").data; 2 | var pageMod = require("sdk/page-mod"); 3 | 4 | var imageUrls = {}; 5 | imageUrls.check = data.url("images/check.png"); 6 | imageUrls.people = data.url("images/people.png"); 7 | imageUrls.peoplecheck = data.url("images/peoplecheck.png"); 8 | 9 | pageMod.PageMod({ 10 | include: [ 11 | "http://stackoverflow.com/questions/*", 12 | "https://stackoverflow.com/questions/*" 13 | ], 14 | contentScriptFile: "./content.js", 15 | contentScriptOptions: imageUrls, 16 | contentStyleFile: "./style.css" 17 | }); -------------------------------------------------------------------------------- /firefox/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Sorted Stack", 3 | "name": "sortedstack", 4 | "version": "0.0.1", 5 | "description": "Sorts stackoverflow.com answers and puts the highest scored answer first, removing the special treatment the askers selected answer usually gets.", 6 | "main": "index.js", 7 | "author": "Shaun Wickett", 8 | "engines": { 9 | "firefox": ">=38.0a1" 10 | }, 11 | "license": "MIT", 12 | "keywords": [ 13 | "jetpack" 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /firefox/test/test-index.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shaunwickett13/SortedStackPlugin/2ce30ba0472fd3b44463972671a331042806c0a1/firefox/test/test-index.js --------------------------------------------------------------------------------