Jump to content

User:Kxx/fix images.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.
(() => {
    const regex = /^((?:https?:)?\/\/upload\.wikimedia\.org\/.*)\/thumb(\/.*\/(?:\d{14}%21)?)(.*\.([a-zA-Z0-9]+))\/(.*-)?\d+(px-(?:\3|thumbnail\.\4)(?:\.png|\.jpg)?)(\?.*)?$/;
    const processFns = [];

    const processSvg = async ($img, source, [, base, middle, name, ext, prefix, suffix, params]) => {
        const thumbSrc = `${base}/thumb${middle}${name}/${$img.attr('data-file-width')}${suffix}${params || ''}`;
        const blobPromise = fetch(source).then((response) => response.blob());
        const sizePromise = new Promise((resolve) => {
            $('<img>').on('load', function() {
                const { naturalWidth, naturalHeight } = this;
                resolve({ naturalWidth, naturalHeight });
            }).attr('src', thumbSrc);
        });

        let [blob, { naturalWidth, naturalHeight }] = await Promise.all([blobPromise, sizePromise]);
        if (blob.type !== 'image/svg+xml') {
            blob = new Blob([blob], { type: 'image/svg+xml' });
        }

        const width = parseFloat($img.attr('width'));
        const height = parseFloat($img.attr('height'));

        const $div = $('<div>', {
            class: $img.attr('class'),
            css: {
                display: 'inline-block',
                overflow: 'clip',
                verticalAlign: $img.css('vertical-align'),
                width: width,
                height: height,
                position: 'absolute',
                visibility: 'hidden'
            }
        }).appendTo('body');

        const $object = $('<object>', {
            type: 'image/svg+xml',
            src: source,
            css: {
                display: 'block',
                width: naturalWidth,
                height: naturalHeight,
                margin: 0,
                transformOrigin: 'top left',
                scale: `${width / naturalWidth} ${height / naturalHeight}`,
                pointerEvents: 'none'
            },
            attr: { data: URL.createObjectURL(blob) }
        }).on('load', function() {
            const $doc = $(this.contentDocument);
            const $svg = $doc.find('svg');
            if (!$svg.length) return;
            if (!$svg.attr('xmlns')) {
                $svg.attr('xmlns', 'http://www.w3.org/2000/svg');
                const serializer = new XMLSerializer();
                const modifiedSvgText = serializer.serializeToString(this.contentDocument);
                const modifiedBlob = new Blob([modifiedSvgText], { type: 'image/svg+xml' });
                URL.revokeObjectURL(this.data);
                this.data = URL.createObjectURL(modifiedBlob);
                return;
            }
            if ($img) {
                $div.css({ position: '', visibility: '' });
                $img.replaceWith($div);
                $img = undefined;
            }
        }).appendTo($div);
    };
    
    const processGif = ($img, source) => {
        $img.attr('src', source).removeAttr('srcset');
    };

    const processOther = ($img, source, [, base, middle, name, ext, prefix, suffix, params]) => {
        const srcset = $img.attr('srcset') || '';
        const dpr = parseFloat(window.devicePixelRatio.toFixed(3));

        for (const entry of srcset.split(',')) {
            const parts = [...entry.matchAll(/[^\s]+/g)].map((match) => match[0]);
            if (parts[0] === source || (parseFloat(parts[1]) || 1) >= dpr) {
              return;
            }
        }

        const width = parseFloat($img.attr('width'));
        const dataFileWidth = parseFloat($img.attr('data-file-width'));
        const targetWidth = Math.min(Math.round(width * window.devicePixelRatio), dataFileWidth);
        const thumbSrc = targetWidth === dataFileWidth && /^(png|jpg|gif)$/i.test(ext) ? source : `${base}/thumb${middle}${name}/${prefix || ''}${targetWidth}${suffix}${params || ''}`;

        $img.attr('srcset', srcset ? `${srcset}, ${thumbSrc} ${dpr}x` : `${thumbSrc} ${dpr}x`);
    };

    $('img').each(function() {
        const $img = $(this);
        const src = $img.attr('src');
        const matches = regex.exec(src);
        if (!matches) return;

        const [, base, middle, name, ext, lang, suffix, params] = matches;
        const source = `${base}${middle}${name}${params || ''}`;

        if (ext.toLowerCase() === 'svg') {
            processSvg($img, source, matches);
        } if (ext.toLowerCase() === 'gif') {
            processGif($img, source);
        } else if (source !== src) {
            processFns.push(processOther.bind(null, $img, source, matches));
        }
    });

    const handlePixelRatioChange = () => {
        processFns.forEach((process) => { process(); });
        matchMedia(`(resolution: ${window.devicePixelRatio}dppx)`).addEventListener('change', handlePixelRatioChange, { once: true });
    };

    handlePixelRatioChange();

    $('<style>.filehistory a div, #file a div:hover { background: url(/w/resources/src/mediawiki.action/images/checker.svg?ff513) repeat; }</style>').appendTo('head');
})();