Last active
August 8, 2025 10:44
-
-
Save Kenya-West/9c0ca555d4e229a56dbdf4d02b2b07ef to your computer and use it in GitHub Desktop.
InoReader dynamic height of tiles in the card view
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // ==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]); | |
| }; | |
| })(); | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment