ディレクトリチェック

ディレクトリの検索機能を追加。現在入力されているディレクトリの詳細を表示。存在しないIDや数字以外が入力されている場合は赤枠で表示。

// ==UserScript==
// @name         ディレクトリチェック
// @namespace    http://tampermonkey.net/
// @version      2024/12/13
// @description  ディレクトリの検索機能を追加。現在入力されているディレクトリの詳細を表示。存在しないIDや数字以外が入力されている場合は赤枠で表示。
// @license      MIT
// @match        *://plus-nao.com/forests/*/mainedit/*
// @match        *://plus-nao.com/forests/*/registered_mainedit/*
// @grant        GM_addStyle
// ==/UserScript==

(function() {
    'use strict';

    GM_addStyle(`
    .paste-button-directory {
        background-color: #ffffff;
        color: #4CAF50;
        border: 1px solid #4CAF50;
        padding: 3px;
        cursor: pointer;
        font-size: 12px;
        border-radius: 6px;
        transition: background-color 0.2s ease, transform 0.2s ease;
        display: inline-flex;
        align-items: center;
        justify-content: center;
        width: 24px;
        height: 24px;
        margin-left: 5px;
        position: relative;
        vertical-align: middle;
        transform: scale(0.95);
    }

    .paste-button-directory::before {
        content: '📑';
        font-size: 14px;
        display: block;
        position: relative;
        top: -1px;
        left: 1px;
    }

    .paste-button-directory:hover {
        background-color: #4CAF50;
        color: #ffffff;
        transform: scale(0.95) translateY(-2px);
        box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
    }

    .paste-button-directory:active {
        background-color: #388E3C;
        transform: scale(0.95) translateY(0);
        box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
    }

    .paste-button-directory::after {
        content: '';
        position: absolute;
        top: -5px;
        right: -5px;
        bottom: -5px;
        left: -5px;
        z-index: 0;
    }
    .help-icon {
    font-size: 13px;
    margin-left: 0;
    cursor: pointer;
    vertical-align: super;
   }
   .help-icon:hover {
       color: #000;
   }
        `);

    const targetInputSelector1 = 'input[name="data[TbMainproduct][YAHOOディレクトリID]"]';
    const targetInputSelector2 = 'input[name="data[TbMainproduct][NEディレクトリID]"]';

    const popupStyle = `
        position: absolute;
        background-color: #f9f9f9;
        border: 2px solid #ccc;
        border-radius: 5px;
        padding: 4px 10px;
        z-index: 1001;
        display: none;
    `;

    const createPopup = () => {
        const popup = document.createElement('div');
        popup.setAttribute('style', popupStyle);

        const contentDiv = document.createElement('div');
        popup.appendChild(contentDiv);
        document.body.appendChild(popup);
        return { popup, contentDiv };
    };

    const { popup: popup1, contentDiv: contentDiv1 } = createPopup();
    const { popup: popup2, contentDiv: contentDiv2 } = createPopup();

    const checkInputValue = (inputElement, dataMap) => {
        const value = inputElement.value;
        const isNumeric = /^\d+$/.test(value);
        const hasSpaces = /(^\s|\s$)/.test(value);
        const matches = dataMap[value.trim()] || [];

        if (value !== '' && (hasSpaces || !isNumeric || (matches.length === 0 && value !== ''))) {
            inputElement.style.border = '2px solid red';
        } else {
            inputElement.style.border = '';
        }
    };

    const addInputListener = (selector, dataMap, popup, contentDiv) => {
        const inputElement = document.querySelector(selector);
        if (!inputElement) return;

        const updatePopup = (value) => {
            const matches = dataMap[value.trim()] || [];
            if (matches.length > 0) {
                contentDiv.innerHTML = matches.map(description => `<div>${description}</div>`).join('');
                popup.style.display = 'flex';
                const rect = inputElement.getBoundingClientRect();
                const popupHeight = popup.offsetHeight;

                if (selector === targetInputSelector1) {
                    popup.style.top = `${rect.bottom + window.scrollY}px`;
                } else if (selector === targetInputSelector2) {
                    popup.style.top = `${rect.top + window.scrollY - popupHeight}px`;
                }
                popup.style.left = `${rect.left + window.scrollX}px`;
            } else {
                contentDiv.innerHTML = '';
                popup.style.display = 'none';
            }
        };

        checkInputValue(inputElement, dataMap);
        let blurTimeout;

        inputElement.addEventListener('focus', function(event) {
            if (blurTimeout) clearTimeout(blurTimeout);
            updatePopup(event.target.value);
        });

        inputElement.addEventListener('input', function(event) {
            updatePopup(event.target.value);
        });

        inputElement.addEventListener('blur', function(event) {
            blurTimeout = setTimeout(() => {
                popup.style.display = 'none';
            }, 800);
            checkInputValue(event.target, dataMap);
        });

        document.addEventListener('click', function(event) {
            if (!popup.contains(event.target) && !inputElement.contains(event.target)) {
                popup.style.display = 'none';
            }
        });

        document.addEventListener('keydown', function(event) {
            if (event.key === 'Escape') {
                popup.style.display = 'none';
            }
        });
    };

    const openIndexedDB = () => {
        return new Promise((resolve, reject) => {
            const request = indexedDB.open('DirectoryDB', 1);
            request.onupgradeneeded = function(event) {
                const db = event.target.result;
                if (!db.objectStoreNames.contains('directories')) {
                    db.createObjectStore('directories', { keyPath: 'id' });
                }
            };
            request.onsuccess = function(event) {
                resolve(event.target.result);
            };
            request.onerror = function(event) {
                console.error('IndexedDBエラー: ' + event.target.errorCode);
                reject('IndexedDBエラー: ' + event.target.errorCode);
            };
        });
    };

    const getDataFromIndexedDB = (db) => {
        return new Promise((resolve, reject) => {
            const transaction = db.transaction(['directories'], 'readonly');
            const objectStore = transaction.objectStore('directories');
            const request = objectStore.get('directoryData');
            request.onsuccess = function(event) {
                resolve(event.target.result);
            };
            request.onerror = function(event) {
                console.error('IndexedDBからデータ取得中にエラーが発生しました');
                reject('IndexedDBからデータ取得中にエラーが発生しました');
            };
        });
    };

    const saveDataToIndexedDB = (db, data) => {
        return new Promise((resolve, reject) => {
            const transaction = db.transaction(['directories'], 'readwrite');
            const objectStore = transaction.objectStore('directories');

            const request = objectStore.put({ id: 'directoryData', ...data });

            transaction.onerror = (event) => {
                console.error("IndexedDBへのデータ保存中にエラーが発生しました:", event.target.error);
                reject(event.target.error);
            };

            transaction.oncomplete = () => {
                resolve();
            };
        });
    };

    const needsUpdate = (lastUpdated) => {
        const now = Date.now();
        const lastUpdatedDate = new Date(lastUpdated);
        const oneWeek = 7 * 24 * 60 * 60 * 1000;
        const lastUpdateDay = lastUpdatedDate.getDay();
        const currentDay = new Date().getDay();

        const lastUpdateWasMonday = lastUpdateDay === 1;
        const todayIsMonday = currentDay === 1;

        return !lastUpdated || (now - lastUpdated > oneWeek) || !lastUpdateWasMonday || todayIsMonday;
    };

    const fetchAndUpdateData = async (db) => {
        try {
            const response = await fetch('https://nel227.github.io/my-data-repo/directories.json');
            const data = await response.json();

            const lastUpdated = data.lastUpdated;

            await saveDataToIndexedDB(db, { data, lastUpdated });
            return { data, lastUpdated };
        } catch (error) {
            console.error("データの取得または保存中にエラーが発生しました:", error);
            return null;
        }
    };

    const fetchData = async () => {
        try {
            const db = await openIndexedDB();
            let directoryData = await getDataFromIndexedDB(db);

            if (!directoryData || needsUpdate(directoryData.lastUpdated)) {
                directoryData = await fetchAndUpdateData(db);
            }

            if (directoryData && directoryData.data) {
                const yahooDirectory = directoryData.data.YahooDirectory || {};
                const neDirectory = directoryData.data.NEDirectory || {};

                addInputListener(targetInputSelector1, yahooDirectory, popup1, contentDiv1);
                addInputListener(targetInputSelector2, neDirectory, popup2, contentDiv2);
            } else {
                console.error('データが正しく取得されませんでした。デフォルトの空データを使用します。');
                const emptyData = {};
                addInputListener(targetInputSelector1, emptyData, popup1, contentDiv1);
                addInputListener(targetInputSelector2, emptyData, popup2, contentDiv2);
            }
        } catch (error) {
            console.error('データの取得中にエラーが発生しました:', error);
        }
    };

    fetchData();

    const createSearchWindow = () => {
        const searchWindow = document.createElement('div');
        searchWindow.setAttribute('style', `
        position: fixed;
        top: 50%;
        left: 50%;
        width: 85vw;
        height: 90vh;
        background-color: #fff;
        border: 2px solid #ccc;
        border-radius: 5px;
        padding: 2vh;
        z-index: 10001;
        box-shadow: 0 0 10px rgba(0,0,0,0.1);
        display: none;
        overflow: hidden;
        transform: translate(-50%, -50%);
    `);

        const idDisplay = document.createElement('div');
        idDisplay.setAttribute('style', `
        font-size: 14px;
        color: #555;
        padding-top: 8.5vh;
        padding-bottom: 2vh;
    `);

        searchWindow.appendChild(idDisplay);

        const getDetailsById = async (id) => {
            const db = await openIndexedDB();
            const directoryData = await getDataFromIndexedDB(db);

            const neDirectory = directoryData.data.NEDirectory || {};
            const yahooDirectory = directoryData.data.YahooDirectory || {};
            const trimmedId = id.trim();

            for (const [key, descriptions] of Object.entries(neDirectory)) {
                if (key === trimmedId) {
                    return descriptions;
                }
            }
            for (const [key, descriptions] of Object.entries(yahooDirectory)) {
                if (key === trimmedId) {
                    return descriptions;
                }
            }
            return null;
        };

        const updateIdDisplay = async () => {
            const neIdInput = document.querySelector('input[name="data[TbMainproduct][NEディレクトリID]"]');
            const yahooIdInput = document.querySelector('input[name="data[TbMainproduct][YAHOOディレクトリID]"]');

            const neId = neIdInput ? neIdInput.value : '未入力';
            const yahooId = yahooIdInput ? yahooIdInput.value : '未入力';

            const neDetails = await getDetailsById(neId);
            const yahooDetails = await getDetailsById(yahooId);

            const formatDetails = (details, id) => {
                return details ? details.map(path => path.split(' > ').join(' > ')).join('<br>') + ` (ID: ${id})` : '未入力、またはIDが見つかりません。';
            };

            idDisplay.innerHTML = `
        <div class="search-result NE-result" style="border-bottom: 0.5px solid #ddd; padding: 5px; margin-bottom: 5px;">
            NE: ${formatDetails(neDetails, neId)}
        </div>
        <div class="search-result Yahoo-result" style="border-bottom: 0.5px solid #ddd; padding: 5px; margin-bottom: 5px;">
            Ya: ${formatDetails(yahooDetails, yahooId)}
        </div>
    `;
        };

        const addInputListenersNeYahoo = () => {
            const neIdInput = document.querySelector('input[name="data[TbMainproduct][NEディレクトリID]"]');
            const yahooIdInput = document.querySelector('input[name="data[TbMainproduct][YAHOOディレクトリID]"]');

            if (neIdInput) {
                neIdInput.addEventListener('input', updateIdDisplay);
            }
            if (yahooIdInput) {
                yahooIdInput.addEventListener('input', updateIdDisplay);
            }
        };

        const closeButton = document.createElement('button');
        closeButton.textContent = '×';
        closeButton.setAttribute('style', `
        position: absolute;
        top: -5px;
        right: 0;
        cursor: pointer;
        background: transparent;
        color: black;
        border: none;
        font-size: 24px;
        padding: 10px;
        line-height: 1;
        border-radius: 5px;
        z-index: 1001;
        margin: 0;
        display: block;
    `);

        searchWindow.appendChild(closeButton);

        closeButton.addEventListener('click', () => {
            searchWindow.style.display = 'none';
        });

        const lastUpdatedElement = document.createElement('div');
        lastUpdatedElement.id = 'lastUpdated';
        lastUpdatedElement.setAttribute('style', 'margin-bottom: 10px; font-size: 14px; color: #555;');
        searchWindow.appendChild(lastUpdatedElement);

        const searchInput = document.createElement('input');
        searchInput.setAttribute('type', 'text');
        searchInput.setAttribute('placeholder', '検索キーワード (半角スペースでアンド検索)');
        searchInput.setAttribute('id', 'search-input');
        searchInput.setAttribute('style', `
        position: absolute;
        top: 4.5vh;
        left: 50%;
        transform: translateX(-50%);
        width: calc(100% - 50px);
        padding: 1vh;
        border: 1px solid #ccc;
        border-radius: 5px;
        margin-bottom: 1vh;
        background: #fff;
        z-index: 1002;
    `);

        searchInput.addEventListener('input', (event) => {
            const query = event.target.value;
            updateSearchResults(query);

        });

        const filterContainer = document.createElement('div');
        filterContainer.setAttribute('style', `
        position: absolute;
        top: 9.5vh;
        left: 0;
        right: 0;
        display: flex;
        justify-content: space-evenly;
        align-items: center;
        margin-bottom: 1vh;
        z-index: 1001;
        background: #fff;
        padding: 1vh;
    `);

        const neCheckboxLabel = document.createElement('label');
        neCheckboxLabel.setAttribute('style', `
        display: flex;
        align-items: center;
        position: relative;
        cursor: pointer;
        padding: 3px;
    `);

        const neCheckbox = document.createElement('input');
        neCheckbox.setAttribute('type', 'checkbox');
        neCheckbox.setAttribute('id', 'ne-directory-checkbox');
        neCheckbox.setAttribute('style', 'margin-right: 10px;');
        neCheckbox.checked = true;

        const neLabelText = document.createElement('span');
        neLabelText.textContent = 'NEディレクトリを表示';
        neLabelText.setAttribute('style', `
        position: relative;
        transform: translateY(-3px);
    `);

        neCheckboxLabel.appendChild(neCheckbox);
        neCheckboxLabel.appendChild(neLabelText);

        const yahooCheckboxLabel = document.createElement('label');
        yahooCheckboxLabel.setAttribute('style', `
        display: flex;
        align-items: center;
        position: relative;
        cursor: pointer;
        padding: 3px;
    `);

        const yahooCheckbox = document.createElement('input');
        yahooCheckbox.setAttribute('type', 'checkbox');
        yahooCheckbox.setAttribute('id', 'yahoo-directory-checkbox');
        yahooCheckbox.setAttribute('style', 'margin-right: 10px;');
        yahooCheckbox.checked = true;

        const yahooLabelText = document.createElement('span');
        yahooLabelText.textContent = 'Yahooディレクトリを表示';
        yahooLabelText.setAttribute('style', `
        position: relative;
        transform: translateY(-3px);
    `);

        yahooCheckboxLabel.appendChild(yahooCheckbox);
        yahooCheckboxLabel.appendChild(yahooLabelText);

        filterContainer.appendChild(neCheckboxLabel);
        filterContainer.appendChild(yahooCheckboxLabel);

        neCheckbox.addEventListener('change', () => {
            const searchQuery = document.getElementById('search-input').value;
            updateSearchResults(searchQuery);
        });

        yahooCheckbox.addEventListener('change', () => {
            const searchQuery = document.getElementById('search-input').value;
            updateSearchResults(searchQuery);
        });

        const searchResults = document.createElement('div');
        searchResults.setAttribute('style', `
        position: relative;
        max-height: 66vh;
        font-size: 14px;
        top: -1.2vh;
        left: 0;
        right: 0;
        bottom: 0;
        background: #fff;
        overflow-y: auto;
        z-index: 1000;
    `);

        searchWindow.appendChild(closeButton);
        searchWindow.appendChild(searchInput);
        searchWindow.appendChild(filterContainer);
        searchWindow.appendChild(idDisplay);
        searchWindow.appendChild(searchResults);
        document.body.appendChild(searchWindow);

        const applyFilters = () => {
            const neResults = searchResults.querySelectorAll('.NE-result');
            const yahooResults = searchResults.querySelectorAll('.Yahoo-result');
            const neChecked = neCheckbox.checked;
            const yahooChecked = yahooCheckbox.checked;

            let neVisible = false;
            let yahooVisible = false;

            neResults.forEach(result => {
                if (neChecked) {
                    result.style.display = 'block';
                    neVisible = true;
                } else {
                    result.style.display = 'none';
                }
            });

            yahooResults.forEach(result => {
                if (yahooChecked) {
                    result.style.display = 'block';
                    yahooVisible = true;
                } else {
                    result.style.display = 'none';
                }
            });

            const neTitle = searchResults.querySelector('.NE-title');
            if (neTitle) {
                neTitle.style.display = neVisible ? 'block' : 'none';
            }

            const yahooTitle = searchResults.querySelector('.Yahoo-title');
            if (yahooTitle) {
                yahooTitle.style.display = yahooVisible ? 'block' : 'none';
            }
        };

        neCheckbox.addEventListener('change', applyFilters);
        yahooCheckbox.addEventListener('change', applyFilters);

        searchResults.addEventListener('click', (event) => {
            if (event.target.classList.contains('paste-button-directory')) {
                const value = event.target.getAttribute('data-value');
                const directoryType = event.target.getAttribute('data-directory');

                let inputSelector = '';

                if (directoryType === 'yahoo') {
                    inputSelector = 'input[name="data[TbMainproduct][YAHOOディレクトリID]"]';
                } else if (directoryType === 'ne') {
                    inputSelector = 'input[name="data[TbMainproduct][NEディレクトリID]"]';
                }

                const input = document.querySelector(inputSelector);
                if (input) {
                    input.value = value;

                    let message = event.target.nextElementSibling;
                    if (!message || !message.classList.contains('paste-message')) {
                        message = document.createElement('span');
                        message.className = 'paste-message';
                        message.setAttribute('style', `
                        margin-left: 10px;
                        color: green;
                        font-size: 14px;
                        display: inline-block;
                    `);
                        event.target.parentNode.insertBefore(message, event.target.nextSibling);
                    }
                    updateIdDisplay();
                    message.textContent = 'ペーストが完了しました!'

                    setTimeout(() => {
                        message.textContent = '';
                    }, 1700);
                }
            }
        });

        return {
            searchWindow,
            searchInput,
            searchResults,
            applyFilters,
            lastUpdatedElement,
            updateIdDisplay,
            addInputListenersNeYahoo
        };
    };

    const { searchWindow, searchInput, searchResults, applyFilters, lastUpdatedElement, updateIdDisplay, addInputListenersNeYahoo } = createSearchWindow();

    const fetchDataAndUpdateUI = async () => {
        const db = await openIndexedDB();
        const directoryData = await getDataFromIndexedDB(db);

        const tooltip = document.createElement('div');
        tooltip.setAttribute('style', `
        position: absolute;
        background: #333;
        color: #fff;
        padding: 5px 10px;
        border-radius: 5px;
        font-size: 12px;
        display: none;
        z-index: 10002;
    `);

        const lastUpdated = directoryData && directoryData.lastUpdated
        ? new Date(directoryData.lastUpdated).toLocaleString("ja-JP", {
            year: 'numeric',
            month: '2-digit',
            day: '2-digit',
            hour: '2-digit',
            minute: '2-digit'
        })
        : 'データなし';

        tooltip.textContent = `ディレクトリ最終更新日時: ${lastUpdated}`;

        document.body.appendChild(tooltip);

        lastUpdatedElement.textContent = '?';
        lastUpdatedElement.style.fontSize = "12px";
        lastUpdatedElement.style.cursor = "pointer";
        lastUpdatedElement.style.display = 'inline-block';
        lastUpdatedElement.style.width = 'auto';
        lastUpdatedElement.style.height = 'auto';
        lastUpdatedElement.style.padding = '0';

        lastUpdatedElement.addEventListener('click', (event) => {
            const rect = lastUpdatedElement.getBoundingClientRect();
            tooltip.style.top = `${rect.bottom + window.scrollY}px`;
            tooltip.style.left = `${rect.left + window.scrollX}px`;
            tooltip.style.display = 'block';
        });

        document.addEventListener('click', (event) => {
            if (!lastUpdatedElement.contains(event.target) && !tooltip.contains(event.target)) {
                tooltip.style.display = 'none';
            }
        });
    };

    const openSearchWindow = async () => {
        searchWindow.style.display = 'block';
        await fetchDataAndUpdateUI();
        await updateIdDisplay();
        addInputListenersNeYahoo();
    };

    const closeSearchWindow = () => {
        searchWindow.style.display = 'none';
    };

    document.addEventListener('keydown', (event) => {
        if (event.key === 'Escape') {
            closeSearchWindow();
        }
    });

    const performSearch = async (queries, directoryData) => {
        const results = [];
        const lowercaseQueries = queries.map(query => query.toLowerCase().trim());

        for (const [key, descriptions] of Object.entries(directoryData)) {
            descriptions.forEach(description => {
                if (lowercaseQueries.every(query => description.toLowerCase().includes(query))) {
                    results.push({ key, description: description.trim() });
                }
            });
        }

        return results;
    };

    const updateSearchResults = async (query) => {
        try {
            const db = await openIndexedDB();
            const directoryData = await getDataFromIndexedDB(db);

            searchResults.innerHTML = '';

            if (directoryData && directoryData.data) {
                const yahooDirectory = directoryData.data.YahooDirectory || {};
                const neDirectory = directoryData.data.NEDirectory || {};

                const queries = query.split(/\s+/).map(q => q.trim()).filter(q => q.length > 0);
                const excludeQueries = queries.filter(q => q.startsWith('-') && q.length > 1).map(q => q.substring(1));
                const includeQueries = queries.filter(q => !q.startsWith('-'));

                const yahooResults = await performSearch(includeQueries, yahooDirectory);
                const neResults = await performSearch(includeQueries, neDirectory);

                let isFilteringFullData = false;

                const filteredYahooResults = yahooResults.filter(result =>
                                                                 !excludeQueries.some(exclude => result.description.includes(exclude))
                                                                );
                const filteredNeResults = neResults.filter(result =>
                                                           !excludeQueries.some(exclude => result.description.includes(exclude))
                                                          );

                let totalResults = 0;

                const neCheckbox = document.querySelector('#ne-directory-checkbox');
                const yahooCheckbox = document.querySelector('#yahoo-directory-checkbox');

                const createClickablePath = (description, isYahooFiltering, highlightPartIndex = null) => {
                    const pathParts = description.split(' > ');

                    const displayPathParts = isYahooFiltering ? pathParts.slice(1) : pathParts;

                    return displayPathParts.map((part, index) => `
        <span class="path-part" data-part="${part}" data-index="${index}" data-description="${description}"
              style="cursor: pointer; ${highlightPartIndex !== null && index <= highlightPartIndex ? 'color: #007bff; font-weight: bold;' : ''}"
              title="ダブルクリックで絞り込み">
            ${part}
        </span>
        ${index < displayPathParts.length - 1 ? ' > ' : ''}
    `).join('');
                };

                const addPathClickHandlers = () => {
                    const pathParts = searchResults.querySelectorAll('.path-part');
                    pathParts.forEach(part => {
                        part.addEventListener('dblclick', (event) => {
                            const clickedPart = event.target.getAttribute('data-part');
                            const fullDescription = event.target.getAttribute('data-description');
                            const directory = event.target.closest('.search-result').classList.contains('NE-result') ? 'ne' : 'yahoo';
                            let partIndex = parseInt(event.target.getAttribute('data-index'));

                            if (directory === 'yahoo' && !isFilterActive) {
                                partIndex -= 1;
                            }

                            filterResultsByPart(clickedPart, fullDescription, partIndex, directory);
                        });
                    });
                };

                let isFilterActive = false;

                const addClearFilterButton = (parentElement, query) => {
                    const clearFilterButton = document.createElement('button');
                    clearFilterButton.innerText = '解除';
                    clearFilterButton.classList.add('clear-filter-button');
                    clearFilterButton.addEventListener('click', () => {
                        updateSearchResults(query);
                        isFilterActive = false;
                    });

                    clearFilterButton.style.backgroundColor = 'rgba(255, 255, 255, 0)';
                    clearFilterButton.style.border = '1px solid #007bff';
                    clearFilterButton.style.color = '#007bff';
                    clearFilterButton.style.fontSize = '0.85em';
                    clearFilterButton.style.padding = '5px 10px';
                    clearFilterButton.style.cursor = 'pointer';
                    clearFilterButton.style.marginLeft = '15px';
                    clearFilterButton.style.borderRadius = '3px';
                    clearFilterButton.style.transition = 'background-color 0.3s ease, border-color 0.3s ease';

                    clearFilterButton.addEventListener('mouseover', () => {
                        clearFilterButton.style.backgroundColor = 'rgba(230, 230, 250, 0.9)';
                        clearFilterButton.style.borderColor = '#007bff';
                    });
                    clearFilterButton.addEventListener('mouseout', () => {
                        clearFilterButton.style.backgroundColor = 'rgba(255, 255, 255, 0)';
                        clearFilterButton.style.borderColor = '#007bff';
                    });

                    parentElement.appendChild(clearFilterButton);
                    isFilterActive = true;
                };

                const addToggleFilterButton = (parentElement, query, part, fullDescription, partIndex, directory) => {
                    const toggleFilterButton = document.createElement('button');
                    toggleFilterButton.innerText = isFilteringFullData ? '全体から絞り込み中' : '検索結果から絞り込み中';
                    toggleFilterButton.classList.add('toggle-filter-button');

                    toggleFilterButton.style.backgroundColor = 'rgba(255, 255, 255, 0)';
                    toggleFilterButton.style.border = '1px solid #007bff';
                    toggleFilterButton.style.color = '#007bff';
                    toggleFilterButton.style.fontSize = '0.85em';
                    toggleFilterButton.style.padding = '5px 10px';
                    toggleFilterButton.style.cursor = 'pointer';
                    toggleFilterButton.style.marginLeft = '15px';
                    toggleFilterButton.style.borderRadius = '3px';
                    toggleFilterButton.style.transition = 'background-color 0.3s ease, border-color 0.3s ease';

                    toggleFilterButton.addEventListener('mouseover', () => {
                        toggleFilterButton.style.backgroundColor = 'rgba(230, 230, 250, 0.9)';
                        toggleFilterButton.style.borderColor = '#007bff';
                    });
                    toggleFilterButton.addEventListener('mouseout', () => {
                        toggleFilterButton.style.backgroundColor = 'rgba(255, 255, 255, 0)';
                        toggleFilterButton.style.borderColor = '#007bff';
                    });

                    toggleFilterButton.addEventListener('click', () => {
                        isFilteringFullData = !isFilteringFullData;
                        toggleFilterButton.innerText = isFilteringFullData ? '全体から絞り込み中' : '検索結果から絞り込み中';
                        filterResultsByPart(part, fullDescription, partIndex, directory);
                    });

                    parentElement.appendChild(toggleFilterButton);
                };

                const filterResultsByPart = async (part, fullDescription, partIndex, directory) => {
                    const allResults = isFilteringFullData
                    ? (directory === 'ne' ? await performSearch([], neDirectory) : await performSearch([], yahooDirectory))
                    : (directory === 'ne' ? neResults : yahooResults);

                    const pathParts = fullDescription.split(' > ');

                    const filteredPathParts = directory === 'yahoo'
                    ? pathParts.slice(1, parseInt(partIndex) + 2)
                    : pathParts.slice(0, parseInt(partIndex) + 1);

                    const fullPathToMatch = filteredPathParts.join(' > ');

                    const matchingResults = allResults.filter(result => {
                        const resultPathParts = result.description.split(' > ');
                        const adjustedDescription = directory === 'yahoo'
                        ? resultPathParts.slice(1).join(' > ')
                        : result.description;

                        if (directory === 'yahoo') {
                            return adjustedDescription.startsWith(fullPathToMatch);
                        } else {
                            return adjustedDescription.startsWith(fullPathToMatch) &&
                                (adjustedDescription.length === fullPathToMatch.length || adjustedDescription[fullPathToMatch.length] === ' ');
                        }
                    });

                    if (matchingResults.length > 2000) {
                        searchResults.innerHTML = '<div>検索結果が多すぎるため、表示できません。</div>';
                        const messageElement = searchResults.querySelector('div');
                        addClearFilterButton(messageElement, query);
                        return;
                    }

                    searchResults.innerHTML = `
        ${directory === 'ne' && matchingResults.length > 0 ? `
            <div class="sticky-title NE-title">
                NE ディレクトリの検索結果 (${matchingResults.length}件)
            </div>
            ${matchingResults.map(result => `
                <div class="search-result NE-result" style="border-bottom: 0.5px solid #ddd; padding-bottom: 1px; margin-bottom: 1px;">
                    ${createClickablePath(result.description, false, parseInt(partIndex))} (ID: ${result.key})
                    <button class="paste-button-directory" data-value="${result.key}" data-directory="ne"></button>
                </div>
            `).join('')}
        ` : ''}
${directory === 'yahoo' && matchingResults.length > 0 ? `
    <div class="sticky-title Yahoo-title">
        Yahoo ディレクトリの検索結果 (${matchingResults.length}件)
        <span class="help-icon" title="Yahooディレクトリでは、ダブルクリックで絞り込み中、比較がしやすいように一列目が非表示になります。\n一列目をダブルクリックすると、検索結果に影響を与えず、一列目のみを非表示にできます。\nこの機能を使用した結果、ディレクトリの意味がわからなくなるケースがあれば、ご報告ください。">?</span>
    </div>
    ${matchingResults.map(result => `
        <div class="search-result Yahoo-result" style="border-bottom: 0.5px solid #ddd; padding-bottom: 1px; margin-bottom: 1px;">
            ${createClickablePath(result.description, true, parseInt(partIndex))} (ID: ${result.key})
            <button class="paste-button-directory" data-value="${result.key}" data-directory="yahoo"></button>
        </div>
    `).join('')}
` : ''}
`;

                    addPathClickHandlers();

                    const titleElement = document.querySelector(`.${directory === 'ne' ? 'NE-title' : 'Yahoo-title'}`);
                    if (titleElement) {
                        addToggleFilterButton(titleElement, query, part, fullDescription, partIndex, directory);
                        addClearFilterButton(titleElement, query);
                    }
                };

                if (filteredNeResults.length > 0 && neCheckbox && neCheckbox.checked) {
                    totalResults += filteredNeResults.length;
                }

                if (filteredYahooResults.length > 0 && yahooCheckbox && yahooCheckbox.checked) {
                    totalResults += filteredYahooResults.length;
                }

                if (totalResults === 0) {
                    searchResults.innerHTML = '<div>検索結果が見つかりませんでした。</div>';
                } else if (totalResults > 2000) {
                    searchResults.innerHTML = '<div>検索結果が多すぎるため、表示できません。</div>';
                } else {
                    searchResults.innerHTML = `
                ${filteredNeResults.length > 0 && neCheckbox && neCheckbox.checked ? `
                    <div class="sticky-title NE-title">NE ディレクトリの検索結果 (${filteredNeResults.length}件)</div>
                    ${filteredNeResults.map(result => `
                        <div class="search-result NE-result" style="border-bottom: 0.5px solid #ddd; padding-bottom: 1px; margin-bottom: 1px;">
                            ${createClickablePath(result.description)} (ID: ${result.key})
                            <button class="paste-button-directory" data-value="${result.key}" data-directory="ne"></button>
                        </div>
                    `).join('')}` : ''}
${filteredYahooResults.length > 0 && yahooCheckbox && yahooCheckbox.checked ? `
                    <div class="sticky-title Yahoo-title">
        Yahoo ディレクトリの検索結果 (${filteredYahooResults.length}件)
        <span class="help-icon" title="Yahooディレクトリでは、ダブルクリックで絞り込み中、比較がしやすいように一列目が非表示になります。\n一列目をダブルクリックすると、検索結果に影響を与えず、一列目のみを非表示にできます。\nこの機能を使用した結果、ディレクトリの意味がわからなくなるケースがあれば、ご報告ください。">?</span>
    </div>
                    ${filteredYahooResults.map(result => `
                        <div class="search-result Yahoo-result" style="border-bottom: 0.5px solid #ddd; padding-bottom: 1px; margin-bottom: 1px;">
                            ${createClickablePath(result.description)} (ID: ${result.key})
                            <button class="paste-button-directory" data-value="${result.key}" data-directory="yahoo"></button>
                        </div>
                    `).join('')}` : ''}
`;

                    addPathClickHandlers();
                }
            } else {
                searchResults.innerHTML = '<div>データが正しく取得されませんでした。</div>';
            }
        } catch (error) {
            console.error('検索中にエラーが発生しました:', error);
        }
    };

    GM_addStyle(`
    .sticky-title {
        position: sticky;
        top: -10px;
        left: 0;
        width: calc(100% + 0px);
        box-sizing: border-box;
        border-bottom: 1px solid #ddd;
        background: #fff;
        z-index: 1001;
        padding: 0.5em 20px;
        font-weight: normal;
        margin: 0;
        color: #993;
        font-family: 'Gill Sans', 'Lucida Grande', Helvetica, Arial, sans-serif;
        font-size: 150%;
        display: block;
        text-align: center;
    }

.sticky-title::before {
    content: "";
    position: absolute;
    top: 0;
    left: 0;
    width: calc(100% + 10px)
    height: 100%;
    background: #fff;
    z-index: -1;
}
`);

    const inputElement = document.querySelector('input[name="data[TbMainproduct][NEディレクトリID]"]');

    const tdElement = inputElement.closest('td');

    const inputDivs = tdElement.querySelectorAll('div.input.text');

    const containerDiv = document.createElement('div');
    containerDiv.setAttribute('style', `
    position: relative;
`);

    inputDivs.forEach(inputDiv => {
        inputDiv.setAttribute('style', `
    width: calc(100% - 40px);
display: inline-block;
`);
        containerDiv.appendChild(inputDiv);
    });

    const searchButton = document.createElement('button');
    searchButton.textContent = '🔍';

    searchButton.setAttribute('style', `
   position: absolute;
top: 50%;
transform: translateY(-50%);
                      right: 1px;
                      background: rgba(255, 255, 255, 0.1);
color: #ffffff;
border: none;
border-radius: 5px;
padding: 10px;
font-size: 16px;
font-weight: bold;
text-align: center;
cursor: pointer;
z-index: 999;
transition: all 0.3s ease-in-out;
`);

    searchButton.addEventListener('mouseover', () => {
        searchButton.style.background = 'rgba(0, 123, 255, 0.3)';
        searchButton.style.boxShadow = '0 6px 12px rgba(0, 0, 0, 0.2)';
        searchButton.style.transform = 'scale(1.05) translateY(-50%)';
    });

    searchButton.addEventListener('mouseout', () => {
        searchButton.style.background = 'rgba(255, 255, 255, 0)';
        searchButton.style.boxShadow = '0 4px 6px rgba(0, 0, 0, 0)';
        searchButton.style.transform = 'translateY(-50%)';
    });

    searchButton.addEventListener('mousedown', () => {
        searchButton.style.background = 'rgba(0, 123, 255, 0.5)';
        searchButton.style.transform = 'scale(0.95) translateY(-50%)';
    });

    searchButton.addEventListener('mouseup', () => {
        searchButton.style.background = 'rgba(0, 123, 255, 0.3)';
        searchButton.style.transform = 'scale(1.05) translateY(-50%)';
    });

    searchButton.addEventListener('click', (event) => {
        event.preventDefault();
        event.stopPropagation();
        openSearchWindow();
        updateIdDisplay();
    });

    containerDiv.appendChild(searchButton);

    tdElement.innerHTML = '';
    tdElement.appendChild(containerDiv);

})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址