BetterTopMenu

mec-itutorのメニュー画面を修正

Vous devrez installer une extension telle que Tampermonkey, Greasemonkey ou Violentmonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey ou Violentmonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey ou Violentmonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey ou Userscripts pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey pour installer ce script.

Vous devrez installer une extension de gestionnaire de script utilisateur pour installer ce script.

(J'ai déjà un gestionnaire de scripts utilisateur, laissez-moi l'installer !)

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

(J'ai déjà un gestionnaire de style utilisateur, laissez-moi l'installer!)

// ==UserScript==
// @name           BetterTopMenu
// @author         Nozom.u
// @match          *://mec-itutor.jp/rpv/home/default.aspx
// @match          *://mec-itutor.jp/rpv/home/
// @version        2.0.7
// @run-at      window-load
// @description  mec-itutorのメニュー画面を修正
// @namespace https://greasyfork.org/users/1534273
// ==/UserScript==
(function() {
    'use strict';
    function addStyles() {
        const styleSheet = document.createElement('style');
        styleSheet.textContent = `
            /* ===== 単元タイトル部分の余白 ===== */
            .mec-unit-title-wrap,
            .mec-unit-title-wrap-small {
              padding: 8px 0 !important;
            }
            #ctl00_cplPageContent_infomationPanel > div.list-group-item,
            #scheduledDisplayListView > div.list-group-item,
            #scheduledRedisplayListView > div.list-group-item {
              padding: 0px 15px;
            }
            /* ===== デッキ・期限リンクの装飾 ===== */
            .list-group > .list-group-item > .mec-unit-title-wrap ul li.mec-list-title > a,
            .list-group > .list-group-item > .mec-unit-title-wrap ul li.mec-list-date > a {
              text-decoration: none; /* 通常はアンダーラインなし */
              line-height: 40px;
            }
            /* hover時にアンダーライン表示 */
            .list-group > .list-group-item:hover > .mec-unit-title-wrap ul li.mec-list-title > a,
            .list-group > .list-group-item:hover > .mec-unit-title-wrap ul li.mec-list-date > a {
              text-decoration: underline;
              display: block;
              line-height: 40px;
            }
            /* ヘッダーのスタイル */
            .mec-header-title.collapsible-header {
                cursor: pointer !important;
                user-select: none !important;
                transition: background-color 0.2s ease !important;
            }
            /* 矢印アイコン */
            .collapse-arrow {
                display: inline-block;
                width: 0;
                height: 0;
                border-left: 8px solid transparent;
                border-right: 8px solid transparent;
                border-top: 10px solid white;
                transition: transform 0.3s ease;
                margin-right: 8px;
                vertical-align: middle;
                position: relative;
                top: -2px;
            }
            .collapse-arrow.collapsed {
                transform: rotate(-90deg);
            }

            /* アイテムのアニメーション */
            .list-group-item.collapsible-item {
                overflow: hidden !important;
                transition: all 0.3s ease !important;
                transform-origin: top !important;
            }
            .list-group-item.collapsible-item.collapsed {
                max-height: 0 !important;
                opacity: 0 !important;
                padding-top: 0 !important;
                padding-bottom: 0 !important;
                margin-top: 0 !important;
                margin-bottom: 0 !important;
                border: none !important;
            }
        `;
        document.head.appendChild(styleSheet);
    }

    // 初期化関数
    function initializeCollapsibleSections() {
        const sections = [
            {
                container: document.getElementById('ctl00_cplPageContent_infomationPanel'),
                name: 'お知らせ',
                storageKey: 'info'
            },
            {
                container: document.getElementById('scheduledRedisplayListView'),
                parentContainer: document.getElementById('ctl00_cplPageContent_scheduledRedisplayPanel'),
                name: '提出期限切れ',
                storageKey: 'expired'
            },
            {
                container: document.getElementById('scheduledDisplayListView'),
                parentContainer: document.getElementById('ctl00_cplPageContent_scheduledPanel'),
                name: '学習予定',
                storageKey: 'scheduled'
            }
        ];

        sections.forEach(section => {
            if (!section.container && !section.parentContainer) {
                return;
            }

            const targetContainer = section.container || section.parentContainer;
            const header = targetContainer.querySelector('.info-item-heading');

            if (!header) {
                return;
            }

            // ヘッダーに折りたたみ機能を追加
            setupCollapsibleSection(targetContainer, header, section.name, section.storageKey);
        });
    }

    // 個別セクションのセットアップ
    function setupCollapsibleSection(container, header, sectionName, storageKey) {
        // すでに設定済みの場合はスキップ
        if (header.classList.contains('collapsible-header')) {
            return;
        }

        // ヘッダーにクラスを追加とデータ属性を設定
        header.classList.add('collapsible-header');
        header.setAttribute('data-storage-key', storageKey);

        // h4.info-item-headingを探す
        const h4Element = header.querySelector('h4.info-item-heading');

        if (h4Element) {
            // 矢印アイコンを作成
            const arrow = document.createElement('span');
            arrow.className = 'collapse-arrow';

            // h4の最初に矢印を挿入(img.mec-header-iconの前)
            h4Element.insertBefore(arrow, h4Element.firstChild);
        } else {
            // h4が見つからない場合は、ヘッダーの最初に追加
            const arrow = document.createElement('span');
            arrow.className = 'collapse-arrow';
            header.insertBefore(arrow, header.firstChild);
        }

        // アイテムを取得(ヘッダー以外のlist-group-item)
        const items = Array.from(container.querySelectorAll('.list-group-item'))
            .filter(item => !item.classList.contains('mec-header-title'));

        // アイテムにクラスを追加
        items.forEach(item => {
            item.classList.add('collapsible-item');
        });

        // 状態を管理
        let isCollapsed = loadSectionState(storageKey);

        // 矢印要素を取得
        const arrow = header.querySelector('.collapse-arrow');

        // 初期状態を設定
        if (isCollapsed && arrow) {
            arrow.classList.add('collapsed');
            items.forEach(item => item.classList.add('collapsed'));
        }

        // クリックイベントを追加
        header.addEventListener('click', function(e) {
            e.preventDefault();
            e.stopPropagation();

            isCollapsed = !isCollapsed;

            // アニメーション
            if (isCollapsed) {
                arrow?.classList.add('collapsed');
                items.forEach(item => item.classList.add('collapsed'));
            } else {
                arrow?.classList.remove('collapsed');
                items.forEach(item => item.classList.remove('collapsed'));
            }

            // 状態を保存
            saveSectionState(storageKey, isCollapsed);
        });
    }

    // 状態を保存
    function saveSectionState(storageKey, isCollapsed) {
        try {
            const key = `collapsible_section_${storageKey}`;
            localStorage.setItem(key, JSON.stringify(isCollapsed));
        } catch (e) {
            // ローカルストレージエラーを無視
        }
    }

    // 状態を読み込み
    function loadSectionState(storageKey) {
        try {
            const key = `collapsible_section_${storageKey}`;
            const saved = localStorage.getItem(key);
            return saved ? JSON.parse(saved) : false; // デフォルトは展開状態
        } catch (e) {
            // ローカルストレージエラーを無視
            return false;
        }
    }

    // キーボードショートカットのセットアップ(サイトに注入)
    function setupKeyboardShortcuts() {
        // サイトのグローバルスコープで実行するスクリプトを作成
        const script = document.createElement('script');
        script.type = 'text/javascript';
        script.textContent = `
        (function() {
            // セクションマップ
            const sectionMap = {
                '1': { storageKey: 'info', sectionName: 'お知らせ' },
                '2': { storageKey: 'expired', sectionName: '提出期限切れ' },
                '3': { storageKey: 'scheduled', sectionName: '学習予定' }
            };

            // キーボードイベントをリッスン
            document.addEventListener('keydown', function(e) {
                // 数字キー(1, 2, 3)を検出
                if (e.key in sectionMap) {
                    const { storageKey, sectionName } = sectionMap[e.key];
                    toggleSectionByKey(storageKey, sectionName);
                }
            });

            // セクションを切り替える関数
            function toggleSectionByKey(storageKey, sectionName) {
                const header = document.querySelector(\`.collapsible-header[data-storage-key="\${storageKey}"]\`);

                if (header) {
                    header.click();
                }
            }
        })();
        `;

        // スクリプトをDOCUMENT_START時点で実行するために、headに追加
        if (document.head) {
            document.head.appendChild(script);
        } else {
            document.documentElement.appendChild(script);
        }
    }

    // 初期化を実行
    function initialize() {
        // スタイルを追加
        addStyles();
        // キーボードショートカットをセットアップ
        setupKeyboardShortcuts();
        // DOMが完全に読み込まれるまで待つ
        if (document.readyState === 'loading') {
            document.addEventListener('DOMContentLoaded', initializeCollapsibleSections);
        } else {
            // 既に読み込まれている場合は直ちに実行
            initializeCollapsibleSections();
        }
    }

    // MutationObserverで動的なコンテンツ変更を監視
    function setupObserver() {
        let debounceTimer = null;
        const observer = new MutationObserver(function(mutations) {
            let shouldReinitialize = false;
            mutations.forEach(function(mutation) {
                if (mutation.type === 'childList' && mutation.addedNodes.length > 0) {
                    // 新しいノードが追加されたかチェック
                    mutation.addedNodes.forEach(function(node) {
                        if (node.nodeType === 1 && node.classList &&
                            (node.classList.contains('list-group') ||
                             node.classList.contains('list-group-item'))) {
                            shouldReinitialize = true;
                        }
                    });
                }
            });

            if (shouldReinitialize) {
                // デバウンス処理:前のタイマーをクリア
                clearTimeout(debounceTimer);
                debounceTimer = setTimeout(initializeCollapsibleSections, 100);
            }
        });

        // main-gadget-areaの監視を開始
        const mainArea = document.getElementById('main-gadget-area');
        if (mainArea) {
            observer.observe(mainArea, {
                childList: true,
                subtree: true
            });
        }
    }

    // グローバル関数として公開(デバッグ用)
    window.collapsibleSections = {
        // すべてのセクションを折りたたむ
        collapseAll: function() {
            const headers = document.querySelectorAll('.mec-header-title.collapsible-header');
            headers.forEach(header => {
                const arrow = header.querySelector('.collapse-arrow');
                const items = header.parentElement.querySelectorAll('.list-group-item.collapsible-item');

                arrow?.classList.add('collapsed');
                items.forEach(item => item.classList.add('collapsed'));
            });
        },

        // すべてのセクションを展開
        expandAll: function() {
            const headers = document.querySelectorAll('.mec-header-title.collapsible-header');
            headers.forEach(header => {
                const arrow = header.querySelector('.collapse-arrow');
                const items = header.parentElement.querySelectorAll('.list-group-item.collapsible-item');

                arrow?.classList.remove('collapsed');
                items.forEach(item => item.classList.remove('collapsed'));
            });
        },

        // 状態をリセット
        resetStates: function() {
            ['info', 'expired', 'scheduled'].forEach(key => {
                localStorage.removeItem(`collapsible_section_${key}`);
            });
            location.reload();
        },

        // 再初期化
        reinitialize: function() {
            initializeCollapsibleSections();
        },

        // 現在の状態を確認
        getStates: function() {
            const states = {};
            ['info', 'expired', 'scheduled'].forEach(key => {
                const saved = localStorage.getItem(`collapsible_section_${key}`);
                states[key] = saved ? JSON.parse(saved) : false;
            });
            return states;
        }
    };

    // エラーハンドリング
    try {
        // 初期化実行
        initialize();

        // オブザーバーをセットアップ
        setupObserver();
    } catch (error) {
        // エラーを無視
    }
})();