Google 연락처 - 조정 가능한 열 너비 및 도구 설명

Google 연락처 테이블 열을 드래그로 크기 조절 가능하게 하고 호버 시 전체 내용 표시

이 스크립트를 설치하려면 Tampermonkey, Greasemonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

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

이 스크립트를 설치하려면 Tampermonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Userscripts와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 유저 스크립트 관리자 확장 프로그램이 필요합니다.

(이미 유저 스크립트 관리자가 설치되어 있습니다. 설치를 진행합니다!)

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

(이미 유저 스타일 관리자가 설치되어 있습니다. 설치를 진행합니다!)

// ==UserScript==
// @name         Google Contacts - Adjustable Column Width with Tooltips
// @name:zh-CN   Google 通讯录 - 可调整列宽与工具提示
// @name:zh-TW   Google 通訊錄 - 可調整欄寬與工具提示
// @name:ja      Google 連絡先 - 調整可能な列幅とツールチップ
// @name:ko      Google 연락처 - 조정 가능한 열 너비 및 도구 설명
// @name:de      Google Kontos - Anpassbare Spaltenbreite mit Tooltips
// @name:fr      Google Contacts - Largeur de colonne adjustable avec info-bulles
// @name:es      Google Contacts - Ancho de columna ajustable con información sobre herramientas
// @name:it      Google Contatti - Larghezza colonna regolabile con suggerimenti
// @name:pt      Google Contacts - Largura de coluna ajustável com dicas de ferramenta
// @name:ru      Google Контакты - Настраиваемая ширина столбца с подсказками
// @name:ar      جهات اتصال Google - عرض عمود قابل للتعديل مع تلميحات أدوات
// @name:nl      Google Contacten - Aanpasbare kolombreedte met knopinfo
// @name:pl      Google Contacts - Regulowana szerokość kolumny z etykietkami narzędzi
// @name:tr      Google Kişiler - Araç İpuçlarıyla Ayarlanabilir Sütun Genişliği
// @name:vi      Google Danh bạ - Chiều rộng cột có thể điều chỉnh với chú thích công cụ
// @name:th      Google รายชื่อติดต่อ - ความกว้างคอลัมน์ปรับได้พร้อมเครื่องมือแนะนำ
// @namespace    http://tampermonkey.net/
// @version      2.2
// @description  Make Google Contacts table columns resizable by dragging and show full content on hover
// @description:zh-CN  通过拖拽使 Google 通讯录表格列宽可调整,悬停时显示完整内容
// @description:zh-TW  透過拖曳使 Google 通訊錄表格欄寬可調整,懸停時顯示完整內容
// @description:ja     Google連絡先のテーブル列をドラッグでサイズ変更可能にし、ホバー時に全文を表示
// @description:ko     Google 연락처 테이블 열을 드래그로 크기 조절 가능하게 하고 호버 시 전체 내용 표시
// @description:de     Google Kontakte-Tabellenspalten durch Ziehen in der Größe anpassbar machen und vollen Inhalt beim Hovern anzeigen
// @description:fr     Rendre les colonnes du tableau Google Contacts redimensionnables par glisser-déposer et afficher le contenu complet au survol
// @description:es     Hacer que las columnas de la tabla de Google Contacts se puedan redimensionar arrastrando y mostrar el contenido completo al pasar el mouse
// @description:it     Rende ridimensionabili le colonne della tabella di Google Contatti trascinando e mostra il contenuto completo al passaggio del mouse
// @description:pt     Tornar as colunas da tabela do Google Contacts redimensionáveis por arrastar e mostrar o conteúdo completo ao passar o mouse
// @description:ru     Сделать столбцы таблицы Google Контакты изменяемыми путем перетаскивания и отображать полное содержимое при наведении
// @description:ar     جعل أعمدة جدول جهات اتصال Google قابلة لتغيير الحجم بالسحب وإظهار المحتوى الكامل عند التمرير
// @description:nl     Maak kolommen van de Google Contacten-tabel aanpasbaar door slepen en toon de volledige inhoud bij het aanwijzen
// @description:pl     Spraw, aby kolumny tabeli Google Contacts można było zmieniać rozmiar przez przeciąganie i wyświetlać pełną zawartość po najechaniu myszką
// @description:tr     Google Kişiler tablo sütunlarını sürükleme ile yeniden boyutlandırılabilir yapın ve üzerine gelindiğinde tam içeriği gösterin
// @description:vi     Làm cho các cột bảng Google Danh bạ có thể thay đổi kích thước bằng cách kéo và hiển thị đầy đủ nội dung khi di chuột qua
// @description:th     ทำให้คอลัมน์ตาราง Google รายชื่อติดต่อปรับขนาดได้ด้วยการลากและแสดงเนื้อหาทั้งหมดเมื่อวางเมาส์
// @author       aspen138
// @match        https://contacts.google.com/*
// @grant        none
// @license      MIT
// @icon         data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGAAAABgCAYAAADimHc4AAAEGklEQVR4Ae2bA5AcXRSF+7dt22bpN8JCPIht21yjsqXYmulCbBbCmY2dFGLbejfvpmaNmVeZzO3ZnK/qrLdf1Tndj7ctxwIAAAAAAAAANe2HLHfgL8sVHKq1TGuX1vmQdoV+NpT/5s7fRglQe82z2tQEbe4pLRWhTvH/3PnfuwC4Am0sd/BEjrHG4v/laxgCaq56Qhs3jU2MivhafE0QAQ02PK+7jwAbF1XxNfnaYcBA6wosNjLWQHxtDNClUSuYZW6qobiNYgDewK/aoFv3PABug9sqBHAHVrBBMRG3lQ/gXvcvGxNTcZshgCswPOYBcJtAQ/SAHhgPxzwAbpPbxlZD8Bs2RETcNvr/YGWxALht9P/BJmIBcNtY/QZ7iAXAbd/X4AnAGIAAMAvCOgBgJYy9IIDdUJwHAJyI4UwYoCoCdUEAlXGoDQVlujoaAAAAAAAAMGQavZPgo3qJfspKsmleoo/2dhxz5VjFwUfV5x32qLeab1fPNdgc8Qr4yTqb1YuNtqs3m+9R33U5qioPuapc6VSiaqXTflcardOfZ7pTqYsrlT6yyjrJNr2b7Kc+ST7aoY1XrCE+paqmnFCvNtka9Z3QZxtsVb90P6FqpSk2PbzSaJs7jRoPGEAPli3jffRdok22NvymlspRvayz0TM+TBC/9znLJpsEEf/lK9r0V5L8ND7H8Hx3vTbkQMxLUj5vf4CfBpMgUvlpiNc+3qXNPl3Y/P6Tb6pP2+1hQ0T0RrM9qnrKTZMQ5tYcQE9b8cKIEfRIgo+Gs9mFlaD1WXsJ8wuF0HwPD8IRh1ArjeZQPNQTpU2kp7TRi7RUMSqt2xHpjthck+7I+ebbtKYE83nALWSCvEwGZhYPzI7tdrT5C0synwfdV5tudVwAPDsyGJRZWx05KOf0+SWI5/lhzBATrxNMn4LGTpztqFJkcPeLPAVsrIm2Wk6h/0R6NWeqWZI6j7lqYIiMKiVcNQqhThJ9bDmBnEVWaao45KjjA+C9I5MAPJnUWb7fn0Lfa4NvhQvgiw57HR/AW833GgVQM4NmWNKE9nZUOPGuptMD4F1UwzXBOtmBdwq9l7OxFk7P1t/k+ACeqLPJdCA+KNv329SbzY1ED7qzHR/AA+5s0wBuSM/7t0cYgIERsjIMQImeZLGxCECI0DEiApCCz3AlA0AANi2QDQBPwB7JADAG+Om4YACYhmpTrwgGgIUYmyoQALYiBALAZpxkANiOlg8ABzLyAeBIUj4AHMrLB4CyFPkAUJglHwBKE+UDQHGu+VbEw96oHkmiPD3RpnU55gpUReAFjQSbupoEUGHwkWiaiFeU+I5ItGlZpAFwZfSHrXcZmoaX9MKH4KcuWkGtq5GEwK+fvqm7owc92TcNTIz711QBAAAAAAAAAABwGzu2xPmLj8HiAAAAAElFTkSuQmCC
// ==/UserScript==

(function() {
    'use strict';

    let isResizing = false;
    let currentColumn = null;
    let startX = 0;
    let startWidth = 0;
    const columnWidths = new Map();

    // Tooltip element
    let tooltip = null;
    let tooltipTimeout = null;

    function createTooltip() {
        if (tooltip) return;

        tooltip = document.createElement('div');
        tooltip.className = 'contacts-tooltip';
        tooltip.style.cssText = `
            position: fixed;
            background: rgba(0, 0, 0, 0.85);
            color: white;
            padding: 8px 12px;
            border-radius: 4px;
            font-size: 13px;
            z-index: 10000;
            pointer-events: none;
            opacity: 0;
            transition: opacity 0.2s;
            max-width: 400px;
            word-wrap: break-word;
            box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
        `;
        document.body.appendChild(tooltip);
    }

    function showTooltip(text, x, y) {
        if (!text || !tooltip) return;

        tooltip.textContent = text;
        tooltip.style.opacity = '0';
        tooltip.style.display = 'block';

        // Position tooltip
        const tooltipRect = tooltip.getBoundingClientRect();
        let left = x + 10;
        let top = y + 10;

        // Adjust if tooltip goes off screen
        if (left + tooltipRect.width > window.innerWidth) {
            left = x - tooltipRect.width - 10;
        }
        if (top + tooltipRect.height > window.innerHeight) {
            top = y - tooltipRect.height - 10;
        }

        tooltip.style.left = left + 'px';
        tooltip.style.top = top + 'px';
        tooltip.style.opacity = '1';
    }

    function hideTooltip() {
        if (tooltip) {
            tooltip.style.opacity = '0';
            setTimeout(() => {
                if (tooltip) tooltip.style.display = 'none';
            }, 200);
        }
        if (tooltipTimeout) {
            clearTimeout(tooltipTimeout);
            tooltipTimeout = null;
        }
    }

    function addTooltipListeners() {
        // Add tooltip to all data cells in .JcPRM columns
        const dataColumns = document.querySelectorAll('.pkxbt > .JcPRM');

        dataColumns.forEach(column => {
            if (column.dataset.tooltipAdded) return;
            column.dataset.tooltipAdded = 'true';

            column.addEventListener('mouseenter', (e) => {
                // Get text content from the column
                const textContent = column.textContent.trim();
                const isOverflowing = column.scrollWidth > column.clientWidth ||
                                     column.scrollHeight > column.clientHeight;

                if (textContent && (isOverflowing || textContent.length > 30)) {
                    tooltipTimeout = setTimeout(() => {
                        showTooltip(textContent, e.clientX, e.clientY);
                    }, 500);
                }
            });

            column.addEventListener('mousemove', (e) => {
                if (tooltip && tooltip.style.opacity === '1') {
                    const left = e.clientX + 10;
                    const top = e.clientY + 10;
                    tooltip.style.left = left + 'px';
                    tooltip.style.top = top + 'px';
                }
            });

            column.addEventListener('mouseleave', () => {
                hideTooltip();
            });
        });

        // Add tooltip to specific data cells
        const dataCells = document.querySelectorAll('.AYDrSb, .phtrWd');
        dataCells.forEach(cell => {
            if (cell.dataset.tooltipCellAdded) return;
            cell.dataset.tooltipCellAdded = 'true';

            cell.addEventListener('mouseenter', (e) => {
                const textContent = cell.textContent.trim();
                const isOverflowing = cell.scrollWidth > cell.clientWidth ||
                                     cell.scrollHeight > cell.clientHeight;

                if (textContent && (isOverflowing || textContent.length > 30)) {
                    tooltipTimeout = setTimeout(() => {
                        showTooltip(textContent, e.clientX, e.clientY);
                    }, 500);
                }
            });

            cell.addEventListener('mousemove', (e) => {
                if (tooltip && tooltip.style.opacity === '1') {
                    const left = e.clientX + 10;
                    const top = e.clientY + 10;
                    tooltip.style.left = left + 'px';
                    tooltip.style.top = top + 'px';
                }
            });

            cell.addEventListener('mouseleave', () => {
                hideTooltip();
            });
        });

        // Add tooltip to contact names in the main button
        const nameElements = document.querySelectorAll('.d5NbRd-EScbFb-JIbuQc');
        nameElements.forEach(nameEl => {
            if (nameEl.dataset.tooltipNameAdded) return;
            nameEl.dataset.tooltipNameAdded = 'true';

            const nameText = nameEl.getAttribute('aria-label');
            if (nameText) {
                nameEl.addEventListener('mouseenter', (e) => {
                    tooltipTimeout = setTimeout(() => {
                        showTooltip(nameText, e.clientX, e.clientY);
                    }, 500);
                });

                nameEl.addEventListener('mousemove', (e) => {
                    if (tooltip && tooltip.style.opacity === '1') {
                        const left = e.clientX + 10;
                        const top = e.clientY + 10;
                        tooltip.style.left = left + 'px';
                        tooltip.style.top = top + 'px';
                    }
                });

                nameEl.addEventListener('mouseleave', () => {
                    hideTooltip();
                });
            }
        });
    }

    function addColumnSeparators() {
        // Add separators to header columns
        const headerColumns = document.querySelectorAll('.ca2xib');
        headerColumns.forEach((column, index) => {
            if (column.querySelector('.column-separator')) return;

            const separator = document.createElement('div');
            separator.className = 'column-separator';
            separator.style.cssText = `
                position: absolute;
                right: 0;
                top: 0;
                width: 1px;
                height: 100%;
                background: rgba(0, 0, 0, 0.12);
                pointer-events: none;
                z-index: 1;
            `;
            column.style.position = 'relative';
            column.appendChild(separator);
        });

        // Add separators to data rows
        const dataRows = document.querySelectorAll('.pkxbt');
        dataRows.forEach(row => {
            const dataCells = row.querySelectorAll('.JcPRM');
            dataCells.forEach((cell, index) => {
                if (cell.querySelector('.column-separator')) return;

                const separator = document.createElement('div');
                separator.className = 'column-separator';
                separator.style.cssText = `
                    position: absolute;
                    right: 0;
                    top: 0;
                    width: 1px;
                    height: 100%;
                    background: rgba(0, 0, 0, 0.08);
                    pointer-events: none;
                    z-index: 1;
                `;
                cell.style.position = 'relative';
                cell.appendChild(separator);
            });
        });
    }

    function addResizeHandles() {
        const headerColumns = document.querySelectorAll('.ca2xib');

        headerColumns.forEach((column, index) => {
            if (column.querySelector('.resize-handle')) return;

            const handle = document.createElement('div');
            handle.className = 'resize-handle';
            handle.dataset.columnIndex = index;
            handle.style.cssText = `
                position: absolute;
                right: -2px;
                top: 0;
                width: 8px;
                height: 100%;
                cursor: col-resize;
                z-index: 1000;
                background: transparent;
            `;

            handle.addEventListener('mouseenter', () => {
                handle.style.background = 'rgba(66, 133, 244, 0.2)';
                handle.style.borderRight = '2px solid rgba(66, 133, 244, 0.6)';
            });

            handle.addEventListener('mouseleave', () => {
                if (!isResizing) {
                    handle.style.background = 'transparent';
                    handle.style.borderRight = 'none';
                }
            });

            handle.addEventListener('mousedown', (e) => {
                e.preventDefault();
                e.stopPropagation();
                isResizing = true;
                currentColumn = { element: column, index: index };
                startX = e.pageX;
                startWidth = column.offsetWidth;
                document.body.style.cursor = 'col-resize';
                document.body.style.userSelect = 'none';
                handle.style.background = 'rgba(66, 133, 244, 0.3)';
                handle.style.borderRight = '2px solid rgba(66, 133, 244, 0.8)';
            });

            column.style.position = 'relative';
            column.style.minWidth = '50px';
            column.appendChild(handle);
        });

        applyColumnWidths();
    }

    function applyColumnWidths() {
        columnWidths.forEach((width, index) => {
            // Apply to header
            const headerColumn = document.querySelectorAll('.ca2xib')[index];
            if (headerColumn) {
                headerColumn.style.width = width + 'px';
                headerColumn.style.minWidth = width + 'px';
                headerColumn.style.maxWidth = width + 'px';
                headerColumn.style.flexBasis = width + 'px';
                headerColumn.style.flexGrow = '0';
                headerColumn.style.flexShrink = '0';
            }

            // Apply to all data rows - target .pkxbt > .JcPRM
            const allDataColumns = document.querySelectorAll('.pkxbt > .JcPRM');
            allDataColumns.forEach(dataColumn => {
                const parent = dataColumn.parentElement;
                const siblings = Array.from(parent.children).filter(el => el.classList.contains('JcPRM'));
                const columnIndex = siblings.indexOf(dataColumn);

                if (columnIndex === index) {
                    dataColumn.style.width = width + 'px';
                    dataColumn.style.minWidth = width + 'px';
                    dataColumn.style.maxWidth = width + 'px';
                    dataColumn.style.flexBasis = width + 'px';
                    dataColumn.style.flexGrow = '0';
                    dataColumn.style.flexShrink = '0';
                    dataColumn.style.overflow = 'hidden';
                }
            });

            // Also apply to the checkbox columns
            const checkboxColumns = document.querySelectorAll('.Mqnmfe.JcPRM');
            checkboxColumns.forEach(col => {
                if (index === 0) {
                    col.style.width = width + 'px';
                    col.style.minWidth = width + 'px';
                    col.style.maxWidth = width + 'px';
                    col.style.flexBasis = width + 'px';
                    col.style.flexGrow = '0';
                    col.style.flexShrink = '0';
                }
            });
        });
    }

    function handleMouseMove(e) {
        if (!isResizing || !currentColumn) return;

        const diff = e.pageX - startX;
        const newWidth = Math.max(50, startWidth + diff);

        // Apply to header
        currentColumn.element.style.width = newWidth + 'px';
        currentColumn.element.style.minWidth = newWidth + 'px';
        currentColumn.element.style.maxWidth = newWidth + 'px';
        currentColumn.element.style.flexBasis = newWidth + 'px';
        currentColumn.element.style.flexGrow = '0';
        currentColumn.element.style.flexShrink = '0';

        columnWidths.set(currentColumn.index, newWidth);

        // Apply to all data columns - target .pkxbt > .JcPRM
        const allDataColumns = document.querySelectorAll('.pkxbt > .JcPRM');
        allDataColumns.forEach(dataColumn => {
            const parent = dataColumn.parentElement;
            const siblings = Array.from(parent.children).filter(el => el.classList.contains('JcPRM'));
            const columnIndex = siblings.indexOf(dataColumn);

            if (columnIndex === currentColumn.index) {
                dataColumn.style.width = newWidth + 'px';
                dataColumn.style.minWidth = newWidth + 'px';
                dataColumn.style.maxWidth = newWidth + 'px';
                dataColumn.style.flexBasis = newWidth + 'px';
                dataColumn.style.flexGrow = '0';
                dataColumn.style.flexShrink = '0';
                dataColumn.style.overflow = 'hidden';
            }
        });

        // Apply to checkbox columns if resizing first column
        if (currentColumn.index === 0) {
            const checkboxColumns = document.querySelectorAll('.Mqnmfe.JcPRM');
            checkboxColumns.forEach(col => {
                col.style.width = newWidth + 'px';
                col.style.minWidth = newWidth + 'px';
                col.style.maxWidth = newWidth + 'px';
                col.style.flexBasis = newWidth + 'px';
                col.style.flexGrow = '0';
                col.style.flexShrink = '0';
            });
        }
    }

    function handleMouseUp() {
        if (isResizing) {
            isResizing = false;
            currentColumn = null;
            document.body.style.cursor = '';
            document.body.style.userSelect = '';

            const handles = document.querySelectorAll('.resize-handle');
            handles.forEach(handle => {
                handle.style.background = 'transparent';
                handle.style.borderRight = 'none';
            });
        }
    }

    document.addEventListener('mousemove', handleMouseMove);
    document.addEventListener('mouseup', handleMouseUp);

    const observer = new MutationObserver((mutations) => {
        let shouldUpdate = false;
        mutations.forEach(mutation => {
            if (mutation.addedNodes.length > 0) {
                mutation.addedNodes.forEach(node => {
                    if (node.nodeType === 1 && (
                        node.classList?.contains('XXcuqd') ||
                        node.classList?.contains('pkxbt') ||
                        node.querySelector?.('.pkxbt')
                    )) {
                        shouldUpdate = true;
                    }
                });
            }
        });

        if (shouldUpdate) {
            // addResizeHandles();
            applyColumnWidths();
            addColumnSeparators();
            addTooltipListeners();
        }
    });

    const init = () => {
        const headerRow = document.querySelector('.dFPtBe');
        if (headerRow) {
            createTooltip();
            // addResizeHandles();
            addColumnSeparators();
            applyColumnWidths();
            addTooltipListeners();
            observer.observe(document.body, {
                childList: true,
                subtree: true
            });
        } else {
            setTimeout(init, 500);
        }
    };

    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', init);
    } else {
        init();
    }
})();