Mobile Element Selector

모바일 요소 선택기

当前为 2025-04-16 提交的版本,查看 最新版本

// ==UserScript==
// @name         Mobile Element Selector
// @author       ZNJXL
// @version      1.1.3
// @namespace    http://tampermonkey.net/
// @description  모바일 요소 선택기
// @description:en Mobile Element Selector (especially for cromium browsers)
// @match        *://*/*
// @license      MIT
// @grant        GM_setClipboard
// ==/UserScript==

(function() {
    'use strict';

    let selecting = false;
    let selectedEl = null;
    let initialTouchedElement = null;
    let includeSiteName = true;
    let touchStartX = 0, touchStartY = 0;
    let touchMoved = false;
    const moveThreshold = 10;

    // 다국어 텍스트 정의
    const i18n = {
        ko: {
            blockerInfo: "선택된 요소:",
            blockerCopy: "복사",
            blockerToggleSite: (siteNameOn) => `사이트명: ${siteNameOn ? "ON" : "OFF"}`,
            blockerBlock: "미리보기",
            blockerCancel: "취소",
            blockerMode: "차단 모드",
            blockerSelecting: "선택 중...",
            blockerPreview: "미리보기",
            blockerRevert: "되돌리기",
            alertNoElement: "선택된 요소가 없습니다.",
            alertCopySuccess: "✅ 선택자가 복사되었습니다!",
            alertCopyFail: "❌ 클립보드 복사에 실패했습니다.",
            alertDirectCopy: "선택자를 직접 복사하세요:",
        },
        en: {
            blockerInfo: "Selected Element:",
            blockerCopy: "Copy",
            blockerToggleSite: (siteNameOn) => `Site Name: ${siteNameOn ? "ON" : "OFF"}`,
            blockerBlock: "Preview",
            blockerCancel: "Cancel",
            blockerMode: "Block Mode",
            blockerSelecting: "Selecting...",
            blockerPreview: "Preview",
            blockerRevert: "Revert",
            alertNoElement: "No element selected.",
            alertCopySuccess: "✅ Selector copied!",
            alertCopyFail: "❌ Failed to copy to clipboard.",
            alertDirectCopy: "Copy the selector manually:"
        }
    };

    // 언어 감지 및 선택 (기본값은 한국어)
    const lang = navigator.language.startsWith('ko') ? 'ko' : 'en';
    const t = i18n[lang];

    const style = document.createElement('style');
    style.textContent = `
        .mobile-block-ui { /* 스타일 내용은 동일 */ }
        #blocker-slider { /* 스타일 내용은 동일 */ }
        #blocker-slider::-webkit-slider-thumb { /* 스타일 내용은 동일 */ }
        #blocker-slider::-moz-range-thumb { /* 스타일 내용은 동일 */ }
        .selected-element { /* 스타일 내용은 동일 */ }
        #mobile-block-panel { /* 스타일 내용은 동일 */ }
        #mobile-block-toggleBtn { /* 스타일 내용은 동일 */ }
        .mb-btn { /* 스타일 내용은 동일 */ }
        #blocker-info-wrapper { /* 스타일 내용은 동일 */ }
        #blocker-info { /* 스타일 내용은 동일 */ }
    `;
    document.head.appendChild(style);

    const panel = document.createElement('div');
    panel.id = 'mobile-block-panel';
    panel.classList.add('mobile-block-ui', 'ui-ignore');
    panel.innerHTML = `
        <div id="blocker-info-wrapper">
            <span style="font-size: 12px; color: #ccc;">${t.blockerInfo}</span>
            <span id="blocker-info">없음</span>
        </div>
        <input type="range" id="blocker-slider" min="0" max="10" value="0" class="ui-ignore">
        <div class="button-grid">
            <button id="blocker-copy" class="mb-btn ui-ignore">${t.blockerCopy}</button>
            <button id="blocker-toggle-site" class="mb-btn ui-ignore">${t.blockerToggleSite(includeSiteName)}</button>
            <button id="blocker-block" class="mb-btn ui-ignore">${t.blockerBlock}</button>
            <button id="blocker-cancel" class="mb-btn ui-ignore">${t.blockerCancel}</button>
        </div>
    `;
    document.body.appendChild(panel);

    const toggleBtn = document.createElement('button');
    toggleBtn.id = 'mobile-block-toggleBtn';
    toggleBtn.classList.add('mobile-block-ui', 'ui-ignore');
    toggleBtn.textContent = t.blockerMode;
    document.body.appendChild(toggleBtn);

    function setBlockMode(enabled) {
        selecting = enabled;
        toggleBtn.textContent = enabled ? t.blockerSelecting : t.blockerMode;
        toggleBtn.classList.toggle('selecting', enabled);
        panel.style.display = enabled ? 'block' : 'none';
        if (!enabled && selectedEl) {
            selectedEl.classList.remove('selected-element');
            selectedEl = null;
            initialTouchedElement = null;
        }
        panel.querySelector('#blocker-slider').value = 0;
        updateInfo();
    }

    function updateInfo() {
        const infoSpan = panel.querySelector('#blocker-info');
        infoSpan.textContent = selectedEl ? generateSelector(selectedEl) : '없음';
    }

    function generateSelector(el) { /* generateSelector 함수는 그대로 유지 */ }

    const uiExcludeClass = '.ui-ignore';
    document.addEventListener('touchstart', e => {
        if (!selecting || e.target.closest(uiExcludeClass)) return;
        const touch = e.touches[0];
        touchStartX = touch.clientX; touchStartY = touch.clientY; touchMoved = false;
    }, { passive: true });

    document.addEventListener('touchmove', e => {
        if (!selecting || e.target.closest(uiExcludeClass) || !e.touches[0]) return;
        if (!touchMoved) {
            const touch = e.touches[0];
            const dx = touch.clientX - touchStartX, dy = touch.clientY - touchStartY;
            if (Math.sqrt(dx * dx + dy * dy) > moveThreshold) touchMoved = true;
        }
    }, { passive: true });

    document.addEventListener('touchend', e => {
        if (!selecting || e.target.closest(uiExcludeClass)) return;
        if (touchMoved) { touchMoved = false; return; }
        e.preventDefault(); e.stopImmediatePropagation();
        const touch = e.changedTouches[0];
        const targetEl = document.elementFromPoint(touch.clientX, touch.clientY);
        if (!targetEl || targetEl.closest(uiExcludeClass)) return;
        if (selectedEl) selectedEl.classList.remove('selected-element');
        selectedEl = targetEl;
        initialTouchedElement = targetEl;
        selectedEl.classList.add('selected-element');
        panel.querySelector('#blocker-slider').value = 0;
        updateInfo();
    }, { capture: true, passive: false });

    const slider = panel.querySelector('#blocker-slider');
    slider.addEventListener('input', handleSlider);
    function handleSlider(e) { /* handleSlider 함수는 그대로 유지 */ }

    panel.querySelector('#blocker-copy').addEventListener('click', () => {
        if (selectedEl) {
            const fullSelector = generateSelector(selectedEl);
            let finalSelector = "##" + fullSelector;
            if (includeSiteName) finalSelector = location.hostname + finalSelector;
            try {
                GM_setClipboard(finalSelector);
                alert(t.alertCopySuccess + '\n' + finalSelector);
            } catch (err) {
                console.error("클립보드 복사 실패:", err);
                alert(t.alertCopyFail);
                prompt(t.alertDirectCopy, finalSelector);
            }
        } else { alert(t.alertNoElement); }
    });

    panel.querySelector('#blocker-toggle-site').addEventListener('click', () => {
        includeSiteName = !includeSiteName;
        panel.querySelector('#blocker-toggle-site').textContent = t.blockerToggleSite(includeSiteName);
    });

    const blockBtn = panel.querySelector('#blocker-block');
    let isHidden = false;

    blockBtn.textContent = t.blockerPreview;
    blockBtn.addEventListener('click', () => {
        if (!selectedEl) {
            alert(t.alertNoElement);
            return;
        }
        if (!isHidden) {
            selectedEl.dataset._original_display = selectedEl.style.display || '';
            selectedEl.style.display = 'none';
            blockBtn.textContent = t.blockerRevert;
            isHidden = true;
        } else {
            selectedEl.style.display = selectedEl.dataset._original_display || '';
            blockBtn.textContent = t.blockerPreview;
            isHidden = false;
        }
    });

    panel.querySelector('#blocker-cancel').addEventListener('click', () => setBlockMode(false));
    toggleBtn.addEventListener('click', () => setBlockMode(!selecting));

    function makeDraggable(el) { /* makeDraggable 함수는 그대로 유지 */ }

    makeDraggable(panel);
    makeDraggable(toggleBtn);

})();