Long-press copy selection with formatting

드래그 완료 후 선택 영역 위에서 길게 누르면 서식 포함 복사

// ==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();
  });

})();