メインコンテンツへスキップ
見出し画像

【Script】NotebookLM:ソースをフィルタリング #NotebookLM

    こんにちは!一歩です!(自己紹介はこちら)

    (既に活用済みの方向けです)

    NotebookLMでは、ソースのチェックボックスを外すことで、チェックが付いているソースのみ参照した回答が可能ですが、
    現状はあまり使い勝手が良くありません。。。
    (いずれ改善されると思いますが)

    ソースを最大300件登録できますが、スクロールしてチェックを外すのは現実的ではないですよね。

    そこで簡単なScriptを用意しました!

    後ほど拡張機能に取り込みますが、お試し版として使ってみてください!

    取り込み済です!


    利用方法

    1.ブラウザの開発者ツールを起動する。
    ※ ブラウザによってメニュー名は異なりますが、
     Chromeは「検証」
     Edgeは「開発者ツールで調査する」です。

    2.コンソールに以下Scriptを貼り付けてEnterで実行(コンソールが表示されていない場合、「Esc」キーを押下。キーボードの一番左上あたりにあると思います)

    3.ソースエリアに入力欄が表示されます。

    画像

    4.文字を入力。ソースのタイトルでフィルタリングされます。

    画像

    チャット欄の右側の表示で個数が変わっていれば成功です。
    そのままチャットで質問してみてください(_ _)

    画像


    Script

    (function() {
        'use strict';
        
        // ソースパネルのセレクタ
        const sourcePanel = document.querySelector('.source-panel');
        if (!sourcePanel) {
            console.log('ソースパネルが見つかりません');
            return;
        }
        
        // 既にフィルターが追加されていれば削除(テスト用に再実行できるように)
        const existingFilter = document.getElementById('source-filter-input');
        if (existingFilter) {
            existingFilter.closest('.filter-container').remove();
        }
        
        // 「すべてのソースを選択」の行を見つける
        const selectAllRow = document.querySelector('.row:has(.header-title-muted)');
        if (!selectAllRow) {
            console.log('「すべてのソースを選択」の行が見つかりません');
            return;
        }
        
        // フィルターコンテナ作成
        const filterContainer = document.createElement('div');
        filterContainer.className = 'filter-container';
        filterContainer.style.margin = '0 0 8px 0';
        filterContainer.style.padding = '0 8px';
        
        // 入力ラッパー作成
        const inputWrapper = document.createElement('div');
        inputWrapper.className = 'filter-input-wrapper';
        inputWrapper.style.position = 'relative';
        inputWrapper.style.display = 'flex';
        inputWrapper.style.alignItems = 'center';
        filterContainer.appendChild(inputWrapper);
        
        // 検索アイコン作成
        const searchIconWrapper = document.createElement('div');
        searchIconWrapper.style.position = 'absolute';
        searchIconWrapper.style.left = '10px';
        searchIconWrapper.style.top = '50%';
        searchIconWrapper.style.transform = 'translateY(-50%)';
        searchIconWrapper.style.pointerEvents = 'none';
        searchIconWrapper.style.color = '#666';
        
        // SVG要素を作成
        const svgNS = "http://www.w3.org/2000/svg";
        const svg = document.createElementNS(svgNS, "svg");
        svg.setAttribute("width", "16");
        svg.setAttribute("height", "16");
        svg.setAttribute("viewBox", "0 0 24 24");
        svg.setAttribute("fill", "none");
        svg.setAttribute("stroke", "currentColor");
        svg.setAttribute("stroke-width", "2");
        svg.setAttribute("stroke-linecap", "round");
        svg.setAttribute("stroke-linejoin", "round");
        
        // サークル要素を作成
        const circle = document.createElementNS(svgNS, "circle");
        circle.setAttribute("cx", "11");
        circle.setAttribute("cy", "11");
        circle.setAttribute("r", "8");
        svg.appendChild(circle);
        
        // ライン要素を作成
        const line = document.createElementNS(svgNS, "line");
        line.setAttribute("x1", "21");
        line.setAttribute("y1", "21");
        line.setAttribute("x2", "16.65");
        line.setAttribute("y2", "16.65");
        svg.appendChild(line);
        
        searchIconWrapper.appendChild(svg);
        inputWrapper.appendChild(searchIconWrapper);
        
        // 入力欄作成
        const filterInput = document.createElement('input');
        filterInput.id = 'source-filter-input';
        filterInput.type = 'text';
        filterInput.placeholder = 'ソースをフィルター...';
        filterInput.style.width = '100%';
        filterInput.style.padding = '8px 8px 8px 32px';
        filterInput.style.border = '1px solid #ddd ';
        filterInput.style.borderRadius = '4px';
        filterInput.style.fontSize = '14px';
        filterInput.style.outline = 'none';
        inputWrapper.appendChild(filterInput);
        
        // クリアボタン作成
        const clearButton = document.createElement('button');
        clearButton.id = 'source-filter-clear';
        clearButton.textContent = '×';
        clearButton.style.position = 'absolute';
        clearButton.style.right = '8px';
        clearButton.style.background = 'none';
        clearButton.style.border = 'none';
        clearButton.style.cursor = 'pointer';
        clearButton.style.fontSize = '18px';
        clearButton.style.color = '#999';
        clearButton.style.display = 'none';
        inputWrapper.appendChild(clearButton);
        
        // フィルター入力欄を「すべてのソースを選択」の上に挿入
        selectAllRow.parentNode.insertBefore(filterContainer, selectAllRow);
        
        // チェックボックスの状態を変更し、すべての可能なイベントを発火させる関数
        function setCheckboxStateAndTriggerEvents(checkbox, checked) {
            if (!checkbox) return;
            
            // 既に希望の状態ならば何もしない
            if (checkbox.checked === checked) return;
            
            // 値を変更
            checkbox.checked = checked;
            
            // チェックボックスのクラスを更新
            const checkboxParent = checkbox.closest('.mat-mdc-checkbox');
            if (checkboxParent) {
                if (checked) {
                    checkboxParent.classList.add('mat-mdc-checkbox-checked');
                } else {
                    checkboxParent.classList.remove('mat-mdc-checkbox-checked');
                }
            }
            
            // イベントを発火させる
            try {
                // 1. change イベント
                const changeEvent = new Event('change', { bubbles: true });
                checkbox.dispatchEvent(changeEvent);
                
                // 2. inputイベント
                const inputEvent = new Event('input', { bubbles: true });
                checkbox.dispatchEvent(inputEvent);
                
                // 3. clickイベント
                const clickEvent = new MouseEvent('click', {
                    bubbles: true,
                    cancelable: true,
                    view: window
                });
                checkbox.dispatchEvent(clickEvent);
            } catch (e) {
                console.warn('イベント発火中にエラー:', e);
            }
            
            console.log(`チェックボックス状態変更: ${checked ? '選択' : '解除'}`);
        }
        
        // 消去ボタンのイベントリスナー
        clearButton.addEventListener('click', function() {
            filterInput.value = '';
            clearButton.style.display = 'none';
            filterSources(''); // フィルターをクリア
        });
        
        // 入力イベントのリスナー
        filterInput.addEventListener('input', function() {
            const searchText = this.value.trim();
            
            // 消去ボタンの表示制御
            if (searchText) {
                clearButton.style.display = 'block';
            } else {
                clearButton.style.display = 'none';
            }
            
            // ソースのフィルタリング
            filterSources(searchText);
        });
        
        // フィルタリング機能
        function filterSources(searchText) {
            // すべてのソースコンテナを取得
            const sourceContainers = document.querySelectorAll('.single-source-container');
            let visibleCount = 0;
            
            // 表示・非表示を設定(チェックボックスはまだ触らない)
            sourceContainers.forEach(container => {
                // ソースのタイトルを取得
                const titleElement = container.querySelector('.source-title');
                if (!titleElement) return;
                
                const title = titleElement.textContent.trim();
                const matchesFilter = !searchText || title.toLowerCase().includes(searchText.toLowerCase());
                
                // フィルタに一致するかどうかで表示/非表示を切り替え
                if (matchesFilter) {
                    container.style.display = '';
                    visibleCount++;
                } else {
                    container.style.display = 'none';
                }
            });
            
            // 非表示になったソースのみチェックを外す
            sourceContainers.forEach(container => {
                if (container.style.display === 'none') {
                    const checkbox = container.querySelector('input[type="checkbox"]');
                    if (checkbox && checkbox.checked) {
                        // 非表示のソースのチェックだけを外す
                        setCheckboxStateAndTriggerEvents(checkbox, false);
                    }
                }
            });
            
            // 「すべてのソースを選択」チェックボックスの状態を更新
            updateSelectAllCheckbox();
            
            // フィルター結果がない場合の表示
            showNoResultsMessage(visibleCount === 0 && searchText !== '');
            
            console.log(`フィルタリング: ${visibleCount} 件表示`);
        }
        
        // 「すべてのソースを選択」チェックボックスの状態を更新
        function updateSelectAllCheckbox() {
            const selectAllCheckbox = document.querySelector('.select-checkbox-all-sources input[type="checkbox"]');
            if (!selectAllCheckbox) return;
            
            // 表示されているソースのチェックボックスをすべて取得
            const visibleSourceCheckboxes = Array.from(document.querySelectorAll('.single-source-container'))
                .filter(container => container.style.display !== 'none')
                .map(container => container.querySelector('input[type="checkbox"]'))
                .filter(Boolean);
            
            // 表示されているソースが1つもない場合は何もしない
            if (visibleSourceCheckboxes.length === 0) return;
            
            // すべてがチェックされているかどうか確認
            const allChecked = visibleSourceCheckboxes.every(checkbox => checkbox.checked);
            
            // 「すべてのソースを選択」チェックボックスの状態を更新(既に同じ状態なら何もしない)
            if (selectAllCheckbox.checked !== allChecked) {
                selectAllCheckbox.checked = allChecked;
                
                // クラスを更新
                const checkboxParent = selectAllCheckbox.closest('.mat-mdc-checkbox');
                if (checkboxParent) {
                    if (allChecked) {
                        checkboxParent.classList.add('mat-mdc-checkbox-checked');
                    } else {
                        checkboxParent.classList.remove('mat-mdc-checkbox-checked');
                    }
                }
            }
        }
        
        // 「すべてのソースを選択」チェックボックスの機能を拡張
        const selectAllCheckbox = document.querySelector('.select-checkbox-all-sources input[type="checkbox"]');
        if (selectAllCheckbox) {
            // 元のクリックイベントハンドラを保存
            const originalOnClick = selectAllCheckbox.onclick;
            
            // 新しいクリックイベントハンドラを設定
            selectAllCheckbox.onclick = function(e) {
                // 元のイベントを呼び出し
                if (typeof originalOnClick === 'function') {
                    originalOnClick.call(this, e);
                }
                
                // もし元のイベントによって状態が変わったら、それを元に戻す
                // (ダブルクリック防止)
                setTimeout(() => {
                    const isChecked = this.checked;
                    
                    // 表示されているソースのみチェック状態を変更
                    const sourceContainers = document.querySelectorAll('.single-source-container');
                    
                    sourceContainers.forEach(container => {
                        if (container.style.display !== 'none') {
                            const checkbox = container.querySelector('input[type="checkbox"]');
                            if (checkbox && checkbox.checked !== isChecked) {
                                setCheckboxStateAndTriggerEvents(checkbox, isChecked);
                            }
                        }
                    });
                }, 0);
            };
        }
        
        // 検索結果がない場合のメッセージを表示/非表示
        function showNoResultsMessage(show) {
            // 既存のメッセージがあれば削除
            const existingMessage = document.getElementById('no-results-message');
            if (existingMessage) {
                existingMessage.remove();
            }
            
            if (show) {
                // 検索結果がない場合のメッセージを作成して表示
                const scrollArea = document.querySelector('.scroll-area-desktop');
                if (scrollArea) {
                    const messageElement = document.createElement('div');
                    messageElement.id = 'no-results-message';
                    messageElement.textContent = '検索結果がありません';
                    messageElement.style.padding = '16px';
                    messageElement.style.textAlign = 'center';
                    messageElement.style.color = '#666';
                    
                    scrollArea.appendChild(messageElement);
                }
            }
        }
        
        console.log('ソースフィルターが正常に追加されました!');
    })();


    最後までお読みいただき、ありがとうございます!

    何か1つでも参考になれば幸いです!



    オススメマガジン


    オススメ拡張機能


    人生を豊かにするおすすめ書籍はこちら!



    #NotebookLM

    #GPT #Felo #Gemini #Claude #NotebookLM

    #IT #プログラミング #AI #エンジニア #RPA #PowerAutomate #PowerAutomateDesktop #Javascript #CSS #HTML #プログラマー
    #アプリ #ツール #業務改善 #効率化 #業務効率化 #自動化 #時短 #システム #システムエンジニア #便利ツール #テクノロジー #Youtube #ソース #ソース追加 #一括 #URL #ブックマークレット

    #JavaScript #プログラミング #Web開発 #エンジニア #IT #技術 #便利ツール #効率化 #ライフハック #生産性向上 #時短術 #Note #note #ノート #ウェブ制作 #クリエイティブ #AI #Javascript #CSS #HTML #プログラマー #アプリ #ツール #業務改善 #効率化 #業務効率化 #自動化 #時短 #システム #システムエンジニア #便利ツール #テクノロジー

    #カスタマイズ #拡張機能 #ライフハック #PC仕事術 #スクレイピング

    #リンク収集ツール #Chrome拡張機能 #ウェブ効率化 #デジタルマーケティング #リサーチツール #クリップボード #リンク管理 #情報収集 #ブログ運営 #コンテンツ制作 #ウェブ調査 #業務効率化 #個人開発 #生産性向上 #ウェブスクレイピング #データ収集 #作業自動化

     
     
    SE歴15年。個人事業・副業の「面倒・迷い・消耗」を、PC・AIの小さなツールと、心理・健康・判断の知見で減らします。効率化の目的は、もっと働くことではなく、自分で使える時間と余白を増やすこと。数百円から使える効率化ツールを開発・販売しています。

    あなたへのおすすめ