G00DB0Y / Cello Extract with One click Copy and Scrollable Table

// ==UserScript==
// @name         Cello Extract with One click Copy and Scrollable Table
// @namespace    http://tampermonkey.net/
// @version      5.1
// @description  Display all rows with a star (*) on the second page in a scrollable table format. Copy functionality ensures data from the Short Form column is pasted into a single Excel cell per index. Scan the entire page for Gene Name after SeqID:, and place each Gene Name in its respective row, adding ">" before each Gene Name without space. Adds notification on successful copy and fixed title on the table.
// @author       Faisal Ahmed
// @license      MIT
// @match        http://cello.life.nctu.edu.tw/
// @match        http://cello.life.nctu.edu.tw/cgi/main.cgi
// @grant        none
// ==/UserScript==

(function () {
    'use strict';

    // Function to show a brief notification for 1 second
    function showNotification(message) {
        let notification = document.createElement('div');
        notification.innerText = message;
        notification.style.position = 'fixed';
        notification.style.top = '50px';
        notification.style.right = '10px';
        notification.style.backgroundColor = 'green';
        notification.style.color = 'white';
        notification.style.padding = '10px';
        notification.style.borderRadius = '5px';
        notification.style.zIndex = '9999';

        document.body.appendChild(notification);

        // Remove the notification after 1 second
        setTimeout(() => {
            notification.remove();
        }, 1000);
    }

    // Mapping of full forms to short forms with Unicode superscript "a"
    const fullToShortForm = {
        "Extracellular": "Exᵃ",
        "Cytoplasmic": "Cyᵃ",
        "Lysosomal": "Lyᵃ",
        "Vacuole": "Vᵃ",
        "Chloroplast": "Chᵃ",
        "Nuclear": "Nuᵃ",
        "Golgi": "Gᵃ",
        "ER": "ERᵃ",
        "Mitochondrial": "Mᵃ",
        "InnerMembrane": "IMᵃ",
        "OuterMembrane": "OMᵃ",
        "Periplasmic": "Periᵃ",
        "PlasmaMembrane": "Pmᵃ"
    };

    // Function to scan the entire page and extract all Gene Names after "SeqID:"
    function extractAllGeneNames() {
        let pageText = document.body.innerText.split('\n'); // Split the entire page text into lines
        console.log("Page text for scanning Gene Names:", pageText);

        let geneNames = [];

        // Look for all lines that contain "SeqID:" and extract the gene names
        for (let line of pageText) {
            if (line.includes("SeqID:")) {
                let geneName = line.split("SeqID:")[1].split("|")[0].trim(); // Extract Gene Name after SeqID: and before |
                console.log("Extracted Gene Name:", geneName);
                geneNames.push(geneName); // Store each extracted Gene Name
            }
        }

        return geneNames; // Return the list of all extracted Gene Names
    }

    function handleSecondPage() {
        let geneNames = extractAllGeneNames(); // Extract all Gene Names from the page

        let lines = document.body.innerText.split('\n'); // Split the page text into individual lines
        let sequences = []; // Array to store sequences
        let currentSequence = []; // Store the current sequence being processed

        // Search for all lines, grouping them by sequence (assuming empty lines separate sequences)
        lines.forEach((line) => {
            if (line.trim() === "") {
                if (currentSequence.length > 0) {
                    sequences.push(currentSequence); // Push current sequence to the sequences array
                    currentSequence = []; // Reset current sequence
                }
            } else {
                currentSequence.push(line.trim()); // Add line to current sequence
            }
        });

        // Handle last sequence if not empty
        if (currentSequence.length > 0) {
            sequences.push(currentSequence);
        }

        // Prepare data with star (*) containing lines for each sequence
        let sequenceData = sequences.map((sequence) => {
            return sequence.filter(line => {
                let starCount = (line.match(/\*/g) || []).length; // Count the number of * symbols in the line
                return starCount > 0 && starCount <= 3; // Include lines with 1 to 3 stars only
            });
        }).filter(data => data.length > 0); // Keep only sequences with star-containing lines

        if (sequenceData.length > 0) {
            // Create a scrollable table element
            let tableContainer = document.createElement('div');
            tableContainer.style.position = 'fixed';
            tableContainer.style.top = '10px';
            tableContainer.style.right = '10px';
            tableContainer.style.width = '400px';
            tableContainer.style.height = '300px'; // Limit the table height to 300px
            tableContainer.style.overflowY = 'auto'; // Add vertical scrolling
            tableContainer.style.border = '1px solid #ccc'; // Softer border color
            tableContainer.style.backgroundColor = '#B0B0B0'; // Ash background color
            tableContainer.style.borderRadius = '10px'; // Rounded corners
            tableContainer.style.boxShadow = '0px 4px 12px rgba(0, 0, 0, 0.1)'; // Subtle shadow for depth
            tableContainer.style.zIndex = '9999';
            tableContainer.style.transition = 'all 0.3s ease'; // Smooth transition for interactions

            // Create the title element with rainbow animation
            let title = document.createElement('div');
            title.innerText = 'Made By Faisal Ahmed';
            title.style.position = 'sticky';
            title.style.top = '0';
            title.style.backgroundImage = 'linear-gradient(90deg, red, orange, yellow, green, blue, indigo, violet)';
            title.style.backgroundSize = '200%'; // Make the gradient larger for movement
            title.style.backgroundClip = 'text'; // Apply gradient only to text
            title.style.color = 'transparent'; // Hide the background
            title.style.textAlign = 'center';
            title.style.padding = '5px';
            title.style.fontWeight = 'bold';
            title.style.zIndex = '1000'; // Ensure it stays on top
            title.style.borderBottom = '1px solid #ccc';

            // Add the animation to the title
            title.style.animation = 'rainbowAnimation 5s linear infinite'; // Slower and right-to-left animation

            // Append the title to the table container
            tableContainer.appendChild(title);

            // Create the table inside the scrollable container
            let table = document.createElement('table');
            table.style.width = '100%'; // Make the table take up full width of the container

            // Create table headers with copy buttons
            let headerRow = document.createElement('tr');
            let th1 = document.createElement('th');
            let th2 = document.createElement('th');
            let th3 = document.createElement('th');

            // Create Copy Buttons for each column
            let copyGeneNameButton = document.createElement('button');
            copyGeneNameButton.innerText = 'Copy Gene Name';
            copyGeneNameButton.style.margin = '5px';
            copyGeneNameButton.addEventListener('click', function () {
                copyColumnData(0); // Copy the first column (Gene Name)
            });

            let copyShortFormButton = document.createElement('button');
            copyShortFormButton.innerText = 'Copy Short Form';
            copyShortFormButton.style.margin = '5px';
            copyShortFormButton.addEventListener('click', function () {
                copyColumnData(1); // Copy the second column (Short Form)
            });

            let copyDataButton = document.createElement('button');
            copyDataButton.innerText = 'Copy Data';
            copyDataButton.style.margin = '5px';
            copyDataButton.addEventListener('click', function () {
                copyColumnData(2); // Copy the third column (Data)
            });

            th1.appendChild(copyGeneNameButton);
            th2.appendChild(copyShortFormButton);
            th3.appendChild(copyDataButton);
            headerRow.appendChild(th1);
            headerRow.appendChild(th2);
            headerRow.appendChild(th3);
            table.appendChild(headerRow);

            // Populate the table with sequence-related star data
            sequenceData.forEach((data, index) => {
                let row = document.createElement('tr');

                let geneNameCell = document.createElement('td');
                // Assign each gene name to the respective row (1st gene name -> 1st row, 2nd gene name -> 2nd row)
                let geneName = geneNames[index] || 'N/A'; // Handle cases where there are fewer gene names than rows
                geneNameCell.innerText = `>${geneName}`; // Add ">" directly before the Gene Name without any space
                geneNameCell.style.border = '1px solid black';

                let shortFormCell = document.createElement('td');
                shortFormCell.innerText = generateShortForm(data);  // Create the correct short form for each line in the sequence
                shortFormCell.style.border = '1px solid black';

                let dataCell = document.createElement('td');
                dataCell.innerText = data.join('\n'); // Join multiple lines into a single cell
                dataCell.style.border = '1px solid black';

                row.appendChild(geneNameCell); // Append Gene Name to the first column
                row.appendChild(shortFormCell);
                row.appendChild(dataCell);
                table.appendChild(row);
            });

            // Append the table to the container
            tableContainer.appendChild(table);

            // Append the container to the body
            document.body.appendChild(tableContainer);

            // Function to copy column data to the clipboard
            function copyColumnData(columnIndex) {
                let tableRows = table.querySelectorAll('tr');
                let columnData = Array.from(tableRows).slice(1) // Skip the header row
                    .map(row => row.cells[columnIndex].innerText.replace(/\n/g, ' ')) // Replace newlines with spaces for Excel single-cell entries
                    .join('\n');

                // Create a temporary textarea element to copy the content
                let tempTextarea = document.createElement('textarea');
                tempTextarea.value = columnData;
                document.body.appendChild(tempTextarea);
                tempTextarea.select();
                document.execCommand('copy');
                document.body.removeChild(tempTextarea);

                // Show a brief notification when copied
                showNotification(`${['Gene Name', 'Short Form', 'Data'][columnIndex]} column copied!`);
            }

            // Function to generate a short form using Unicode superscript "a" for each line in the sequence
            function generateShortForm(sequence) {
                return sequence.map(line => {
                    let fullForm = Object.keys(fullToShortForm).find(form => line.includes(form));
                    if (fullForm) {
                        return fullToShortForm[fullForm]; // Return the short form with Unicode superscript
                    }
                    return ''; // If no match is found, return an empty string
                }).join(', '); // Join multiple entries with commas to keep them in a single Excel cell
            }
        } else {
            console.log("No lines with a valid number of stars (*) found.");
        }
    }

    // Handle the second page
    handleSecondPage();

    // CSS for rainbow animation
    const style = document.createElement('style');
    style.innerHTML = `
        @keyframes rainbowAnimation {
            0% { background-position: 200%; }
            100% { background-position: 0%; }
        }
    `;
    document.head.appendChild(style);
})();