Introducing Grouping and Filter of positions on Kite-beta.zerodha.com

4 Likes

Where is the source for users visiting the chrome addon?

Do you mean source code ?

(() => {
β€œuse strict”;

// ── Constants ────────────────────────────────────────────────────────────
const BUTTON_ID       = "zerodha-expand-collapse-btn";
const SELECTORS = {
    section : "section.open-positions",
    header  : "header.data-table-header",
    icons   : "span.expand-icon",
    expanded: "expanded",
};
const COLORS = {
    expand  : "#2e7d32", // green
    collapse: "#c62828", // red
};
const LABELS = {
    expand  : "Expand All",
    collapse: "Collapse All",
};

// ── Helpers ──────────────────────────────────────────────────────────────

/**
 * Applies a plain object of style properties to an element.
 * Avoids inline style strings; keeps styling declarative and easy to diff.
 */
function applyStyles(el, styles) {
    Object.assign(el.style, styles);
}

/** Creates the button with all attributes set via safe DOM APIs (no innerHTML). */
function createButton() {
    const btn = document.createElement("button");
    btn.id          = BUTTON_ID;
    btn.textContent = LABELS.expand;   // textContent β€” XSS-safe
    btn.setAttribute("aria-pressed", "false");
    btn.setAttribute("title", LABELS.expand);

    applyStyles(btn, {
        marginLeft  : "10px",
        padding     : "3px 8px",
        cursor      : "pointer",
        borderRadius: "4px",
        border      : "none",
        fontWeight  : "500",
        fontSize    : "12px",
        color       : "white",
        background  : COLORS.expand,
        transition  : "background 0.2s ease",
    });

    return btn;
}

/** Syncs button label, colour, and ARIA state to the current expanded flag. */
function syncButton(btn, isExpanded) {
    const label = isExpanded ? LABELS.collapse : LABELS.expand;
    btn.textContent = label;
    btn.setAttribute("aria-pressed", String(isExpanded));
    btn.setAttribute("title", label);
    applyStyles(btn, { background: isExpanded ? COLORS.collapse : COLORS.expand });
}

/**
 * Derives the true expanded state from the DOM.
 * Returns true only if every icon is currently expanded.
 */
function areAllExpanded() {
    const icons = document.querySelectorAll(SELECTORS.icons);
    if (!icons.length) return false;
    return [...icons].every(icon => icon.classList.contains(SELECTORS.expanded));
}

// ── Core logic ───────────────────────────────────────────────────────────

function injectButton() {
    // Idempotency β€” bail if already injected (handles SPA re-renders)
    if (document.getElementById(BUTTON_ID)) return;

    const section = document.querySelector(SELECTORS.section);
    if (!section) return;

    const header = section.querySelector(SELECTORS.header);
    if (!header) return;

    const btn = createButton();

    btn.addEventListener("click", () => {
        try {
            const icons = document.querySelectorAll(SELECTORS.icons);
            if (!icons.length) return;

            // Read actual DOM state β€” not an internal flag
            const currentlyExpanded = areAllExpanded();

            icons.forEach(icon => {
                const isExpanded = icon.classList.contains(SELECTORS.expanded);
                if (!currentlyExpanded && !isExpanded) icon.click();
                if ( currentlyExpanded &&  isExpanded) icon.click();
            });

            // Disable during settle window β€” prevents double-click race condition
            btn.disabled = true;

            // Defer DOM read β€” Zerodha updates classes asynchronously after icon.click()
            setTimeout(() => {
                // Guard β€” button may have been removed if user navigated away
                if (!document.getElementById(BUTTON_ID)) return;
                syncButton(btn, areAllExpanded());
                btn.disabled = false;
            }, 50);
        } catch (err) {
            // Absorb errors so a broken icon never bricks the button
            btn.disabled = false;
            console.warn("[Zerodha Ext] Error toggling positions:", err);
        }
    });

    header.appendChild(btn);

    // Defer injection sync β€” Zerodha's classes may not have settled on page load
    setTimeout(() => {
        if (!document.getElementById(BUTTON_ID)) return;
        syncButton(btn, areAllExpanded());
    }, 50);

    // Button is live β€” stop watching for it
    domObserver.disconnect();

    // Watch only the header for button removal (SPA navigation away)
    // When removed, restart the main observer to wait for it again
    removalObserver.observe(header, { childList: true });
}

// ── Observers ────────────────────────────────────────────────────────────

// Watches for the button being removed from the DOM (user navigated away)
const removalObserver = new MutationObserver((mutations) => {
    const removed = mutations.some(m =>
        [...m.removedNodes].some(node => node.id === BUTTON_ID)
    );
    if (removed) {
        removalObserver.disconnect();
        // Resume watching for the section to reappear
        domObserver.observe(document.body, { childList: true, subtree: true });
    }
});

// Watches for the positions section to appear, then injects the button
const domObserver = new MutationObserver(() => {
    if (!window.location.pathname.includes("positions")) return;
    injectButton();
});

domObserver.observe(document.body, {
    childList: true,
    subtree  : true,
});

// Also attempt immediately in case the DOM is already ready
injectButton();

})();

Yeah… You said you can examine every line of source. Right? How do end users do that? Did you put the code in Github?