├── .gitignore ├── .editorconfig ├── LICENSE ├── src ├── GreenRedirects.css ├── GreenRedirects.js ├── FloatHead.js ├── FloatSide.js ├── ToTop.js ├── NullEdit.js ├── Subpages.js ├── MobileView.js ├── GlobalPrefs.js ├── RedirectNotification.js ├── CitationStyleMarker.css ├── ShowRevisionID.js ├── FindSources.js ├── ToTopButton.js ├── ToTopButton.css ├── WatchDoc.js ├── WPBannerNavbar.js ├── FloatSide.css ├── CollapseSections.js ├── FloatHead.css ├── NoRedirect.js ├── Autoref.js ├── ToBottom.js ├── EasySummary.js ├── AJAXUndo.js ├── ShowUserGender.js ├── CitationStyleMarker.js ├── Restorer.js ├── UpdateNotifications.js ├── ToggleSidebar.js ├── MoreDiffInfo.js ├── ContribsByPage.js ├── ParameterSpacing.js ├── CompareRevisions.js ├── IncrementParameters.js ├── SubpageMover.js ├── TestWikitext.js ├── FloatingTOC.js ├── HotDefaultSort.js ├── SVGEditor.js ├── Invert.js ├── ShowTemplates.js ├── ListSorter.js ├── PortletLinks.js ├── CSSImageCrop.js ├── AddCopied.js ├── ReferenceExpander.js ├── FFUHelper.js ├── QuickEdit.js └── TodoList.js ├── package.json ├── .stylelintrc.json ├── update-readme.py ├── .eslintrc.json └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .env 3 | sync.py 4 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | [*] 2 | insert_final_newline = false 3 | indent_style = tab -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This work is licensed under the Creative Commons Attribution-ShareAlike 3.0 Unported License. To view a copy of this license, visit http://creativecommons.org/licenses/by-sa/3.0/ or send a letter to Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. 2 | -------------------------------------------------------------------------------- /src/GreenRedirects.css: -------------------------------------------------------------------------------- 1 | /*** Green Redirects ***/ 2 | 3 | /* Makes redirects green */ 4 | /* Documentation at [[en:w:User:BrandonXLF/GreenRedirects]] */ 5 | /* By [[en:w:User:BrandonXLF]] */ 6 | 7 | a.mw-redirect { 8 | color: #060 !important; /* Make redirects green */ 9 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "devDependencies": { 3 | "eslint": "^7.21.0", 4 | "stylelint": "^14.7.1", 5 | "stylelint-config-standard": "^25.0.0" 6 | }, 7 | "scripts": { 8 | "test": "eslint src/*.js", 9 | "fix": "eslint src/*.js --fix" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/GreenRedirects.js: -------------------------------------------------------------------------------- 1 | /*** Green Redirects ***/ 2 | 3 | // Makes redirects green 4 | // Documentation at [[en:w:User:BrandonXLF/GreenRedirects]] 5 | // By [[en:w:User:BrandonXLF]] 6 | 7 | mw.loader.load('//en.wikipedia.org/w/index.php?title=User:BrandonXLF/GreenRedirects.css&action=raw&ctype=text/css', 'text/css'); -------------------------------------------------------------------------------- /.stylelintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "stylelint-config-standard", 3 | "rules": { 4 | "indentation": "tab", 5 | "comment-empty-line-before": null, 6 | "string-quotes": "single", 7 | "no-missing-end-of-source-newline": null, 8 | "selector-class-pattern": null, 9 | "selector-id-pattern": null 10 | } 11 | } -------------------------------------------------------------------------------- /src/FloatHead.js: -------------------------------------------------------------------------------- 1 | /*** Float Head ***/ 2 | 3 | // Makes the side menu float and makes it scroll-able 4 | // Documentation at [[en:w:User:BrandonXLF/FloatHead]] 5 | // By [[en:w:User:BrandonXLF]] 6 | 7 | mw.loader.load('//en.wikipedia.org/w/index.php?title=User:BrandonXLF/FloatHead.css&action=raw&ctype=text/css', 'text/css'); -------------------------------------------------------------------------------- /src/FloatSide.js: -------------------------------------------------------------------------------- 1 | /*** Float Side ***/ 2 | 3 | // Makes the side menu float and makes it scroll-able 4 | // Documentation at [[en:w:User:BrandonXLF/FloatSide]] 5 | // By [[en:w:User:BrandonXLF]] 6 | 7 | mw.loader.load('//en.wikipedia.org/w/index.php?title=User:BrandonXLF/FloatSide.css&action=raw&ctype=text/css', 'text/css'); -------------------------------------------------------------------------------- /src/ToTop.js: -------------------------------------------------------------------------------- 1 | /*** To Top ***/ 2 | 3 | // Adds a link to got back to the top of the page from the footer 4 | // Documentation at [[en:w:User:BrandonXLF/ToTop]] 5 | // By [[en:w:User:BrandonXLF]] 6 | 7 | $(function() { 8 | $('
  • ') 9 | .html('Back to top') 10 | .attr('id', 'footer-places-totop') 11 | .on('click', function() { 12 | $('html').animate({scrollTop: 0}); 13 | }) 14 | .appendTo('#footer-places'); 15 | }); -------------------------------------------------------------------------------- /src/NullEdit.js: -------------------------------------------------------------------------------- 1 | /*** Null Edit ***/ 2 | 3 | // Adds a link to perform a [[WP:NULLEDIT]] on the current page 4 | // Documentation at [[en:w:User:BrandonXLF/NullEdit]] 5 | // By [[en:w:User:BrandonXLF]] 6 | 7 | $(mw.util.addPortletLink('p-cactions', '#', 'Null edit')).click(function(e) { 8 | e.preventDefault(); 9 | new mw.Api().edit(mw.config.get('wgPageName'), function(rev) { 10 | return rev.content; 11 | }).then(function() { 12 | window.location.reload(); 13 | }); 14 | }); -------------------------------------------------------------------------------- /src/Subpages.js: -------------------------------------------------------------------------------- 1 | /*** Subpages ***/ 2 | 3 | // Creates a subpages link in the right navigation area 4 | // Documentation at [[en:w:User:BrandonXLF/Subpages]] 5 | // By [[en:w:User:BrandonXLF]] 6 | 7 | $(function() { 8 | mw.util.addPortletLink( 9 | 'p-cactions', 10 | mw.config.get('wgArticlePath').replace('$1', 'Special:PrefixIndex/' + encodeURIComponent(mw.config.get('wgPageName')) + '/'), 11 | 'Subpages', 12 | 'subpages', 13 | 'Run the Special:PrefixIndex tool', 14 | 's' 15 | ); 16 | }); -------------------------------------------------------------------------------- /src/MobileView.js: -------------------------------------------------------------------------------- 1 | /*** Mobile View ***/ 2 | 3 | // Adds a link to see the mobile version of a page 4 | // Documentation at [[en:w:User:BrandonXLF/MobileView]] 5 | // By [[en:w:User:BrandonXLF]] 6 | 7 | $(function() { 8 | mw.util.addPortletLink( 9 | 'p-tb', 10 | location.href.replace(/[?&]useformat=[A-Za-z]/g, '') + (location.href.includes('?') ? '&' : '?') + 'useformat=mobile', 11 | 'Mobile view', 12 | 'Mobile view', 13 | 'See the page in the mobile version of the page', 14 | '' 15 | ); 16 | }); 17 | 18 | //[[Category:Wikipedia scripts]] -------------------------------------------------------------------------------- /src/GlobalPrefs.js: -------------------------------------------------------------------------------- 1 | /*** Global Prefs ***/ 2 | 3 | // Adds link to Global Preferences 4 | // Documentation at [[en:w:User:BrandonXLF/GlobalPrefs]] 5 | // By [[en:w:User:BrandonXLF]] 6 | 7 | $(function() { 8 | ['', '-sticky-header'].forEach(suffix => { 9 | mw.util.addPortletLink( 10 | 'p-personal' + suffix, 11 | mw.config.get('wgArticlePath').replace('$1', 'Special:GlobalPreferences'), 12 | window.globalprefs || '(Global)', 13 | 'globalpreferences', 14 | 'Go to Special:GlobalPreferences', 15 | '', 16 | $('#pt-preferences' + suffix).next() 17 | ); 18 | }); 19 | }); -------------------------------------------------------------------------------- /src/RedirectNotification.js: -------------------------------------------------------------------------------- 1 | /*** Redirect Notification ***/ 2 | 3 | // Get notified when you are redirected 4 | // Documentation at [[en:w:User:BrandonXLF/RedirectNotification]] 5 | // By [[en:w:User:BrandonXLF]] 6 | 7 | $(function() { 8 | var redirectedFrom = mw.config.get('wgRedirectedFrom'); 9 | 10 | if (redirectedFrom) mw.notify($( 11 | '
    Redirected from "' + 15 | redirectedFrom.replace(/_/g, ' ') + 16 | '".
    ' 17 | )); 18 | }); -------------------------------------------------------------------------------- /src/CitationStyleMarker.css: -------------------------------------------------------------------------------- 1 | /*** Citation Style Marker ***/ 2 | 3 | /* Puts the type of citation style a citation is using next to it */ 4 | /* Documentation at [[en:w:User:BrandonXLF/CitationStyleMarker]] */ 5 | /* By [[en:w:User:BrandonXLF]] */ 6 | 7 | body:not(.nocsmarker) cite.cs1::after { 8 | content: ' CS1'; 9 | color: #b4b; 10 | font-weight: bold; 11 | } 12 | 13 | body:not(.nocsmarker) cite.cs2::after { 14 | content: ' CS2'; 15 | color: #b40; 16 | font-weight: bold; 17 | } 18 | 19 | body:not(.nocsmarker) .csblue::after { 20 | content: ' CSBLUE'; 21 | color: #848; 22 | font-weight: bold; 23 | } -------------------------------------------------------------------------------- /src/ShowRevisionID.js: -------------------------------------------------------------------------------- 1 | /*** Show Revision ID ***/ 2 | 3 | // Shows the revison id on history pages 4 | // Documentation at [[en:w:User:BrandonXLF/ShowRevisionID]] 5 | // By [[en:w:User:BrandonXLF]] 6 | 7 | $(function() { 8 | if (location.search.includes('action=history') || location.href.includes('Special:Watchlist') || location.href.includes('Special:Contributions')) { 9 | var items = document.querySelectorAll('li[data-mw-revid]'); 10 | for (var i = 0; i < items.length; i++) { 11 | items[i].getElementsByClassName('mw-changeslist-date')[0].appendChild(document.createTextNode(' | ' + items[i].getAttribute('data-mw-revid'))); 12 | } 13 | } 14 | }); -------------------------------------------------------------------------------- /src/FindSources.js: -------------------------------------------------------------------------------- 1 | /*** Find Sources ***/ 2 | 3 | // Add {{Find sources mainspace}} below the title of articles you view 4 | // Documentation at [[en:w:User:BrandonXLF/FindSources]] 5 | // By [[en:w:User:BrandonXLF]] 6 | 7 | $.when(mw.loader.using('mediawiki.api'), $.ready).then(function() { 8 | if (mw.config.get('wgIsMainPage') || mw.config.get('wgNamespaceNumber') !== 0) return; 9 | 10 | (new mw.Api()).parse('{{Template:Find_sources_mainspace}}', {title: mw.config.get('wgPageName')}).done(function(e) { 11 | $('#siteSub').after('
    ' + $(e).find('p').first().html().replace(/(.*?)<\/i>/g, '$1') + '
    '); 12 | }); 13 | }); -------------------------------------------------------------------------------- /src/ToTopButton.js: -------------------------------------------------------------------------------- 1 | /*** To Top Button ***/ 2 | 3 | // Adds a floating "go to top" button on the bottom-right of the screen 4 | // Documentation at [[en:w:User:BrandonXLF/TopButton]] 5 | // By [[en:w:User:BrandonXLF]] 6 | 7 | $(function() { 8 | mw.loader.load('https://en.wikipedia.org/wiki/User:BrandonXLF/ToTopButton.css?action=raw&ctype=text/css', 'text/css'); 9 | 10 | var circle = $('
    ') 11 | .appendTo('body') 12 | .addClass('topButtonCircle') 13 | .on('click', function() { 14 | $('html').animate({scrollTop: 0}); 15 | }) 16 | .append('
    '); 17 | 18 | $(window).scroll(function() { 19 | if ($(window).scrollTop() > 100) { 20 | circle.fadeIn(); 21 | } else { 22 | circle.fadeOut(); 23 | } 24 | }); 25 | }); -------------------------------------------------------------------------------- /update-readme.py: -------------------------------------------------------------------------------- 1 | import os 2 | import re 3 | 4 | for subdir, dirs, files in os.walk(os.getcwd() + '/src'): 5 | prev = '' 6 | line = '' 7 | out = '## List\n\n' 8 | markdown = '' 9 | 10 | for file in files: 11 | curr, type = file.split('.') 12 | type = 'JS' if type == 'js' else 'CSS' 13 | if prev == curr: 14 | line += ', [{0}](/src/{1})'.format(type, file) 15 | else: 16 | if line: 17 | out += '* {}\n'.format(line) 18 | page = 'https://en.wikipedia.org/wiki/User:BrandonXLF/' + curr 19 | line = '{0} - [Doc]({1}), [{2}](/src/{3})'.format(curr, page, type, file) 20 | prev = curr 21 | out += '* {}\n\n'.format(line) 22 | 23 | with open('README.md', 'r') as f: 24 | markdown = f.read() 25 | 26 | markdown = re.sub(r'## List[^#]*', out, markdown) 27 | 28 | with open('README.md', 'w') as f: 29 | f.write(markdown) 30 | 31 | print('Updated script list.') -------------------------------------------------------------------------------- /src/ToTopButton.css: -------------------------------------------------------------------------------- 1 | /*** To Top Button ***/ 2 | 3 | /* Adds a floating "go to top" button on the bottom-right of the screen */ 4 | /* Documentation at [[en:w:User:BrandonXLF/TopButton]] */ 5 | /* By [[en:w:User:BrandonXLF]] */ 6 | 7 | .topButtonCircle { 8 | height: 3em; 9 | width: 3em; 10 | border-radius: 100%; 11 | box-shadow: 0.05em 0.2em 0.35em 0.05em #b8babd; 12 | background: #2273f7; 13 | position: fixed; 14 | right: 1em; 15 | bottom: 1em; 16 | display: none; 17 | z-index: 999999; 18 | cursor: pointer; 19 | } 20 | 21 | .topButtonCircle:hover { 22 | background: #1369f4; 23 | } 24 | 25 | .topButtonCircle > div { 26 | border-left: 7px solid transparent; 27 | border-right: 7px solid transparent; 28 | border-bottom: 12.5px solid #fff; 29 | position: absolute; 30 | top: 50%; 31 | left: 50%; 32 | transform: translate(-50%, -50%); 33 | } 34 | 35 | .backtotop { 36 | display: none; 37 | } -------------------------------------------------------------------------------- /src/WatchDoc.js: -------------------------------------------------------------------------------- 1 | /*** Watch Doc ***/ 2 | 3 | // Watch the documentation page of a template when you watch the template 4 | // Documentation at [[en:w:User:BrandonXLF/WatchDoc]] 5 | // By [[en:w:User:BrandonXLF]] 6 | 7 | $(function() { 8 | var namespace = mw.config.get('wgNamespaceNumber'), 9 | title = mw.config.get('wgPageName'), 10 | watchlink = $('#ca-watch a, #ca-unwatch a'); 11 | 12 | if (namespace !== 10 && namespace !== 11 || title.includes('/doc')) return; 13 | 14 | watchlink.click(function() { 15 | mw.loader.using('mediawiki.page.watch.ajax').then(function(require) { 16 | var clone = watchlink.clone().removeClass('loading'), 17 | oldNotify = mw.notify; 18 | 19 | mw.notify = function(msg, opts) { 20 | // Both the doc and non-doc messages need to be shown... 21 | if (~msg.text().indexOf(title + '/doc')) opts.tag += '-doc'; 22 | oldNotify(msg, opts); 23 | }; 24 | 25 | require('mediawiki.page.watch.ajax').watchstar(clone, title + '/doc', function() { 26 | mw.notify = oldNotify; 27 | }); 28 | 29 | clone.click(); 30 | }); 31 | }); 32 | }); -------------------------------------------------------------------------------- /src/WPBannerNavbar.js: -------------------------------------------------------------------------------- 1 | mw.hook('wikipage.content').add(function(content) { 2 | var headers = content.find('.wpb .wpb-header'); 3 | 4 | if (!headers.length) return; 5 | 6 | var wikitext = '', 7 | ids = {}; 8 | 9 | headers.each(function(i) { 10 | var header = $(this), 11 | template = header.next().find('.wpb-banner_name').text(), 12 | id = 'banner-navbar-' + i; 13 | 14 | if (!template) return; 15 | 16 | wikitext += '
    {{Navbar|' + template + '|mini=y}}
    '; 17 | ids[id] = header; 18 | }); 19 | 20 | new mw.Api().parse(wikitext).then(function(html) { 21 | var container = document.createElement('div'); 22 | container.innerHTML = html; 23 | document.body.append(container); 24 | 25 | $.each(ids, function(id, header) { 26 | var navbar = $('#' + id).children(); 27 | 28 | navbar.css('float', 'right'); 29 | 30 | if (header.css('display') === 'none') { 31 | header.next().find('> :first-child .mbox-text').first().prepend(navbar); 32 | return; 33 | } 34 | 35 | header.find('> :nth-child(2)').append(navbar); 36 | navbar.css('margin', '.15em .5em 0 0'); 37 | }); 38 | }); 39 | }); -------------------------------------------------------------------------------- /src/FloatSide.css: -------------------------------------------------------------------------------- 1 | /*** Float Side ***/ 2 | 3 | /* Makes the side menu float and makes it scrollable */ 4 | /* Documentation at [[en:w:User:BrandonXLF/FloatSide]] */ 5 | /* By [[en:w:User:BrandonXLF]] */ 6 | 7 | #mw-panel, /* Vector */ 8 | #quickbar, /* CologneBlue */ 9 | #mw_portlets, /* Modern */ 10 | #sidebar, /* Monobook */ 11 | #mw-site-navigation, /* Timeless */ 12 | #mw-related-navigation /* Timeless right */ { 13 | position: fixed; 14 | overflow: hidden; 15 | height: 100%; 16 | } 17 | 18 | #mw-related-navigation /* Timeless right */ { 19 | right: calc(calc(calc(100% - 115em) / 2) - 1em); 20 | padding-bottom: 14em; 21 | } 22 | 23 | #mw-site-navigation /* Timeless */ { 24 | padding-bottom: 14em; 25 | } 26 | 27 | #mw-panel:hover, /* Vector */ 28 | #quickbar:hover, /* CologneBlue */ 29 | #mw_portlets:hover, /* Modern */ 30 | #sidebar:hover, /* Monobook */ 31 | #mw-site-navigation:hover, /* Timeless */ 32 | #mw-related-navigation:hover /* Timeless right */ { 33 | overflow-y: auto; 34 | overflow-x: hidden; 35 | } 36 | 37 | .skin-timeless #mw-content-wrapper /* Timeless */ { 38 | margin: 0 14em; 39 | } 40 | 41 | .skin-monobook .mw-wiki-logo /* Monobook */ { 42 | position: fixed; 43 | } -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true 4 | }, 5 | "extends": "eslint:recommended", 6 | "globals": { 7 | "jQuery": "readonly", 8 | "$": "readonly", 9 | "mw": "readonly", 10 | "OO": "readonly", 11 | "ve": "readonly" 12 | }, 13 | "parserOptions": { 14 | "ecmaVersion": 6 15 | }, 16 | "rules": { 17 | "indent": ["error", "tab", {"SwitchCase": 1}], 18 | "quotes": ["error", "single"], 19 | "semi": ["error", "always"], 20 | "array-bracket-spacing": "error", 21 | "object-curly-spacing": "error", 22 | "space-before-function-paren": ["error", "never"], 23 | "no-lonely-if": "error", 24 | "no-unneeded-ternary": "error", 25 | "brace-style": "error", 26 | "space-before-blocks": "error", 27 | "one-var": ["error", "consecutive"], 28 | "one-var-declaration-per-line": ["error", "initializations"], 29 | "space-in-parens": "error", 30 | "func-call-spacing": "error", 31 | "function-call-argument-newline": ["error", "consistent"], 32 | "comma-spacing": "error", 33 | "max-len": ["error", {"code": 160}], 34 | "key-spacing": "error", 35 | "semi-style": "error", 36 | "operator-linebreak": "error", 37 | "no-trailing-spaces": "error", 38 | "no-implicit-globals": "error" 39 | } 40 | } -------------------------------------------------------------------------------- /src/CollapseSections.js: -------------------------------------------------------------------------------- 1 | /*** Collapse Sections ***/ 2 | 3 | // Adds an arrow to sections to make them collapsible 4 | // Documentation at [[en:w:User:BrandonXLF/CollapseSections]] 5 | // By [[en:w:User:BrandonXLF]] 6 | 7 | // window.collapseSections - Set to true to collapse all sections by default 8 | 9 | mw.hook('wikipage.content').add(function(content) { 10 | if (mw.config.get('skin') === 'minerva') return; 11 | 12 | mw.util.addCSS('[class*="hide-sec"]{display:none!important}'); 13 | 14 | content.find('.mw-parser-output :header:has(*)').each(function() { 15 | var level = +this.nodeName[1], 16 | heading = $(this), 17 | icon = $(''); 18 | 19 | icon.click(function() { 20 | var levelMatch = 'h1'; 21 | for (var i = 2; i <= level; i++) levelMatch += ',h' + i + ':has(*)'; 22 | 23 | icon.toggleClass('mw-ui-icon-collapse'); 24 | icon.toggleClass('mw-ui-icon-expand'); 25 | heading.nextUntil(levelMatch).toggleClass('hide-sect-h' + level); 26 | }); 27 | 28 | if (window.collapseSections) icon.click(); 29 | heading.prepend(icon); 30 | }); 31 | }); 32 | 33 | mw.loader.load(['mediawiki.ui.icon', 'oojs-ui.styles.icons-movement']); 34 | mw.util.addCSS('[class*="hide-sec"]{display:none!important}'); -------------------------------------------------------------------------------- /src/FloatHead.css: -------------------------------------------------------------------------------- 1 | 2 | /*** Float Head ***/ 3 | 4 | /* Makes the side menu float and makes it scroll-able */ 5 | /* Documentation at [[en:w:User:BrandonXLF/FloatHead]] */ 6 | /* By [[en:w:User:BrandonXLF]] */ 7 | /* Based off of [[en:w:User:Rezonansowy/FloatHead.css]] */ 8 | 9 | /* head */ 10 | #mw-head { 11 | position: fixed !important; 12 | top: 0; 13 | left: 0; 14 | right: 0; 15 | width: 100%; 16 | } 17 | 18 | /* Bottom border */ 19 | #mw-head::before { 20 | position: absolute; 21 | content: ''; 22 | top: -1px; 23 | left: 11em; 24 | right: 0; 25 | height: 100%; 26 | border-bottom: 1px solid #a7d7f9; 27 | z-index: -1; 28 | } 29 | 30 | /* Background */ 31 | #mw-page-base { 32 | position: fixed; 33 | top: -1px; 34 | right: 0; 35 | left: 0; 36 | z-index: 3; 37 | height: 5em; 38 | width: 100%; 39 | } 40 | 41 | #mw-head, 42 | #mw-panel { 43 | z-index: 4; 44 | } 45 | 46 | #mw-head-base { 47 | margin-top: 0; 48 | } 49 | 50 | /* Visual editor toolbar */ 51 | .ve-active .ve-init-mw-desktopArticleTarget { 52 | margin-top: 4em; 53 | } 54 | 55 | .ve-init-mw-desktopArticleTarget > .ve-ui-toolbar > .oo-ui-toolbar-bar { 56 | position: fixed !important; 57 | top: 5.714em !important; 58 | right: -0.004em !important; 59 | left: 12.647em !important; 60 | } 61 | 62 | /* StickyTableHeaders gadget */ 63 | .jquery-tablesorter > thead, 64 | .mw-sticky-header > thead { 65 | top: 5rem !important; 66 | } -------------------------------------------------------------------------------- /src/NoRedirect.js: -------------------------------------------------------------------------------- 1 | /*** No Redirect Link ***/ 2 | 3 | // Adds a link beside links to redirects that doesn't get redirected 4 | // Documentation at [[en:w:User:BrandonXLF/NoRedirect]] 5 | // By [[en:w:User:BrandonXLF]] 6 | 7 | mw.hook('wikipage.content').add(function(content) { 8 | content.find('.mw-redirect') 9 | .filter(function() { 10 | return this.href.indexOf('redirect=no') == -1 && 11 | this.href.indexOf('oldid=') == -1 && 12 | this.href.indexOf('diff=') == -1 && 13 | (this.href.indexOf('action=') == -1 || this.href.indexOf('action=view') != -1); 14 | }) 15 | .after(function() { 16 | return $('') 17 | .attr('href', this.href + (this.href.includes('?') ? '&' : '?') + 'redirect=no') 18 | .attr('title', this.title + ' (no redirect)') 19 | .css('cssText', 'margin-left: 1px; user-select: none;') 20 | .append( 21 | $('') 22 | .attr('alt', 'no redirect') 23 | .attr('src', 'data:image/svg+xml,<%2Fsvg>') 24 | .css('cssText', 'height: 0.6em !important; vertical-align: text-top;') 25 | ); 26 | }); 27 | }); -------------------------------------------------------------------------------- /src/Autoref.js: -------------------------------------------------------------------------------- 1 | /*** Autoref ***/ 2 | 3 | // Insert auto-filled references in the 2010 Wikitext editor like you can in VE 4 | // Documentation at [[en:w:User:BrandonXLF/Autoref]] 5 | // By [[en:w:User:BrandonXLF]] 6 | 7 | /* global getCitoidRef */ 8 | 9 | $.when(mw.loader.using('ext.wikiEditor'), mw.loader.getScript( 10 | 'https://en.wikipedia.org/w/index.php?title=User:BrandonXLF/Citoid.js&action=raw&ctype=text/javascript' 11 | ), $.ready).then(function() { 12 | $('#wikiEditor-section-main [rel="reference"] > a').unbind().click(function() { 13 | var pos = { 14 | start: $('#wpTextbox1').textSelection('getCaretPosition'), 15 | end: $('#wpTextbox1').textSelection('getCaretPosition') + $('#wpTextbox1').textSelection('getSelection').length 16 | }; 17 | 18 | OO.ui.prompt($('Enter a source:'), { 19 | textInput: { 20 | placeholder: 'Leave blank for none' 21 | } 22 | }).done(function(source) { 23 | $('#wpTextbox1').textSelection('setSelection', pos); 24 | 25 | if (source === null) return; 26 | 27 | if (source === '') { 28 | $('#wpTextbox1').textSelection('encapsulateSelection', {pre: '', post: ''}); 29 | return; 30 | } 31 | 32 | getCitoidRef(source).then(function(ref) { 33 | $('#wpTextbox1').textSelection('replaceSelection', '' + ref + ''); 34 | }, function(err) { 35 | mw.notify(err, {type: 'error'}); 36 | }); 37 | }); 38 | }); 39 | }); -------------------------------------------------------------------------------- /src/ToBottom.js: -------------------------------------------------------------------------------- 1 | /*** To Bottom ***/ 2 | 3 | // Adds a link in the right navigation menu to got to the bottom of the page 4 | // Documentation at [[en:w:User:BrandonXLF/ToBottom]] 5 | // By [[en:w:User:BrandonXLF]] 6 | 7 | // window.arrow = 'never'; // To always see Bottom 8 | // window.arrow = 'always; // To always see ↓ (down arrow) 9 | // window.arrow = 'hybrid'; // To see ↓ (down arrow) normally and Bottom when in menu (default) 10 | 11 | $(function() { 12 | function scroll(e) { 13 | e.preventDefault(); 14 | $('html').animate({scrollTop: $(document).height()}); 15 | } 16 | 17 | var mode = 'hybrid'; 18 | 19 | if (window.arrow == 'never' || window.arrow == 'always') { 20 | mode = window.arrow; 21 | } 22 | 23 | $(mw.util.addPortletLink('p-views', '#', mode == 'never' ? 'Bottom' : '↓', 'ca-to-bottom', null, null, '.mw-watchlink')) 24 | .addClass('collapsible') 25 | .click(scroll); 26 | 27 | if (mode == 'hybrid') { 28 | $(mw.util.addPortletLink('p-views', '#', 'Bottom', 'ca-to-bottom-text', null, null, '.mw-watchlink')) 29 | .addClass('collapsible') 30 | .click(scroll); 31 | } 32 | 33 | if (window.arrow != 'never' && window.arrow != 'always') { 34 | mw.util.addCSS( 35 | '#ca-to-bottom { display: initial !important; }' + 36 | '#ca-to-bottom-text { display: none !important; }' + 37 | '#p-cactions #ca-to-bottom { display: none !important; }' + 38 | '#p-cactions #ca-to-bottom-text { display: initial !important; }' 39 | ); 40 | } 41 | }); -------------------------------------------------------------------------------- /src/EasySummary.js: -------------------------------------------------------------------------------- 1 | /*** Easy Summary ***/ 2 | 3 | // Easily use the last summary you used or load and save a summary 4 | // Documentation at [[en:w:User:BrandonXLF/EasySummary]] 5 | // By [[en:w:User:BrandonXLF]] 6 | 7 | $(function() { 8 | function addLinks(label, field) { 9 | function updateField(storage) { 10 | return function() { 11 | var summary = $.trim(field.val()) || ''; 12 | field.val((summary && summary + ' ' || '') + mw.storage.get(storage)); 13 | }; 14 | } 15 | 16 | function saveField(storage) { 17 | return function() { 18 | mw.storage.set(storage, field.val()); 19 | }; 20 | } 21 | 22 | function makeLink(object, title, onClick) { 23 | return $('').text(object).attr('title', title).on('click', function(e) { 24 | e.preventDefault(); 25 | onClick(); 26 | }); 27 | } 28 | 29 | if (! $('#easySummary-container')[0]) { 30 | var $last = makeLink('Last', 'Use the last typed edit summary', updateField('easySummary-last')), 31 | $load = makeLink('Load', 'Load the saved edit summary', updateField('easySummary-saved')), 32 | $save = makeLink('Save', 'Save an edit summary for later', saveField('easySummary-saved')); 33 | label.append($('').attr('id', 'easySummary-container').append(' ( ', $last, ' | ', $load, ' | ', $save, ' )')); 34 | field.on('change', saveField('easySummary-last')); 35 | } 36 | } 37 | 38 | addLinks($('[for=\'wpSummary\']'), $('#wpSummary')); 39 | 40 | mw.hook('ve.saveDialog.stateChanged').add(function() { 41 | addLinks(ve.init.target.saveDialog.$editSummaryLabel, ve.init.target.saveDialog.editSummaryInput.$input); 42 | }); 43 | }); -------------------------------------------------------------------------------- /src/AJAXUndo.js: -------------------------------------------------------------------------------- 1 | /*** AJAX Undo ***/ 2 | 3 | // Adds a button to undo changes using AJAX to history pages 4 | // Documentation at [[en:w:User:BrandonXLF/AJAXUndo]] 5 | // By [[en:w:User:BrandonXLF]] 6 | 7 | $(function() { 8 | if (mw.config.get('wgAction') != 'history') return; 9 | 10 | function undo(undoId, undoafter) { 11 | return new mw.Api().postWithEditToken({ 12 | action: 'edit', 13 | undo: undoId, 14 | undoafter: undoafter, 15 | title: mw.config.get('wgPageName'), 16 | }).then( 17 | function() { 18 | mw.notify('Edit undone successfully! Reloading...'); 19 | location.reload(); 20 | }, 21 | function(_, data) { 22 | mw.notify(new mw.Api().getErrorMessage(data), {type: 'error'}); 23 | } 24 | ); 25 | } 26 | 27 | $('.mw-history-undo').parent().after( 28 | $('').append( 29 | $('') 30 | .text('ajax undo') 31 | .click(function() { 32 | var el = $(this), 33 | undoLink = el 34 | .closest('.mw-changeslist-links') 35 | .find('.mw-history-undo a') 36 | .attr('href'); 37 | 38 | el.addClass('ajax-undo-loading'); 39 | 40 | undo( 41 | undoLink.match(/undo=([0-9]+)/)[1], 42 | undoLink.match(/undoafter=([0-9]+)/)[1] 43 | ).always(function() { 44 | el.removeClass('ajax-undo-loading'); 45 | }); 46 | }) 47 | ) 48 | ); 49 | 50 | mw.loader.addStyleTag( 51 | '@keyframes ajax-undo-loading {' + 52 | '0%, 100% {content: " ⡁"} 16% {content: " ⡈"} 33% {content: " ⠔"} 50% {content: " ⠒"} 66% {content: " ⠢"} 83% {content: " ⢁"}}' + 53 | '.ajax-undo-loading::after {white-space: pre; content: ""; animation: ajax-undo-loading 0.5s infinite}' 54 | ); 55 | }); -------------------------------------------------------------------------------- /src/ShowUserGender.js: -------------------------------------------------------------------------------- 1 | /** Show User Gender **/ 2 | 3 | // Show the gender of users 4 | // [[en:w:User:BrandonXLF/ShowUserGender]] 5 | // By [[en:w:User:BrandonXLF]] 6 | 7 | window.SHOW_USER_GENDER = $.extend({ 8 | male: ' [he]', 9 | female: ' [she]', 10 | unknown: ' [?]', 11 | }, window.SHOW_USER_GENDER); 12 | 13 | mw.hook('wikipage.content').add(function(content) { 14 | var USER_NS = mw.config.get('wgFormattedNamespaces')[2] + ':'; 15 | 16 | function getUser(el) { 17 | return decodeURIComponent(el.href).match(/^.*\/(.*?)$/)[1].replace(/_/g, ' ').match(/(index\.php\?title=|)([^&]*).*?/)[2]; 18 | } 19 | 20 | var elements = content.find('.userlink').filter(function() { 21 | return getUser(this).startsWith(USER_NS); 22 | }), 23 | 24 | users = elements.map(function() { 25 | return getUser(this).substr(USER_NS.length); 26 | }).toArray().filter(function(val, i, a) { 27 | return i === a.indexOf(val); 28 | }), 29 | 30 | genders = {}, 31 | requests = 0, 32 | done = 0; 33 | 34 | for (var i = 0; i < users.length; i += 50) { 35 | var reqUsers = users.slice(i, i + 50); 36 | requests++; 37 | 38 | new mw.Api().get({ 39 | action: 'query', 40 | list: 'users', 41 | usprop: 'gender', 42 | ususers: reqUsers.join('|') 43 | }).then(function(res) { 44 | done++; 45 | res = res.query.users; 46 | 47 | for (var i = 0; i < res.length; i++) { 48 | genders[res[i].name] = res[i].gender; 49 | } 50 | 51 | if (requests == done) { 52 | elements.each(function() { 53 | var user = getUser(this).substr(USER_NS.length); 54 | $(this).after(window.SHOW_USER_GENDER[genders[user] || 'unknown']); 55 | }); 56 | } 57 | }); 58 | } 59 | }); -------------------------------------------------------------------------------- /src/CitationStyleMarker.js: -------------------------------------------------------------------------------- 1 | /*** Citation Style Marker ***/ 2 | 3 | // Puts the type of citation style a citation is using next to it 4 | // Adds a link in the toolbar to toggle the markers 5 | // Documentation at [[en:w:User:BrandonXLF/CitationStyleMarker]] 6 | // By [[en:w:User:BrandonXLF]] 7 | 8 | // window.CSMarkerMode = 'always'; - Always how the style marker, no option to disable 9 | // window.CSMarkerMode = 'both'; - Show the markers when there is more than one type of citation of a page 10 | // window.CSMarkerMode = 'disabled'; - Hide the markers by default 11 | // window.CSMarkerMode = 'enabled'; - Show the markers by default 12 | 13 | $(function() { 14 | mw.loader.load('//en.wikipedia.org/w/index.php?title=User:BrandonXLF/CitationStyleMarker.css&action=raw&ctype=text/css', 'text/css'); 15 | if (window.CSMarkerMode != 'always') { 16 | var portletItem = mw.util.addPortletLink('p-tb', '#', 'Hide CS marker'), 17 | classes = ['cs1', 'cs2', 'csblue'], 18 | typeCount = classes.reduce(function(acc, cur) { 19 | return acc + !!document.getElementsByClassName(cur).length; 20 | }, 0); 21 | 22 | if (!portletItem) return; 23 | 24 | var portlet = portletItem.getElementsByTagName('a')[0]; 25 | 26 | portlet.addEventListener('click', function(e) { 27 | var enabled = document.body.classList.contains('nocsmarker'); 28 | e.preventDefault(); 29 | document.body.classList[enabled ? 'remove' : 'add']('nocsmarker'); 30 | portlet.innerText = enabled ? 'Hide CS marker' : 'Show CS marker'; 31 | }); 32 | 33 | if (window.CSMarkerMode == 'disabled' || window.CSMarkerMode == 'both' && typeCount < 2) { 34 | document.body.classList.add('nocsmarker'); 35 | portlet.innerText = 'Show CS marker'; 36 | } 37 | } 38 | }); -------------------------------------------------------------------------------- /src/Restorer.js: -------------------------------------------------------------------------------- 1 | /*** Restorer ***/ 2 | 3 | // Easily restore an older version of a page 4 | // Documentation at [[en:w:User:BrandonXLF/Restorer]] 5 | // By [[en:w:User:BrandonXLF]] 6 | 7 | $(function() { 8 | if (mw.config.get('wgAction') != 'history') return; 9 | 10 | window.restorerSummary = window.restorerSummary || 11 | 'Restored revision $ID by [[Special:Contributions/$USER|$USER]] ([[en:w:User:BrandonXLF/Restorer|Restorer]])'; 12 | 13 | function restore(revid) { 14 | var api = new mw.Api(); 15 | 16 | return api.get({ 17 | action: 'query', 18 | revids: revid, 19 | prop: 'revisions', 20 | rvprop: 'user', 21 | format: 'json', 22 | formatversion: '2' 23 | }).then(function(res) { 24 | var user = res.query.pages[0].revisions[0].user; 25 | 26 | return api.postWithEditToken({ 27 | action: 'edit', 28 | pageid: mw.config.get('wgArticleId'), 29 | undo: mw.config.get('wgCurRevisionId'), 30 | undoafter: revid, 31 | summary: window.restorerSummary.replace(/\$ID/g, revid).replace(/\$USER/g, user) 32 | }); 33 | }).then( 34 | function() { 35 | mw.notify('Restored revision successfully.'); 36 | location.reload(); 37 | }, 38 | function(_, data) { 39 | mw.notify(api.getErrorMessage(data), {type: 'error'}); 40 | } 41 | ); 42 | } 43 | 44 | function addLink(item) { 45 | var revid = item.getAttribute('data-mw-revid'); 46 | if (revid == mw.config.get('wgCurRevisionId')) return; 47 | 48 | var links = item.querySelector('.comment + .mw-changeslist-links'); 49 | if (!links) return; 50 | 51 | var parent = document.createElement('span'), 52 | el = document.createElement('a'); 53 | 54 | el.addEventListener('click', function() { 55 | el.className = 'restorer-loading'; 56 | 57 | restore(revid).always(function() { 58 | el.className = ''; 59 | }); 60 | }); 61 | 62 | el.textContent = 'restore'; 63 | parent.appendChild(el); 64 | links.appendChild(parent); 65 | } 66 | 67 | var parents = document.querySelectorAll('li[data-mw-revid]'); 68 | 69 | for (var i = 0; i < parents.length; i++) { 70 | addLink(parents[i]); 71 | } 72 | 73 | mw.loader.addStyleTag( 74 | '@keyframes restorer-loading {' + 75 | '0%, 100% {content: " ⡁"} 16% {content: " ⡈"} 33% {content: " ⠔"} 50% {content: " ⠒"} 66% {content: " ⠢"} 83% {content: " ⢁"}}' + 76 | '.restorer-loading::after {white-space: pre; content: ""; animation: restorer-loading 0.5s infinite}' 77 | ); 78 | }); -------------------------------------------------------------------------------- /src/UpdateNotifications.js: -------------------------------------------------------------------------------- 1 | /*** Update Notifications ***/ 2 | 3 | // Updates the alert and notification counts every few seconds 4 | // Documentation at [[en:w:User:BrandonXLF/UpdateNotifications]] 5 | // By [[en:w:User:BrandonXLF]] 6 | 7 | $(function() { 8 | var crossWiki = mw.user.options.get('echo-cross-wiki-notifications'), 9 | shownTime = Date.now(); 10 | 11 | function updateIcon(id, data) { 12 | $('#' + id + ' a') 13 | .toggleClass('mw-echo-unseen-notifications', data.latest > data.seen) 14 | .toggleClass('mw-echo-notifications-badge-all-read', !data.count) 15 | .attr('data-counter-num', data.count) 16 | .attr('data-counter-text', data.count); 17 | } 18 | 19 | function updateCount(status) { 20 | if (!window.noUpdateNotificationNotice && (status.alert.latest > shownTime || status.message.latest > shownTime)) { 21 | shownTime = Date.now(); 22 | mw.notify('New notification received!'); 23 | } 24 | 25 | updateIcon('pt-notifications-alert', status.alert); 26 | updateIcon('pt-notifications-notice', status.message); 27 | } 28 | 29 | function getData() { 30 | new mw.Api().get({ 31 | action: 'query', 32 | format: 'json', 33 | meta: 'notifications', 34 | notprop: 'list|count|seenTime', 35 | notlimit: 1, 36 | notgroupbysection: true, 37 | notalertunreadfirst: true, 38 | notmessageunreadfirst: true, 39 | notcrosswikisummary: crossWiki 40 | }).then(function(res) { 41 | var info = res.query.notifications, 42 | status = { 43 | alert: { 44 | seen: Date.parse(info.alert.seenTime), 45 | latest: info.alert.list[0].timestamp.utcunix, 46 | count: info.alert.rawcount 47 | }, 48 | message: { 49 | seen: Date.parse(info.message.seenTime), 50 | latest: info.message.list[0].timestamp.utcunix, 51 | count: info.message.rawcount 52 | } 53 | }; 54 | 55 | localStorage.setItem('update-notifications-status', JSON.stringify(status)); 56 | updateCount(status); 57 | }); 58 | } 59 | 60 | window.addEventListener('storage', function(e) { 61 | if (e.key == 'update-notifications-status') updateCount(JSON.parse(e.newValue)); 62 | }); 63 | 64 | setInterval(function() { 65 | var lastRequestTime = +localStorage.getItem('update-notifications-last-request-time'), 66 | now = Date.now(); 67 | 68 | if (now - lastRequestTime >= 4900) { 69 | localStorage.setItem('update-notifications-last-request-time', now); 70 | getData(); 71 | } 72 | }, 5000); 73 | }); -------------------------------------------------------------------------------- /src/ToggleSidebar.js: -------------------------------------------------------------------------------- 1 | /*** Toggle Sidebar ***/ 2 | 3 | // Adds a button to show/hide the sidebar in the Vector skin 4 | // Documentation at [[en:w:User:BrandonXLF/ToggleSidebar]] 5 | // By [[en:w:User:BrandonXLF]] 6 | 7 | // VAR: window.hidesidebar = true -- hide by default 8 | // VAR: window.hidesidebar = false -- do not hide by default (default) 9 | // VAR: window.hidesidebar = 'editing' -- hide when editing 10 | 11 | // NOTE: Only supports the Vector skin 12 | 13 | $.when(mw.loader.using(['oojs-ui-core', 'oojs-ui.styles.icons-layout']), $.ready).done(function() { 14 | if (mw.config.get('skin') === 'minerva') return; 15 | var opts = { 16 | editing: !!document.getElementById('editform') || !!document.getElementsByClassName('ve-init-target-source')[0], 17 | true: true, 18 | false: false, 19 | no: false, 20 | yes: true, 21 | always: true, 22 | never: false 23 | }, 24 | hidden = opts[window.hidesidebar] || false, 25 | style = $( 26 | '' 29 | ), 30 | showicon = new OO.ui.IconWidget({ 31 | icon: 'menu' 32 | }), 33 | showlabel = new OO.ui.LabelWidget({ 34 | label: 'Show menu' 35 | }), 36 | show = new OO.ui.HorizontalLayout({ 37 | items: [showicon, showlabel] 38 | }), 39 | hideicon = new OO.ui.IconWidget({ 40 | icon: 'menu' 41 | }), 42 | hidelabel = new OO.ui.LabelWidget({ 43 | label: 'Hide menu' 44 | }), 45 | hide = new OO.ui.HorizontalLayout({ 46 | items: [hideicon, hidelabel] 47 | }); 48 | 49 | showicon.$element.css({ 50 | width: '20px', 51 | height: '20px', 52 | opacity: 0.51, 53 | cursor: 'pointer', 54 | marginBottom: 0 55 | }); 56 | 57 | showlabel.$element.css({ 58 | marginBottom: 0, 59 | cursor: 'pointer', 60 | color: '#222222' 61 | }); 62 | 63 | show.$element.css({ 64 | position: 'absolute', 65 | top: '0.33em', 66 | left: '8px' 67 | }).on('click', function() { 68 | show.$element.detach(); 69 | style.remove(); 70 | }); 71 | 72 | hideicon.$element.css({ 73 | width: '20px', 74 | height: '20px', 75 | opacity: 0.51, 76 | cursor: 'pointer', 77 | marginBottom: 0 78 | }); 79 | 80 | hidelabel.$element.css({ 81 | marginBottom: 0, 82 | cursor: 'pointer', 83 | color: '#222222' 84 | }); 85 | 86 | hide.$element.css({ 87 | display: 'inline-block' 88 | }).on('click', function() { 89 | show.$element.prependTo('#mw-head'); 90 | style.appendTo(document.body); 91 | }); 92 | 93 | $('#p-navigation').css('margin-top', '0.5em').prepend($('
    ').append(hide.$element)); 94 | 95 | if (hidden) { 96 | show.$element.prependTo('#mw-head'); 97 | style.appendTo(document.body); 98 | } 99 | }); -------------------------------------------------------------------------------- /src/MoreDiffInfo.js: -------------------------------------------------------------------------------- 1 | /*** More Diff Info ***/ 2 | 3 | // Adds more information to diff pages such as revision ID, size, and ORES score 4 | // Documentation at [[en:w:User:BrandonXLF/MoreDiffInfo]] 5 | // By [[en:w:User:BrandonXLF]] 6 | 7 | $.when(mw.loader.using('moment'), $.ready).then(function(require) { 8 | var moment = require('moment'), 9 | DA_IMG = 'https://upload.wikimedia.org/wikipedia/commons/thumb/3/3d/Rating-Christgau-dud.svg/64px-Rating-Christgau-dud.svg.png', 10 | GF_IMG = 'https://upload.wikimedia.org/wikipedia/commons/thumb/2/21/Ic_thumb_up_48px.svg/32px-Ic_thumb_up_48px.svg.png'; 11 | 12 | function createImage(type, url) { 13 | return '' + type + ''; 14 | } 15 | 16 | function getInnerORES(scores) { 17 | var innerORES = []; 18 | 19 | if (scores.damaging) { 20 | innerORES.push(Math.round(scores.damaging.true * 100) + '% ' + createImage('Damaging', DA_IMG)); 21 | } 22 | 23 | if (scores.goodfaith) { 24 | innerORES.push(Math.round(scores.goodfaith.true * 100) + '% ' + createImage('Good Faith', GF_IMG)); 25 | } 26 | 27 | return innerORES.join(' ') || 'No ORES'; 28 | } 29 | 30 | function generateInfo(revision, previousRevision) { 31 | var out = [ 32 | revision.revid, 33 | revision.size.toLocaleString() +' bytes', 34 | getInnerORES(revision.oresscores), 35 | ], 36 | help = '(?)'; 37 | 38 | if (previousRevision) { 39 | var diff = revision.size - previousRevision.size; 40 | 41 | out[1] += ' (' + (diff > 0 ? '+' : '') + diff + ')'; 42 | 43 | out.push(moment(revision.timestamp).from(previousRevision.timestamp, true) + ' later'); 44 | } 45 | 46 | return out.join(' | ') + ' ' + help; 47 | } 48 | 49 | mw.hook('wikipage.diff').add(function() { 50 | var ids = mw.config.get(['wgDiffOldId', 'wgDiffNewId']); 51 | 52 | if (!ids.wgDiffOldId || !ids.wgDiffNewId) return; 53 | 54 | new mw.Api().get({ 55 | action: 'query', 56 | prop: 'revisions', 57 | revids: [ids.wgDiffOldId, ids.wgDiffNewId], 58 | rvprop: ['ids', 'size', 'oresscores', 'timestamp'], 59 | rvslots: 'main', 60 | formatversion: 2 61 | }).then(function(res) { 62 | var revisions = res.query.pages[0].revisions, 63 | oldRevision, 64 | newRevision; 65 | 66 | for (var i = 0; i < revisions.length; i++) { 67 | if (revisions[i].revid == ids.wgDiffOldId) { 68 | oldRevision = revisions[i]; 69 | } else if (revisions[i].revid == ids.wgDiffNewId) { 70 | newRevision = revisions[i]; 71 | } 72 | } 73 | 74 | if (!oldRevision || !newRevision) return; 75 | 76 | $('#mw-diff-otitle2').after($('
    ').append(generateInfo(oldRevision))); 77 | $('#mw-diff-ntitle2').after($('
    ').append(generateInfo(newRevision, oldRevision))); 78 | }); 79 | }); 80 | }); 81 | 82 | mw.loader.addStyleTag( 83 | '.morediffinto-icon { height: 1em; vertical-align: text-top; }' + 84 | '@media screen { .skin-theme-clientpref-night .morediffinto-icon { filter: invert(1); } }' + 85 | '@media screen and ( prefers-color-scheme: dark) { html.skin-theme-clientpref-os .morediffinto-icon { filter: invert(1); } }' 86 | ); -------------------------------------------------------------------------------- /src/ContribsByPage.js: -------------------------------------------------------------------------------- 1 | /*** Group Contributions By Page ***/ 2 | 3 | // Groups revisions at Special:Contributions by page 4 | // Documentation at [[en:w:User:BrandonXLF/ContribsByPage]] 5 | // By [[en:w:User:BrandonXLF]] 6 | 7 | $(function() { 8 | if (mw.config.get('wgCanonicalSpecialPageName') != 'Contributions') return; 9 | mw.loader.load(['mediawiki.special.changeslist.enhanced', 'jquery.makeCollapsible.styles', 'mediawiki.icon']); 10 | var pages = {}, 11 | arrowClass = 'mw-collapsible-toggle mw-collapsible-arrow mw-enhancedchanges-arrow mw-enhancedchanges-arrow-space mw-collapsible-toggle-collapsed'; 12 | 13 | $('[data-mw-revid]').each(function() { 14 | var title = $('.mw-contributions-title', this).attr('title'); 15 | if (!pages[title]) { 16 | pages[title] = []; 17 | } 18 | pages[title].push(this); 19 | }); 20 | 21 | function merge(page) { 22 | if (pages[page].length > 1) { 23 | var diff = $(''), 24 | toggle = $('').click(function() { 25 | if (toggle.hasClass('mw-collapsible-toggle-collapsed')) { 26 | toggle.removeClass('mw-collapsible-toggle-collapsed').addClass('mw-collapsible-toggle-expanded'); 27 | sub.show(); 28 | } else { 29 | toggle.removeClass('mw-collapsible-toggle-expanded').addClass('mw-collapsible-toggle-collapsed'); 30 | sub.hide(); 31 | } 32 | }), 33 | head = $('
  • ').append( 34 | toggle, 35 | $('.mw-changeslist-date', pages[page][0]).first().text(), 36 | ' ', 37 | $('').append( 38 | '' + pages[page].length + ' changes', 39 | $('.mw-changeslist-history', pages[page][0]).parent().clone().children().text('history').parent() 40 | ), 41 | ' ', 42 | diff, 43 | ' ', 44 | $('.newpage', pages[page]).clone(), 45 | $('.newpage', pages[page]).length ? ' ' : '', 46 | $('.mw-contributions-title', pages[page][0]).clone(), 47 | $('.mw-uctop', pages[page][0]).length ? ' ' : '', 48 | $('.mw-uctop', pages[page][0]).clone(), 49 | $('.mw-rollback-link', pages[page][0]).length ? ' ' : '', 50 | $('.mw-rollback-link', pages[page][0]).clone() 51 | ).insertBefore(pages[page][0]), 52 | sub = $('