// ==UserScript== // @name Google+ Tweaks // @description Tweaks to the layout and features of Google+ // @author Jerome Dane // @website http://userscripts.org/scripts/show/106166 // @version 1.113 // // @updateURL https://userscripts.org/scripts/source/106166.meta.js // // @include http://plus.google.com/* // @include https://plus.google.com/* // // @require https://ajax.googleapis.com/ajax/libs/jquery/1.6.2/jquery.min.js // @require https://userscripts.org/scripts/source/106368.user.js // @require https://userscripts.org/scripts/source/106223.user.js // @require https://userscripts.org/scripts/source/112968.user.js // // @history 1.112 Removed hide welcome link option since it's no longer included in the layout // @history 1.111 Fixed removal of links from spark headers when using Markdown parsing // @history 1.11 Fixed copyright footer placement when using fixed navigation // @history 1.11 Fixed number of comments not showing up for closed threads when using toggle comments // @history 1.11 Fixed number of comments messed up on new comments when using toggle comments // @history 1.1 Plugged massive memory leak due to carryover from old polling methods // @history 1.1 Fixed messed up layout of edit profile with fixed navigation enabled // @history 1.1 Added check to prevent memory leaks on failed Markdown parsing of expanded posts // @history 1.1 Added simple standard Chrome extension option page explaining how to set options // @history 1.1 The contents of shared posts are now parsed for Markdown if enabled // @history 1.1 Full width no longer enabled by default for new installs // @history 1.1 Fixed mute button due to G+ DOM change // @history 1.1 Fixed posts being processed too quickly in Chrome // @history 0.035 Fixed inability to disable thumbnails only // @history 0.0341 Quick fix for links on new lines not getting parsed in markdown // @history 0.034 Scrapped full width code and started over to simplify things and get rid of a lot of bugs // @history 0.034 Fixed a lot of fixed navigation errors // @history 0.034 Changed primary post processing method from polling to DOM node insertion event handling // @history 0.033 Fixed mute button due to change in G+ DOM // @history 0.033 Removed a redundant polling option for slight improvement in performance // @history 0.033 Fixed detection of current post, so post shortcuts should work again (M, C, T etc.) // @history 0.033 Fixed tweaks menu not showing up after enabling post previews // @history 0.033 Readability no longer enabled by default for installs // @history 0.033 Fixed profile name position when using full width // @history 0.033 Fixed share box on stream being pushed off to the right making it impossible to close it with the "X" // @history 0.033 Fixed layout of photo albums when using full width // @history 0.033 Fixed comment and post edit boxes having grey background in Firefox // @history 0.033 Fixed Google+ copyright to bottom of window instead of stuck over stream // @history 0.033 Added option to hide Google+ copyright completely // @history 0.033 Fixed extra space above stream when using fixed navigation in Chrome // @history 0.033 Added option to parse Markdown in posts // @history 0.032 Fixed easy mentions // @history 0.032 Fixed inability to disable inline plusses and shares // @history 0.032 Fixed inability plusses not showing up // @history 0.032 Fixed background color occasionally switching back to white when using visibility tweaks // @history 0.032 Updated posts muted class to fix hiding/fading of mute notices // @history 0.031 Fixed a glitch in the options script that was causing the whole thing to fail // @history 0.030 Fixed comments collapsing even when turned off // @history 0.030 Fixed easy mentions not linking in comments // @history 0.029 Fixed mute button // @history 0.029 Fixed left column of photos not staying in place with fixed navigation // @history 0.029 Fixed layout of new incoming shares notice when using full width // @history 0.028 Implemented aggressive auto selector detection to combat DOM and class changes // @history 0.028 Fixed image previews of images across top of profile pages // @history 0.027 Fixed muted posts not hiding correctly // @history 0.026 Fixes for another G+ DOM change // @history 0.026 Changed primary selector method to relative rather than relying on constantly changing class names // @history 0.026 Greatly improved stability and simplicity of full width code // @history 0.026 Fixed full width of stream when hiding entire right column // @history 0.026 Removed dynamic external Flattr script inclusion to increase security. Used simple link instead. // @history 0.025 Yet another massive update of classes to fix G+ DOM changes // @history 0.025 Pressing "M" on the keyboard now mutes the current post if mute button is enabled // @history 0.025 Pressing "C" on the keyboard now toggled comments if toggle comments is enabled // @history 0.025 The document will now scroll back to the top of a post when collapsing long comment threads // @history 0.025 Added readability tweak (enabled by default) // @history 0.025 Fixed "add comment" box flickering when new comments are added while collapsed // @history 0.025 Full width and fixed navigation now apply to games section // @history 0.025 Fixed options dialog showing up under bad when using fixed navigation // @history 0.025 Fixed image for most images // @history 0.025 Changed default for image previews to .5 second delay instead of none // @history 0.025 Updated info in about tab in options // @history 0.025 Moved donate button to the footer of the options dailog and added Flattr button // @history 0.024 Easy mentions enabled by default once again // @history 0.024 Fixed easy mentions not linking to profiles in comments // @history 0.024 Fixed hide right column, hide invites, and hide go mobile // @history 0.023 Fixed easy mentions and simplified its code // @history 0.022 Fixed circles editor not visible when using full width // @history 0.021 Fixes to handle change in Google Plus code // @history 0.021 Fixed stream not jumping to top when clicking stream links with fixed navigation // @history 0.021 Added ability to hide notice of new people sharing with you // @history 0.021 Added collapse comments button to end of post comment lists // @history 0.021 Number of comments now auto-updates when comments are collapsed // @history 0.021 Increased polling interval to 3 seconds from 1 to reduce resource usage // @history 0.021 Fixed Sparks layout in full width mode // @history 0.021 Replaced "mute this post" in incoming feed with mute button when using fast mute // @history 0.020 Added check for /h#/ image resize format for previews // @history 0.020 Imposed max height and width on preview images of window size - 40px // @history 0.019 Quick attempt to fix persistent "all images to preview image" bug // @history 0.019 Fixed preview images stretching off the window to the right // @history 0.018 Fixed broken/buggy image preview // @history 0.018 You can now choose the delay before showing image previews // @history 0.018 Avatars in "added you to circles" notifications now use image previews // @history 0.018 Profile pictures in left column of profiles now use image previews // @history 0.018 Photos in profile pages now use image previews // @history 0.018 Fixed left column width on notifications feed when using full width // @history 0.017 Improve stability of mute button // @history 0.016 Started refactoring all code to make it easier to maintain // @history 0.016 Removed hide post button options (not really used anymore) // @history 0.016 Better control of hiding/fading mute notices // @history 0.016 Ability to fix just the google bar or all navigation // @history 0.016 Attempted to fix posts out of view when using J/K key navigation // @history 0.016 Attempted to fix some fixed navigation issues // @history 0.016 Clicking on streams now auto scrolls back to top when using fixed navigation // @history 0.016 Clicking on buttons in top bar now auto scrolls back to top when using fixed navigation // @history 0.016 Added inline +1 and shares option to clean up the page // @history 0.016 Added toggle comments option // @history 0.015 Improved positioning and stability of image previews // @history 0.015 Increased max size of previews to window height - 40 pixels // @history 0.015 Fixed circles view being unusable with fixed navigation enabled // @history 0.015 Added easy mentioning [+] symbol after profile names (enabled by default) // @history 0.015 "Add to circles" and "in circles" button now fixed on profile pages for fixed nav (thanks Matt Kruse) // @history 0.015 Fixed inability to scroll profile columns when using fixed navigation and small windows (thanks Terry May) // @history 0.014 Added fast mute option to add a mute button and remove muted notice // @history 0.014 Removed post buttons option // @history 0.013 Added favicon badge to show number of new notices // @history 0.012 Added option to fix navigation elements // @history 0.011 Fixed stretching of images in previews to 400px when the original is smaller // @history 0.011 Preview images now work on hangout notice avatars // @history 0.010 Prevented execution of script within iFrames such as notifications preview popup // @history 0.010 Increased the width of rollover previews to 400 from 300 (may be a setting eventually) // @history 0.010 Image rollover preview will now reposition itself to stay within the viewable window // @history 0.010 Image rollover preview now works on avatars in posts // @history 0.010 Image rollover preview now works on avatars in vCards (little person previews) // @history 0.010 Fixed handling of preview images with sz=## in the source // @history 0.009 Added rollover previews to images in posts // @history 0.009 Added MIT license to source // @history 0.008 Display post actions as buttons instead of a dropdown menu (disabled by default) // @history 0.008 Added a "Hide" tab to the options window to allow hiding Google+ elements // @history 0.008 Added a donate button to the about tab of the options window // @history 0.007 Yellow "post muted" notices will now fade out after a few seconds // @history 0.006 Fixes to Thumbnails Only feature // @history 0.005 Images in posts can now forced to thumbnails // @history 0.004 Added options dialog with link in Google settings menu // @history 0.004 Full width feature can now be disabled // @history 0.003 Fixed header not left aligned in Google Chrome // @history 0.003 Notifications view is now full width // @history 0.003 Included jQuery UI in anticipation of next version // @history 0.003 Included Script Options in anticipation of next version // @history 0.002 Profiles are now full width as well // @history 0.001 Initial release // // ==/UserScript== // License Creative Commons Attribution 3.0 Unported License http://creativecommons.org/licenses/by/3.0/ // // Copyright (c) 2011 Jerome Dane // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the "Software"), // to deal in the Software without restriction, including without limitation the // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or // sell copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A // PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF // CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE // OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. var version = 1.113; var debugSelectors = false; var debugAlign = 'right'; // 'left' or 'right' var selectors = { content: '#content', contentPane: '#contentPane', googleBar: '#gb', toolBar: '#notify-widget-pane + div + div + div' }; var s = selectors; var dynamicSelectors = {}; function detectWidthRestrictor() { var widthInhibiterWidthPattern = /574/; // selectorWidthRestrictor var inhibiterSelctor = ''; $(s.contentPane + ' *[width=574]').each(function() { // determine classes of width inhibitor var classes = this.className.split(" "); // test for specific width inhibitor class for(var i = 0; i < classes.length; i++) { var selector = '.' + classes[i]; if($(selector).css('width').match(widthInhibiterWidthPattern)) inhibiterSelctor += selector + ', '; } }); if(inhibiterSelctor != '') { Config.set('selectorWidthRestrictor', inhibiterSelctor.replace(/,\s*$/, '')); document.location = document.location; } } function detectIncomingWrapper() { var incomingNotice = $('a[href*="/stream/incoming"]', s.streamContent); if(incomingNotice.size() > 0) { var noticeWrapper = incomingNotice.parent().parent(); var str = '.' + noticeWrapper.attr('class').replace(/\s+/g, '.'); compareAndSaveSelector('selectorStreamIncomingNotice', str); } } function compareAndSaveSelector(key, val) { if(val.match(/\.[^\.]+\./) && Config.get(key) != val) { Config.set(key, val); // save the new selector // update width inhibiter if(key == 'selectorStreamContent') { detectWidthRestrictor(); detectIncomingWrapper(); } document.location = document.location; // refresh the page } } function updateSelectors() { if($(s.contentPane).size() == 1) { var str = '.' + $(s.content + ' > div:first-child').attr('class').replace(/\s+/g, '.'); compareAndSaveSelector('selectorMainWrapper', str); // main stream if(document.location.toString().match(/plus\.google\.com\/(stream)?$/)) { var str = '.' + $(s.contentPane).attr('class').replace(/\s+/g, '.'); compareAndSaveSelector('selectorStreamContent', str); if(!Config.get('selectorWidthRestrictor').match(/^\./)) detectWidthRestrictor(); if(!Config.get('selectorStreamIncomingNotice').match(/^\./)) detectIncomingWrapper(); } // notifications if(document.location.toString().match(/\/notifications\//)) { var str = '.' + $(s.contentPane).attr('class').replace(/\s+/g, '.'); compareAndSaveSelector('selectorStreamNotificationCol', str); } // photos if(document.location.toString().match(/plus\.google\.com\/photos/)) { var str = '.' + $(s.contentPane).attr('class').replace(/\s+/g, '.'); compareAndSaveSelector('selectorPhotos', str); } // user profile if(document.location.toString().match(/plus\.google\.com\/\d+\//)) { var str = '.' + $(s.contentPane).attr('class').replace(/\s+/g, '.'); compareAndSaveSelector('selectorProfile', str); } } } function populateSelectors() { // class used to restrict content width when viewing stream after another section like a profile or photos. Usually a 2 character code s.widthRestrictor = Config.get('selectorWidthRestrictor'); s.chatRoster = '#oz-chat-roster'; s.postCommentEditorMention = 'f-IE'; s.stream = s.content + ' ' + Config.get('selectorMainWrapper'); s.streamLeftCol = s.stream + ' > div:first-child'; s.streamContent = s.stream + ' ' + Config.get('selectorStreamContent'); // #contentPane *class only* when in stream view (don't include ID) s.streamShareWrapper = s.streamContent + ' > div:first-child > div:first-child + div'; s.streamIncomingNotice = s.streamContent + ' ' + (Config.get('selectorStreamIncomingNotice') != '' ? Config.get('selectorStreamIncomingNotice') : '.nope'); s.streamRightCol = s.streamContent + ' + div' s.streamLinksWrapper = s.streamLeftCol + ' > div:first-child > div:first-child + div > div:first-child'; s.streamWelcomeLink = s.streamLinksWrapper + ' > h2 + div > a:first-child'; s.streamRightColSuggestions = s.streamRightCol + ' > div:first-child > div:nth-child(2)'; s.streamRightColSendInvites = s.streamRightCol + ' > div:first-child > div:nth-child(3)'; s.streamNotificationCol = s.stream + ' ' + (Config.get('selectorStreamNotificationCol') != '' ? Config.get('selectorStreamNotificationCol') : '.nope'); // When in notifications: #contentPane *class only* > div:first-child s.incomingPostedBy = ' .Ov.GB'; s.incomingPostedByAddToCircles = s.incomingPostedBy + ' > div:first-child > div:first-child'; s.incomingPostedByMutePostButton = s.incomingPostedBy + ' > div:first-child > div:first-child + [role="button"]'; s.sparksSearchButton = s.streamNotificationCol + ' .d-q-p.j-e.j-e-Y.l2.e1'; s.sparksAddInterestButton = s.streamNotificationCol + ' .b-o-l.g-d.g-d-R.Oba.B9'; s.profile = s.content + ' ' + (Config.get('selectorProfile') != '' ? Config.get('selectorProfile') : '.nope'); s.profileContent = s.profile + ' .vcard > div:first-child'; s.profileLeftCol = s.profile + ' .a-e-b-Ob-ac.a-b-Ob-ac'; s.post = 'div[id^="update"]'; s.postSelected = s.post + '.rh'; // you just have to figure out what class they're using dynamicSelectors.postBody = s.post + ' > div:first-child > div:nth-child(2)'; // dynamically detected dynamicSelectors.postCommentButton = s.post + ' > div:first-child > div:nth-child(2) > div:nth-child(2) > span:nth-child(2)'; dynamicSelectors.postShareButton = s.post + ' > div:first-child > div:nth-child(2) > div:nth-child(2) > span:nth-child(3)'; dynamicSelectors.postPlussesAndShares = s.post + ' > div:first-child > div:nth-child(2) > div:nth-child(3)'; // wrapper for the plusses and shares dynamicSelectors.postPlussesWrapper = s.post + ' > div:first-child > div:nth-child(2) > div:nth-child(3) > div:first-child'; dynamicSelectors.postPlusses = s.post + ' > div:first-child > div:nth-child(2) > div:nth-child(3) > div:first-child > span:first-child '; dynamicSelectors.postSharesWrapper = s.post + ' > div:first-child > div:nth-child(2) > div:nth-child(3) > div:nth-child(2)'; dynamicSelectors.postShares = s.post + ' > div:first-child > div:nth-child(2) > div:nth-child(3) > div:nth-child(2) > span:first-child'; dynamicSelectors.postCommentsWrapper = s.post + ' > div:first-child > div:nth-child(3)'; dynamicSelectors.postCommentsNumBar = s.post + ' > div:first-child > div:nth-child(3) > div:first-child'; dynamicSelectors.postButton = s.post + ' > div:first-child > div:first-child > span'; s.postMuteButton = Config.get('selectorMuteButton') != '' ? Config.get('selectorMuteButton') : '.nope'; s.postMutedNotice = '.En';//s.post + (Config.get('selectorPostMutedNotice') != '' ? Config.get('selectorPostMutedNotice') : '.nope'); s.postPlusOneButton = ' button[g\\:entity^="buzz"]'; s.postImages = 'img[src*="googleusercontent"]'; s.photosWrapper = (Config.get('selectorPhotos') != '' ? Config.get('selectorPhotos') : '.nope'); s.photosLeftCol = s.photosWrapper + ' > div:first-child > div:first-child'; s.photosContent = s.photosWrapper + ' > div:first-child > div:first-child'; s.photosRightCol = s.photosWrapper + ' > div:first-child > div:nth-child(3)'; s.sendFeedback = 'a[onclick*="appfeedback"]'; s.profileLink = 'a[oid]'; s.userContentImages = 'img[src*="googleusercontent.com"]'; if(debugSelectors && $(s.content).size() > 0) { var possibleColors = ['pink', 'orange', 'yellow', 'green', 'blue', 'purple', 'gray', 'brown', 'red']; var colors = {}; setTimeout(function() { var html = '<div id="bcGPTksSelectorDebug" style="opacity:.9; position:absolute; top:100px; ' + debugAlign + ':0; font-weignt:normal; padding:1em; border:1px solid #000; background:#333; z-index:100;">'; html += '</div>'; function debugSelectors() { var html = '<p>Selectors Found:</p>';s for(var x in selectors) { var elem = $(selectors[x]) var numFound = elem.size(); // generate a random color to identify the element if(typeof(colors[x]) == 'undefined') colors[x] = possibleColors[Math.floor(Math.random() * possibleColors.length)]; var color = colors[x]; elem.css('background', color); elem.attr('title', x); html += '<span style="color:' + (numFound == 0 ? 'black' : color + '; font-weight:bold') + ';">' + x + ' ' + numFound + '</span><br/>'; } $('#bcGPTksSelectorDebug').html(html); } debugSelectors(); setInterval(debugSelectors, 3000); $('body').append(html); }, 2000); } } var playVideoIconSelector = '.ea-S-ei'; var profileColumnSelector = '.a-b-c-ka-Mc.a-c-ka-Mc' var profileLinkSelector = 'a[oid]'; var rightColSelector = '.a-b-Cs-T.a-Cs-T.d-s-r'; var suggestionsSelector = '.a-b-j-lc-Rd-A'; var sharedByIncomingNoticeSelector = '.a-f-i-Jf-Om.a-b-f-i-Jf-Om'; var vCardSelector = 'table.a-ia-ta'; var vcardAvatarSelector = vCardSelector + ' img.a-ia-tk[src*="googleusercontent"]'; var vcardAvatarInCommonSelector = vCardSelector + ' img.a-ia-Bq[src*="googleusercontent"]'; var previewHeightMax = $(window).height() - 40; var previewWidthMax = previewHeightMax; var currentPreviewTarget = false; function eventIsFromTyping(e) { if(e.target.nodeName.match(/input/i)) return true; if(e.target.className.match(/editable/)) return true; return false; } function GTweaks() { var self = this; this.css = ''; this.donateButtonsHtml = '<a href="https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=773DHSKBK7PXQ" title="Donate through PayPal to support Google+ Tweaks"><img src="https://www.paypalobjects.com/en_US/i/btn/btn_donate_SM.gif" /></a> ' + '<a href="http://flattr.com/thing/371262/Google-Tweaks" target="_blank"><img src="http://api.flattr.com/button/flattr-badge-large.png" alt="Flattr Google+ Tweaks" title="Flattr this" border="0" /></a>'; this.pollInterval = 3000; // in milliseconds this.pollFuncions = []; this.version = version; this.muteButtonClassFound = false; this.options = { "General":{ "faviconBadge":{ label:'Favicon Badge', type:'checkbox', description:'See the number of new alerts in your favicon', 'default':true }, "fullWidth":{ label:'Full Width', type:'checkbox', description:'View Google+ in the full width of your browser', 'default':false }, "fixedNav":{ label:'Fixed Navigation', type:'checkbox', description:'Pin navigational elements so they stay visible', 'default':'none' }, "readability":{ label:'Readability', type:'checkbox', description:'Improve readibility by shifting the focus to posts', 'default':false }, "postPreviews":{ label:'Post Previews', type:'checkbox', description:'Show posts as previews. Click or press "T" to expand', 'default':false }, "muteButton":{ label:'Mute Button', type:'checkbox', description:'Add button to posts for one click muting', 'default':true }, "muteNotices":{ label:'Mute Notices', type:'select', options:{ none:'Do nothing', fade:'Fade out', hide:'Hide' }, description:'Treatment of "post muted" notices', 'default':'none' }, "easyMentions":{ label:'Easy Mentions', type:'checkbox', description:'Add links next to names in posts to easily mention them', 'default':true }, "markdown":{ label:'Parse Markdown', type:'checkbox', description:'Parse <a href="http://en.wikipedia.org/wiki/Markdown" target="_blank">markdown</a> in posts and comments', 'default':true }, "comments":{ label:'Toggle Comments', type:'checkbox', description:'Collapse post comments with click to expand', 'default':false }, "inlinePlusShare":{ label:'Inline +1/shares', type:'checkbox', description:'Show the number of +1s and shares inline', 'default':true }, "thumbsOnly":{ label:'Thumbnails Only', type:'checkbox', description:'Force all images in posts to thumbnails', 'default':false }, "imagePreviews":{ label:'Image Previews', type:'select', options:{ 'none':'Disabled', '0':'No delay', '500': '0.5 second delay', '1000': '1 second delay', '2000': '2 second delay' }, description:'Show image preview on rollover', 'default':'500' }, 'seeAbout':{ type:'html', text:'<div><br/></div>' + '<p>More features coming soon. See <strong>About</strong> tab for more details information.</p>' } }, "Hide":{ "hideChatRoster":{ label:'Chat List', type:'checkbox', description:'Hide the chat list in the left column' }, "hideIncomingNotice":{ label:'Incoming Notice', type:'checkbox', description:'Hide "# new people are sharing with you" notice' }, "hideSendFeedback":{ label:'Send Feedback', type:'checkbox', description:'Hide the send feedback link in the borrom right' }, "hidePlusMention":{ label:'Mention Prefix', type:'checkbox', description:'Hide the "+" symbol before mentions in posts' }, "hideRightColLabel":{ type:'html', text:'<p"> </p>' }, "hideRightCol":{ label:'Right Column', type:'checkbox', description:'Hide the entire right column' }, "hideSuggestions":{ label:'Suggestions', type:'checkbox', description:'Hide suggestions of people to follow' }, "hideSendInvites":{ label:'Send Invites', type:'checkbox', description:'Hide the "Send Invites" section' }, "hideCopyrightLabel":{ type:'html', text:'<p"> </p>' }, "hideCopyright":{ label:'Copyright', type:'checkbox', description:'Hide Google+ copyright footer' } }, "Selectors":{ "selectorsIntroHtml":{ type:'html', text:'<p>This tab is for advanced users only. The Google+ DOM and element classes are constantly changing. These settings are an attempt to combat these changes and create some stability for end-users.</p>' + '' }, "selectorAutoUpdate":{ label:'Auto Update', type:'checkbox', description:'Automatically detect and update selectors below', 'default':true }, "selectorWidthRestrictor":{ label:'Width Restrictors', type:'text', description:'Classes that restrict width', 'default':'' }, "selectorMainWrapper":{ label:'Main Wrapper', type:'text', description:'Contains main content', 'default':'.a-o-I-R.a-o-I-R-jk-Yb' }, "selectorStreamContent":{ label:'Content', type:'text', description:'Main stream content wrapper', 'default':'.a-o-I-R.a-o-I-R-jk-Yb' }, "selectorStreamIncomingNotice":{ label:'Incoming Notice', type:'text', description:'New incoming wrapper', 'default':'' }, "selectorStreamNotificationCol":{ label:'Notifications', type:'text', description:'Notifications content wrapper', 'default':'' }, "selectorPhotos":{ label:'Photos', type:'text', description:'Photos wrapper', 'default':'' }, "selectorProfile":{ label:'Profile', type:'text', description:'Profile wrapper', 'default':'' }, "selectorMuteButton":{ label:'Mute Button', type:'text', description:'Post menu mute item', 'default':'.a-Q-j.b-O.a-Q-j-xd.Fj.cg' }, "selectorManualNoticeHtml":{ type:'html', 'text':'<p>The following must be updated manually for now.</p>' }, "selectorCurrentPost":{ label:'Current Post', type:'text', description:'Highlighted/active post', 'default':'.ki' }, "selectorPostMutedNotice":{ label:'Muted Notice', type:'text', description:'Post muted notice', 'default':'.Un' } }, "Issues & Planned Features":{ 'todo':{ type:'html', text:'' + '<p>If you find something that has not been addressed, please submit it to <a href="http://profiles.google.com/JeromeDane">Jerome Dane</a>. To report issues and bugs, please include screenshots of your options window, as well of the resulting behavior in Google+. To request a new feature, please be as specific as possible, and include a Photoshop mockup of what you would like if you can make one.</p>' + '<iframe src="http://blankcanvas.me/googleplustweaksissues.html" style="width:100%; border:none; height:300px;"></iframe>' } }, "About":{ 'about':{ type:'html', text:'' + '<p style="margin-top:0;"><strong><a href="http://userscripts.org/scripts/show/106166">' + 'Google+ Tweaks</a></strong> v' + self.version + ' by <a href="http://jeromedane.com">Jerome Dane</a></p>' + '<p>Tweaks to the layout and features of Google+</p>' + '<p>This project is maintained as a hobby. All real-life obligations take precedence. Take it or leave it.</p>' + '<p>I don\'t get paid for this, and do all of this in what little free time I have. If you like these tweaks, show your support by buying me a cup of coffee ☺.</p>' + '<p>' + '<a href="https://plus.google.com/107905455800180378660/posts">' + '<img style="display:inline-block; vertical-align:middle;" src="https://lh3.googleusercontent.com/-IvtEWk93sCM/ThitYp1QudI/AAAAAAAAAGY/piLimw2CogM/google_plus_badge_32.png"/>' + '</a>' + ' For updates, <a href="https://plus.google.com/107905455800180378660/posts">follow me on Google+</a>' + '</p>' } } }; this.features = { markdown:{ processPost:function(post) { if(Config.get('markdown') && !post.className.match(/mdParsed/)) { post.className += ' mdParsed'; function parseMarkdown(elem) { if(!elem.className.match(/mdParsed/)) { elem.className = "mdParsed"; var converter = new Showdown.converter(); elem.innerHTML = elem.innerHTML .replace(/<\/?i><\/?b>/g, "***") .replace(/<\/?b><\/?i>/g, "***") .replace(/<\/?b>/g, "__") .replace(/<\/?i>/g, "*") .replace(/<br>/g, "\n"); //.replace(/"http/g, '"_ttp') // return Google+ translated links back to plain text $('a', elem).each(function() { if(this.className != 'proflink' && this.innerHTML.match(/^http/)) { $(this).after($(this).text()); $(this).remove(); } }); //var text = $(elem).text(); var text = elem.innerHTML; // this produces generally bad results // text.replace(/>/g, '>'); text = converter.makeHtml(text); // reparse unparsed URLs to links var startingLinks = /^(\b(https?|ftp|file):\/\/[\-A-Z0-9+&@#\/%?=~_|!:,.;]*[\-A-Z0-9+&@#\/%=~_|])/ig; text = text.replace(startingLinks, '<a href="$1">$1</a>'); var embeddedLinks = /(>|\s|[a-z0-9]|\(|\[|\{)(\b(https?|ftp|file):\/\/[\-A-Z0-9+&@#\/%?=~_|!:,.;]*[\-A-Z0-9+&@#\/%=~_|])/ig; text = text.replace(embeddedLinks, '$1<a href="$2">$2</a>'); elem.innerHTML = text; } } $('div:first-child > div:first-child + div > div:first-child > div:first-child > div:first-child', post).each(function() { var postBody = this; parseMarkdown(postBody); // try to parse shared posts var sharedPostBody = null; try { sharedPostBody = $(postBody).parent().next().next()[0].querySelector('div:first-child > div:first-child > div:first-child'); parseMarkdown(sharedPostBody); $('span[role="button"]', $(sharedPostBody).parent()).click(function() { var numTries = 0; function check() { numTries++; var quotedBody = $(postBody).parent().next().next()[0].querySelector('div:first-child > div:first-child + div > div:first-child'); if($(quotedBody).text() != '') { parseMarkdown(quotedBody); } else { if(numTries < 20) { setTimeout(check, 100); } } } setTimeout(check, 100); }); } catch(e) {} $('span[role="button"]', $(this).parent()).click(function() { var parent = $(this).parent(); var targetOne = $('div:first-child', $(this).parent().parent()); var numTries = 0; function check() { numTries++; if(targetOne.text() != '') { parseMarkdown($('div:first-child', parent.next())[0]); } else { if(numTries < 20) { setTimeout(check, 100); } } } setTimeout(check, 100); }); // todo: listen for change to body and re-parse after edit save }); } } }, favicon: { src: 'data:image/x-icon;base64,AAABAAEAEBAAAAEAIABoBAAAFgAAACgAAAAQAAAAIAAAAAEAIAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AJubm3guLi7/Ly8v/zExMf8yMjL/MjIy/zMzM/8yMjL/MjIy/zExMf8vLy//Li4u/y0tLf+enp50////AP///wAvLy//MTEx/zMzM/80NDT/NTU1/zY2Nv8mJib/JiYm/zU1Nf80NDT/MjIy/zAwMP8uLi7/LS0t/////wD///8AMjIy/zQ0NP82Njb/ODg4/zo6Ov87Ozv/09HR/9PR0f86Ojr/ODg4/zY2Nv80NDT/MjIy/y8vL/////8A////ADU1Nf84ODj/Ozs7/z09Pf8/Pz//QEBA/+Pi4v/j4uL/Pz8//z09Pf87Ozv/ODg4/zU1Nf8yMjL/////AP///wA5OTn/PDw8/z8/P/8tLS3/Ly8v/y8vL//o5+f/6Ofn/y8vL/8tLS3/LCws/zw8PP85OTn/NTU1/////wD///8APDw8/0BAQP9CQkL/7ezs/+3s7P/t7Oz/7ezs/+3s7P/t7Oz/7ezs/+3s7P8/Pz//Ozs7/zg4OP////8A////AD8/P/9CQkL/RUVF//Hx8f/x8fH/8fHx//Hx8f/x8fH/8fHx//Hx8f/x8fH/QkJC/z4+Pv86Ojr/////AP///wBAQED/RERE/0dHR/9JSUn/SUlJ/0lJSf/29fX/9vX1/0lJSf9JSUn/R0dH/0RERP9AQED/PDw8/////wD///8AQkJC/0ZGRv9ISEj/SUlJ/0lJSf9JSUn/+fn5//n5+f9ISEj/SEhI/0dHR/9GRkb/QkJC/z4+Pv////8A////AENDQ/9HR0f/SUlJ/0lJSf9JSUn/SUlJ//39/f/9/f3/SUlJ/0lJSf9JSUn/R0dH/0NDQ/8/Pz//////AP///wBERET/R0dH/0lJSf9JSUn/SUlJ/0lJSf9JSUn/SUlJ/0lJSf9JSUn/SUlJ/0dHR/9DQ0P/Pz8//////wD///8ANzc3/zo6Ov88PDz/PDw8/zw8PP88PDz/PDw8/zw8PP88PDz/PDw8/zw8PP86Ojr/Nzc3/zQ0NP////8A////AA8P1f8PD9X/Dw/V/+hpM//oaTP/6Gkz/+hpM/8lmQD/JZkA/yWZAP8mkwP/EbLu/xGy7v8Rsu7/////AP///wCHh+p+Dw/V/w8P1f/oaTP/6Gkz/+hpM//oaTP/JZkA/yWZAP8lmQD/JpQE/xGy7v8Rsu7/jNr2ef///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A//8AAMADAACAAQAAgAEAAIABAACAAQAAgAEAAIABAACAAQAAgAEAAIABAACAAQAAgAEAAIABAADAAwAA//8AAA%3D%3D', icon: null, init: function() { var icon = new FavIcon(self.features.favicon.src); icon.foreground = '#fef4ac'; icon.background = '#bb0000'; self.features.favicon.icon = icon; self.features.favicon.checkForNotices(); }, checkForNotices: function() { var numNotices = $('#gbgs1').text(); if(numNotices.toString().match(/^\d+$/) && numNotices.toString() != '0') { self.features.favicon.icon.set(numNotices); } else { self.features.favicon.icon.set(); } setTimeout(self.features.favicon.checkForNotices, 5000); } }, fixedNavigation: { init: function() { var leftWidth = 188; var rightColOffset = ($(window).width() / 2) + 280; function fixGbar(height) { self.addStyle(selectors.googleBar + ' { position:fixed; top:0; width:100%; } #content { position:absolute; top:90px; }'); //$(selectors.googleBar).parent().after('<div style="height:' + height + 'px;"> <div>'); } if(Config.get('fixedNav')) { fixGbar(navigator.userAgent.match(/chrome/i) ? 0 : 29); self.addStyle(selectors.toolBar + ' { position:fixed; top:30px; z-index:1000; }' + selectors.googleBar + ' { z-index:1200; }' + // stream view selectors.streamLeftCol + ' { position:fixed; top:90px; width:' + (leftWidth + 20) + 'px; height:' + ($(window).height() - 90) + 'px; overflow-y:auto; overflow-x:hidden; width:188px;}' + selectors.streamContent + ' { position:absolute; top:0; left:' + (leftWidth + 10) + 'px; }' + selectors.streamRightCol + ' { position:fixed !important; top:90px !important; ' + (Config.get('fullWidth') ? 'right:0;' : 'left:' + rightColOffset + 'px;') + ' }' + selectors.streamNotificationCol + ' { position:absolute; left:' + (leftWidth) + 'px; }' + // profile (normal) selectors.profileContent + ' > div:first-child + div + div > div:first-child + div + div { position:fixed; top:90px; left:0; height:' + ($(window).height() - 90) + 'px; overflow-y:auto; overflow-x:hidden; }' + // profile (with pics) selectors.profileContent + ' > div:first-child + div + div + div > div:first-child + div + div { position:fixed; top:90px; left:0; height:' + ($(window).height() - 90) + 'px; overflow-y:auto; overflow-x:hidden; }' + // profile (verified with pics - scoble) selectors.profileContent + ' > div:first-child + div + div + div + div > div:first-child + div + div { position:fixed; top:90px; left:0; height:' + ($(window).height() - 90) + 'px; overflow-y:auto; overflow-x:hidden; }' + // personal profile selectors.profileContent + ' > div:first-child + div + div + div + div + div > div:first-child + div + div + div { position:fixed; top:90px; left:0; height:' + ($(window).height() - 90) + 'px; overflow-y:auto; overflow-x:hidden; }' + selectors.profileContent + ' > div:first-child + div + div + div + div + div > div:first-child + div + div + div[role="tabpanel"] { top:300px; position: absolute; height:inherit; left:260px; }' + // footer/copyright '#content + div { position:fixed; bottom:0; background:#' + (Config.get('readability') ? 'f1f1f1' : 'fff') + '; }' + '#content + div * { line-height:16px; height:16px; margin-top:0; margin-bottom:0; }' + '' ); // make the doc scroll to the top on top grey bar button click $(selectors.streamLinksWrapper).click(function() { $(document).scrollTop(0); }); } } }, fullWidth: { init: function() { if(Config.get('fullWidth')) { var s = selectors; var docWidth = $(window).width(); var streamLeftColWidth = 188; var streamRightColWidth = Config.get('hideRightCol') ? 0 : 205; var profileLeftColWidth = 232; self.addStyle( // stream s.stream + ' { width:' + (docWidth - 20) + 'px; } ' + s.streamRightCol + ' { position:absolute; right:10px; top:0; }' + s.streamContent + ' { width:' + (docWidth - streamLeftColWidth - streamRightColWidth - 22) + 'px; }' + s.streamShareWrapper + ' { width:100% !important; }' + s.streamContent + ' > div:first-child > div { margin-left:0; margin-right:0; }' + // notifications s.streamNotificationCol + ' { width:' + (docWidth - streamLeftColWidth - 25) + 'px !important; }' + // profile s.profileContent + ' { width:' + (docWidth - 260) + 'px !important; }' + ''); } }, adjustNotificationView:function() { var s = selectors; var docWidth = $(window).width(); var leftColWidth = 188; // notifications stream if(document.location.toString().match(/\/notifications\//)) { var basePath = s.streamNotificationCol + ' > div > div'; $(basePath).css('width', '100%'); $(basePath + ' > div:first-child + div > div[data-nid] > div').css('width', docWidth - leftColWidth - 50); } }, onViewChange:function() { self.features.fullWidth.adjustNotificationView(); //stream view (reposition and expand width of share box) if(document.location.toString().match(/\/$/) || document.location.toString().match(/\/stream\//)) { var base = '#contentPane > div > div > div'; $(base).css('margin-left', '0'); $(base).css('width', '100%'); } }, processPost:function() { // doesn't actually process a post. Uses new posts as a trigger to resize required elements self.features.fullWidth.adjustNotificationView(); } }, inlinePlusShare: { init: function() { if(Config.get('inlinePlusShare')) { self.addStyle( selectors.postPlusOneButton + ' { margin-right:.5em; } ' + selectors.postPlussesAndShares + ' { display:none !important; position:absolute; top:0; left:0; visibility:hidden; }' ); } }, processPost:function(post) { if(Config.get('inlinePlusShare')) { var postBody = getPostBody(post); var buttonsWrapper = $(postBody).next()[0]; var statWrapper = $(postBody).next().next()[0]; if(statWrapper) { //statWrapper.style.display = 'none'; var plussesWrapper = statWrapper.querySelector('div:first-child'); if(plussesWrapper) { var plusses = plussesWrapper.querySelector('div:first-child'); var plusButton = buttonsWrapper.querySelector('button'); $(plusButton).after(plusses); } var sharesWrapper = statWrapper.querySelector('div:first-child + div'); if(sharesWrapper) { try { // there isn't always a share button (e.g. locked albums etc.) var shares = sharesWrapper.querySelector('span:first-child'); var shareButton = buttonsWrapper.querySelectorAll('span[role="button"]')[1]; $(shareButton).after(shares); $(shareButton).after(' ('); $(shares).after(')'); $(shares).html($(shares).html().replace(/[^\d]/g, '') + ''); } catch(e) {} } $(statWrapper).before('<div style="display:none;" class="statWrapHider"> </div>'); $('.statWrapHider', post).append(statWrapper); } } } }, imagePreviews: { wrapper: null, init: function() { if(Config.get('imagePreviews') != 'none' && $('#gb').size() > 0) { var previewDiv = document.createElement('div'); previewDiv.id = 'bcGplusTweaksPreview'; previewDiv.innerHTML = '<div class="loading"> </div><img id="bcGPTwksPrvImg" src=""/>'; $(previewDiv).mousemove(self.features.imagePreviews.reposition); $('body').append(previewDiv); self.features.imagePreviews.wrapper = previewDiv; self.addStyle( '#bcGplusTweaksPreview { ' + 'border:2px solid red; position:absolute; z-index:5000; top:0; left:0; display:none;' + 'max-width:' + previewWidthMax + 'px; border:4px double #333; background:#fff; box-shadow: 0 5px 8px rgba(0, 0, 0, 0.6);' + '}' + '#bcGplusTweaksPreview img { ' + 'margin-bottom:-3px; max-width:' + previewWidthMax + 'px; display:none; ' + 'max-height:' + ($(window).height() - 40) + 'px; ' + 'max-width:' + ($(window).width() - 40) + 'px; ' + '}' + '#bcGplusTweaksPreview .loading { ' + 'background:url(https://lh4.googleusercontent.com/-6CrxAryPl6o/TigTcQV2HmI/AAAAAAAAAQg/tFbeZcP4Mro/loading.gif) no-repeat center; height:50px; width:50px; }' ); } }, loaded:{}, mousePos: null, reposition: function() { var previewDiv = self.features.imagePreviews.wrapper; var e = self.features.imagePreviews.mousePos; $(previewDiv).css('left', e.pageX + 30); $(previewDiv).css('top', e.pageY - 30); var isLoading = $('.loading', previewDiv).css('display') == 'block'; var h = isLoading ? 50 : $('#bcGPTwksPrvImg').height(); var w = isLoading ? 50 : $('#bcGPTwksPrvImg').width(); var bottom = parseInt($(previewDiv).css('top').replace(/[^\d]/, '')) + h - $(document).scrollTop(); var right = parseInt($(previewDiv).css('left').replace(/[^\d]/, '')) + w; if(right > $(window).width()) { if(e.pageX - w - 30 > 10) { $(previewDiv).css('left', e.pageX - w - 30); } else { $('#bcGPTwksPrvImg').width($(window).width() - e.pageX - 60); } } if(bottom > $(window).height()) { $(previewDiv).css('top', $(window).height() - 20 - h + $(document).scrollTop()); } }, enableTarget: function(selector) { var maxHeight = $(window).height() - 40; var previewDiv = self.features.imagePreviews.wrapper; $(selector).each(function() { if($(playVideoIconSelector, $(this).parent()).size() == 0) { // no preview for videos var img = this; if($(img).attr('rel') != 'bcGplusTwImgPrvw') { $(img).attr('rel', 'bcGplusTwImgPrvw'); if($(img).width() < 500 && $(img).height() < 400) { // no preview for images that are already large $(img).mouseover(function(e) { self.features.imagePreviews.mousePos = e; self.features.imagePreviews.reposition(); currentPreviewTarget = img; setTimeout(function() { if(currentPreviewTarget) { $('.loading', previewDiv).show(); $('#bcGPTwksPrvImg').hide(); $(previewDiv).fadeIn('fast'); var src = currentPreviewTarget.src.replace(/\/w\d+[^\/]+\//, '/w' + previewWidthMax + '/'); src = src.replace(/\/s\d+[^\/]+\//, '/'); src = src.replace(/\/h\d+[^\/]+\//, '/h' + maxHeight + '/'); src = src.replace(/resize_w=\d+/, ''); src = src.replace(/sz=\d+/, ''); src = src.replace(/resize_h=\d+/, ''); src = src.replace(/&$/, ''); $('#bcGPTwksPrvImg').load(function() { if(currentPreviewTarget) { function showImage() { $('.loading', previewDiv).hide(); $('#bcGPTwksPrvImg').show(); self.features.imagePreviews.reposition(); self.features.imagePreviews.loaded[src] = true; } if(typeof(self.features.imagePreviews.loaded[src]) != 'undefined') showImage(); else setTimeout(showImage, 500); } }); $('img', previewDiv).attr('src', src); } }, parseInt(Config.get('imagePreviews'))); }); $(img).mouseout(function() { currentPreviewTarget = false; $(previewDiv).fadeOut('fast', function() { $('#bcGPTwksPrvImg').attr('src', ''); }); }); $(img).mousemove(function(e) { self.features.imagePreviews.mousePos = e; currentPreviewTarget = this; self.features.imagePreviews.reposition(e); }); } } } }); }, processPost:function() { self.features.imagePreviews.enableTarget(selectors.userContentImages); } }, comments: { init: function() { if(Config.get('comments')) { self.addStyle( selectors.post + ' .bcGTweaksNumComments { margin-left:.5em; cursor:pointer; }' + '.bcGTweaksNumComments:hover { text-decoration:underline; }' + '.bcGPlusTwCollapseComments { cursor:pointer; height:20px; border:1px solid #ccc; background:#FBFBFB url(https://lh5.googleusercontent.com/-rHmDn0yOCqw/TjxKkh6qsnI/AAAAAAAAAVs/IXdBYIlby2k/bullet-arrow-up-icon.png) center no-repeat; margin-bottom:10px; }' + '.bcGPlusTwCollapseComments:hover { background-color:#eee; }' + '.bcTweaksHiddenComments + div { display: none; }' ); // press "M" to mute the current post $('body').keyup(function(e) { if(!eventIsFromTyping(e)) { switch(e.which.toString()) { case '67': // "C" var currentPost = $(selectors.postSelected); var numCommentsButton = $('.bcGTweaksNumComments', currentPost); if(numCommentsButton.size() > 0) { simulateClick(numCommentsButton[0]); } break; } } }); } }, processPost: function(post) { if(Config.get('comments')) { try { var hasBeenCollapsed = false; var postBody = getPostBody(post); var commentsWrapper = $(postBody).parent().next()[0]; var _comments = commentsWrapper; var buttonsWrapper = $(postBody).next()[0]; var spanButtons = buttonsWrapper.querySelectorAll('span[role="button"]'); var postComment = spanButtons[0]; if(spanButtons.length < 2) { var postComment = document.createElement('span'); $(spanButtons[0]).before(postComment); $(postComment).after(' - '); $(postComment).attr('role', 'button'); postComment.title = 'Togle comments'; postComment.innerHTML = ''; } function getNumComments() { numWrapper = commentsWrapper.querySelector('div:first-child > span:first-child + div > span:first-child') if(!numWrapper) numWrapper = commentsWrapper.querySelector('div:first-child > div:first-child > span:first-child'); return parseInt(numWrapper.innerHTML.replace(/[^\d]/g, '')); } function updateComments() { function showComments() { if(parseInt($(_comments).css('height').replace(/[^\d]/g, '')) < 5) { $(_comments).css('height', 'auto'); $(_comments).css('visibility', 'visible'); _comments.className = _comments.className.replace(/bcTweaksHiddenComments/, ''); } } function hideComments() { _comments.className += ' bcTweaksHiddenComments'; $(_comments).css('height', '0'); $(_comments).css('overflow', 'hidden'); $(_comments).css('visibility', 'hidden'); // scroll to top of post if(hasBeenCollapsed) { var postTop = $(post).offset().top - 120; //alert(postTop); $(document).scrollTop(postTop); } hasBeenCollapsed = true; } numTotal = getNumComments(); if($('.bcGTweaksNumComments', post).size() == 0) { $('.bcGTweaksNumComments', post).remove(); if(numTotal > 0) { var commentsButton = document.createElement('span'); commentsButton.className = 'bcGTweaksNumComments'; commentsButton.innerHTML = '(' + numTotal + ')'; postComment = $(postComment); postComment.after(commentsButton); postComment.click(function() { showComments(_comments); }); function toggleComments() { if(parseInt($(_comments).css('height').replace(/[^\d]/g, '')) < 5) { showComments(_comments); } else { hideComments(_comments); } } $(commentsButton).click(toggleComments); $(_comments).append('<div class="bcGPlusTwCollapseComments" title="Collapse comments"> </div>'); $('.bcGPlusTwCollapseComments', _comments).click(toggleComments); hideComments(_comments); } else { showComments(_comments); } } else { var oldNum = parseInt($('.bcGTweaksNumComments', post).text().replace(/[^\d]/g, '')); if(numTotal != oldNum) { $('.bcGTweaksNumComments', post).html('(' + numTotal + ')'); if(parseInt($(_comments).css('height').replace(/[^\d]/g, '')) < 5) { $(_comments).next().hide(); // hide the "add comments" box } } } } _comments.addEventListener ("DOMNodeInserted", function() { updateComments(); }, false); } catch(e) {} } } }, mention: { init: function() { if(Config.get('easyMentions')) { self.addStyle(s.post + ' .bcGTweakEzMntn { cursor:pointer; opacity:0.5; font-size:8px; position:relative; top:-1px; ' + 'margin:0 5px 0 3px; white-space: nowrap; background-color: rgb(238, 238, 238); border:1px solid rgb(221, 221, 221);' + 'display: inline-block; padding:0 4px; color: rgb(51, 102, 204); }' + s.post + ' .bcGTweakEzMntn:hover { opacity:1; }' + // don't display in shares dropdown '.TQ0zYb .bcGTweakEzMntn { display:none !important; }' ); } }, processPost: function(post) { $(selectors.profileLink, post).each(function() { var link = this; if(link.rel != 'bcGTweakEzMntn' && $('img', link).size() == 0) { link.rel = 'bcGTweakEzMntn'; var mention = document.createElement('span'); mention.innerHTML = "+"; mention.title = "Mention " + link.innerHTML + " in your comment"; mention.className = 'bcGTweakEzMntn'; $(mention).click(function() { // find post wrapper var addCommentLink = $(selectors.postCommentButton, post)[0]; simulateClick(addCommentLink); function insertMentionRef(name, id) { var editor = $('.editable', post); if(editor.size() > 0) { var editorMentionClass = selectors.postCommentEditorMention.replace(/^\./, ''); if(navigator.userAgent.match(/chrome/i)) { var html = ' <span> </span><button contenteditable="false" tabindex="-1" style="white-space: nowrap; background-image: initial; background-attachment: initial; background-origin: initial; background-clip: initial; background-color: rgb(238, 238, 238); border-top-width: 1px; border-right-width: 1px; border-bottom-width: 1px; border-left-width: 1px; border-top-style: solid; border-right-style: solid; border-bottom-style: solid; border-left-style: solid; border-top-color: rgb(221, 221, 221); border-right-color: rgb(221, 221, 221); border-bottom-color: rgb(221, 221, 221); border-left-color: rgb(221, 221, 221); border-top-left-radius: 2px 2px; border-top-right-radius: 2px 2px; border-bottom-right-radius: 2px 2px; border-bottom-left-radius: 2px 2px; display: inline-block; font: normal normal normal 13px/1.4 Arial, sans-serif; margin-top: 0px; margin-right: 1px; margin-bottom: 0px; margin-left: 1px; padding-top: 0px; padding-right: 1px; padding-bottom: 0px; padding-left: 1px; vertical-align: baseline; color: rgb(51, 102, 204); background-position: initial initial; background-repeat: initial initial; " class="' + editorMentionClass + '" data-token-entity="@' + id + '" oid="' + id + '"><span style="color: rgb(136, 136, 136); ">+</span>' + name + '</button><span> </span>'; } else { var html = ' <span> </span><input type="button" tabindex="-1" value="+' + name + '" style="white-space: nowrap; background: none repeat scroll 0% 0% rgb(238, 238, 238); border: 1px solid rgb(221, 221, 221); border-radius: 2px 2px 2px 2px; display: inline-block; font: 13px/1.4 Arial,sans-serif; margin: 0pt 1px; padding: 0pt 1px; vertical-align: baseline; color: rgb(51, 102, 204);" class="' + editorMentionClass + '" data-token-entity="@' + id + '" oid="' + id + '"><span> </span>'; } editor.attr('tabfocus', '0'); //editor.focus(); if($('iframe', editor).size() > 0) { editor = $('iframe', editor).contents().find("body"); } var existingHtml = editor.html().replace(/^(\n|\s)*<\/*br>(\n|\s)*/, '').replace(/(\n|\s)*<\/*br>(\n|\s)*$/, ''); editor.html(existingHtml + html); editor.focus(); editor.attr(''); setTimeout(function() { placeCaretAtEnd( editor[0]); }, 100); } else { setTimeout(function() { insertMentionRef(name, id); }, 200); } } // http://stackoverflow.com/questions/4233265/contenteditable-set-caret-at-the-end-of-the-text-cross-browser function placeCaretAtEnd(el) { el.focus(); if (typeof window.getSelection != "undefined" && typeof document.createRange != "undefined") { var range = document.createRange(); range.selectNodeContents(el); range.collapse(false); var sel = window.getSelection(); sel.removeAllRanges(); sel.addRange(range); } else if (typeof document.body.createTextRange != "undefined") { var textRange = document.body.createTextRange(); textRange.moveToElementText(el); textRange.collapse(false); textRange.select(); } } insertMentionRef(link.innerHTML, $(link).attr('oid')); }); $(link).after(mention); } }); } }, postPreviews: { init: function() { if(Config.get('postPreviews')) { self.addStyle( selectors.post + ' { display:none; }' + '.bcTweaksMiniPostCollapsed { display:block !important; padd }' + '.bcTweaksMiniPostExpanded { display:block !important; }' + '.bcTweaksMiniPostCollapsed { display:block !important; padd }' + '.bcTweaksMiniPostCollapsed { height:32px; overflow:hidden; padding:0; position:relative; }' + '.bcTweaksMiniPostCollapsed img { display:none; }' + '.bcTweaksMiniPostCollapsed:hover { border-color:#333 !important; }' + '.bcTweaksMiniPostCollapsed br { display:none; }' + '.bcTweaksMiniPostCollapsed span { margin:0 .5em; }' + '.bcTweaksMiniPostCollapsed * { line-height:32px; background:none; margin:0 !important; padding:0 !important; text-align:left; }' + '.bcTweaksMiniPostCollapsed > div:first-child { width:100%; }' + '.bcTweaksMiniPostCollapsed > div { position:absolute; left:40px; top:0; }' + // post time, public, etc. '.bcTweaksMiniPostCollapsed > div:first-child > div:first-child > h3 + span + a + div > span:first-child + span { display:none !important; }' + '.bcTweaksMiniPostCollapsed ' + selectors.incomingPostedBy + ' { display:none; }' + '.bcTweaksMiniPostCollapsed .bcTweaksMiniPostCollapse { display:none; }' + '.bcTweaksMiniPostCollapsed .bcGTweakEzMntn{ display:none !important;}' + '.bcTweaksMiniPostCollapsed .bcGTweaksMute { display:none; }' + // post avatar '.bcTweaksMiniPostCollapsed > div:first-child > div:first-child > h3:first-child + span + a img { display:block !important; height:32px; width:32px; position:absolute; top:0; left:-40px; }' + // post body '.bcTweaksMiniPostCollapsed > div:first-child > div:first-child + div { position:absolute; top:0; left:130px; }' + '.bcTweaksMiniPostCollapsed ' + selectors.postCommentsMoreButtonWrapper + ' { position:absolute; top:0; right:45px; background:#fff; z-index:3000; }' + '.bcTweaksMiniPostCollapse { position:absolute; top:0; cursor:pointer; right:0; width:15px; background:#eee; border-left:1px solid #d2d2d2; height:100%; }' + ''); // press "T" to mute the current post $('body').keyup(function(e) { if(!eventIsFromTyping(e)) { switch(e.which.toString()) { case '84': // "T" var currentPost = $(selectors.postSelected); if(currentPost.size() > 0) { self.features.postPreviews.togglePost(currentPost[0]); } break; case '74': // "J" var currentPost = $(selectors.postSelected); if(currentPost.size() > 0) { self.features.postPreviews.collapsePost(currentPost[0]); } break; } } }); } }, processPost: function(post) { if(!post.className.match(/bcTweaksMiniPost/)) { post.className += ' bcTweaksMiniPostCollapsed'; $(post).click(function() { if(this.className.match(/bcTweaksMiniPostCollapsed/)) { self.features.postPreviews.expandPost(post); } }); $(post).append('<div class="bcTweaksMiniPostCollapse" title="Collapse this post"> </div>'); $('.bcTweaksMiniPostCollapse', post).click(function(e) { e.stopPropagation(); self.features.postPreviews.collapsePost(post); }); } }, collapsePost:function(post) { post.className = post.className.replace(/bcTweaksMiniPostExpanded/, 'bcTweaksMiniPostCollapsed'); }, expandPost:function(post) { post.className = post.className.replace(/bcTweaksMiniPostCollapsed/, 'bcTweaksMiniPostExpanded'); }, togglePost:function(post) { if(post.className.match(/bcTweaksMiniPostCollapsed/)) { self.features.postPreviews.expandPost(post); } else { self.features.postPreviews.collapsePost(post); } } }, muteButton: { init: function() { if(Config.get('muteButton')) { self.addStyle('.bcGTweaksMute { ' + 'background:url(https://lh3.googleusercontent.com/-lZDyA7RaVHQ/Thw6SRRp-lI/AAAAAAAAAI0/qRIid0xFWJs/sound_mute_desaturated.png) center no-repeat;' + 'padding:1px 15px; opacity:.5;' + 'position:absolute; right:50px; top:11px; border:1px solid #aaa; border-radius:2px; }' + '.bcGTweaksMute:hover { opacity:1;' + 'background-image:url(https://lh4.googleusercontent.com/-5NYOXadhJOs/Thw5O1SYBoI/AAAAAAAAAIs/zOvmkFAcrks/ound_mute.png); background-color:#eee;' + '}' + selectors.incomingPostedByMutePostButton + ' { display:none; }' + selectors.incomingPostedBy + ' + .bcGTweaksMute { top:26px; }' + selectors.incomingPostedByAddToCircles + ' { margin-right:55px; }' ); // press "M" to mute the current post $('body').keyup(function(e) { if(!eventIsFromTyping(e)) { switch(e.which.toString()) { case '77': // "M" var postToMute = $(selectors.postSelected); var mb = $(selectors.postMuteButton, postToMute); if(mb.size() > 0) { simulateClick(mb[0]); } break; } } }); } }, processPost: function(post) { try { var postButton = post.querySelector('div:first-child > div:first-child > span'); // detect mute button class if(!self.muteButtonClassFound && $('div[role="menuitem"]', post).size() == 4) { self.muteButtonClassFound = true; var muteButtonSelector = '.' + $('div[role="menuitem"]', post).eq(2).attr('class').replace(/\s+/g, '.'); if(Config.get('selectorMuteButton') != muteButtonSelector) { Config.set('selectorMuteButton', muteButtonSelector); document.location = document.location; } } if($('.bcGTweaksMute', post).size() == 0) { var _this = postButton; setTimeout(function() { var m = document.createElement('div'); m.className = 'bcGTweaksMute'; m.innerHTML = ' '; m.style.cursor = 'pointer'; var mb = $(selectors.postMuteButton, post)[0]; if(mb) { $(_this).before(m); m.title = $(mb).text(); $(m).click(function() { var mb = $(selectors.postMuteButton, post)[0]; if(mb) simulateClick(mb); }); } }, 500); } } catch(e) { } } }, muteNotices: { init: function() { switch(Config.get('muteNotices')) { case 'fade': self.addPolling(self.features.muteNotices.fadeNewNotices); break; case 'hide': self.addStyle(selectors.postMutedNotice + ' { display:none !important; }'); break; } }, fadeNewNotices: function() { $(selectors.postMutedNotice).each(function() { var _this = this; setTimeout(function() { $(_this).fadeOut(1000, function() { $(this).remove(); }); }, 3000); }); } }, readability: { init: function() { if(Config.get('readability') && $('#content').size() == 1) { var bgColor = '#f1f1f1'; self.addStyle( 'body { background:' + bgColor + ' !important; }' + 'body { background-color:' + bgColor + ' !important; }' + selectors.toolBar + ' { border-bottom:none; }' + selectors.content + ' { border-top:none; }' + selectors.content + ' > div:first-child { background:' + bgColor + '; }' + selectors.profileContent + ', #contentPane { background:' + bgColor + '; border:none; }' + selectors.post + ' { margin-top:4px; border:1px solid #d2d2d2; border-top:1px solid #d2d2d2 !important; padding-bottom:0; }' + selectors.postSelected + '{ border-color:#cc3333 !important; }' ); } } }, thumbnailsOnly: { processPost: function(post) { if(Config.get('thumbsOnly')) { var maxHeight = 50; var maxWidth = 62; $('img[src*="googleusercontent"]', post).each(function() { this.style.maxHeight = maxHeight + 'px'; this.style.maxWidth = maxWidth + 'px'; }); } } } }; this.construct = function() { Config.scriptName = 'Google+ Tweaks'; Config.footer = '<span style="position:relative; top:5px">' + self.donateButtonsHtml + '</span>'; Config.options = this.options; populateSelectors(); self.initFeatures(); self.applyStyle(); self.startPolling(); self.insertOptionsLink(); $(selectors.streamShareWrapper).click(function() { typing = true; }); if(!document.location.toString().match(/frame/)) { var css = ''; // if(Config.get('postButtons')) stylePostButtons(); if(Config.get('hideCopyright')) css += '#content + div { display:none !important; }'; if(Config.get('hideIncomingNotice')) css += selectors.streamIncomingNotice + ' { display:none; }'; if(Config.get('hideChatRoster')) css += selectors.chatRoster + ' { display:none !important; }'; if(Config.get('hideSendFeedback')) css += selectors.sendFeedback + ' { display:none !important; }'; if(Config.get('hidePlusMention')) css += '.proflinkPrefix { display:none !important; }'; // right column if(Config.get('hideRightCol')) css += selectors.streamRightCol + ' { display:none; }'; if(Config.get('hideSuggestions')) css += selectors.streamRightColSuggestions + ' { display:none; }'; if(Config.get('hideSendInvites')) css += selectors.streamRightColSendInvites + ' { display:none; }'; // implement CSS if(css != '') { if(typeof(GM_addStyle) == 'function') { GM_addStyle(css); } else { var sheet = document.createElement('style') ; sheet.innerHTML = css; document.body.appendChild(sheet); } } } return true; }; this.addPolling = function(fnct) { self.pollFuncions.push(fnct); }; this.addStyle = function(code) { self.css += code; }; this.applyStyle = function() { // implement CSS if(self.css != '') { if(typeof(GM_addStyle) == 'function') { GM_addStyle(self.css); } else { var sheet = document.createElement('style') ; sheet.innerHTML = self.css; document.body.appendChild(sheet); } } }; this.initFeatures = function() { for(var ftr in self.features) { if(typeof(self.features[ftr].init) == 'function') self.features[ftr].init(); } }; this.insertOptionsLink = function() { $('a[href*="preferences"].gbgt + div ol.gbmcc li:eq(1)').after( '<li class="gbkc gbmtc"><a class="gbmt" href="javascript:void(0)" id="bcGTweaksOptLnk">Google+ tweaks</a></li>' ); $('#bcGTweaksOptLnk').click(function() { Config.open(); $('.ui-dialog').css('top', '100px'); $(document).scrollTop(0); }); }; this.detectElementClass = function(selectorKey, path) { var s = selectors; // detect missing selector classes if(typeof(s[selectorKey]) == 'undefined' || s[selectorKey] == null) { var elems = $(path); if(elems.size() > 0) { s[selectorKey] = '.' + elems.attr('class').replace(/\s+/g, '.'); } } }; this.processPost = function(post) { self.processPostBody(getPostBody(post)); // launch feature processPost functions for(var k in self.features) { var feature = self.features[k]; if(typeof(feature.processPost) == 'function') { feature.processPost(post); } } }; this.processPostBody = function(postBody) { // launch feature processPostBody functions for(var k in self.features) { var feature = self.features[k]; if(typeof(feature.processPostBody) == 'function') { feature.processPostBody(postBody); } } }; this.processView = function() { self.numPostsFound = 0; self.numPostsParsed = 0; // launch feature processPostBody functions for(var k in self.features) { var feature = self.features[k]; if(typeof(feature.onViewChange) == 'function') { feature.onViewChange(); } } }; this.startPolling = function() { function contentPaneChanged() { if(typeof(self.numPostsFound) == 'undefined') self.numPostsFound = 0; if(typeof(self.numPostsParsed) == 'undefined') self.numPostsParsed = 0; // check for new view if(self.oldLocation != document.location.toString()) { self.oldLocation = document.location.toString(); self.processView(); } // process posts nodes = document.querySelectorAll('#contentPane div[id^="update"]'); self.numPostsFound = nodes.length; for (self.numPostsParsed = 0; self.numPostsParsed < self.numPostsFound; self.numPostsParsed++) { var post = nodes[self.numPostsParsed]; if(!post.className.match(/bcPoll/)) { post.className += ' bcPoll'; self.processPost(post); } } } if(document.getElementById('contentPane')) { document.getElementById('contentPane').addEventListener('DOMNodeInserted', function() { setTimeout(contentPaneChanged, 100); }, false); } //document.getElementById('contentPane').addEventListener('DOMNodeRemoved', contentPaneChanged, false); // auto detect main classes if(Config.get('selectorAutoUpdate')) updateSelectors(); // auto detect any missing class names using relative DOM paths for(var x in dynamicSelectors) { self.detectElementClass(x, dynamicSelectors[x]); } // fire feature polls for(var i = 0; i < self.pollFuncions.length; i++) { if(typeof(self.pollFuncions[i]) == 'function') self.pollFuncions[i](); } //setTimeout(self.startPolling, self.pollInterval); }; return this.construct(); } // much of the code for this section originated at http://userscripts.org/scripts/show/24430 // and is used here in a modified form with Peter Wooley's consent function FavIcon(gPlusIcon) { var self = this; this.self = this; this.src = gPlusIcon; this.foreground = "#2c3323"; this.background = "#fef4ac"; this.borderColor = "#fef4ac"; this.construct = function() { this.head = document.getElementsByTagName('head')[0]; this.pixelMaps = { numbers: [ [ [0,1,1,0], [1,0,0,1], [1,0,0,1], [1,0,0,1], [0,1,1,0] ], [ [0,1,0], [1,1,0], [0,1,0], [0,1,0], [1,1,1] ], [ [1,1,1,0], [0,0,0,1], [0,1,1,0], [1,0,0,0], [1,1,1,1] ], [ [1,1,1,0], [0,0,0,1], [0,1,1,0], [0,0,0,1], [1,1,1,0] ], [ [0,0,1,0], [0,1,1,0], [1,0,1,0], [1,1,1,1], [0,0,1,0] ], [ [1,1,1,1], [1,0,0,0], [1,1,1,0], [0,0,0,1], [1,1,1,0] ], [ [0,1,1,0], [1,0,0,0], [1,1,1,0], [1,0,0,1], [0,1,1,0] ], [ [1,1,1,1], [0,0,0,1], [0,0,1,0], [0,1,0,0], [0,1,0,0] ], [ [0,1,1,0], [1,0,0,1], [0,1,1,0], [1,0,0,1], [0,1,1,0] ], [ [0,1,1,0], [1,0,0,1], [0,1,1,1], [0,0,0,1], [0,1,1,0] ], ] }; return true; }; this.getIconCanvas = function(callback) { if(!self.iconCanvas) { self.iconCanvas = document.createElement('canvas'); self.iconCanvas.height = self.iconCanvas.width = 16; var image = new Image(); $(image).load(function() { // fill the canvas with the background favicon's data var ctx = self.iconCanvas.getContext('2d'); ctx.drawImage(image, 0, 2, 14, 14); callback(self.iconCanvas); }); image.src = self.src; } else { callback(self.iconCanvas); } }; this.getBadgedIcon = function(unread, callback) { if(!self.textedCanvas) { self.textedCanvas = []; } if(!self.textedCanvas[unread]) { self.getIconCanvas(function(iconCanvas) { var textedCanvas = document.createElement('canvas'); textedCanvas.height = textedCanvas.width = iconCanvas.width; var ctx = textedCanvas.getContext('2d'); ctx.drawImage(iconCanvas, 0, 0); ctx.fillStyle = self.background; ctx.strokeStyle = self.border ? self.border : '#000000'; ctx.strokeWidth = 1; var count = unread.length; var bgHeight = self.pixelMaps.numbers[0].length; var bgWidth = 0; var padding = count > 2 ? 0 : 1; for(var index = 0; index < count; index++) { bgWidth += self.pixelMaps.numbers[unread[index]][0].length; if(index < count-1) { bgWidth += padding; } } bgWidth = bgWidth > textedCanvas.width-4 ? textedCanvas.width-4 : bgWidth; ctx.fillRect(textedCanvas.width-bgWidth-4,1,bgWidth+4,bgHeight+4); var digit; var digitsWidth = bgWidth; for(var index = 0; index < count; index++) { digit = unread[index]; if (self.pixelMaps.numbers[digit]) { var map = self.pixelMaps.numbers[digit]; var height = map.length; var width = map[0].length; ctx.fillStyle = self.foreground; for (var y = 0; y < height; y++) { for (var x = 0; x < width; x++) { if(map[y][x]) { ctx.fillRect(14- digitsWidth + x, y+3, 1, 1); } } } digitsWidth -= width + padding; } } if(self.border) { ctx.strokeRect(textedCanvas.width-bgWidth-3.5,1.5,bgWidth+3,bgHeight+3); } self.textedCanvas[unread] = textedCanvas; callback(self.textedCanvas[unread].toDataURL('image/png')); }); } else { callback(self.textedCanvas[unread].toDataURL('image/png')); } }; this.setIcon = function(icon) { var links = self.head.getElementsByTagName("link"); for (var i = 0; i < links.length; i++) if ((links[i].rel == "shortcut icon" || links[i].rel=="icon") && links[i].href != icon) self.head.removeChild(links[i]); else if(links[i].href == icon) return; var newIcon = document.createElement("link"); newIcon.type = "image/png"; newIcon.rel = "shortcut icon"; newIcon.href = icon; self.head.appendChild(newIcon); var shim = document.createElement('iframe'); shim.width = shim.height = 0; document.body.appendChild(shim); shim.src = "icon"; document.body.removeChild(shim); }; this.set = function(num) { if(typeof(num) == 'undefined' || (!num && num.toString() != '0')) num = ''; if(num != '') { self.getBadgedIcon(num.toString(), function(src) { self.setIcon(src); }); } else { self.setIcon(this.src); } }; this.toString = function() { return '[object FavIconAlerts]'; }; return this.construct(); } function simulateClick(element) { var clickEvent; clickEvent = document.createEvent("MouseEvents") clickEvent.initEvent("mousedown", true, true) element.dispatchEvent(clickEvent); clickEvent = document.createEvent("MouseEvents") clickEvent.initEvent("click", true, true) element.dispatchEvent(clickEvent); clickEvent = document.createEvent("MouseEvents") clickEvent.initEvent("mouseup", true, true) element.dispatchEvent(clickEvent); } function getPostBody(post) { var postBody = post.querySelector('div:first-child > div:first-child > div:first-child + div > div:first-child'); if(!postBody) { var postBody = post.querySelector('div:first-child > div:first-child + div > div:first-child'); } return postBody; } new GTweaks();
Mozilla add on,User script,Grease Monkey Script, greasemonkey userscripts, updater userscripts mafia wars userscripts mafia wars autoplayer userscripts mafia wars wall userscripts scripts userscripts travian greasemonkey greasemonkey download greasemonkey facebook greasemonkey tutorial greasemonkey youtube greasemonkey travian greasemonkey chrome greasemonkey mafia wars greasemonkey mafia wars autoplayer
Friday, September 23, 2011
Google+ Tweaks
Subscribe to:
Post Comments (Atom)
0 comments:
Post a Comment