Jump to content

User:Polygnotus/Scripts/Sections.js

From Wikipedia, the free encyclopedia
Note: After saving, you have to bypass your browser's cache to see the changes. Google Chrome, Firefox, Microsoft Edge and Safari: Hold down the ⇧ Shift key and click the Reload toolbar button. For details and instructions about other browsers, see Wikipedia:Bypass your cache.
// <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>