User:Polygnotus/Scripts/Sections.js
Appearance
Code that you insert on this page could contain malicious content capable of compromising your account. If you import a script from another page with "importScript", "mw.loader.load", "iusc", or "lusc", take note that this causes you to dynamically load a remote script, which could be changed by others. Editors are responsible for all edits and actions they perform, including by scripts. User scripts are not centrally supported and may malfunction or become inoperable due to software changes. A guide to help you find broken scripts is available. If you are unsure whether code you are adding to this page is safe, you can ask at the appropriate village pump.
This code will be executed when previewing this page.
This code will be executed when previewing this page.
Documentation for this user script can be added at User:Polygnotus/Scripts/Sections.
// <nowiki>
// Adds link options to h2 section headers, styled like [edit].
// Desktop: [ link ] expands on hover to [ section link ] [ permalink ] [ wikilink ]
// Mobile: three bare icons always visible: 🔗 📌 [[
(function() {
mw.util.addCSS([
// Desktop: hide submenu until hover.
// visibility+opacity allows transition-delay so the menu lingers 1s after the mouse leaves.
'.sect-link-submenu {',
' visibility: hidden;',
' opacity: 0;',
' transition: visibility 0s linear 1s, opacity 0s linear 1s;',
'}',
'.sect-link-desktop:hover .sect-link-submenu {',
' visibility: visible;',
' opacity: 1;',
' transition: none;', // appear instantly on hover-in
'}',
// Mobile icons: CSS mask technique matching Minerva's pencil icon.
// Shape = mask-image URL, color = background-color (set to --color-subtle).
'.sect-link-mobile {',
' display: none;', // overridden below by the @media swap
'}',
'.sect-link-mobile a {',
' display: inline-block;',
' width: 1em;',
' height: 1em;',
' margin-right: 3px;',
' vertical-align: middle;',
' background-color: var(--color-subtle);',
' -webkit-mask-size: contain;',
' mask-size: contain;',
' -webkit-mask-repeat: no-repeat;',
' mask-repeat: no-repeat;',
' -webkit-mask-position: center;',
' mask-position: center;',
'}',
'.sect-link-mobile a.sect-icon-url {',
' -webkit-mask-image: url(https://upload.wikimedia.org/wikipedia/commons/c/c3/Sectionlink.svg);',
' mask-image: url(https://upload.wikimedia.org/wikipedia/commons/c/c3/Sectionlink.svg);',
'}',
'.sect-link-mobile a.sect-icon-permalink {',
' -webkit-mask-image: url(https://upload.wikimedia.org/wikipedia/commons/6/68/Permalink.svg);',
' mask-image: url(https://upload.wikimedia.org/wikipedia/commons/6/68/Permalink.svg);',
'}',
'.sect-link-mobile a.sect-icon-wikitext {',
' -webkit-mask-image: url(https://upload.wikimedia.org/wikipedia/commons/7/74/Wikilink.svg);',
' mask-image: url(https://upload.wikimedia.org/wikipedia/commons/7/74/Wikilink.svg);',
'}',
// Swap desktop vs mobile
'@media (hover: none) and (pointer: coarse) {',
' .sect-link-desktop { display: none; }',
' .sect-link-mobile { display: inline; }',
'}'
].join('\n'));
function addLinkIcons() {
if (mw.config.get('wgIsMainPage')) {
return;
}
const headers = document.querySelectorAll('.mw-heading.mw-heading2 h2[id]');
headers.forEach(header => {
if (header.id === 'mw-toc-heading') {
return;
}
const isCurrentVersion = !window.location.search.match(/[?&]oldid=(\d+)/) &&
!document.querySelector('.diff-currentversion-title');
const editSection = header.parentElement.querySelector('.mw-editsection');
const desktopGroup = buildDesktopGroup(header, isCurrentVersion);
const mobileGroup = buildMobileGroup(header, isCurrentVersion);
if (editSection && editSection.parentElement) {
// Mobile goes before the pencil, desktop goes after
editSection.parentElement.insertBefore(mobileGroup, editSection);
editSection.parentElement.insertBefore(desktopGroup, editSection.nextSibling);
} else {
header.parentElement.appendChild(mobileGroup);
header.parentElement.appendChild(desktopGroup);
}
});
}
// Desktop: [ link ] trigger that reveals [ section link ] [ permalink ] [ wikilink ] on hover
function buildDesktopGroup(header, isCurrentVersion) {
const group = document.createElement('span');
group.className = 'sect-link-desktop';
// [ link ] trigger (purely visual, no click action — hover handles the reveal)
const triggerWrapper = document.createElement('span');
triggerWrapper.className = 'mw-editsection';
const bo = document.createElement('span');
bo.className = 'mw-editsection-bracket';
bo.textContent = '[';
const trigger = document.createElement('span');
trigger.style.cursor = 'default';
trigger.textContent = 'link';
const bc = document.createElement('span');
bc.className = 'mw-editsection-bracket';
bc.textContent = ']';
triggerWrapper.appendChild(bo);
triggerWrapper.appendChild(trigger);
triggerWrapper.appendChild(bc);
const submenu = document.createElement('span');
submenu.className = 'sect-link-submenu';
if (isCurrentVersion) {
appendBracketLink(submenu, header, 'section link', 'url', 'Copy URL to this section');
appendBracketLink(submenu, header, 'permalink', 'permalink', 'Copy URL to this section in this specific revision');
appendBracketLink(submenu, header, 'wikilink', 'wikitext', 'Copy wikitext markup linking to this section');
} else {
appendBracketLink(submenu, header, 'section link', 'oldid', 'Copy URL to this section of this specific revision');
appendBracketLink(submenu, header, 'wikilink', 'wikitext', 'Copy wikitext markup linking to this section');
}
group.appendChild(triggerWrapper);
group.appendChild(submenu);
return group;
}
// Mobile: bare mask icons, no brackets, always visible
function buildMobileGroup(header, isCurrentVersion) {
const group = document.createElement('span');
group.className = 'sect-link-mobile';
if (isCurrentVersion) {
appendIconLink(group, header, 'sect-icon-url', 'url', 'Copy URL to this section');
appendIconLink(group, header, 'sect-icon-permalink', 'permalink', 'Copy URL to this section in this specific revision');
appendIconLink(group, header, 'sect-icon-wikitext', 'wikitext', 'Copy wikitext markup linking to this section');
} else {
appendIconLink(group, header, 'sect-icon-url', 'oldid', 'Copy URL to this section of this specific revision');
appendIconLink(group, header, 'sect-icon-wikitext', 'wikitext', 'Copy wikitext markup linking to this section');
}
return group;
}
// Appends a [ label ] link (desktop style)
function appendBracketLink(parent, header, labelText, linkType, tooltipText) {
const wrapper = document.createElement('span');
wrapper.className = 'mw-editsection';
const bo = document.createElement('span');
bo.className = 'mw-editsection-bracket';
bo.textContent = '[';
const label = document.createElement('a');
label.href = '#';
label.title = tooltipText;
label.textContent = labelText;
const bc = document.createElement('span');
bc.className = 'mw-editsection-bracket';
bc.textContent = ']';
wrapper.appendChild(bo);
wrapper.appendChild(label);
wrapper.appendChild(bc);
parent.appendChild(wrapper);
label.addEventListener('click', makeCopyHandler(header, linkType));
}
// Appends a bare mask icon link (mobile style)
function appendIconLink(parent, header, iconClass, linkType, tooltipText) {
const link = document.createElement('a');
link.href = '#';
link.title = tooltipText;
link.className = iconClass;
parent.appendChild(link);
link.addEventListener('click', makeCopyHandler(header, linkType));
}
// Returns a click handler that copies the appropriate URL/wikitext
function makeCopyHandler(header, linkType) {
return function(e) {
e.preventDefault();
e.stopPropagation();
let textToCopy;
let notifyLabel;
if (linkType === 'permalink') {
const currentId = mw.config.get('wgRevisionId');
textToCopy = `${window.location.origin}${window.location.pathname}?oldid=${currentId}#${header.id}`;
notifyLabel = 'Permalink';
} else if (linkType === 'oldid') {
textToCopy = `${window.location.origin}${window.location.pathname}${window.location.search}#${header.id}`;
notifyLabel = 'Revision link';
} else if (linkType === 'wikitext') {
const pageName = mw.config.get('wgPageName');
textToCopy = `[[${pageName}#${header.id}]]`;
notifyLabel = 'Wikitext link';
} else {
textToCopy = `${window.location.origin}${window.location.pathname}#${header.id}`;
notifyLabel = 'Section link';
}
navigator.clipboard.writeText(textToCopy).then(() => {
mw.notify(`${notifyLabel} copied to clipboard!`, {
type: 'success',
tag: 'urlCopy'
});
}).catch(() => {
mw.notify('Failed to copy', {
type: 'error',
tag: 'urlCopy'
});
});
};
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', addLinkIcons);
} else {
addLinkIcons();
}
})();
// Collapsible h2 sections (desktop only — Minerva handles its own collapsing)
mw.hook('wikipage.content').add(function(content) {
if (mw.config.get('skin') === 'minerva') return;
mw.util.addCSS('[class*="hide-sec"]{display:none!important}');
// Only select h2's with id inside mw-heading mw-heading2 divs
content.find('.mw-parser-output .mw-heading.mw-heading2:has(h2[id])').each(function() {
var $element = $(this);
var heading = $element.find('h2[id]').first();
if (heading.length === 0) return;
// Skip empty headings
if (heading.children().length === 0 && heading.text().trim() === '') return;
var icon = $('<i class="mw-ui-icon-before mw-ui-icon-small mw-ui-icon mw-ui-icon-collapse"></i>');
icon.click(function(e) {
e.stopPropagation();
// Build selector for headings of equal or higher level
var levelMatch = '.mw-heading.mw-heading1:has(h1[id]), .mw-heading.mw-heading2:has(h2[id])';
icon.toggleClass('mw-ui-icon-collapse');
icon.toggleClass('mw-ui-icon-expand');
// Toggle visibility for content after this heading up to the next same/higher level heading
$element.nextUntil(levelMatch).toggleClass('hide-sect-h2');
});
if (window.collapseSections) icon.click();
heading.prepend(icon);
});
});
mw.loader.load(['mediawiki.ui.icon', 'oojs-ui.styles.icons-movement']);
// </nowiki>