您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
드래그 완료 후 선택 영역 위에서 길게 누르면 서식 포함 복사
// ==UserScript== // @name Long-press copy selection with formatting // @namespace http://tampermonkey.net/ // @version 1.0 // @description 드래그 완료 후 선택 영역 위에서 길게 누르면 서식 포함 복사 // @author ChatGPT // @match *://*/* // @grant none // ==/UserScript== (function () { 'use strict'; // 설정값 const LONG_PRESS_MS = 600; // 길게 누름 감지 시간 (밀리초) const MOVE_TOLERANCE = 10; // 허용 이동 거리(px) let pressTimer = null; let startX = 0, startY = 0; let isDragging = false; // 선택된 HTML을 가져오기 function getSelectionHTML() { const sel = window.getSelection(); if (!sel || sel.rangeCount === 0) return null; const container = document.createElement("div"); for (let i = 0; i < sel.rangeCount; i++) { container.appendChild(sel.getRangeAt(i).cloneContents()); } return container.innerHTML; } // 선택된 순수 텍스트 가져오기 function getSelectedText() { const sel = window.getSelection(); return sel && sel.toString().trim() ? sel.toString() : null; } // 길게 누른 좌표가 선택 영역 내부인지 확인 function isPointInSelection(x, y) { const sel = window.getSelection(); if (!sel || sel.rangeCount === 0) return false; const range = sel.getRangeAt(0); const rects = range.getClientRects(); for (const rect of rects) { if (x >= rect.left && x <= rect.right && y >= rect.top && y <= rect.bottom) { return true; } } return false; } // 서식 포함 복사 async function copySelectionWithFormatting() { const text = getSelectedText(); const html = getSelectionHTML(); if (!text) return; if (navigator.clipboard?.write && window.ClipboardItem) { // 최신 API (HTML + 텍스트 동시 복사) const blobText = new Blob([text], { type: "text/plain" }); const blobHTML = new Blob([html], { type: "text/html" }); await navigator.clipboard.write([ new ClipboardItem({ "text/plain": blobText, "text/html": blobHTML }) ]); } else { // 구형 브라우저 fallback (서식 일부 손실 가능) const ta = document.createElement("textarea"); ta.value = text; ta.style.position = "fixed"; ta.style.left = "-9999px"; document.body.appendChild(ta); ta.select(); document.execCommand("copy"); document.body.removeChild(ta); } } // 길게 누름 시 처리 function handleLongPress(x, y) { if (isDragging) return; const text = getSelectedText(); if (!text) return; if (!isPointInSelection(x, y)) return; if (confirm(`선택한 텍스트(서식 포함)를 클립보드에 복사할까요?\n\n"${text}"`)) { copySelectionWithFormatting() .then(() => alert("복사되었습니다.")) .catch(err => alert("복사 실패: " + err)); } } function startPressTimer(x, y) { if (!getSelectedText()) return; // 선택된 텍스트가 없으면 무시 pressTimer = setTimeout(() => handleLongPress(x, y), LONG_PRESS_MS); } function clearPressTimer() { clearTimeout(pressTimer); pressTimer = null; } // 드래그 감지 document.addEventListener("selectionchange", () => { if (document.getSelection()?.toString()) { isDragging = true; } }); // 터치 이벤트 window.addEventListener("touchstart", e => { if (e.touches.length !== 1) return; const t = e.touches[0]; startX = t.clientX; startY = t.clientY; isDragging = false; startPressTimer(startX, startY); }, { passive: true }); window.addEventListener("touchmove", e => { if (!pressTimer) return; const t = e.touches[0]; if (Math.hypot(t.clientX - startX, t.clientY - startY) > MOVE_TOLERANCE) { clearPressTimer(); } }, { passive: true }); window.addEventListener("touchend", () => { if (isDragging) { isDragging = false; } clearPressTimer(); }); // 마우스 이벤트 window.addEventListener("mousedown", e => { if (e.button !== 0) return; // 왼쪽 버튼만 startX = e.clientX; startY = e.clientY; isDragging = false; startPressTimer(startX, startY); }); window.addEventListener("mousemove", e => { if (!pressTimer) return; if (Math.hypot(e.clientX - startX, e.clientY - startY) > MOVE_TOLERANCE) { clearPressTimer(); } }); window.addEventListener("mouseup", () => { if (isDragging) { isDragging = false; } clearPressTimer(); }); })();