MediaWiki:Common.js: Difference between revisions

From MuhRO
Jump to navigation Jump to search
m Add floating table of contents
m Restore floating expand collapse controls
 
(2 intermediate revisions by the same user not shown)
Line 14: Line 14:


});
});
(function () {
    window.muhroGetFloatingTools = function () {
        var $tools = $('#muhro-floating-tools');
        if (!$tools.length) {
            $tools = $('<div>')
                .attr('id', 'muhro-floating-tools')
                .addClass('muhro-floating-tools noprint');
            $('body').append($tools);
        }
        return $tools;
    };
}());


(function () {
(function () {
Line 34: Line 49:
     function findCollapsibles($root) {
     function findCollapsibles($root) {
         return $root
         return $root
             .find('.mw-collapsible.mw-made-collapsible')
             .find('.mw-collapsible')
             .add($root.filter('.mw-collapsible.mw-made-collapsible'));
             .add($root.filter('.mw-collapsible'));
     }
     }


Line 44: Line 59:
         $expand.prop('disabled', $items.length === 0 || collapsedCount === 0);
         $expand.prop('disabled', $items.length === 0 || collapsedCount === 0);
         $collapse.prop('disabled', $items.length === 0 || collapsedCount === $items.length);
         $collapse.prop('disabled', $items.length === 0 || collapsedCount === $items.length);
    }
    function triggerFallback($element, collapse) {
        if ($element.hasClass('mw-collapsed') === collapse) {
            return;
        }
        $element.find('> .muhro-early-toggle, .muhro-early-toggle').first().trigger('click');
     }
     }


Line 51: Line 74:
             var api = $element.data('mw-collapsible');
             var api = $element.data('mw-collapsible');


             if (!api || $element.hasClass('mw-collapsed') === collapse) {
             if ($element.hasClass('mw-collapsed') === collapse) {
                 return;
                 return;
             }
             }


             if (collapse) {
             if (api) {
                 api.collapse();
                if (collapse) {
                    api.collapse();
                 } else {
                    api.expand();
                }
             } else {
             } else {
                 api.expand();
                 triggerFallback($element, collapse);
             }
             }
         });
         });


         updateButtons($root, $expand, $collapse);
         window.setTimeout(function () {
            updateButtons($root, $expand, $collapse);
        }, 0);
     }
     }


Line 84: Line 113:
                 'aria-label': 'Collapsible content controls'
                 'aria-label': 'Collapsible content controls'
             })
             })
             .addClass('muhro-collapse-controls noprint')
             .addClass('muhro-collapse-controls')
             .append($expand, $collapse);
             .append($expand, $collapse);


Line 100: Line 129:


         updateButtons($root, $expand, $collapse);
         updateButtons($root, $expand, $collapse);
         $('body').append($controls);
         window.muhroGetFloatingTools().prepend($controls);
         return true;
         return true;
     }
     }
Line 116: Line 145:
         function tryAttach() {
         function tryAttach() {
             attempts++;
             attempts++;
             if (attachControls($root) || attempts >= 20) {
             if (attachControls($root) || attempts >= 40) {
                 return;
                 return;
             }
             }
Line 131: Line 160:


(function () {
(function () {
    function filterToc($panel, query) {
        var normalized = query.trim().toLowerCase();
        $panel.find('li').removeClass('muhro-toc-match muhro-toc-hidden muhro-toc-ancestor');
        if (!normalized) {
            return;
        }
        $panel.find('li').each(function () {
            var $item = $(this);
            var text = $item.children('a').first().text().toLowerCase();
            if (text.indexOf(normalized) !== -1) {
                $item.addClass('muhro-toc-match');
                $item.parents('li').addClass('muhro-toc-ancestor');
            }
        });
        $panel.find('li').each(function () {
            var $item = $(this);
            if (!$item.hasClass('muhro-toc-match') && !$item.hasClass('muhro-toc-ancestor')) {
                $item.addClass('muhro-toc-hidden');
            }
        });
    }
     function addFloatingToc($content) {
     function addFloatingToc($content) {
         var $toc = $content.find('#toc').first();
         var $toc = $content.find('#toc').first();
Line 136: Line 192:
         var $button;
         var $button;
         var $panel;
         var $panel;
        var $header;
        var $filter;
        var $body;
         var $wrapper;
         var $wrapper;


Line 161: Line 220:
             .addClass('muhro-floating-toc-button')
             .addClass('muhro-floating-toc-button')
             .text('Contents');
             .text('Contents');
        $filter = $('<input>')
            .attr({
                type: 'search',
                placeholder: 'Filter contents',
                'aria-label': 'Filter contents'
            })
            .addClass('muhro-floating-toc-filter');
        $header = $('<div>')
            .addClass('muhro-floating-toc-header')
            .append($filter);
        $body = $('<div>')
            .addClass('muhro-floating-toc-body')
            .append($tocList);


         $panel = $('<nav>')
         $panel = $('<nav>')
Line 168: Line 243:
             })
             })
             .addClass('muhro-floating-toc-panel')
             .addClass('muhro-floating-toc-panel')
             .append($tocList);
             .append($header, $body);


         $wrapper = $('<div>')
         $wrapper = $('<div>')
             .attr('id', 'muhro-floating-toc')
             .attr('id', 'muhro-floating-toc')
             .addClass('muhro-floating-toc noprint')
             .addClass('muhro-floating-toc')
             .append($button, $panel);
             .append($button, $panel);


Line 179: Line 254:
             $wrapper.toggleClass('is-open', open);
             $wrapper.toggleClass('is-open', open);
             $button.attr('aria-expanded', open ? 'true' : 'false');
             $button.attr('aria-expanded', open ? 'true' : 'false');
            if (open) {
                window.setTimeout(function () {
                    $filter.trigger('focus');
                }, 0);
            }
        });
        $filter.on('input', function () {
            filterToc($panel, $filter.val());
         });
         });


Line 188: Line 272:
         });
         });


         $('body').append($wrapper);
         window.muhroGetFloatingTools().append($wrapper);
     }
     }


     mw.hook('wikipage.content').add(addFloatingToc);
     mw.hook('wikipage.content').add(addFloatingToc);
}());
}());

Latest revision as of 14:49, 27 May 2026

/* Any JavaScript here will be loaded for all users on every page load. */
$('.copy-link').on('click', function() {

    var link    = $(this);
    var copy_id = $(this).attr('id');
    var text    = $(this).text();
    var clipboard = new ClipboardJS('#' + copy_id );
    clipboard.on('success', function(e) {
        link.text('Copied!');
        setTimeout(function() {
            link.text(''+text);
        }, 2000);
    });

});

(function () {
    window.muhroGetFloatingTools = function () {
        var $tools = $('#muhro-floating-tools');

        if (!$tools.length) {
            $tools = $('<div>')
                .attr('id', 'muhro-floating-tools')
                .addClass('muhro-floating-tools noprint');
            $('body').append($tools);
        }

        return $tools;
    };
}());

(function () {
    function normalizeLegacyCollapsibles($root) {
        var $legacy = $root.find('table.collapsible, div.collapsible, ul.collapsible, ol.collapsible')
            .not('.mw-collapsible');

        $legacy.each(function () {
            var $element = $(this);

            $element.addClass('mw-collapsible');
            if ($element.hasClass('collapsed')) {
                $element.addClass('mw-collapsed');
            }
        });

        $legacy.makeCollapsible();
    }

    function findCollapsibles($root) {
        return $root
            .find('.mw-collapsible')
            .add($root.filter('.mw-collapsible'));
    }

    function updateButtons($root, $expand, $collapse) {
        var $items = findCollapsibles($root);
        var collapsedCount = $items.filter('.mw-collapsed').length;

        $expand.prop('disabled', $items.length === 0 || collapsedCount === 0);
        $collapse.prop('disabled', $items.length === 0 || collapsedCount === $items.length);
    }

    function triggerFallback($element, collapse) {
        if ($element.hasClass('mw-collapsed') === collapse) {
            return;
        }

        $element.find('> .muhro-early-toggle, .muhro-early-toggle').first().trigger('click');
    }

    function setAll($root, collapse, $expand, $collapse) {
        findCollapsibles($root).each(function () {
            var $element = $(this);
            var api = $element.data('mw-collapsible');

            if ($element.hasClass('mw-collapsed') === collapse) {
                return;
            }

            if (api) {
                if (collapse) {
                    api.collapse();
                } else {
                    api.expand();
                }
            } else {
                triggerFallback($element, collapse);
            }
        });

        window.setTimeout(function () {
            updateButtons($root, $expand, $collapse);
        }, 0);
    }

    function attachControls($root) {
        if ($('#muhro-collapse-controls').length || !findCollapsibles($root).length) {
            return false;
        }

        var $expand = $('<button>')
            .attr('type', 'button')
            .addClass('muhro-collapse-button')
            .text('Expand all');
        var $collapse = $('<button>')
            .attr('type', 'button')
            .addClass('muhro-collapse-button')
            .text('Collapse all');
        var $controls = $('<div>')
            .attr({
                id: 'muhro-collapse-controls',
                role: 'group',
                'aria-label': 'Collapsible content controls'
            })
            .addClass('muhro-collapse-controls')
            .append($expand, $collapse);

        $expand.on('click', function () {
            setAll($root, false, $expand, $collapse);
        });
        $collapse.on('click', function () {
            setAll($root, true, $expand, $collapse);
        });
        $root.on('click keydown', '.mw-collapsible-toggle', function () {
            window.setTimeout(function () {
                updateButtons($root, $expand, $collapse);
            }, 0);
        });

        updateButtons($root, $expand, $collapse);
        window.muhroGetFloatingTools().prepend($controls);
        return true;
    }

    function addCollapsibleControls($content) {
        var $root = $content.find('.mw-parser-output').first();
        var attempts = 0;

        if (!$root.length) {
            $root = $content;
        }

        normalizeLegacyCollapsibles($root);

        function tryAttach() {
            attempts++;
            if (attachControls($root) || attempts >= 40) {
                return;
            }
            window.setTimeout(tryAttach, 250);
        }

        tryAttach();
    }

    mw.loader.using('jquery.makeCollapsible').then(function () {
        mw.hook('wikipage.content').add(addCollapsibleControls);
    });
}());

(function () {
    function filterToc($panel, query) {
        var normalized = query.trim().toLowerCase();

        $panel.find('li').removeClass('muhro-toc-match muhro-toc-hidden muhro-toc-ancestor');

        if (!normalized) {
            return;
        }

        $panel.find('li').each(function () {
            var $item = $(this);
            var text = $item.children('a').first().text().toLowerCase();

            if (text.indexOf(normalized) !== -1) {
                $item.addClass('muhro-toc-match');
                $item.parents('li').addClass('muhro-toc-ancestor');
            }
        });

        $panel.find('li').each(function () {
            var $item = $(this);
            if (!$item.hasClass('muhro-toc-match') && !$item.hasClass('muhro-toc-ancestor')) {
                $item.addClass('muhro-toc-hidden');
            }
        });
    }

    function addFloatingToc($content) {
        var $toc = $content.find('#toc').first();
        var $tocList;
        var $button;
        var $panel;
        var $header;
        var $filter;
        var $body;
        var $wrapper;

        if ($('#muhro-floating-toc').length || !$toc.length) {
            return;
        }

        $tocList = $toc.find('> ul, .toc > ul').first().clone(false);
        if (!$tocList.length) {
            $tocList = $toc.find('ul').first().clone(false);
        }
        if (!$tocList.length) {
            return;
        }

        $tocList.find('[id]').removeAttr('id');
        $tocList.find('.tocnumber').attr('aria-hidden', 'true');

        $button = $('<button>')
            .attr({
                type: 'button',
                'aria-expanded': 'false',
                'aria-controls': 'muhro-floating-toc-panel'
            })
            .addClass('muhro-floating-toc-button')
            .text('Contents');

        $filter = $('<input>')
            .attr({
                type: 'search',
                placeholder: 'Filter contents',
                'aria-label': 'Filter contents'
            })
            .addClass('muhro-floating-toc-filter');

        $header = $('<div>')
            .addClass('muhro-floating-toc-header')
            .append($filter);

        $body = $('<div>')
            .addClass('muhro-floating-toc-body')
            .append($tocList);

        $panel = $('<nav>')
            .attr({
                id: 'muhro-floating-toc-panel',
                'aria-label': 'Page contents'
            })
            .addClass('muhro-floating-toc-panel')
            .append($header, $body);

        $wrapper = $('<div>')
            .attr('id', 'muhro-floating-toc')
            .addClass('muhro-floating-toc')
            .append($button, $panel);

        $button.on('click', function () {
            var open = !$wrapper.hasClass('is-open');
            $wrapper.toggleClass('is-open', open);
            $button.attr('aria-expanded', open ? 'true' : 'false');
            if (open) {
                window.setTimeout(function () {
                    $filter.trigger('focus');
                }, 0);
            }
        });

        $filter.on('input', function () {
            filterToc($panel, $filter.val());
        });

        $panel.on('click', 'a', function () {
            if (window.matchMedia('(max-width: 720px)').matches) {
                $wrapper.removeClass('is-open');
                $button.attr('aria-expanded', 'false');
            }
        });

        window.muhroGetFloatingTools().append($wrapper);
    }

    mw.hook('wikipage.content').add(addFloatingToc);
}());