InoReader dynamic height of tiles in the card view

Makes cards' heights to be dynamic depending on image height

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         InoReader dynamic height of tiles in the card view
// @namespace    http://tampermonkey.net/
// @version      0.2.0
// @description  Makes cards' heights to be dynamic depending on image height
// @author       Kenya-West
// @match        https://*.inoreader.com/*
// @icon         https://inoreader.com/favicon.ico?v=8
// @grant        GM_addStyle
// @license      MIT
// ==/UserScript==
// @ts-check


(async function () {
    const arriveEventConfigArticle = {
        fireOnAttributesModification: false,
        existing: true
    };
    
    const style = `
        .tm_dynamic_height {
            height: auto !important;
        }
        .tm_remove_position_setting {
            position: unset !important;
        }
        `;

    // @ts-ignore
    GM_addStyle(style);

    // @ts-ignore
    const arrive = await import("https://cdnjs.cloudflare.com/ajax/libs/arrive/2.5.2/arrive.min.js");

    // @ts-ignore
    document.querySelector("#reader_pane")?.arrive(".ar", arriveEventConfigArticle, (article) => start(article));

    const querySelectorPathArticleRoot =
        ".article_full_contents .article_content";
    const querySelectorArticleContentWrapper = ".article_tile_content_wraper";
    const querySelectorArticleFooter = ".article_tile_footer";


    function start(element) {

        if (notHaveDynamicHeight(element)) {
            // @ts-ignore
            const cardWidth = element.clientWidth ?? element.offsetWidth ?? element.scrollWidth;
            // @ts-ignore
            const cardHeight = element.clientHeight ?? element.offsetHeight ?? element.scrollHeight;

            // 1. Set card height dynamic
            setDynamicHeight(element);

            // 2. Set content wrapper height dynamic
            const articleContentWrapperElement = element.querySelector(
                querySelectorArticleContentWrapper
            );
            if (articleContentWrapperElement) {
                setDynamicHeight(articleContentWrapperElement);
            }

            // 3. Remove position setting from article footer
            const articleFooter = element.querySelector(
                querySelectorArticleFooter
            );
            if (articleFooter) {
                removePositionSetting(articleFooter);
            }

            // 4. Find image height
            /**
             * @type {HTMLDivElement | null}
             */
            const divImageElement = element.querySelector(
                "a[href] > .article_tile_picture[style*='background-image']"
            );
            if (!divImageElement) {
                return;
            }
            const imageUrl = getImageLink(divImageElement);
            if (!imageUrl) {
                return;
            }
            const dimensions = getImageDimensions(imageUrl);

            // 5. Set image height (and - automatically - the card height)
            dimensions.then(([width, height]) => {
                if (height > 0) {
                    const calculatedHeight = Math.round(
                        (cardWidth / width) * height
                    );
                    const pictureOldHeight =
                        (divImageElement.clientHeight ??
                            divImageElement.offsetHeight ??
                            divImageElement.scrollHeight) ||
                        cardHeight;
                    /**
                     * @type {HTMLDivElement}
                     */
                    const div = divImageElement;
                    if (calculatedHeight > pictureOldHeight) {
                        div.style.height = `${calculatedHeight}px`;
                    }

                    // 5.1. Set card class to `.tm_dynamic_height` to not process it again next time
                    element.classList?.add("tm_dynamic_height");
                }
            });
        }
    }

    /**
     * Checks if article already has dynamic height set.
     */
    function notHaveDynamicHeight(element) {
        return element?.hasChildNodes() &&
            element?.id?.includes("article_") &&
            element?.classList.contains("ar") &&
            !element?.classList.contains("tm_dynamic_height");
    }

    /**
     * 
     * @param {Element} element 
     * @returns {void}
     */
    function setDynamicHeight(element) {
        element.classList?.add("tm_dynamic_height");
    }

    /**
     * 
     * @param {Element} element 
     * @returns {void}
     */
    function removeDynamicHeight(element) {
        const div = element.querySelector("img");
        if (!div) {
            return;
        }
        div.classList?.remove("tm_dynamic_height");
    }

    /**
     * 
     * @param {Element} element
     * @returns {void}
     */
    function removePositionSetting(element) {
        element.classList?.add("tm_remove_position_setting");
    }

    /**
     * 
     * @param {Element} element
     * @returns {void}
     */
    function restorePositionSetting(element) {
        element.classList?.remove("tm_remove_position_setting");
    }


    /**
     *
     * @param {HTMLDivElement} div
     * @returns {string | null}
     */
    function getImageLink(div) {
        const backgroundImageUrl = div?.style.backgroundImage;
        /**
         * @type {string | undefined}
         */
        let imageUrl;
        try {
            imageUrl = backgroundImageUrl?.match(/url\("(.*)"\)/)?.[1];
        } catch (error) {
            imageUrl = backgroundImageUrl?.slice(5, -2);
        }

        if (!imageUrl || imageUrl == "undefined") {
            return null;
        }

        if (!imageUrl?.startsWith("http")) {
            console.error(
                `The image could not be parsed. Image URL: ${imageUrl}`
            );
            return null;
        }
        return imageUrl;
    }

    /**
     * 
     * @param {string} url 
     * @returns {Promise<[number, number]>}
     */
    async function getImageDimensions(url) {
        const img = new Image();
        img.src = url;
        try {
            await img.decode();
        } catch (error) {
            return Promise.reject(error);
        }
        return Promise.resolve([img.width, img.height]);
    };
})();