DeGiro improved filters

Adds missing sort options and amount of shares to buy calculator

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         DeGiro improved filters
// @namespace    https://yelidmod.com/degiro
// @version      0.7
// @description  Adds missing sort options and amount of shares to buy calculator
// @author       DonNadie
// @match        https://trader.degiro.nl/*
// @grant        none
// ==/UserScript==

// jshint esversion: 6

(function() {
    'use strict';

    const section = {
        equity: 1,
        etf: 131
    };

    let filtersIndex = {
        volume: 0,
        isin: null,
    };
    const sortButton = '<i role="img" data-name="icon" data-type="sort" aria-hidden="true" class="ife-sort-icon"><svg viewBox="0 0 24 24"><path d="M16.8 13.2L12 18l-4.8-4.8h9.6zM12 6l4.8 4.8H7.2L12 6z"></path></svg></i>';

    const addStyle = (styleString) => {
        const style = document.createElement('style');
        style.textContent = styleString;
        document.head.append(style);
    };

    const sort = () => {
        const trList = document.querySelectorAll('[data-name="productTypeSearch"] tr');

        new Promise((resolve, reject) => {
            let list = [];
            let val;
            trList.forEach((tr, e) => {
                tr.querySelectorAll('td').forEach((td, i) => {
                    if (i !== filtersIndex.volume) {
                        return;
                    }
                    val = parseInt(td.innerText.replace(".", ""));
                    list.push({
                        tr: tr,
                        value : isNaN(val) ? 0 : val
                    });
                });
                if (e == (trList.length - 1)) {
                    resolve(list);
                }
            });
        }).then(list => {
            list.sort((a, b) => (a.value < b.value) ? 1 : -1);
            list.forEach(entry => {
                document.querySelector('[data-name="productTypeSearch"] tbody').appendChild(entry.tr);
            });
        });

    };

    const onLoaded = () => {
        const url = new URL(location.href.replace("#", ''));

        if (!url.searchParams.has('productType')) {
            return;
        }

        const currentSection = parseInt(url.searchParams.get('productType'));

        document.querySelectorAll('[data-name="productTypeSearch"] th').forEach((el, i) => {
            if (el.innerText == "Volumen") {
                filtersIndex.volume = i;
                el.classList.add('ife-container');
                el.innerHTML += sortButton;
                el.addEventListener("click", sort);
            } else if (el.innerText.includes("ISIN")) {
                filtersIndex.isin = i;
            }
        });

        if (filtersIndex.isin == null || ![section.etf, section.equity].includes(currentSection)) {
            return;
        }
        const morningType = currentSection == section.etf ? 'ETF' : 'STOCK';

        document.querySelectorAll('[data-name="productTypeSearch"] tr').forEach((tr, e) => {
             tr.querySelectorAll('td').forEach((td, i) => {
                 if (i !== filtersIndex.isin) {
                     return;
                 }
                 let isin;

                 if (td.innerText.includes("/")) {
                   isin = td.innerText.split(" / ")[1];
                 } else if (td.innerText.length > 5) {
                   isin = td.innerText;
                 }

                 td.querySelector("span").innerHTML = td.innerText.replace(isin, '<a href="https://www.morningstar.es/es/funds/SecuritySearchResults.aspx?type=' + morningType + '&search=' + isin + '" class="ife-link" target="_blank">' + isin + '</a>');
             });
         });
    };

    const showCalculator = (mutationsList) => {
        let section = document.querySelector('[data-name="orderForm"] section');

        if (section == null ||
            document.getElementById('simple-calculator') != null ||
            typeof window.calculatorInjected !== "undefined") {
            return;
        }
        window.calculatorInjected = true;

        const calculatorContainer = document.createElement("div");
        calculatorContainer.id = "simple-calculator";
        calculatorContainer.classList.add("ife-calculator-container");
        calculatorContainer.innerHTML += '<div class="ife-input-container"><div class="ife-input-label"><input id="calculator-money" type="number" min="0" step="1" placeholder="0" class="ife-input"><span>€</span></div></div>'
        + "." +
        '<div class="ife-input-container"><div class="ife-input-label"><input id="calculator-shares" type="text" class="ife-input" placeholder="0" disabled><span>shares</span></div></div>';

        const moneyInput = calculatorContainer.querySelector("#calculator-money");
        const sharesInput = calculatorContainer.querySelector("#calculator-shares");

        moneyInput.addEventListener("input", () => {
            const sharePrice = parseFloat(document.querySelector('[data-field="CurrentPrice"]').title.replace(".", "").replace(",", "."));
            sharesInput.value = Math.floor(moneyInput.value / sharePrice);
        });

        setTimeout(() => {
            // it seems like section is recreated in just a few secs :S
            section = document.querySelector('[data-name="orderForm"] section');
            section.parentNode.insertBefore(calculatorContainer, section);
            delete(window.calculatorInjected);
        }, 1000);
    };

    addStyle(`
     .ife-container {
        align-items: center;
        display: flex;
        flex-direction: row;
        padding-right: 12px;
        position: relative;
     }
     .ife-sort-icon {
        contain: strict;
        display: inline-block;
        flex-shrink: 0;
        font-style: normal;
        font-weight: 400;
        line-height: 1;
        opacity: 1;
        overflow: hidden;
        width: 20px;
        height: 20px;
        text-align: center;
        -webkit-user-select: none;
        -moz-user-select: none;
        -ms-user-select: none;
        user-select: none;
        vertical-align: middle;
        position: absolute;
        right: -8px;
        top: 50%;
        transform: translateY(-50%);
        cursor: pointer;
   }
   .ife-link {
        color: #009fdf;
   }
   .ife-calculator-container {
        align-items: flex-start;
        display: flex;
        flex-direction: row;
    }
   .ife-input-container {
        display: flex;
        flex: 1 1 50%;
        flex-direction: column;
        max-width: 50%;
        margin-top: 10px;
        margin-bottom: 10px;
    }
   .ife-input-label {
       background-color: #f3f4f5;
       border: 1px solid transparent;
       display: inline-block;
       height: 32px;
       min-height: 32px;
       position: relative;
   }
   .ife-input {
       background-color: inherit;
       border: 0;
       border-radius: inherit;
       display: block;
       font-size: 1rem;
       height: 100%;
       line-height: 1.5;
       min-height: 100%;
       padding: 0 8px;
       width: 100%;
   }
   .ife-input[disabled], .ife-input[disabled]::placeholder {
       color: #00a658;
   }
   .ife-input-label span {
       color: #00a658;
       position: absolute;
       top: 30%;
       right: 10px;
   }
   `);

    let observerTimeout = null;
    const observerCallback = (mutationsList, observer) => {
        showCalculator(mutationsList);

        // since we can't track an specific id, we observe any body changes and
        // check if our classes are present.
        if (document.querySelectorAll('.ife-container').length > 0) {
            return;
        }
        if (observerTimeout != null) {
            clearTimeout(observerTimeout);
        }
        observerTimeout = setTimeout(onLoaded, 1000 * 2);
    };

    (new MutationObserver(observerCallback)).observe(document.body, {childList: true, subtree: true });
})();