From 9fd34e70f5ab4813d0c713b1304f051d506fd8ab Mon Sep 17 00:00:00 2001 From: NovemLinguae Date: Tue, 24 Feb 2026 21:22:40 -0800 Subject: [PATCH 01/50] Links: fix not showing on user pages that don't exist --- Links.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Links.js b/Links.js index 3952983..41a845e 100644 --- a/Links.js +++ b/Links.js @@ -54,7 +54,7 @@ class Links { createBottomLeftMenuContainer() { // Works as expected in vector, modern, monobook, timeless. Is in right menu instead of left menu in vector-2022. Doesn't show up at all in minerva. // Could fix in those skins by adding it before an existing nearby portlet, then using the p.parentNode.appendChild( p ); trick. - this.mw.util.addPortlet( 'p-links', 'More tools', '#p-coll-print_export' ); + this.mw.util.addPortlet( 'p-links', 'More tools', '#p-tb' ); } async generateUserspaceLinks() { From f865ce5e03675af8c02ab74d008f9d94335c7d3e Mon Sep 17 00:00:00 2001 From: NovemLinguae Date: Tue, 24 Feb 2026 21:59:32 -0800 Subject: [PATCH 02/50] Links: remove comment might be out of date because of the last commit --- Links.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/Links.js b/Links.js index 41a845e..43f2825 100644 --- a/Links.js +++ b/Links.js @@ -52,8 +52,6 @@ class Links { } createBottomLeftMenuContainer() { - // Works as expected in vector, modern, monobook, timeless. Is in right menu instead of left menu in vector-2022. Doesn't show up at all in minerva. - // Could fix in those skins by adding it before an existing nearby portlet, then using the p.parentNode.appendChild( p ); trick. this.mw.util.addPortlet( 'p-links', 'More tools', '#p-tb' ); } From b3c5de40a77853b750d055c3f465554965669b89 Mon Sep 17 00:00:00 2001 From: NovemLinguae Date: Sat, 14 Mar 2026 09:56:39 -0700 Subject: [PATCH 03/50] Links: improve minerva support --- Links.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/Links.js b/Links.js index 43f2825..5ef2e7f 100644 --- a/Links.js +++ b/Links.js @@ -29,10 +29,9 @@ - modern - monobook - timeless + - minerva (TODO: make less ugly) - right menu (tools menu) - vector-2022 - - not displayed at all - - minerva */ class Links { @@ -52,7 +51,11 @@ class Links { } createBottomLeftMenuContainer() { - this.mw.util.addPortlet( 'p-links', 'More tools', '#p-tb' ); + if ( this.mw.config.get( 'skin' ) === 'minerva' ) { + this.mw.util.addPortlet( 'p-links', 'More tools', '#p-navigation' ); + } else { + this.mw.util.addPortlet( 'p-links', 'More tools', '#p-tb' ); + } } async generateUserspaceLinks() { From ba8044f91b240201204edff036c5a754c4e0a467 Mon Sep 17 00:00:00 2001 From: dw31415wp-glitch Date: Thu, 26 Mar 2026 03:54:17 -0500 Subject: [PATCH 04/50] Update edit summary to include page name (#280) * Update edit summary to include page name Add page name to summary * add wikilink, add section, remove "discussion" --------- Co-authored-by: NovemLinguae --- anrfc-lister.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/anrfc-lister.js b/anrfc-lister.js index d90518c..3d8b992 100644 --- a/anrfc-lister.js +++ b/anrfc-lister.js @@ -251,6 +251,7 @@ class ANRFC { } // Grab what the user typed into the form. + // For the targetSection dropdown list, the values are 0, 1, 2, etc. const targetSection = dropDown.getMenu().findSelectedItem().getData(); const message = messageInput.getValue(); @@ -293,7 +294,7 @@ class ANRFC { action: 'edit', title: 'Wikipedia:Closure_requests', text: wikitext, - summary: 'Listing new discussion using [[User:Novem Linguae/Scripts/anrfc-lister.js|anrfc-lister]]', + summary: 'Listing [[' + pageName + '#' + sectionTitle + ']] using [[User:Novem Linguae/Scripts/anrfc-lister.js|anrfc-lister]]', nocreate: true } ); From 63043daa6ebbd4841b430597d7fe2c7f38c9c85d Mon Sep 17 00:00:00 2001 From: NovemLinguae Date: Thu, 26 Mar 2026 01:58:22 -0700 Subject: [PATCH 05/50] anrfc-lister: in the edit summary, mention the target section --- anrfc-lister.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/anrfc-lister.js b/anrfc-lister.js index 3d8b992..cebdcd4 100644 --- a/anrfc-lister.js +++ b/anrfc-lister.js @@ -294,7 +294,7 @@ class ANRFC { action: 'edit', title: 'Wikipedia:Closure_requests', text: wikitext, - summary: 'Listing [[' + pageName + '#' + sectionTitle + ']] using [[User:Novem Linguae/Scripts/anrfc-lister.js|anrfc-lister]]', + summary: '/* ' + this.sections[ targetSection ] + ' */ Listing [[' + pageName + '#' + sectionTitle + ']] using [[User:Novem Linguae/Scripts/anrfc-lister.js|anrfc-lister]]', nocreate: true } ); From c53effc7fe81d574e235e1da35bd1a0ebbc2dac9 Mon Sep 17 00:00:00 2001 From: NovemLinguae Date: Fri, 3 Apr 2026 17:51:49 -0700 Subject: [PATCH 06/50] Superlinks: fork from Bradv version --- Superlinks.js | 598 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 598 insertions(+) create mode 100644 Superlinks.js diff --git a/Superlinks.js b/Superlinks.js new file mode 100644 index 0000000..f5ea016 --- /dev/null +++ b/Superlinks.js @@ -0,0 +1,598 @@ +(function($, mw) { + 'use strict'; + + var ns = mw.config.get("wgNamespaceNumber"); + if (mw.config.get("skin")=="vector") { + var app = { + styleSheet: mw.util.addCSS(` + .mw-indicators { + z-index: 1 !important; + } + #superlinks { + display:inline-block; + line-height: 24px + } + #superlinks-links { + font-size: 0.75em; + color:#555; + } + #superlinks-links label { + padding: 0 0.2em; + } + #superlinks-links > span:before { + content: '(' + } + #superlinks-links > span:after { + content: ') ' + } + #superlinks-links > span > a { + text-transform: lowercase; + } + #superlinks-links a { + padding: 0 0.2em; + color: #0645ad !important; + text-decoration: none !important; + } + #superlinks-links a.active, + #superlinks-links a:hover { + border-bottom: 2px solid #a7d7f9 + } + #superlinks-window { + position: absolute; + top: 48px; + right: 5px; + border: 2px solid #a7d7f9; + width: 45vw; + height: 75vh; + box-shadow: 0px 0px 4px 2px #eee + } + #superlinks-window:before { + content: ''; + position: absolute; + top: -20px; + width: 0; + height: 0; + border: 10px solid transparent; + border-bottom-color: #a7d7f9; + margin-right: -16px + } + #superlinks-window:after { + content: ''; + position: absolute; + top: -17px; + width: 0; + height: 0; + border: 10px solid transparent; + border-bottom-color: transparent; + margin-right: -16px; + } + #superlinks-icons { + position:absolute; + left:-2px; + top:-14px; + font-size:10px; + line-height:10px; + background-color:#a7d7f9; + padding: 2px 8px; + } + #superlinks-icons a:not(:first-child) { + margin-left: 20px + } + #superlinks-icons img { + height: 8px; + margin-top: -1px; + } + #superlinks-content { + overflow: auto; + height: calc(100% - 10px); + width: calc(100% - 10px); + font-size: 90%; + padding:5px; + background-color: #f6fbfe; + } + #superlinks-content > ul#pagehistory > li > input { + display: none + } + #superlinks-content > #empty { + position:absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + color: #a7d7f9; + font-size: 1.5em; + font-weight: bold; + text-align:center + } + #superlinks-content > #empty img { + width: 30px; + height: 30px; + opacity:0.5; + } + #superlinks-content > #empty #empty-sub { + font-size: 0.8em; + font-weight: normal; + padding-top: 1em; + } + #superlinks-content > #empty #empty-sub a { + opacity: 0.5 + } + #superlinks-content .mw-changeslist-separator:empty:before { + content: '. .' + } + #superlinks-content .mw-changeslist-links { + display: inline-block; + margin: 0; + padding: 0 + } + #superlinks-content .mw-changeslist-links:before { + content: '(' + } + #superlinks-content .mw-changeslist-links:after { + content: ')' + } + #superlinks-content .mw-changeslist-links > span:not(:first-child):before { + content: ' | ' + } + #superlinks-content .snippet { + line-height: 1.5em; + margin-bottom: 1.5em + } + #superlinks-content .searchmatch { + font-weight: bold + } + `), + links: {}, + init: function () { + function mk(caption, group) { + var $link = $("", {'href': '#'}).text(caption); + if (group) group.append($link) + else $("#superlinks-links").append($link); + $link.click(app.click); + return $link[0]; + + } + function mkgroup(caption) { + if (caption) { + $("#superlinks-links").append($("
", {'id': 'superlinks'}).text("[").append($("", {'id': 'superlinks-links'})); + app.$root.prependTo($("div.mw-indicators")); + + app.articleElement = $("#p-namespaces ul > li:first-child"); + app.talkElement = $("#ca-talk"); + app.historyElement = $("#ca-history"); + app.relevantUser = mw.config.get("wgRelevantUserName"); + if (app.relevantUser) app.relevantUser = mw.util.wikiUrlencode(app.relevantUser); + + if (app.articleElement.attr('id') == "ca-nstab-special") { //on special page, like contribs + if (app.relevantUser) { + var cspn = mw.config.get("wgCanonicalSpecialPageName"); + app.links.userpage = mk("User", usergrp); + var usergrp = mkgroup(); + app.links.usertalk = mk("Talk", usergrp); + if (cspn != "Contributions") { + app.links.contribs = mk("Contribs", usergrp); + } + if (mw.config.get("wgUserGroups").includes("sysop") && cspn != "DeletedContributions") { + app.links.deleted = mk("Deleted", usergrp); + } + app.links.actions = mk("Actions", usergrp); + app.links.userFilter = mk("Filter", usergrp); + app.links.rights = mk("Rights", usergrp); + app.links.blocklog = mk("Blocks", usergrp); + if (mw.config.get("wgWikiID")=="enwiki") { + app.links.dsalerts = mk("DS Alerts", usergrp); + app.links.restrict = mk("Restrictions", usergrp); + } + if (mw.config.get("wgUserGroups").includes("checkuser")) { + app.links.culog = mk("checks", usergrp); + } + } + } else { + if (app.articleElement.hasClass("selected")) { //on article or article history + if (!app.articleElement.hasClass("new")) { + if (app.historyElement.hasClass("selected")) { //on history page + app.links.article = mk(app.articleElement.find("a").text()); + } + if (app.historyElement.length && (!app.historyElement.hasClass("selected"))) { //not on history page (but it exists) + app.links.history = mk("History"); + } + } + app.links.logs = mk("Log"); + app.links.filter = mk("Filter"); + if (!app.talkElement.hasClass("new")) { //talk exists + app.links.talk = mk("Talk"); + } + app.links.notice = mk("Page Notice"); + } else { //on talk or talk history + var pagegrp, talkgrp; + if (!app.articleElement.hasClass("new")) { //article exists + app.links.article = mk(app.articleElement.find("a").text()); + pagegrp = mkgroup(); + app.links.articleHistory = mk("History", pagegrp); + } else { + pagegrp = mkgroup(app.articleElement.find("a").text()); + } + app.links.articleLogs = mk("Log", pagegrp); + app.links.articleFilter = mk("Filter", pagegrp); + app.links.notice = mk("Page Notice", pagegrp); + + if (!app.talkElement.hasClass("new")) { //talk exists + if (app.historyElement.hasClass("selected")) { //on history page + app.links.talk = mk("Talk"); + talkgrp = mkgroup(); + } else { + talkgrp = mkgroup("Talk"); + app.links.history = mk("History", talkgrp); + } + } else { + talkgrp = mkgroup("Talk"); + } + app.links.logs = mk("Log", talkgrp); + app.links.filter = mk("Filter", talkgrp); + } + + if (app.relevantUser) { + var usergrp = mkgroup("User"); + app.links.contribs = mk("Contribs", usergrp); + if (mw.config.get("wgUserGroups").includes("sysop")) { + app.links.deleted = mk("Deleted", usergrp); + } + app.links.actions = mk("Actions", usergrp); + app.links.userFilter = mk("Filter", usergrp); + app.links.rights = mk("Rights", usergrp); + app.links.blocklog = mk("Blocks", usergrp); + if (mw.config.get("wgWikiID")=="enwiki") { + app.links.dsalerts = mk("DS Alerts", usergrp); + app.links.restrict = mk("Restrictions", usergrp); + } + if (mw.config.get("wgUserGroups").includes("checkuser")) { + app.links.culog = mk("checks", usergrp); + } + } + + console.log(mw.loader.getState("ext.pageTriage.util")); + if (mw.config.get("wgWikiID")=="enwiki" && ns==0 && mw.loader.getState("ext.pageTriage.util")!="registered") { + app.links.nppflowchart = mk("NPP Flowchart"); + } + + if (mw.config.get("wgWikiID")=="enwiki" && mw.config.get("wgCategories").indexOf("Pending AfC submissions")>-1) { + app.links.afcflowchart = mk("AfC Flowchart"); + } + } + + app.$root.append("]"); + }, + click: function(e) { + function msg(text) { //blank for loading icon + app.$content.empty(); + var $div = $("
", {'id': 'empty'}); + if (text) $div.text(text); + else $div.append($("", {'src': "https://upload.wikimedia.org/wikipedia/commons/3/30/Chromiumthrobber.svg"})); + app.$content.append($div); + } + function msgErr() { + app.$content.empty().append($(` + + `)); + } + function afterLoad() { + if (app.$content.children().length===0) msg("No results"); + app.$content.find("a[href]").not('[href^="#"]').attr('target', '_blank'); + /*app.$content.find("a[href]").filter('[href^="#"]').click(function(e) { + e.preventDefault(); + href = $(this).attr('href'); + console.log(href); + app.$content.scrollTop($($(this).attr('href')).position().top); + });*/ + app.$content.find("input").remove(); + try { + app.$content.find(".mw-collapsible").makeCollapsible(); + } catch (e) {} + } + function loadcheck(response, status, xhr) { + if(status=="success") afterLoad(); + else msgErr(); + } + function loadpopout(url) { + app.$root.find("#superlinks-popout").remove(); + if (url) { + app.$root.find("#superlinks-icons").append( + $("", {'id': 'superlinks-popout', 'href': url, 'target': '_blank'}).append( + $("", {'src': 'https://upload.wikimedia.org/wikipedia/commons/e/eb/OOjs_UI_icon_newWindow-ltr.svg'}) + ) + ); + } + } + + e.preventDefault(); + if (app.active == e.target) { + app.close(); + } else { + app.close(); + app.active=e.target; + $(app.active).addClass("active"); + + app.$wnd = $("
", {'id': 'superlinks-window'}).appendTo(app.$root); + + var el = $(e.target)[0]; + var pos = el.offsetParent.offsetWidth - el.offsetLeft - (el.offsetWidth/2); + app.sstemp = mw.util.addCSS('#superlinks-window:before {right: ' + pos + 'px} #superlinks-window:after {right: ' + pos + 'px}'); + + app.$wnd.append( + $("", {'id':'superlinks-icons'}) + .append( + $("", {'href':'#'}) + .text("close") + .click(app.close) + ) + ); + + app.$content = $("
", {'id': 'superlinks-content'}).appendTo(app.$wnd); + msg(); + + switch (e.target) { + case app.links.userpage: + var url='/w/index.php?action=render&title=User:' + app.relevantUser; + app.$content.load(url, loadcheck); + loadpopout('/wiki/User:' + app.relevantUser); + break; + case app.links.usertalk: + var url='/w/index.php?action=render&title=User_talk:' + app.relevantUser; + app.$content.load(url, loadcheck); + loadpopout('/wiki/User_talk:' + app.relevantUser); + break; + case app.links.article: + var url=app.articleElement.find("a")[0].href.replace('/wiki/', '/w/index.php?action=render&title='); + app.$content.load(url, loadcheck); + loadpopout(app.articleElement.find("a")[0].href); + break; + case app.links.articleHistory: + var url=app.articleElement.find("a")[0].href.replace('/wiki/', '/w/index.php?action=history&safemode=1&limit=100&title='); + app.$content.load(url + " #pagehistory", loadcheck); + loadpopout(app.articleElement.find("a")[0].href.replace('/wiki/', '/w/index.php?action=history&title=')); + break; + case app.links.talk: + var url=$("#ca-talk > a")[0].href.replace('/wiki/', '/w/index.php?action=render&title='); + app.$content.load(url, loadcheck); + loadpopout($("#ca-talk > a")[0].href); + break; + case app.links.history: + var url=$("#ca-history > a")[0].href+'&safemode=1&limit=100'; + app.$content.load(url + " #pagehistory", loadcheck); + loadpopout($("#ca-history > a")[0].href); + break; + case app.links.articleLogs: + var url=app.articleElement.find("a")[0].href.replace('/wiki/', '/wiki/Special:Log?wpfilters%5B%5D=patrol&safemode=1&limit=100&page='); + app.$content.load(url + " #mw-content-text ul", loadcheck); + loadpopout(app.articleElement.find("a")[0].href.replace('/wiki/', '/wiki/Special:Log?wpfilters%5B%5D=patrol&page=')); + break; + case app.links.logs: + var url='/w/index.php?title=Special:Log&wpfilters%5B%5D=patrol&page='+mw.config.get("wgPageName")+'&safemode=1&limit=100'; + app.$content.load(url + " #mw-content-text ul", loadcheck); + loadpopout('/w/index.php?title=Special:Log&wpfilters%5B%5D=patrol&page='+mw.config.get("wgPageName")); + break; + case app.links.notice: + var url=app.articleElement.find("a")[0].href.replace('/wiki/', '/w/index.php?action=render&title=Template:Editnotices/Page/'); + console.log(url); + app.$content.load(url, loadcheck); + loadpopout(url.replace('action=render&','')); + break; + case app.links.contribs: + var url='/w/index.php?title=Special:Contributions/'+app.relevantUser+'&safemode=1&limit=100'; + app.$content.load(url + " #mw-content-text ul.mw-contributions-list", loadcheck); + loadpopout('/w/index.php?title=Special:Contributions/'+app.relevantUser); + break; + case app.links.deleted: + var url='/w/index.php?title=Special:DeletedContributions/'+app.relevantUser+'&safemode=1&limit=100'; + app.$content.load(url + " #mw-content-text > section", loadcheck); + loadpopout('/w/index.php?title=Special:DeletedContributions/'+app.relevantUser); + break; + case app.links.actions: + var url='/wiki/Special:Log/' + app.relevantUser; + app.$content.load(url + " #mw-content-text ul", loadcheck); + loadpopout('/wiki/Special:Log/' + app.relevantUser); + break; + case app.links.blocklog: + var url='/w/index.php?title=Special:Log/block&page='+app.relevantUser+'&safemode=1'; + app.$content.load(url + " #mw-content-text ul", loadcheck); + loadpopout('/w/index.php?title=Special:Log/block&page='+app.relevantUser); + break; + case app.links.filter: + var url='/w/index.php?title=Special:AbuseLog&wpSearchTitle='+mw.config.get("wgPageName")+'&safemode=1'; + app.$content.load(url + " #mw-content-text ul", loadcheck); + loadpopout('/w/index.php?title=Special:AbuseLog&wpSearchTitle='+mw.config.get("wgPageName")); + break; + case app.links.articleFilter: + var url=app.articleElement.find("a")[0].href.replace('/wiki/', '/wiki/Special:AbuseLog?safemode=1&wpSearchTitle='); + app.$content.load(url + " #mw-content-text ul", loadcheck); + loadpopout(app.articleElement.find("a")[0].href.replace('/wiki/', '/wiki/Special:AbuseLog?wpSearchTitle=')); + break; + case app.links.userFilter: + var url='/w/index.php?title=Special:AbuseLog&wpSearchUser='+app.relevantUser+'&safemode=1'; + app.$content.load(url + " #mw-content-text ul", loadcheck); + loadpopout('/w/index.php?title=Special:AbuseLog&wpSearchUser='+app.relevantUser); + break; + case app.links.rights: + var url='/w/index.php?title=Special:UserRights&user='+app.relevantUser+'&safemode=1'; + app.$content.load(url + " #mw-content-text > ul", loadcheck); + loadpopout('/wiki/Special:UserRights/' + app.relevantUser); + break; + case app.links.culog: + var url='/w/index.php?title=Special:CheckUserLog&cuSearchType=target&cuSearch='+app.relevantUser; + app.$content.load(url + " #mw-content-text > ul", loadcheck); + loadpopout('/w/index.php?title=Special:CheckUserLog&cuSearchType=target&cuSearch='+app.relevantUser); + break; + case app.links.nppflowchart: + var obj=$("", { + 'id': 'nppsvg', + 'type': 'image/svg+xml', + 'style': 'width: 100%' + }); + app.$content.empty().append(obj); + obj.attr('data', 'https://upload.wikimedia.org/wikipedia/commons/f/f4/NPP_flowchart.svg'); + break; + case app.links.afcflowchart: + app.$content.empty().append( + $("", { + 'src': 'https://upload.wikimedia.org/wikipedia/en/a/a8/Flow_chart_for_AFC_3.1.png', + 'style': 'width: 100%' + }) + ); + break; + case app.links.dsalerts: + loadpopout('/wiki/Special:AbuseLog?wpSearchTitle=User_talk%3A' + app.relevantUser + '&wpSearchFilter=602'); + $.getJSON('/w/api.php', { + action: 'query', + format: 'json', + list: 'abuselog', + afldir: 'older', + afltitle: 'User talk:' + app.relevantUser, + aflfilter: 602 + }) + .done(function(data) { + app.$content.empty(); + if (data.query.abuselog.filter(item => (item.revid)).length) { + $.each(data.query.abuselog, function (i, item) { + if (item.result=='tag' && item.revid) { + var $item = $("
").appendTo(app.$content); + $.getJSON('/w/api.php', { + action: 'compare', + format: 'json', + fromrev: item.revid, + torelative: 'prev', + prop: 'user|comment|diff' + }) + .done(function(comparedata) { + console.log(comparedata); + var ts = new Date(item.timestamp).toUTCString(); + ts = ts.substring(5, ts.indexOf("GMT")-1); + var sum = ts + " [[User:" + comparedata.compare.touser + "|" + comparedata.compare.touser + "]] ([[Special:Diff/" + item.revid + "|diff]])\n"; + + var diff = ''; + $(comparedata.compare["*"]).find("td.diff-addedline > div").each(function (i,item) { + diff += $(item).html() + "\n"; + }); + + diff = $(" + - + - +