├── .gitattributes ├── SEReputationAuditHelper.user.js ├── README.md ├── SEModifications.user.js ├── SECommentLinkHelper.user.js ├── SEChatFaviconNotification.user.js ├── SEKeyboardShortcuts.user.js └── SEChatModifications.user.js /.gitattributes: -------------------------------------------------------------------------------- 1 | # Disable LF normalization for all files 2 | * -text -------------------------------------------------------------------------------- /SEReputationAuditHelper.user.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name Stack Exchange Reputation Audit Helper 3 | // @namespace yijiang 4 | // @include http://stackoverflow.com/reputation 5 | // @include http://meta.stackoverflow.com/reputation 6 | // @include http://superuser.com/reputation 7 | // @include http://serverfault.com/reputation 8 | // @include http://askubuntu.com/reputation 9 | // @include http://answers.onstartups.com/reputation 10 | // @include http://stackapps.com/reputation 11 | // @include http://*.stackexchange.com/reputation 12 | // ==/UserScript== 13 | 14 | (function(){ 15 | function json (data) { 16 | var sites = data.api_sites, 17 | list = document.createElement('div'); 18 | 19 | list.innerHTML = "more reputation:
"; 20 | list.style.position = 'absolute'; 21 | list.style.top = 0; 22 | list.style.left = '40em'; 23 | list.style.fontFamily = 'Ubuntu, Arial, sans-serif'; 24 | list.style.lineHeight = '1.8em'; 25 | 26 | document.body.appendChild(list); 27 | 28 | for(var i = 0; i < sites.length; i++) { 29 | // Exclude SE 2.0 linked Metas 30 | if(sites[i].state !== 'linked_meta') { 31 | var a = document.createElement('a'), 32 | icon = document.createElement('img'); 33 | 34 | // Include the favicon in the link 35 | icon.src = sites[i].icon_url.replace('apple-touch-icon.png', 'favicon.ico'); 36 | icon.style.border = '0'; 37 | icon.style.verticalAlign = 'middle'; 38 | icon.style.paddingRight = '3px'; 39 | 40 | a.href = sites[i].site_url + '/reputation'; 41 | a.style.color = '#999'; 42 | a.style.display = 'block'; 43 | 44 | a.appendChild(icon); 45 | a.innerHTML += sites[i].name; 46 | 47 | // Stick ya links in the pre element 48 | list.appendChild(a); 49 | } 50 | } 51 | } 52 | 53 | function spaces (n) { 54 | if (n < 0) return ''; 55 | return new Array(n + 1).join(' '); 56 | } 57 | 58 | function inObject (value, obj) { 59 | for(var i in obj) { 60 | if(i === value) return obj[i]; 61 | } 62 | 63 | return false; 64 | } 65 | 66 | var voteTypesComplex = { 67 | 1: { 68 | 15: 'your answer accepted', 69 | 2: 'answer accepted by you' 70 | }, 71 | 3: { 72 | '-1': 'downvote by you', 73 | '-2': 'downvote to you' 74 | } 75 | }, voteTypeSimple = { 76 | 2: 'upvote', 77 | 4: 'penalty for post flagged as offensive', 78 | 8: 'bounty granted by you', 79 | 9: 'bounty awarded to you', 80 | 12: 'penalty for post flagged as spam', 81 | 16: 'post edit accepted' 82 | }; 83 | 84 | var pre = document.getElementsByTagName('pre')[0], 85 | html = pre.innerHTML, 86 | lines = html.split(/\n/g), 87 | maxLen = Math.max.apply(Math, lines.filter(function(c){ 88 | var n = c.split(/\s/g)[1]; 89 | return !c || inObject(n, voteTypesComplex) || inObject(n, voteTypeSimple); 90 | }).map(function(c){ 91 | return c.length; 92 | })); 93 | 94 | for(var i = 0; i < lines.length; i++) { 95 | var chunks = lines[i].split(/\s+/g); 96 | 97 | if(chunks.length > 3) { 98 | if(inObject(chunks[1], voteTypesComplex)) { 99 | chunks.push(voteTypesComplex[chunks[1]][(/^[([](-?\d+)[)\]]$/).exec(chunks[3])[1]]); 100 | } else if(inObject(chunks[1], voteTypeSimple)) { 101 | chunks.push(voteTypeSimple[chunks[1]]); 102 | } 103 | 104 | if(!isNaN(chunks[1]) && chunks[0] !== 'earned') { 105 | lines[i] = spaces(1) + chunks[1] + spaces(2) + chunks[2] + spaces(10 - chunks[2].length - chunks[1].length) + chunks[3] + spaces(maxLen - lines[i].length + 3) + chunks[4]; 106 | } 107 | } 108 | } 109 | 110 | html = lines.join('\n'); 111 | 112 | // link the question/post id 113 | html = html.replace(/^\s*(12|16|1|2|3|4|8|9)\s*(\d+)/gm, " $1 $2"); 114 | 115 | pre.innerHTML = html; 116 | 117 | // Inject json function into page 118 | var script = document.createElement('script'); 119 | script.innerHTML = json.toString(); 120 | document.getElementsByTagName('head')[0].appendChild(script); 121 | 122 | // Grab site list from StackAuth 123 | var jsonScript = document.createElement('script'); 124 | jsonScript.src = 'http://stackauth.com/1.0/sites?jsonp=json'; 125 | 126 | document.getElementsByTagName('head')[0].appendChild(jsonScript); 127 | })(); 128 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## SEChatModifications 2 | Provides keyboard shortcuts and other commands via input text reducing 3 | the dependency on mouse usage. Currently the script adds the id to the 4 | right of each message and the commands take in this id to identify the 5 | selected message. Keyboard navigation support has also been added allowing 6 | for individual messages to be navigatable through the keyboard. 7 | 8 | [Stack Apps link](https://stackapps.com/questions/2105/se-chat-modifications-keyboard-navigation-and-commands-for-chat) 9 | 10 | ### Performing an action on a message 11 | 12 | All of the following commands take a single argument - the id of the 13 | message you want the action to be performed. You can find the id on the 14 | right side of each message beside the timestamp 15 | 16 | * `/star` - star the message 17 | * `/del` - deletes the message 18 | * `/edit` - edits the message 19 | * `/quote` - quote the message whose id you pass, by posting a link to it 20 | * `/flag` - will flag the message as offensive, spam or noise 21 | * `/history` - displays the edit history of that message 22 | * `/jump` - scrolls to that message if it is available on the page 23 | 24 | `/edit` and `/delete` can only be performed during the two minute grace 25 | period, after which the message is locked. The message id can be left 26 | out for these two commands to perform the action on your last message. 27 | 28 | ### Room commands 29 | 30 | * `/load` - loads more messages 31 | * `/list ` - list all rooms, ordered by activity. Rooms will be 32 | filtered by `` if it is provided 33 | * `/join ` - join the room with the ``. Usually used in 34 | conjunction with `/list` 35 | * `/switch ` - switch to a room which you have already joined. 36 | Takes a (partial) substring of the room name you are switching to 37 | * `/transcript ` - view the room transcript. Performs a transcript 38 | search if `` is provided 39 | * `/leave ` - leaves the room with name or id matching ``, 40 | or all rooms if `all` is used, or the current room if nothing is passed 41 | 42 | ### User commands 43 | 44 | * `/last ` - scrolls to the last message said by the user. `` 45 | can be a partial match 46 | * `/profile ` - searches the `` for users with display name 47 | matching ``. `` will go through common abbreviations like 48 | MSO, AU and 8bitlavapwnpwniesbossstagesixforhelp 49 | before defaulting to `.stackexchange.com` 50 | * `/me ` - wraps the `` in a pair of `*` to italicise it in a 51 | lame attempt at emulating IRC `/me` 52 | 53 | ### Pseudo-Oneboxes 54 | 55 | The `/ob [url]` command will create onebox-like series of messages that attempt 56 | to add onebox support for sites not "officially supported". The following sites 57 | are currently supported: 58 | 59 | * **Vimeo:** Video links will be turned into a preview frame 60 | image of the video plus a link to the video in question. 61 | *Only the link, not the image, will take you to the video.* 62 | 63 | Supported URL formats: 64 | * `http://vimeo.com/{video-id}` 65 | * `http://vimeo.com/channels/{channel-name}#{video-id}` 66 | * `http://vimeo.com/category/{group-name}/videos/{video-id}` 67 | 68 | * **Stack Exchange:** Comment links will be turned into a `>` quote with a link 69 | back to the comment on the site. 70 | 71 | Supported URL formats: 72 | * `http://*.stackexchange.com/...#comment-{comment-id}` 73 | 74 | ### Highlighting messages 75 | 76 | In high volume rooms messages or conversations can often be lost under the 77 | deluge of activity. Highlighting messages can be a useful way of keeping 78 | a temporary bookmark or keeping track of one or more user's messages. 79 | 80 | The `/hl ` command takes a message id or a username match to highlight 81 | that particular message or user's messages. Calling it without any arguments 82 | will list all active highlight rules. 83 | 84 | ### Clipboard 85 | 86 | The clipboard is a place to store messages and notes to yourself. 87 | 88 | * `/jot [match]` is used to take down messages or notes. 89 | * If `[match]` is a number, than the message with that id is taken 90 | down into the clipboard, else everything else after `/jot` is 91 | stored into the clipboard as a note. 92 | * To take transcript message to the clipboard simply use 93 | `/jot [id]|Description` format. 94 | * After jotting down things into the clipboard, an id will be shown. 95 | This id will be used to refer to the note when pasting or 96 | removing the message from the clipboard. 97 | * `/clips` will show a list of all messages and notes taken. Clips and also be managed from here. 98 | * `/paste [id]` if a message is pasted, that message will be **quoted**, 99 | else a new message with the note's content will be generated 100 | * `/rmclip [id]` will remove the clip with that id from the clipboard 101 | 102 | ### Keyboard Navigation 103 | 104 | * Ctrl + Up Arrow begins navigation 105 | * Ctrl + Down Arrow cancels navigation and jumps to message input 106 | * Up Arrow and Down Arrow navigate between messages 107 | * Page Up and Page Down navigate more quickly (5 messages at a time) 108 | * On a selected message: 109 | * Q quotes the selected message 110 | * E edits the selected message 111 | * R begins a reply to the selected message 112 | * D removes the selected message 113 | * S stars the selected message 114 | * H shows the history of the message 115 | * F puts the command to flag a message in the input box 116 | * J jumps to the replied-to message, if the selected message is 117 | an explicit reply 118 | * C jots the selected message 119 | * Right Arrow edits or begins a reply to the selected message, 120 | depending on if you own it 121 | * Left Arrow and P display the replied-to message, if the 122 | selected message is an explicit reply 123 | 124 | ### Other features 125 | 126 | The script will add the id of every message as well as its timestamp on 127 | the right of every message, to aid in using some of the commands. Rate 128 | limited messages can be retried with `ctrl` + `space` 129 | 130 | ## SEModifications 131 | Provides minor tweaks to StackExchange sites. 132 | 133 | [Stack Apps link](https://stackapps.com/questions/2138/se-modifications-username-autocomplete-in-comments-inline-revision-source-a) 134 | 135 | * Adds a timeline link to each question page 136 | * Turns comment timestamps into links to the comment, so that they can be linked to. 137 | This will only work if the comment is visible when the page is linked to - posts with more than 138 | 5 comments and 20 comments on Metas will only show their top voted comments 139 | (Unless you have this script). 140 | * Adds audit link to /reputation 141 | * Adds history link to each question and answer 142 | 143 | ### Autocomplete 144 | 145 | ![Comment reply autocomplete in action](http://i.imgur.com/eTq50.png) 146 | 147 | This script also adds chat-like autocomplete for comments replies, in a way (relatively) 148 | consistent with [these criteria](http://meta.stackoverflow.com/questions/43019/how-do-comment-replies-work/43020#43020). 149 | Note that while editors can be notified, only the **last** editor's name will show up in the list. 150 | 151 | ## SEReputationAuditHelper 152 | This script adds certain useful information to the reputation audit page (`/reputation` on all SE 2.0 sites): 153 | 154 | * Autolinkifies the post ID associated with each reputation event 155 | * Adds description for each vote type instead of seeing vote ID 156 | * Adds a links to reputation audit pages for all other SE sites for faster navigation 157 | 158 | ## SEChatFaviconNotifier 159 | ![Chatroom favicons with different numbers of unread message counts](http://i.imgur.com/llq97.png) 160 | 161 | Adds unread count and @-mention notifications directly to the favicon of 162 | the chatroom page. The circle shows the number of unread messages and 163 | turns green when someone @-mentions you. 164 | 165 | ## SECommentLinkHelper 166 | Automatically converts raw Stack Exchange question links in comments into their 167 | correctly-titled `[title](link)` syntax equivalents before posting. 168 | 169 | [Stack Apps link](https://stackapps.com/questions/2378/se-comment-link-helper) 170 | -------------------------------------------------------------------------------- /SEModifications.user.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name SE Modifications 3 | // @description A collection of modifications for the Stack Exchange network of sites 4 | // @match *://stackoverflow.com/* 5 | // @match *://meta.stackoverflow.com/* 6 | // @match *://superuser.com/* 7 | // @match *://meta.superuser.com/* 8 | // @match *://serverfault.com/* 9 | // @match *://meta.serverfault.com/* 10 | // @match *://askubuntu.com/* 11 | // @match *://meta.askubuntu.com/* 12 | // @match *://*.stackexchange.com/* 13 | // @match *://answers.onstartups.com/* 14 | // @match *://meta.answers.onstartups.com/* 15 | // @match *://stackapps.com/* 16 | // @match *://mathoverflow.net/* 17 | // @exclude http://chat.stackexchange.com/* 18 | // @exclude http://chat.*.stackexchange.com/* 19 | // @exclude http://api.*.stackexchange.com/* 20 | // @exclude http://data.stackexchange.com/* 21 | // @exclude http://*/reputation 22 | // @author @rchern 23 | // ==/UserScript== 24 | 25 | function inject() { 26 | for (var i = 0; i < arguments.length; ++i) { 27 | if (typeof(arguments[i]) == 'function') { 28 | var script = document.createElement('script'); 29 | 30 | script.type = 'text/javascript'; 31 | script.textContent = '(' + arguments[i].toString() + ')(jQuery)'; 32 | 33 | document.body.appendChild(script); 34 | } 35 | } 36 | } 37 | 38 | inject(function ($) { 39 | var profile = $('#hlinks-user a.profile-link'), 40 | My = { 41 | 'name': profile.text(), 42 | 'profile': profile.attr('href') 43 | }; 44 | 45 | $(function () { 46 | // Initialize the general stuff here 47 | var locationBits = location.hostname.split('.'); 48 | 49 | if(locationBits[0] !== 'discuss' && (locationBits[0] !== 'meta' || locationBits[1] === 'stackoverflow')) { 50 | if (window.profileLink) { 51 | var initialized = false; 52 | 53 | profileLink._show = profileLink.show; 54 | profileLink.show = function (u, v) { 55 | profileLink._show(u, v); 56 | 57 | if (!initialized && (initialized = true)) { 58 | $('
  • audit
  • ').appendTo('.profile-links'); 59 | } 60 | }; 61 | } 62 | } 63 | 64 | initQuestions(); 65 | initPostRevisions(); 66 | //initSearchResults(); 67 | }); 68 | 69 | /* 70 | * Initializes functionality that only appears on the questions page: 71 | * - Question timeline linking 72 | * - Explicit post history linking 73 | * - Comment linking 74 | */ 75 | function initQuestions() { 76 | if (location.pathname.search("^/questions/\\d+/") === -1) { 77 | return; 78 | } 79 | 80 | var needsHighlight = false, 81 | question = $("#question-header a")[0].href; 82 | 83 | if (!question) { 84 | return; 85 | } 86 | 87 | var post = question.replace("questions", "posts").replace(/\/[^\/]*$/, ""), 88 | timeline = post + "/timeline", 89 | revisions = post + "/revisions", 90 | questionPost = $('#question'), 91 | ownedByMe = $('.post-signature', questionPost).length == 2 && $('.post-signature.owner .user-details a:first').attr('href') === My.profile; 92 | $(".post-menu", questionPost).append( 93 | (ownedByMe ? "
    " : "|") + 94 | "timeline" 95 | ); 96 | $(".post-menu").each(function() { 97 | var self = $(this), 98 | postLink = question, 99 | id = self.find("a.short-link")[0].href.replace(/^.*\/a\//, "").replace(/\/\d+(?:#.*)?$/, "");; 100 | 101 | if (!revisions) { 102 | revisions = "/posts/" + id + "/revisions"; 103 | postLink = postLink + '/' + id; 104 | } 105 | 106 | self.append("|history") 107 | .find('a').each(function () { 108 | var nodes = this.childNodes, i; 109 | 110 | for (i = 0; i < nodes.length; ++i) { 111 | if (nodes[i].nodeType == Node.TEXT_NODE) { 112 | nodes[i].nodeValue = nodes[i].nodeValue.replace(/ +/g, '\u00a0'); 113 | } 114 | } 115 | }); 116 | 117 | revisions = null; 118 | }); 119 | } 120 | 121 | /* 122 | * Initializes functionality that only appears on the post revisions page: 123 | * - Inline revision source loading 124 | */ 125 | function initPostRevisions() { 126 | if (location.pathname.search("^/posts/\\d+/revisions") === -1) { 127 | return; 128 | } 129 | 130 | $('.revision, .owner-revision').find('a[href$="/view-source"]').one('click', function () { 131 | var self = $(this), 132 | original = self.text(); 133 | 134 | self.text('loading...'); 135 | 136 | $.ajax({ 137 | 'url': this.href, 138 | 'context': self.closest('tr').next().find('.post-text')[0], 139 | 'success': function (response) { 140 | var id = "inline-" + this.parentNode.id; 141 | 142 | self.removeAttr('target')[0].href = "#" + id; 143 | 144 | $('
    ', {
    145 |                         'text': $(response).filter('pre').text(),
    146 |                         'css': {
    147 |                             'white-space': 'pre-wrap'
    148 |                         }
    149 |                     }).appendTo(this)[0].id = id;
    150 |                 },
    151 |                 'complete': function() {
    152 |                     self.text(original);
    153 |                 }
    154 |             });
    155 |             
    156 |             return false;
    157 |         });
    158 |     }
    159 |     
    160 |     /*
    161 |      * Initializes functionality that only applies to the search results page
    162 |      *   - Removing/untaggifying tags from search results
    163 |      */
    164 |     function initSearchResults() {
    165 |         var tagpage = false;
    166 |     
    167 |         if (location.pathname.indexOf('/search') !== 0 &&
    168 |                 !(tagpage = (location.pathname.indexOf('/questions/tagged/') === 0))) {
    169 |             return;
    170 |         }
    171 |         
    172 |         function on() {
    173 |             $(this).animate({ 'opacity': 1 }, 750);
    174 |         }
    175 |         
    176 |         function off() {
    177 |             $(this).animate({ 'opacity': 0 }, 750);
    178 |         }
    179 |         
    180 |         var criteria,
    181 |             tags = $('#sidebar .tagged a.post-tag');
    182 |             
    183 |         tags.each(function () {
    184 |             var tag = $(this),
    185 |                 wrapper = $('
    ') 186 | .append( 187 | $('') 188 | .append( 189 | $('"') 190 | .click(function () { 191 | updateCriteria(tag.text(), '"', true); 192 | wrapper.fadeOutAndRemove(); 193 | }) 194 | .add('\u00D7') 195 | .css({ 196 | 'font-family': 'Consolas, monospace', 197 | 'border-radius': '7px 7px', 198 | 'display': 'inline-block', 199 | 'width': '15px', 200 | 'height': '15px', 201 | 'margin-right': '3px', 202 | 'text-align': 'center', 203 | 'border': '1px solid #915857', 204 | 'cursor': 'pointer' 205 | }) 206 | .last() 207 | .click(function () { 208 | updateCriteria(tag.text(), false, true); 209 | }) 210 | .end() 211 | ) 212 | .css({ 213 | 'margin-left': '5px', 214 | 'color': '#6C0000', 215 | 'font-weight': 'bold', 216 | 'opacity': 0 217 | }) 218 | .hover(on, off) 219 | ) 220 | .insertBefore(this) 221 | .prepend(this); 222 | 223 | // Should just not add it in the first place, will fix later 224 | if (tagpage && tags.length === 1) { 225 | wrapper.find('.remove-tag').remove(); 226 | } 227 | }); 228 | 229 | function updateCriteria(existing, update, isTag) { 230 | update = !update ? '' : update + existing + update; 231 | 232 | if (isTag) { 233 | existing = '\\[' + existing + '\\]|' + existing; 234 | } 235 | 236 | var search = $('#search'), input = search.find('input[name="q"]'); 237 | 238 | // Temporary until I expand on this feature 239 | input.val( 240 | input.val() 241 | .replace(new RegExp('(^| )(' + existing + ')( |$)'), '$1' + update + '$3') 242 | .replace(/^ +| +$/, '') 243 | ); 244 | search.submit(); 245 | } 246 | } 247 | }); 248 | -------------------------------------------------------------------------------- /SECommentLinkHelper.user.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name SE Comment Link Helper 3 | // @description A hook to transform raw links to properly titled links in comments 4 | // @version 1.24 5 | // @homepageURL https://stackapps.com/questions/2378/se-comment-link-helper 6 | // @namespace https://github.com/rchern/StackExchangeScripts 7 | // @match *://stackoverflow.com/* 8 | // @match *://meta.stackoverflow.com/* 9 | // @match *://es.stackoverflow.com/* 10 | // @match *://es.meta.stackoverflow.com/* 11 | // @match *://ja.stackoverflow.com/* 12 | // @match *://ja.meta.stackoverflow.com/* 13 | // @match *://pt.stackoverflow.com/* 14 | // @match *://pt.meta.stackoverflow.com/* 15 | // @match *://ru.stackoverflow.com/* 16 | // @match *://ru.meta.stackoverflow.com/* 17 | // @match *://mathoverflow.net/* 18 | // @match *://meta.mathoverflow.net/* 19 | // @match *://superuser.com/* 20 | // @match *://meta.superuser.com/* 21 | // @match *://serverfault.com/* 22 | // @match *://meta.serverfault.com/* 23 | // @match *://askubuntu.com/* 24 | // @match *://meta.askubuntu.com/* 25 | // @match *://stackapps.com/* 26 | // @match *://*.stackexchange.com/* 27 | // @match *://*.meta.stackexchange.com/* 28 | // @exclude *://chat.stackexchange.com/* 29 | // @exclude *://chat.*.stackexchange.com/* 30 | // @exclude *://api.*.stackexchange.com/* 31 | // @exclude *://data.stackexchange.com/* 32 | // @exclude *://*/reputation 33 | // @author @TimStone 34 | // ==/UserScript== 35 | 36 | function inject() { 37 | for (var i = 0; i < arguments.length; ++i) { 38 | if (typeof(arguments[i]) == 'function') { 39 | var script = document.createElement('script'); 40 | 41 | script.type = 'text/javascript'; 42 | script.textContent = '(' + arguments[i].toString() + ')(jQuery)'; 43 | 44 | document.body.appendChild(script); 45 | } 46 | } 47 | } 48 | 49 | inject(function ($) { 50 | function HijackedTextarea(t) { 51 | var filters = { questions: '-ox0X.YDyJfh', answers: '!b6vl_mZrb8iVXs' }, 52 | textarea = t.addClass('link-hijacked')[0], 53 | form = t.closest('form'), 54 | span = document.createElement('span'), 55 | link = new RegExp('(?:^|[^\\w\\\\])https?://([^\\s/]+)/(q(?:uestions)?|a)/([0-9]+)', 'ig'), 56 | lock = 0, 57 | submitComment = $._data(form[0], 'events').submit[0].handler, 58 | // regex tested with 59 | // $.getJSON('https://api.stackexchange.com/2.2/sites?pagesize=1000&filter=!6P.Ehvyb0IX7X') 60 | // .done(data => { 61 | // const domains = data.items.map(item => new URL(item.site_url).hostname), 62 | // missed = domains.filter(d => !validSites.test(d)) 63 | // if (missed.length) { console.error('Domains missed', missed) } 64 | // else { console.log('Test passed') } 65 | // }); 66 | validSites = new RegExp( 67 | '^(?:' + // one of: top-level names, with their meta sites 68 | '(?:' + 69 | '(?:meta\\.)?' + 70 | '(?:' + // one of: .com names 71 | '(?:stackoverflow|serverfault|askubuntu|superuser)\\.com' + 72 | '|' + // or: mathoverflow.net 73 | 'mathoverflow\\.net' + 74 | ')' + 75 | ')' + 76 | '|' + // or: stackapps, which has no meta. 77 | 'stackapps\\.com' + 78 | '|' + // or: the language-variants of stackoverflow.com 79 | '(?:es|ja|pt|ru)\\.(?:meta\\.)?stackoverflow\\.com' + 80 | '|' + // or: .stackexchange sites, with their meta sites. 81 | '[^.]+\\.(?:meta\\.)?stackexchange\\.com' + 82 | ')$', 83 | 'i' 84 | ), 85 | miniLink = /(^|\W)(\[([^\]]+)\]\((?:(?:https?|ftp):\/\/[^)\s]+?)(?:\s(?:"|")(?:[^"]+?)(?:"|"))?\))/g, 86 | miniCode = /(^|\W)(`(?:.+?)`)(?=\W|$)/g, 87 | results = []; 88 | 89 | $._data(form[0], 'events').submit[0].handler = handler; 90 | 91 | function handler() { 92 | if (lock) 93 | return; 94 | 95 | lock = -1; 96 | 97 | var url, questions = {}, answers = {}, 98 | comment = textarea.value.replace(miniLink, "$1##").replace(miniCode, "$1##"); 99 | 100 | while (url = link.exec(comment)) { 101 | var type = url[2] === 'a' ? answers : questions, 102 | domain = url[1]; 103 | 104 | if (!type[domain]) 105 | type[domain] = []; 106 | 107 | type[domain].push(url[3]); 108 | } 109 | 110 | if (Object.keys(questions).length || Object.keys(answers).length) { 111 | request(questions, 'questions', callback); 112 | request(answers, 'answers', callback); 113 | } 114 | 115 | if (lock < 0) { 116 | // either no question and answer links were detected, *or* none of the 117 | // links were for valid sites and so no AJAX requests were started. 118 | // In either case we need to trigger the comment submit at this point. 119 | submit(); 120 | } 121 | 122 | link.lastIndex = 0; 123 | 124 | return false; 125 | } 126 | 127 | function callback(data, domain) { 128 | lock = lock - 1 === 0 ? -1 : lock - 1; 129 | 130 | if (!data.items || !data.items.length) { 131 | if (lock < 0) { 132 | submit(); 133 | } 134 | 135 | return; 136 | } 137 | 138 | data.domain = domain; 139 | 140 | results.push(data); 141 | 142 | if (lock < 0) { 143 | submit(); 144 | } 145 | } 146 | 147 | function submit() { 148 | var i, j, id, post, pattern, swaps = [], 149 | swapper = function (s, m1, m2) { 150 | swaps.push(m2); 151 | 152 | return m1 + "~%" + (swaps.length - 1) + "#"; 153 | }, 154 | comment = textarea.value; 155 | 156 | if (results.length) { 157 | for (i = 0; i < results.length; ++i) { 158 | comment = comment.replace(miniLink, swapper).replace(miniCode, swapper); 159 | 160 | for (j = 0; j < results[i].items.length; ++j) { 161 | post = results[i].items[j]; 162 | id = post.question_id || post.answer_id; 163 | pattern = '(^|[^\\w\\\\])http(s?)://' + results[i].domain.replace('.', '\\.') + '/(q(?:uestions)?|a)/' + id + '(?:/[-\\w]*)?(/[0-9]+)?(?:\\?[a-z]+=1)?(#\\w+)?'; 164 | comment = comment.replace(new RegExp(pattern, 'gi'), function (s, leading, https, type, trailing, anchor) { 165 | leading = leading || ''; 166 | trailing = trailing || ''; 167 | anchor = /^#comment(\d+)_/.exec(anchor || ''); 168 | 169 | var url; 170 | 171 | if (anchor) { 172 | url = '/posts/comments/' + anchor[1]; 173 | } else if (type === 'questions' && trailing) { 174 | url = '/a' + trailing; 175 | } else if (type === 'a') { 176 | url = '/a/' + id; 177 | } else { 178 | url = '/q/' + id; 179 | } 180 | 181 | return leading + '[' + escapeMarkdown(cleanWhitespace(toText(post.title))) + '](https://' + results[i].domain + url + ')'; 182 | }); 183 | } 184 | } 185 | 186 | textarea.value = comment.replace(/~%(\d+)#/g, function (s, m1) { 187 | return swaps[+m1]; 188 | }); 189 | $(textarea).trigger('keyup'); 190 | } 191 | 192 | submitComment.call(form[0]); 193 | 194 | results = []; 195 | lock = 0; 196 | } 197 | 198 | function toText(html) { 199 | span.innerHTML = html; 200 | 201 | return span.textContent; 202 | } 203 | 204 | function cleanWhitespace(text) { 205 | // remove leading and trailing whitespace, collapse multiple spaces 206 | // into one everywhere else. 207 | return text.replace(/^\s+|\s+$/g, '').replace(/\s+/g, ' '); 208 | } 209 | 210 | function escapeMarkdown(text) { 211 | return text.replace(/\[/g, '\\[') 212 | .replace(/\]/g, '\\]') 213 | .replace(/\*/g, '\\*') 214 | .replace(/_/g, '\\_') 215 | .replace(/`/g, '\\`'); 216 | } 217 | 218 | function request(ids, type, callback) { 219 | Object.keys(ids).forEach(function (domain) { 220 | if (validSites.test(domain)) { 221 | lock = lock < 0 ? 1 : lock + 1; 222 | 223 | $.get(window.location.protocol + '//api.stackexchange.com/2.1/' + type + '/' + ids[domain].join(';') + '?site=' + domain + '&filter=' + filters[type] + '&key=p0r10MZ01l1H4So8wqT*qA((', 224 | function (data) { 225 | // Go home Firefox you are drunk 226 | if (typeof(data) === 'string') { 227 | data = JSON.parse(data); 228 | } 229 | 230 | callback(data, domain); 231 | } 232 | ); 233 | } 234 | }); 235 | } 236 | } 237 | 238 | $(document).on('focus', 'textarea[name="comment"]:not(.link-hijacked)', function () { 239 | new HijackedTextarea($(this)); 240 | }); 241 | }); 242 | -------------------------------------------------------------------------------- /SEChatFaviconNotification.user.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name Stack Overflow Chat Favicon Notifier 3 | // @namespace yijiang 4 | // @description Watches the SO Chat for new messages, and changes the Favicon accordingly 5 | // @include http://chat.stackoverflow.com/rooms/* 6 | // @include http://chat.meta.stackoverflow.com/rooms/* 7 | // @include http://chat.stackexchange.com/rooms/* 8 | // @include http://chat.askubuntu.com/rooms/* 9 | // @exclude http://chat.stackoverflow.com/rooms/info/* 10 | // @exclude http://chat.meta.stackoverflow.com/rooms/info/* 11 | // @exclude http://chat.stackexchange.com/rooms/info* 12 | // @exclude http://chat.askubuntu.com/rooms/info/* 13 | // ==/UserScript== 14 | 15 | (function(){ 16 | function getLink () { 17 | return document.querySelectorAll('head link[rel*=shortcut]')[0]; 18 | } 19 | 20 | var title = document.title, 21 | iconURL = getLink().href, 22 | img = new Image(), 23 | canvas = document.createElement('canvas'), 24 | ctx = canvas.getContext('2d'), 25 | host = window.location.hostname, 26 | sites = { 27 | gaming: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAXklEQVQ4jWNgoBa4z8HxHx0TI0e5AdgkScFEG/D7yZP/v588GUADkA2iyACKXYBhwFUGBrb9DAwcJGp8R1Q6wInZ2b+TlCKR8FOykjQM3+PgeE3QAGyGkaSJqgYQAwBdcz9MXASc9AAAAABJRU5ErkJggg==', 28 | cooking: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAACD0lEQVQ4jc2SwWsaQRTG50/In7DgLBXcoYalF09DL5ZiJBQPEXIoQQI9Vei1Bw9SlAqCiLK7B10oIaWpbTEgZAl7SEBLFHvRaoOYQrJCECXrQUIOXw/BJVuTS0/94F1m3m/ee/M+Qv5LqT7Br0g0sYiiRHlRolxhNKtKYltjIiqRMFRJbC/DTCztBjnq6STODg20Cjnsb21if2sT9XQS414XC1UiYbhghXnWd4Mc1/YV5iML9mnfSZ6PLPTzGcxHlnOmMRGk6BUEhdGsJlFTY3T6c0dHM76NTioB+7QPq1ZFM76NRizqevDX189QJfqFqEwc1tNJWCffMe510UklYHAZjVgUjVgU/XwGl0emA1q1KgBAD8hQfYKfaEx0Lu9WuKtBWQEA3MxsdFIJjHvd2/YJIUSTqHnw+hVmF+f4/WnHVW0BmSEOAOjnM7BqVewGOVSf5yUhhJCsIKyoTCxpjE5P3r+DGeKuTgZlBccba5i0mzC4jEFZwd7zpyhKlC/tXmN0evDiGcwQx+WRCatWxfHGGgwu48fbN878rUIOikQTSx5QmGf9W+AxDC6jk0pgUFYwaTdxM7NdY50dGrcb+FtZQVj5+MQHg8uYtJv3fui1fYVKJAyF0ey9Nv6w6h0aXEY/n3GBs4tztAo56AH5YZgQQjQfjev+R2191etYWA/IUJk4VJlYKnoF4UH4X/QHlvTYdeSZTVAAAAAASUVORK5CYII=', 29 | webapps: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAADIElEQVQ4jc1Tf0jTCRx9tX5B0CEUdVdcctcPgovyrAjKSqikFZOkEWdDrAxu+KMyM/JuZYZrXWszdf5Ylj+WmQ2bVlp2l9qmo5xzS115itXcmtbKH5tu+y63T/9MIYj+7sH768N7vA+8B3xXUOtM+24/aNZkKe51i4vvvrxxr6khq6h8C4BZAKZ9W6zvisirqOvMyFe+sQ+NfBq0D3lTLpW9EhYo27nR0Rui+fyg23XqBVwul/VVs9KaRo3W2O1gGK/fwzBUpnpIo84xf1x6Xm+q+LrxqOha+0FBTkd2WU1VWFjEjwBmTIn/1batzpQrn/fb7IzX6yWNzkjJwlzS6Iw07nL7Tb1mV89rq9syYGcEORXmv0WyLAALALBQ36L7raS6oT5RWNQ7Nu7yCfMVlCzMneJ5WSkxDEMMw5DL5aLHWoNzb8L5ToFEruTy+CtxuaTmyX21fsgxNu5r0XdQikhGow4nnRDm0qjDSSkXZNT0rJ0+Dg+T7d176jNb6MGTZ54UUYH9lChPg5i07B6Ph/Ezgb/zy1Xk9njoqaGLhkZGKLtUSTJFFfWZLWTq6SN95wvS6NpJVd/k2xj15xtwEkXdkxHVrUY6nplD7+wfyDowSK8tVopPl1Bl7X+ke95F6lY9PVJrqfpRIylUtf41ETFvwYkXGlo7/ncNvreT2WqjkyIZJaRL6S+JnPhnxJSYIaUGbSvVNTbTnYePqby6jooqVXT8nNgdFhXXj4sFFYXhvNRuabFyuFln8LW0GUh+q5oycor9FwvLfIo7tRPyiqoJ6bWbn85kFXrjBSJ3ZFyycz2bNxAVHavCitDQ+Qfjk/lJAnFbKCeuP116dexYhsQRwo61rt8daw3ZxXv7+64DtnVsnm3L3kMWTkzCq0OJqW2bt+64AmA/ALAABC0ODl6blJZZeSDhtOnIibOmPfv+KA3fyZZsZ3P+2RS+I3Ptug1pwb8uT/ppydLDYLEiAWwGsGyyS9MBzAawCEAogO0AtgEIAbAKwEoAKwD8AuBnAAsBzAts5AuwAMwFEATgBwBzAMwM1HZG4D49sIVpAPAZrcDAsT9JO/sAAAAASUVORK5CYII=', 30 | programmers: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABcUlEQVQ4ja3PsYoaYRSG4VPogJ2d6AXI3oBgOc0MgzCChUiCyEyzLLvl3sCylZ07zcKAoGA6y8Q6hB8kF2FhiEbCVDY6M+L/ptphA6sSNh+c7pyH74j8z6xvboiCgHchvx8eiB4f+X53x5erK9T19WUwen4+ufTZsvhkmueR6OmJH67Lzw8f+XV/T3R7yzfHyY6++v773vqnWJaF7/t4nkev16PT6dBut6nX61Sr1fNNWq0Wg8GA19lutywWC5RSNJtNGo3G24ht2/T7fcIw5Hg8kqYpaZpyOBwyYDQaYdv2aUBEZDgcorUmSZJsXoAgCE4DpmnieR5hGKK1Jo7jDEjTFKUU4/H4NCAiUqvVmE6nAH81SJIEpRSTyeQ8ICLiui77/T47jOM4azCbzS4DjuOw2+0A0FqjtQZAKYVS6jLQ7XaZz+csl0vW6zWr1YrNZnO+gWEYGIZBPp+nUCiQy+UoFouUy2VKpRKVSuXNERH5A+OVSIxr3NrHAAAAAElFTkSuQmCC', 31 | math: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAACI0lEQVQ4jX2Tv2/UMBzFvy2VECe18lXt2UnsOCRm759wf8ItqLmNibkSK5RMHbrcxEAz3IBYqSokhJAgMIF0l1BUCRZKEJWgonXzJzyWJPT3kzz5fT+y37OJrtCxigZWRaVVUXnsRoOrfJfqreuPD6WBrdehF+Gb8MfXDpUsYFaa5K8XrKSOyp45CnsiwHf3NnLuY8p1diRN/8gzo5IF7MzwkTSJlVF1ou7ASoMdV1epq5A6CoXQ+LAs8csL0eyfKFP9kWHSAh4Q4VNPwSqD3zLCO66Quj5SVyHnPtLOAjZn5/DVCWClwU8vRC58tIAhEWIiPLnZQc595NzHq2VvtOWoMud+uUEz2ZAIQyK86PaQC41C6LOAIRHWiVrAZEn2R4yxjDEWEyWNJ+0stJ4WUBuqBjDtqXHGGDu1H8RE480bc/gsNHa5rnLur7WAfTdMfnghXi4KHHghTqTZPg0YELFjFY2bWi+EeJ8I75c9WGlw4EXYExoTIftNXTFR/zERdoWGVQalG+LL+QxiImzMzKIQGjn3se+FIyujynpR+XrRGcW1Z6fLL2bQANaJUNQJH3ghrDKwyuBQRtjpcgyJsHVr/iJglWhtSFQ9qkN8Pt+t1oiwzXrtS3yz6FRx3ULBdTUR/v8MmqAeEiUfmQhioqw5VV5facpVdpdo5WlnITkd8KVaJRrHde+F0MiFxrSnrv9M5xUT3YuJyoLrcrIk+1f5/gHh6Co+e0undAAAAABJRU5ErkJggg==', 32 | webmasters: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAADF0lEQVQ4jYWTXUiTcRTG/3URqJGQFUIaZgVRN4FWVF5UBNWFCrpQS6yMog+XkDXLPiFaNa1ZSbmKKDCycGQL3aau5XRzvdv71+Wm5sfr2mzONzffVyuNiqeLLFOkfvDAgfOcm3OeQ8g4Fy5g5q8KMwghhIrIsgWxwRbEBioi6+/ehHccixchDI9IVkCc3YdQKiCeFSCLvlm9JLKobjUrQEYFxNt9CGUFxDE8Ii1ehBBCCPGnpuZ2PSw/x4rIpAIkTYM/jhZSX/WpBm54hcqEVQ8aceJ1zw+Zqbv7hXvkLBUgMQ9+37Nf474ZedEqIf6MDHAlKrhMNom0umvO5sdvnMnP7EhVU1xmPCi096HI0Y+Tpm4kPDSPrVQZE3K1njP7XvQi5ooNpD8trc0tv0pbBkYzI4p0xQuVOsQodTjdyEHB9qHq4xhqhr7hZAOH+YoqRCi0fGlL8GByWYd7kfzNK0IIIUYes2v7v5zY9oT5vv5BA06ZepBv5iBr9qK0/xOe8qOo9IxgzX0T4h+ZkaJ2KI08Zk9aZIamVXrW+h6KZh8ytS4o34uTdJ1+wCFdG7Jr2hF9w6AnU9n90nnxHOPFfmMnlt02QNbiw6WuwIQYD+Lu1SPf3ItNj5n2ScNMANGP3gXuJj93IOnlW4TLNVhQWI0UrQunnTzOOHmU933GTkMHdmpdKGjkWpkAov9kgIpIr/WNbY0o1At7Tb1YfseIcLkG4XINtqibkcf6cK9HxLoyK9aUMThS13mFiki3eBFC9H6EURFSu4i1MSWG/CxdB7LrOcxTVCG2xABJTSdyLB5caxtEdu07RCnrnEZ+NIGKkOr9CCPjsd1Oh5HECjggNXTqE9UObFW/xcYKB7Zp2nHc4kHuaw67NK3+SvdwAR1GEhWxfSLKIuZaA4hiRSy1+xCarXMeKWjkOuYV6b/Ov1aDY8Yef1IFW3xQ71jAilhqDSDKImLulDv8epTfUBFZTUNDG0sHS4vVwfpb//JOi01AbGWw6a4qoML5j4eR409c/N+hqeT4ExfnDaS68gZSDDtcK2dN5/kJwrUeiRdJOWQAAAAASUVORK5CYII=', 33 | askubuntu: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABXElEQVQ4jZ2Tv4rCQBDG5w1sfAg761Q2NmG3FyuxFAKWeQCLq2yFVNfYp0l3xRWBtLKlmEUliErgslxCOCTwXaFZ/8RDz4WBTXbmN7PfzhDdrIVJrZDRu+SUSE44WRJychcmtW799ZJtqoWc3IuguxYy+pRtqlWCJaPZo2BtjGZXkGcy36tE3/mZgCzwsLHNW0iPToI9BOTCrwI4uSQZLSUnRJaBIlUoUoX9eICNbeKwWwEAtqOuBih3gv14oCFUbpadOpadOmLHxmG3wtf0Dbnw9VkufPyEQv+rAGLHRpEq5MIHAESWgVz4KFKlKwCA2LErgKQUSbkTrPsNAMC638C630AWePj+mCIXvk5yqYUWscyYCx9Z4CF2bP0dWQaUO0FkGdiOusgC7yzis8/4Ry/0jo3E6PMVwJxT87VWPtvwepj+WYlu58o4H2cjuXLm5IaMenNOTclpWPr8AvZxHtvnNf8+AAAAAElFTkSuQmCC', 34 | photo: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAACRElEQVQ4jaXDW0tTAQDA8fM9woqEqMAo6EGEHgrrIcUbuZvNTJfOSzl1KmJeZom6s6l5zaUV4iW3SZm99GBSJEihSEQWZMtkOtfOPHA8NMbYv+/QfvATSJAQj8cJ1VhQdVrU/HwO9XpUnZ4Do5avdzKZEVMZHj6L8+l5WlavUSXfpOKwmJZQPfF4HCEWi/G7tJqtnEI2Mwv5mWEgmJHHD8Ml5ptPMdR7lDFHEiNPkrGtpVF+mMOtmIZapZxYLIYQjUZ5Vj3MkN5Of34PI3mdzGjqma3Ipb/7DAN9x5gePMKiO4nnG6fpCl+k4m82VrmcaDSKEIlEsNdOcd84Sqd+gC7dQzpviLRV1tHSno3TcYH5iRMsLx1n+XsyM/sptCqXaZTLiEQiCKqqMmiZQCzoo1cr0qdxIBZ001bWiqXJQr29mEH3VRbWzrH06yRzeyl0yOk0HZhRVRVBURT6rZPYjQM4tA56NSKi3kGL2U6RrYcslxPj63Ycnwx4N9KZ+aDB+bYWcfUBiqIgyLLMvdF1LLb3WGsWaKh0Y61ZwGxbItf1jlTvImkvvJROTzA6OIS7fZy5BjcvOxaRZRlBkiTKvSG0kwfoxvcxjO2iG9/n+mSYrFmJKx4fOY++0Fz3mamSj7wxrbBStM561TckSUIIBoM0vtrD5AlT7Alz2xvC5JEweSRKvBKm+T80DQd5fDfAcrGfTbMfnznAVsMuwWAQIRAIIMvyfw0EAgh+v59ECjs7OyRS2N7eJpGCz+cjkYLL5SKR/wBu9SXq7QC25AAAAABJRU5ErkJggg==', 35 | superuser: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABM0lEQVQ4jZ3TMUsDQRCG4deAkECI4K+wsVBRFKxsLGy1E+wsLARBSyFdsJE0NrFQMEW8na1stBAb/QE2GguVVOLd7qZUMN5aXBSREHM3MNWwD98yu5BUFfBADHjyxRoA9dY12r2jzCWBWaBPfQOnwBqF0iwAtftFlN1FXIh2H4hZ/Q/Y6TkNXucQ10GZkLotpQcAlH1Cu5jAzGQEoiZiYwI3nwbwgGdqfZggaqKdzwLErGyNEkR33QRLWa6Q4/hxH+084m5pmGnK5VwaAI6u8ihzgHYeZTtpE0AjmkTsC9rFiL1JD4g9R2yMMnt/4w8KJFuQcKLXeDAg2cJ4NkDbh+476AucAZvkR5Kfd9hcRpkNVLSN2DeU/UTMWC+gArR/ulCsAnDSukC7drefEVcBhn4f/AKBs7SVr7eXywAAAABJRU5ErkJggg==', 36 | serverfault: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAkUlEQVQ4jWNgoBRcvHTlPzGYgYGB4aWG7v83str/38hq/3+tbvj/tYrif6IMuHb1LtwAZPxWUYw4A5Bd8ExHH8UQioOAYcXKtf+JwQwMDAwzFJX/o+OBNWC+rDQVwiAqKuY/MZiBgYFBX1z8PzoeBAZQA/yHYQ5Obqw0FDNwcHL/R8cM6JrxYPwGYHMJGhurAQAacYAPCrFaGAAAAABJRU5ErkJggg==', 37 | english: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAASwAAACWCAYAAABkW7XSAAAB4UlEQVR4nO3aMZHjMBQGYEMIhIWgJpYsN4FgCIEQCGHwlkEgBEIgBEIgBMJeYTuTzLm9U7HfN6NGav7qzdOTug4AgH8s+mGKXC9R6jlS2rXOA7Ap+mH6LuPPuiLXy8d5qcd5f3xGqcdGMQG6Lko9R66PSGkXuV7+Klj9MM3Fqhwij7dWOQG6yMNpLVKR0u61+mGKUs9RymEuVvWqYAFNrVe+7zL+rDOsyPX+fk18nffD1Dov8Est17xnlPIVeTjFvqal47pHKYdlGH+fB/Llq3Ve4BfbuuYtBev66rjmonVtlRGg67quizzeItf7x15Ku8jjbSlYx2XobnYFtPXeSW2e72uKPD4jj/GfowF8ilIOb/+vHpHHmF8G6/nVZS0zrtZZAdZf7o/NV8FcH7GvqXVGgA/zi+Fwmrur+cWwdSYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC2/QE1fKVGw+uhXAAAAABJRU5ErkJggg==', 38 | apple: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAASwAAACWCAYAAABkW7XSAAADe0lEQVR4nO3WO2iddRgG8D8WHboUHEIXUXTJoCgYvIAVioKgdRAURCiaUkXbqRWkCIEODtqhdqnWSy/BFISAaRI0TSVUKJg0qdY0pxeaNrc25/vynVxMck4yvg6ihVLURc6Q3w9e/nzP9EwPX0q33NXX1/dET0/Pc/903d3dzzQ3N9+XUro7AdTBupTSAzMzMyfm5+fP/NsVRfH9li1bHk0p3VPv4sDa05BlWdfKykr81yuXyz+klO6vd3Fg7XmkUqmcrdVqUavV4mbxe5TGi/jr+07Z/Pz8ZErp4XoXB9aex4uiGFpeXo6R8ZnY03YuPum4EF+cuhzVavWO2dzc3FRK6bF6FwfWnqaiKIaq1Wr0/joZ354ZjcXqauw+2h8rKytx8peJ6Dw7Fvn8cuw+2h+1Ws1gAXXTlGXZ0NLSUgxfz2PH5z/Fgc7zf78tbf3R0jYQLW0DcaDzvD8soK6asiwbWlxcjGq1GjeLhRi+nsdb+0/G8PU8jvReiCO9F+K3a3msrq7G0tJSVCoVgwXURdPFixe7siyLoiji6sR0DF6ajNc++i6GrtyIg52DcbBzMPpL4zE7Oxt5nsfExMSVZLCAOmgqlUpd5XI5Tg9djpc+PB57vjz15/vVqdi270Rs23cimvd1xAeHTkaWZTE+Pm6wgLpoGhkZ6SqXy/F1d398fPx0zMwuxOZdh6NWq0Vrz7k42NEf127ksXnX4cjzPMbGxgwWUBdPDwwMHJqeno6+wUvx1I7P4pWWb2LHpx2xsLAQp89duZXt74g8z6NUKvWmlJ6sd3Fg7Xk2pdQ4PDzcXi6XJ0pXJ+d+HLi0VKlUylNTU9duz0ZHR39ubW19M6X0Yr2LA2vPvSmldzds2PD81q1bXz527NjO9vb29/fu3fvGpk2bnrs9a2xsfCGltD2l9GCdewNrVENK6dV169a93dDQ8N7GjRt3rl+//p2U0vY7ZK+nlB6qb10AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgP/VH5yFPolq4uGdAAAAAElFTkSuQmCC', 39 | unix: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAASwAAACWCAYAAABkW7XSAAACnUlEQVR4nO3bv2sWdwDH8e+f4J/wLJE7pxsaeJ5Hh6dd6iBWSCkm3jUP0kDrEAVNRUhKhkKhlNRFRBDT5cktQh2dPHOLdIqbEFIu+A9EcHD8dsnZ/Kh066X4esF3ueHhM7057rkLST8fJcN89eAJIYRD1/rFOAB0bT9YL9NhEdNhEdtgpYP8bjosYjLI3wgWcGIkw3y1Ddah64PieRswgBPhaLAm1XZW1k11Pl+JC8sP323Uf97oeiNACOF4sDbq5veybuKnXy7tfXXt5xdl3cR/+w2A/8TRYJV1U5V1E8/nK3H+5r2m3GyarjcChBBCOD3IL7XBmpqeyyb1zrism5h9thB/+u1Z3L/jqtozqbazrjcDH7F0kO+mwyKeOfv169nFta3RzFL85PPv4rc/PIpffPPj09nFta2rdx7sXb394FzXW4GP3NT0XJYOiydnL15/O5pZiuPv7/9x65fHu6OZpdjefV2YX32R9C/3ut4KEEIIoaybWNZN3Nhsfp1U29nC8sN3bbDWq1e9rvcBvFduNs1+sPbWq1e92cW1rTZYG5vNludXwIkxqbazg9E6GKz3D93rnXHXOwGO+dBb8FPTc1kvu3Sqq10Ax/xTsJL+5V46LGLSz0cdTgP4236YnrTBSgf53XRw5Ub7sbR/DIETI+kX42RQPP/Q6XofAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8H/0F7olCY2MH7ueAAAAAElFTkSuQmCC' 40 | }; 41 | 42 | canvas.width = 16; 43 | canvas.height = 16; 44 | 45 | ctx.font = "bold 8px Ubuntu, 'Segoe UI', Tahoma, Arial, 'sans serif'"; 46 | ctx.textAlign = 'center'; 47 | 48 | // Find the correct favicon to use 49 | if(host.indexOf('chat.stackoverflow') === 0) { 50 | img.src = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABnElEQVQ4jWNgwAMaGBiYHqdqT36SpvP/carOgdueKuz41OM2JE177ZM0nf+PUrUXkmwAAwMDw91QJf7HKTqnn6Tp/H+UplNNUMOrqaw5r6ew1D2fxCAKE3uYpKv0OEX3KdQlETg1P+5j4Hw9he3q66ls/19NZX35agpLzcsJDOIMDAwMj5O1zR6n6P54nKb942GqpgtWAxoaGJheTWVQeTeNqe31VJbPUINev5rB0ny/gUHgSbaqz5Mk/T+Pk3UbCXrlQT+D5NupTE3vpjG9fTOV9f+racxvXvZzND0tk/JrYGBgwtCwqtCSc3WFQ/GqEqfAZYWO2vMTFDgYGBgYrjaI8jybyFn0bhrTo3fTmf6/mc4gjdXGVcW2WmsrHP/D8OpypzcrS10OrC53bN1QaRVypFkj6FaPSCROJ88sd+FfV2YdtKHCrmV9he3ODeW29zZU2P1fX2H/f2254//VZU7/VxY72WFozM3J+Z+bk/Mfq6HFviJzy31tFld4payscJ44s8BdEkM9sgCMjQtjtZCqBhADcBpAKqaKAQB1iiloT36niAAAAABJRU5ErkJggg=='; 51 | } else if(host.indexOf('chat.meta.stackoverflow') === 0) { 52 | img.src = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABWklEQVQ4ja2QvUpDQRCFLykkhYhFkGApVj6AlVhZikiw8AEsbxpDGiPu2Znd2CQEkXAJ3DuzeQAfwNLa2tpCLERSWaWQtUlAMNcfcODAsMz55uwkyTcFoOKcu/beR+fcHYCl7+ZLId77G+99ZObxnwEzyIpz7n6W5OxHg4ikInIxGo1qnyAbzPzsvY9EdFxqVtWqiDyoalTVFxE5z7JsLUmSxDm3zcxT59yUmffK4lbyPN9U1UsReVPVKCKvIQQeDAar3W53n4jeicj++JXhcFgPIbCqTmaJJnmec7/fPwBQWbS9CuAUwCGALQDVGWi5KIrT8Xj8FEKIRVGsl8XfstbGuYwxE2PMnTHGM/NRr9drZFlWfsBms7kCoAHAE9GttfaRiCIRRWttBBAB7H4xpmka0zSNi6CtVqvW6XR2AJwAuGq32/Uv858f5n2ZFi78V8BvqhTwV/0L4AMhxDwD8Y9esAAAAABJRU5ErkJggg=='; 53 | } else { 54 | var smallIconUrl = document.querySelectorAll('#sidebar-menu .small-site-logo')[0].src, 55 | found = false; 56 | 57 | for(var i in sites){ 58 | if(smallIconUrl.indexOf(i) !== -1) { 59 | img.src = sites[i]; 60 | found = true; 61 | break; 62 | } 63 | } 64 | 65 | if(!found) { 66 | img.src = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAA5ElEQVQ4jWNggIKG/ftZFt19l7Ho9rtKvPjuu4yG/ftZGNDBkrvvtqx88OE/MXjqjTerMQxYee/DT2I0T7/55n/LxRffMAygGBQf+PIfHw7d/PG/2dL3cEySAeiasRpAMUiceec/OnZovvpfNvcsVkzQAHyasRpAMUho3vwkuW3bf6ecZf8VAqfhxfKBUz+LOkzlQTEgqXnLa2I0QwyY9kfGvU8IxQD7rEW+CgHTTsoHTDsDx4FTrysETP2BZsAtuYApsUR7DU1zBslhoxA47b9CwNSP8v5TsknWDDNAzneKNi55AC2gU1dDOLAVAAAAAElFTkSuQmCC'; 67 | } 68 | } 69 | 70 | // Start title watching process 71 | setInterval(function(){ 72 | if(title !== document.title) { 73 | title = document.title; 74 | var times, mention = false, ctitle = title, url; 75 | 76 | if(title.indexOf('(') === 0){ 77 | ctitle = title.substring(1, title.indexOf(')')); 78 | if(ctitle.indexOf('*') > -1){ 79 | mention = true; 80 | 81 | if(ctitle.length === 1){ 82 | times = 0; 83 | } else { 84 | times = parseInt(ctitle, 10); 85 | } 86 | } else { 87 | times = parseInt(ctitle, 10); 88 | } 89 | } else { 90 | times = 0; 91 | } 92 | 93 | if(times > 0 || mention) { 94 | // Draw ma circlez! 95 | ctx.clearRect(0, 0, 16, 16); 96 | ctx.fillStyle = mention ? "rgba(63, 175, 7, 0.8)" : "rgba(200, 0, 30, 0.6)"; 97 | 98 | ctx.drawImage(img, 0, 0); 99 | 100 | ctx.beginPath(); 101 | ctx.arc(11, 11, 5, 0, Math.PI*2, true); 102 | ctx.fill(); 103 | 104 | ctx.fillStyle = "#fff"; 105 | 106 | if(times < 10){ 107 | ctx.fillText(times, 11, 13); 108 | } else if(times < 100){ 109 | ctx.fillText(~~(times/10), 9, 13); 110 | ctx.fillText((times%10), 13, 13); 111 | } else { 112 | ctx.fillText('+', 11, 13); 113 | } 114 | 115 | url = canvas.toDataURL("image/png"); 116 | } else { 117 | url = iconURL; 118 | } 119 | 120 | var linkEle = getLink(), 121 | newLink = document.createElement('link'), 122 | head = document.getElementsByTagName('head')[0]; 123 | 124 | newLink.rel = 'shortcut icon'; 125 | newLink.type = 'image/png'; 126 | newLink.href = url; 127 | 128 | head.removeChild(linkEle); 129 | head.appendChild(newLink); 130 | } 131 | }, 500); 132 | })(); 133 | 134 | -------------------------------------------------------------------------------- /SEKeyboardShortcuts.user.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name SE Keyboard Navigation 3 | // @description VIM like navigation of Stack Exchange sites 4 | // @include http://stackoverflow.com/* 5 | // @include http://meta.stackoverflow.com/* 6 | // @include http://superuser.com/* 7 | // @include http://meta.superuser.com/* 8 | // @include http://serverfault.com/* 9 | // @include http://meta.serverfault.com/* 10 | // @include http://askubuntu.com/* 11 | // @include http://meta.askubuntu.com/* 12 | // @include http://answers.onstartups.com/* 13 | // @include http://meta.answers.onstartups.com/* 14 | // @include http://nothingtoinstall.com/* 15 | // @include http://meta.nothingtoinstall.com/* 16 | // @include http://seasonedadvice.com/* 17 | // @include http://meta.seasonedadvice.com/* 18 | // @include http://stackapps.com/* 19 | // @include http://*.stackexchange.com/* 20 | // @exclude http://chat.stackexchange.com/* 21 | // @exclude http://chat.*.stackexchange.com/* 22 | // @exclude http://api.*.stackexchange.com/* 23 | // @exclude http://odata.stackexchange.com/* 24 | // @exclude http://area51.stackexchange.com/* 25 | // @exclude http://*/reputation 26 | // @author @rchern 27 | // ==/UserScript== 28 | 29 | function with_jquery(callback) { 30 | var script = document.createElement("script"); 31 | script.type = "text/javascript"; 32 | script.textContent = "(" + callback.toString() + ")(jQuery)"; 33 | document.body.appendChild(script); 34 | } 35 | 36 | with_jquery(function ($) { 37 | 38 | /** 39 | * jQuery.ScrollTo - Easy element scrolling using jQuery. 40 | * Copyright (c) 2007-2009 Ariel Flesler - aflesler(at)gmail(dot)com | http://flesler.blogspot.com 41 | * Dual licensed under MIT and GPL. 42 | * Date: 5/25/2009 43 | * @author Ariel Flesler 44 | * @version 1.4.2 45 | * 46 | * http://flesler.blogspot.com/2007/10/jqueryscrollto.html 47 | */ 48 | (function (d) { var k = d.scrollTo = function (a, i, e) { d(window).scrollTo(a, i, e) }; k.defaults = { axis: 'xy', duration: parseFloat(d.fn.jquery) >= 1.3 ? 0 : 1 }; k.window = function (a) { return d(window)._scrollable() }; d.fn._scrollable = function () { return this.map(function () { var a = this, i = !a.nodeName || d.inArray(a.nodeName.toLowerCase(), ['iframe', '#document', 'html', 'body']) != -1; if (!i) return a; var e = (a.contentWindow || a).document || a.ownerDocument || a; return d.browser.safari || e.compatMode == 'BackCompat' ? e.body : e.documentElement }) }; d.fn.scrollTo = function (n, j, b) { if (typeof j == 'object') { b = j; j = 0 } if (typeof b == 'function') b = { onAfter: b }; if (n == 'max') n = 9e9; b = d.extend({}, k.defaults, b); j = j || b.speed || b.duration; b.queue = b.queue && b.axis.length > 1; if (b.queue) j /= 2; b.offset = p(b.offset); b.over = p(b.over); return this._scrollable().each(function () { var q = this, r = d(q), f = n, s, g = {}, u = r.is('html,body'); switch (typeof f) { case 'number': case 'string': if (/^([+-]=)?\d+(\.\d+)?(px|%)?$/.test(f)) { f = p(f); break } f = d(f, this); case 'object': if (f.is || f.style) s = (f = d(f)).offset() } d.each(b.axis.split(''), function (a, i) { var e = i == 'x' ? 'Left' : 'Top', h = e.toLowerCase(), c = 'scroll' + e, l = q[c], m = k.max(q, i); if (s) { g[c] = s[h] + (u ? 0 : l - r.offset()[h]); if (b.margin) { g[c] -= parseInt(f.css('margin' + e)) || 0; g[c] -= parseInt(f.css('border' + e + 'Width')) || 0 } g[c] += b.offset[h] || 0; if (b.over[h]) g[c] += f[i == 'x' ? 'width' : 'height']() * b.over[h] } else { var o = f[h]; g[c] = o.slice && o.slice(-1) == '%' ? parseFloat(o) / 100 * m : o } if (/^\d+$/.test(g[c])) g[c] = g[c] <= 0 ? 0 : Math.min(g[c], m); if (!a && b.queue) { if (l != g[c]) t(b.onAfterFirst); delete g[c] } }); t(b.onAfter); function t(a) { r.animate(g, j, b.easing, a && function () { a.call(this, n, b) }) } }).end() }; k.max = function (a, i) { var e = i == 'x' ? 'Width' : 'Height', h = 'scroll' + e; if (!d(a).is('html,body')) return a[h] - d(a)[e.toLowerCase()](); var c = 'client' + e, l = a.ownerDocument.documentElement, m = a.ownerDocument.body; return Math.max(l[h], m[h]) - Math.min(l[c], m[c]) }; function p(a) { return typeof a == 'object' ? a : { top: a, left: a} } })(jQuery); 49 | 50 | /* 51 | * jqModal - Minimalist Modaling with jQuery 52 | * (http://dev.iceburg.net/jquery/jqModal/) 53 | * 54 | * Copyright (c) 2007,2008 Brice Burgess 55 | * Dual licensed under the MIT and GPL licenses: 56 | * http://www.opensource.org/licenses/mit-license.php 57 | * http://www.gnu.org/licenses/gpl.html 58 | * 59 | * $Version: 03/01/2009 +r14 60 | */ 61 | (function ($) { $.fn.jqm = function (o) { var p = { overlay: 50, overlayClass: 'jqmOverlay', closeClass: 'jqmClose', trigger: '.jqModal', ajax: F, ajaxText: '', target: F, modal: F, toTop: F, onShow: F, onHide: F, onLoad: F }; return this.each(function () { if (this._jqm) return H[this._jqm].c = $.extend({}, H[this._jqm].c, o); s++; this._jqm = s; H[s] = { c: $.extend(p, $.jqm.params, o), a: F, w: $(this).addClass('jqmID' + s), s: s }; if (p.trigger) $(this).jqmAddTrigger(p.trigger); }); }; $.fn.jqmAddClose = function (e) { return hs(this, e, 'jqmHide'); }; $.fn.jqmAddTrigger = function (e) { return hs(this, e, 'jqmShow'); }; $.fn.jqmShow = function (t) { return this.each(function () { t = t || window.event; $.jqm.open(this._jqm, t); }); }; $.fn.jqmHide = function (t) { return this.each(function () { t = t || window.event; $.jqm.close(this._jqm, t) }); }; $.jqm = { hash: {}, open: function (s, t) { var h = H[s], c = h.c, cc = '.' + c.closeClass, z = (parseInt(h.w.css('z-index'))), z = (z > 0) ? z : 3000, o = $('
    ').css({ height: '100%', width: '100%', position: 'fixed', left: 0, top: 0, 'z-index': z - 1, opacity: c.overlay / 100 }); if (h.a) return F; h.t = t; h.a = true; h.w.css('z-index', z); if (c.modal) { if (!A[0]) L('bind'); A.push(s); } else if (c.overlay > 0) h.w.jqmAddClose(o); else o = F; h.o = (o) ? o.addClass(c.overlayClass).prependTo('body') : F; if (ie6) { $('html,body').css({ height: '100%', width: '100%' }); if (o) { o = o.css({ position: 'absolute' })[0]; for (var y in { Top: 1, Left: 1 }) o.style.setExpression(y.toLowerCase(), "(_=(document.documentElement.scroll" + y + " || document.body.scroll" + y + "))+'px'"); } } if (c.ajax) { var r = c.target || h.w, u = c.ajax, r = (typeof r == 'string') ? $(r, h.w) : $(r), u = (u.substr(0, 1) == '@') ? $(t).attr(u.substring(1)) : u; r.html(c.ajaxText).load(u, function () { if (c.onLoad) c.onLoad.call(this, h); if (cc) h.w.jqmAddClose($(cc, h.w)); e(h); }); } else if (cc) h.w.jqmAddClose($(cc, h.w)); if (c.toTop && h.o) h.w.before('').insertAfter(h.o); (c.onShow) ? c.onShow(h) : h.w.show(); e(h); return F; }, close: function (s) { var h = H[s]; if (!h.a) return F; h.a = F; if (A[0]) { A.pop(); if (!A[0]) L('unbind'); } if (h.c.toTop && h.o) $('#jqmP' + h.w[0]._jqm).after(h.w).remove(); if (h.c.onHide) h.c.onHide(h); else { h.w.hide(); if (h.o) h.o.remove(); } return F; }, params: {} }; var s = 0, H = $.jqm.hash, A = [], ie6 = $.browser.msie && ($.browser.version == "6.0"), F = false, i = $('').css({ opacity: 0 }), e = function (h) { if (ie6) if (h.o) h.o.html('

    ').prepend(i); else if (!$('iframe.jqm', h.w)[0]) h.w.prepend(i); f(h); }, f = function (h) { try { $(':input:visible', h.w)[0].focus(); } catch (_) { } }, L = function (t) { $()[t]("keypress", m)[t]("keydown", m)[t]("mousedown", m); }, m = function (e) { var h = H[A[A.length - 1]], r = (!$(e.target).parents('.jqmID' + h.s)[0]); if (r) f(h); return !r; }, hs = function (w, t, c) { return w.each(function () { var s = this._jqm; $(t).each(function () { if (!this[c]) { this[c] = []; $(this).click(function () { for (var i in { jqmShow: 1, jqmHide: 1 }) for (var s in this[i]) if (H[this[i][s]]) H[this[i][s]].w[i](this); return F; }); } this[c].push(s); }); }); }; })(jQuery); 62 | 63 | /* 64 | * Viewport - jQuery selectors for finding elements in viewport 65 | * 66 | * Copyright (c) 2008-2009 Mika Tuupola 67 | * 68 | * Licensed under the MIT license: 69 | * http://www.opensource.org/licenses/mit-license.php 70 | * 71 | * Project home: 72 | * http://www.appelsiini.net/projects/viewport 73 | * 74 | */ 75 | /* with additions by rchern */ 76 | (function ($) { $.belowthefold = function (element, settings) { var fold = $(window).height() + $(window).scrollTop(); return fold <= $(element).offset().top - settings.threshold; }; $.abovethetop = function (element, settings) { var top = $(window).scrollTop(); return top >= $(element).offset().top + $(element).height() - settings.threshold; }; $.rightofscreen = function (element, settings) { var fold = $(window).width() + $(window).scrollLeft(); return fold <= $(element).offset().left - settings.threshold; }; $.leftofscreen = function (element, settings) { var left = $(window).scrollLeft(); return left >= $(element).offset().left + $(element).width() - settings.threshold; }; $.inviewport = function (element, settings) { return !$.rightofscreen(element, settings) && !$.leftofscreen(element, settings) && !$.belowthefold(element, settings) && !$.abovethetop(element, settings); }; $.topVisible = function (element, settings) { var top = $(window).scrollTop(); var bottom = top + $(window).height(); var elPos = $(element).offset().top - settings.threshold; return (top <= elPos) && (bottom >= elPos); }; $.bottomVisible = function (element, settings) { var top = $(window).scrollTop(); var bottom = $(window).height() + $(window).scrollTop(); var elPos = $(element).offset().top + $(element).height() - settings.threshold; return (top <= elPos) && (bottom >= elPos); }; $.leftVisible = function (element, settings) { var left = $(window).scrollLeft(); var right = left + $(window).width(); var elPos = $(element).offset().left - settings.threshold; return (left <= elPos) && (right >= elPos); }; $.rightVisible = function (element, settings) { var left = $(window).scrollLeft(); var right = left + $(window).width(); var elPos = $(element).offset().left + $(element).width() - settings.threshold; return (left <= elPos) && (right >= elPos); }; $.fullyVisible = function (element, settings) { return $.topVisible(element, settings) && $.bottomVisible(element, settings) && $.leftVisible(element, settings) && $.rightVisible(element, settings); }; $.extend($.expr[':'], { "below-the-fold": function (a, i, m) { return $.belowthefold(a, { threshold: 0 }); }, "above-the-top": function (a, i, m) { return $.abovethetop(a, { threshold: 0 }); }, "left-of-screen": function (a, i, m) { return $.leftofscreen(a, { threshold: 0 }); }, "right-of-screen": function (a, i, m) { return $.rightofscreen(a, { threshold: 0 }); }, "in-viewport": function (a, i, m) { return $.inviewport(a, { threshold: 0 }); }, "top-visible": function (a, i, m) { return $.topVisible(a, { threshold: 0 }); }, "bottom-visible": function (a, i, m) { return $.bottomVisible(a, { threshold: 0 }); }, "left-visible": function (a, i, m) { return $.leftVisible(a, { threshold: 0 }); }, "right-visible": function (a, i, m) { return $.rightVisible(a, { threshold: 0 }); }, "fully-visible": function (a, i, m) { return $.fullyVisible(a, { threshold: 0 }); } }); })(jQuery); 77 | 78 | /* shortcut plugin */ 79 | (function ($) { $.fn.shortcut = function (fn, params) { params = $.extend({}, $.fn.shortcut.params, params); return this.each(function () { $(this).bind('keyup', function (event) { if (this !== event.target && (/textarea|select/i.test(event.target.nodeName) || event.target.type === "text")) { return; } if (event.keyCode === params.code[params.step]) { if (params.step === 0) { params.startTime = new Date(); } params.step++; } else { params.step = 0; } if (params.step === params.code.length) { if (new Date() - params.startTime <= 2E3) { fn(); } params.step = 0; } }); }); }; $.fn.shortcut.params = { 'code': [38, 38, 40, 40, 37, 39, 37, 39, 66, 65], 'step': 0 }; })(jQuery); 80 | 81 | $(function () { 82 | var curIndex = -2, selectedItem = null; 83 | 84 | var goToDestination = function (url) { location = url; }; 85 | 86 | var selectItem = function (items, i) { 87 | if (i < 0) { 88 | // make a guess at the selected post 89 | var fullyVisiblePosts = items.filter(":fully-visible"); 90 | if (fullyVisiblePosts.length > 0) { 91 | i = items.index(fullyVisiblePosts[0]); 92 | } else { 93 | var partiallyVisiblePosts = items.filter(":in-viewport"); 94 | if (partiallyVisiblePosts.length === 1) { 95 | i = items.index(partiallyVisiblePosts[0]); 96 | } else { 97 | if (i === -1) { 98 | i = items.index(partiallyVisiblePosts[1]); 99 | } else { 100 | i = items.index(partiallyVisiblePosts[0]); 101 | } 102 | } 103 | } 104 | } else { 105 | i = i % items.length; 106 | } 107 | selectedItem = items.css("padding", "5px").css("border", "1px solid " + $("#content").css("backgroundColor")).eq(i); 108 | selectedItem.css("border", "1px dashed black"); 109 | if (!selectedItem.is(":fully-visible")) { 110 | $.scrollTo(selectedItem); 111 | } 112 | curIndex = i; 113 | }; 114 | 115 | var getCharacterCode = function (code) { 116 | var c = null; 117 | switch (code) { 118 | case "ENTER": 119 | c = 13; 120 | break; 121 | case "ESC": 122 | c = 27; 123 | break; 124 | case "?": 125 | c = 191; 126 | break; 127 | default: 128 | c = code.charCodeAt(0); 129 | break; 130 | } 131 | return c; 132 | }; 133 | 134 | var convertCode = function (code) { 135 | var chars = code.split(","); 136 | for (var i = 0; i < chars.length; i++) { chars[i] = getCharacterCode(chars[i]); } 137 | return chars; 138 | }; 139 | 140 | var GlobalShortcuts = { 141 | "home": { code: "G,H", url: "/" }, 142 | "questions": { code: "G,Q", url: "/questions" }, 143 | "tags": { code: "G,T", url: "/tags" }, 144 | "users": { code: "G,U", url: "/users" }, 145 | "badges": { code: "G,B", url: "/badges" }, 146 | "unanswered": { code: "G,N", url: "/unanswered" }, 147 | "ask": { code: "G,A", url: "/questions/ask" }, 148 | "recent": { code: "G,E", url: "/users/recent" }, 149 | "rep audit": { code: "G,R", url: "/reputation" }, 150 | "chat": { code: "G,C", url: "http://chat." + location.hostname }, 151 | "profile": { code: "G,P", url: $("#hlinks-user a").attr("href") }, 152 | "profile summary": { code: "G,S", fn: function () { $(".profile-triangle").mouseover(); } }, 153 | "main vs meta": { code: "G,M", url: "http://" + (location.hostname.indexOf("meta") === 0 ? location.hostname.substring(5) : "meta." + location.hostname) }, 154 | "inbox": { code: "G,I", fn: function () { $("#portalLink .genu").click(); $("#portalLink #seTabInbox").click(); } }, 155 | "help": { code: "?", fn: function () { $("#helpOverlay").jqmShow(); } }, 156 | "close help": { code: "ESC", fn: function () { $(".jqmWindow").jqmHide(); } } 157 | }; 158 | 159 | var setupHelpOverlay = function () { 160 | var styleText = ""; // ".easy-navigation-peekable { -moz-border-radius: 4px 4px 4px 4px;background-color: #000000;border-radius: 4px 4px 4px 4px;color: #F0F0F0;margin-top: 5px;padding-right: 0px !important;position: absolute;z-index: 4;} .easy-navigation-peeked-message: {line-height: 1.5em;padding: 3px 0px 3px 15px;},.easy-navigation-subtle: { color: #D2F7D0;display: block;font-size: 10px;}"; 161 | styleText += ".jqmWindow {display: none;position: fixed;top: 17%;left: 50%;margin-left: -300px;width: 600px;background-color: #EEE;color: #333;border: 1px solid black;padding: 12px;}.jqmOverlay { background-color: #000; } * iframe.jqm {position:absolute;top:0;left:0;z-index:-1;width: expression(this.parentNode.offsetWidth+'px');height: expression(this.parentNode.offsetHeight+'px');}* html .jqmWindow {position: absolute;top: expression((document.documentElement.scrollTop || document.body.scrollTop) + Math.round(17 * (document.documentElement.offsetHeight || document.body.clientHeight) / 100) + 'px');}"; 162 | $('