FB Floating Window Enhancer

Widen FB floating windows by 1.4x, center them, perfectly nudge menus, auto-select "All Comments", and fix keyboard scrolling.

スクリプトをインストールするには、Tampermonkey, GreasemonkeyViolentmonkey のような拡張機能のインストールが必要です。

You will need to install an extension such as Tampermonkey to install this script.

スクリプトをインストールするには、TampermonkeyViolentmonkey のような拡張機能のインストールが必要です。

スクリプトをインストールするには、TampermonkeyUserscripts のような拡張機能のインストールが必要です。

このスクリプトをインストールするには、Tampermonkeyなどの拡張機能をインストールする必要があります。

このスクリプトをインストールするには、ユーザースクリプト管理ツールの拡張機能をインストールする必要があります。

(ユーザースクリプト管理ツールは設定済みなのでインストール!)

このスタイルをインストールするには、Stylusなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus などの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus tなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

(ユーザースタイル管理ツールは設定済みなのでインストール!)

このスクリプトの質問や評価の投稿はこちら通報はこちらへお寄せください
// ==UserScript==
// @name         FB Floating Window Enhancer
// @namespace    http://tampermonkey.net/
// @version      V1.7.0
// @description  Widen FB floating windows by 1.4x, center them, perfectly nudge menus, auto-select "All Comments", and fix keyboard scrolling.
// @author       Gemini
// @match        *://*.facebook.com/*
// @grant        none
// ==/UserScript==

(function() {
    'use strict';

    // 1. Resize Floating Window (1.4x width, keep text size, and force centering)
    const resizeObserver = new MutationObserver(() => {
        const dialogs = document.querySelectorAll('div[role="dialog"]');
        dialogs.forEach(dialog => {
            if (dialog.dataset.resized) return;

            const innerDivs = dialog.querySelectorAll('div');
            for (let div of innerDivs) {
                const style = window.getComputedStyle(div);
                const maxWidth = parseInt(style.maxWidth);
                const width = parseInt(style.width);

                if ((maxWidth > 300 && maxWidth < 1200) || (width > 300 && width < 1200 && style.maxWidth === 'none')) {
                    const targetWidth = maxWidth || width;

                    div.style.maxWidth = `${targetWidth * 1.4}px`;
                    div.style.width = '100%';
                    div.style.marginLeft = 'auto';
                    div.style.marginRight = 'auto';

                    if (div.parentElement) {
                        div.parentElement.style.display = 'flex';
                        div.parentElement.style.justifyContent = 'center';
                        div.parentElement.style.width = '100%';
                    }

                    dialog.dataset.resized = "true";
                    break;
                }
            }
        });
    });

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

    // 2. Automation Loop (Menu Boundary Nudge + Auto-Select "All Comments")
    setInterval(() => {
        // --- A. Deep Menu Bounds Fixer (From V1.4.0) ---
        const menuItems = document.querySelectorAll('[role="menuitem"]');
        const containersToFix = new Set();

        menuItems.forEach(item => {
            let el = item.parentElement;
            while (el && el !== document.body) {
                const style = window.getComputedStyle(el);
                if (style.position === 'absolute' || style.position === 'fixed') {
                    containersToFix.add(el);
                    break;
                }
                el = el.parentElement;
            }
        });

        containersToFix.forEach(container => {
            const rect = container.getBoundingClientRect();
            if (rect.width === 0 || rect.height === 0) return;

            if (!container.dataset.posFixed) {
                const currentMarginLeft = parseFloat(window.getComputedStyle(container).marginLeft) || 0;
                // If menu touches or bleeds past the left screen edge (< 20px)
                if (rect.left < 20) {
                    const shift = 30 - rect.left;
                    container.style.setProperty('margin-left', `${currentMarginLeft + shift}px`, 'important');
                    container.dataset.posFixed = "true";
                }
            }
        });

        // --- B. Auto-select "所有留言" (All Comments) ---
        const buttons = document.querySelectorAll('div[role="button"]');
        for (let btn of buttons) {
            const text = btn.textContent.trim();

            // Check if the button says "最相關" and hasn't been clicked by us yet
            if ((text.startsWith("最相關") || text.startsWith("相關留言")) && !btn.dataset.autoSorted) {
                btn.dataset.autoSorted = "processing";

                // Click the dropdown to open it
                btn.click();

                let attempts = 0;
                // Rapidly check for the opened menu items
                const findMenu = setInterval(() => {
                    attempts++;
                    const dropdownItems = document.querySelectorAll('div[role="menuitem"]');

                    for (let item of dropdownItems) {
                        if (item.textContent.trim().startsWith("所有留言")) {
                            clearInterval(findMenu);
                            item.click(); // Select "所有留言"
                            btn.dataset.autoSorted = "done";
                            return;
                        }
                    }

                    // Timeout after ~2 seconds to prevent infinite loops
                    if (attempts > 20) {
                        clearInterval(findMenu);
                        btn.removeAttribute('data-autoSorted'); // Allow retry
                    }
                }, 100);
            }
        }
    }, 200);

    // 3. Enable Keyboard Navigation (Forced Capture Phase)
    window.addEventListener('keydown', (e) => {
        const dialogs = Array.from(document.querySelectorAll('div[role="dialog"]')).filter(d => d.offsetWidth > 0 && d.offsetHeight > 0);
        if (dialogs.length === 0) return;

        const activeDialog = dialogs[dialogs.length - 1];

        const tag = e.target.tagName.toLowerCase();
        if (tag === 'input' || tag === 'textarea' || e.target.isContentEditable) return;

        const scrollKeys = ['ArrowUp', 'ArrowDown', 'PageUp', 'PageDown', 'Home', 'End'];
        if (!scrollKeys.includes(e.key)) return;

        let targetScroller = null;
        let maxScrollHeight = 0;

        const candidates = [activeDialog, ...Array.from(activeDialog.querySelectorAll('*'))];
        for (let el of candidates) {
            if (el.scrollHeight > el.clientHeight + 10) {
                const style = window.getComputedStyle(el);
                if (['auto', 'scroll', 'overlay'].includes(style.overflowY)) {
                    if (el.scrollHeight > maxScrollHeight) {
                        maxScrollHeight = el.scrollHeight;
                        targetScroller = el;
                    }
                }
            }
        }

        if (!targetScroller) return;

        const scrollAmount = 80;
        const pageScrollAmount = targetScroller.clientHeight * 0.8;

        switch (e.key) {
            case 'ArrowUp':
                targetScroller.scrollBy({ top: -scrollAmount, behavior: 'auto' });
                break;
            case 'ArrowDown':
                targetScroller.scrollBy({ top: scrollAmount, behavior: 'auto' });
                break;
            case 'PageUp':
                targetScroller.scrollBy({ top: -pageScrollAmount, behavior: 'auto' });
                break;
            case 'PageDown':
                targetScroller.scrollBy({ top: pageScrollAmount, behavior: 'auto' });
                break;
            case 'Home':
                targetScroller.scrollTo({ top: 0, behavior: 'auto' });
                break;
            case 'End':
                targetScroller.scrollTo({ top: targetScroller.scrollHeight, behavior: 'auto' });
                break;
        }

        e.preventDefault();
        e.stopImmediatePropagation();
        e.stopPropagation();

    }, true);
})();