飞书表格 - 可拖拽按钮复制单元格纯文本

按钮可拖拽,复制表格的纯文本,提示居中

// ==UserScript==
// @name         飞书表格 - 可拖拽按钮复制单元格纯文本
// @license      GPL License
// @namespace    https://bytedance.com
// @version      1.0
// @description  按钮可拖拽,复制表格的纯文本,提示居中
// @author       Sfly-小飞哥
// @match        *://*.feishu.cn/*
// @match        *://*.larkoffice.com/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=feishu.cn
// @grant        none
// ==/UserScript==

(function () {
    'use strict';

    const BUTTON_ID = 'gcsj-copy-button';
    const STORAGE_KEY = 'gcsj_copy_button_position';

    // 读取上次保存的位置
    const savedPos = JSON.parse(localStorage.getItem(STORAGE_KEY) || '{}');
    const { left, top } = savedPos;

    // 创建按钮
    const button = document.createElement('button');
    button.id = BUTTON_ID;
    button.textContent = '📋 复制文本';
    button.title = '点击复制当前单元格纯文本\n可拖动调整位置';

    Object.assign(button.style, {
        position: 'fixed',
        left: left ? `${left}px` : '10px',
        top: top ? `${top}px` : '10px',
        zIndex: '99999',
        padding: '8px 12px',
        background: '#00b96b',
        color: 'white',
        border: 'none',
        borderRadius: '6px',
        cursor: 'move',
        fontSize: '13px',
        boxShadow: '0 2px 6px rgba(0,0,0,0.1)',
        transition: 'background 0.2s',
        userSelect: 'none'
    });

    // 悬停效果
    button.onmouseover = () => {
        button.style.background = '#009a59';
        button.style.boxShadow = '0 4px 12px rgba(0,0,0,0.2)';
    };
    button.onmouseout = () => {
        button.style.background = '#00b96b';
        button.style.boxShadow = '0 2px 6px rgba(0,0,0,0.1)';
    };

    // === 拖拽逻辑 ===
    let isDragging = false;
    let offsetX, offsetY;

    button.onmousedown = function (e) {
        if (e.target !== button) return;
        isDragging = true;
        offsetX = e.clientX - button.offsetLeft;
        offsetY = e.clientY - button.offsetTop;
        button.style.cursor = 'grabbing';
        button.style.opacity = '0.9';
        button.style.boxShadow = '0 6px 16px rgba(0,0,0,0.2)';
        e.preventDefault();
    };

    document.onmousemove = function (e) {
        if (!isDragging) return;
        const x = e.clientX - offsetX;
        const y = e.clientY - offsetY;
        const maxX = window.innerWidth - button.offsetWidth;
        const maxY = window.innerHeight - button.offsetHeight;
        const boundedX = Math.max(0, Math.min(x, maxX));
        const boundedY = Math.max(0, Math.min(y, maxY));
        button.style.left = `${boundedX}px`;
        button.style.top = `${boundedY}px`;
    };

    document.onmouseup = function () {
        if (isDragging) {
            const pos = {
                left: parseInt(button.style.left),
                top: parseInt(button.style.top)
            };
            localStorage.setItem(STORAGE_KEY, JSON.stringify(pos));
        }
        isDragging = false;
        button.style.cursor = 'move';
        button.style.opacity = '1';
        button.style.boxShadow = '0 2px 6px rgba(0,0,0,0.1)';
    };

    // === 复制功能(纯文本)===
    button.onclick = function (e) {
        if (isDragging) return;

        const el = document.querySelector('.gcsj-func-normal-text');
        if (!el) {
            showToast('❌ 未找到目标元素');
            return;
        }

        const textContent = el.innerText || el.textContent || '';
        const trimmedContent = textContent.trim();

        if (!trimmedContent) {
            showToast('⚠️ 当前单元格无文本内容');
            return;
        }

        // 预览文本(最多显示 60 个字符)
        const preview = trimmedContent.length > 60
            ? trimmedContent.substring(0, 60) + '...'
            : trimmedContent;

        // 复制到剪贴板
        navigator.clipboard.writeText(trimmedContent)
            .then(() => {
                showToast(`✅ 已复制: "${preview}"`);
            })
            .catch(err => {
                const errorMsg = err.message || '权限被拒绝';
                showToast(`❌ 复制失败: ${errorMsg.substring(0, 80)}`);
            });
    };

    // === 居中提示组件 ===
    function showToast(message) {
        const existing = document.getElementById('gcsj-toast');
        if (existing) existing.remove();

        const toast = document.createElement('div');
        toast.id = 'gcsj-toast';
        Object.assign(toast.style, {
            position: 'fixed',
            top: '16px',
            left: '50%',
            transform: 'translateX(-50%)',
            maxWidth: '90%',
            padding: '12px 20px',
            background: '#52c41a',
            color: 'white',
            borderRadius: '6px',
            boxShadow: '0 4px 16px rgba(0,0,0,0.2)',
            fontSize: '14px',
            fontWeight: '500',
            zIndex: '99999',
            pointerEvents: 'none',
            opacity: '0',
            transition: 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)'
        });

        toast.textContent = message;
        document.body.appendChild(toast);

        // 淡入
        setTimeout(() => toast.style.opacity = '1', 100);

        // 淡出并移除
        setTimeout(() => {
            toast.style.opacity = '0';
            setTimeout(() => {
                if (toast.parentElement) document.body.removeChild(toast);
            }, 300);
        }, 2000);
    }

    // === 防重复注入 ===
    if (document.getElementById(BUTTON_ID)) {
        console.log('🟡 按钮已存在');
        return;
    }

    // 添加按钮
    document.body.appendChild(button);
    console.log('🎯 纯文本复制按钮已注入');
})();