MediaWiki

MediaWiki:Gadget-CopyTextTool.js

来自卡厄思梦境WIKI

律Rhyme留言 | 贡献2025年10月3日 (五) 20:59的版本

注意:在发布之后,您可能需要清除浏览器缓存才能看到所作出的更改的影响。

  • Firefox或Safari:按住Shift的同时单击刷新,或按Ctrl-F5Ctrl-R(Mac为⌘-R
  • Google Chrome:Ctrl-Shift-R(Mac为⌘-Shift-R
  • Edge:按住Ctrl的同时单击刷新,或按Ctrl-F5
/**
 * 文本复制工具 - 增强版
 * 支持直接插入和包裹选中文字
 */
(function($, mw) {
    'use strict';
    
    // ===== 自定义配置 =====
    var config = {
        '蓝色': {
            template: '{{文本|蓝|{text}}}',
            fallback: '{{文本|蓝|'
        },
        '绿色': {
            template: '{{文本|绿|{text}}}',
            fallback: '{{文本|绿|'
        },
        '蓝色下划线': {
            template: '{{文本|蓝|下划线|{text}}}',
            fallback: '{{文本|蓝|下划线|'
        },
        '绿色描边': {
            template: '{{描边|绿|{text}}}',
            fallback: '{{描边|绿|'
        },
        '词典': {
            template: '{{词典|{text}}}',
            fallback: '{{词典|'
        },
        '红色': {
            template: '{{文本|红|{text}}}',
            fallback: '{{文本|红|'
        },
        '黄色': {
            template: '{{文本|黄|{text}}}',
            fallback: '{{文本|黄|'
        }
    };
    
    // 默认位置配置
    var defaultPosition = {
        top: '100px',
        right: '20px'
    };
    // ====================
    
    var toolPanel = null;
    var floatButton = null;
    var isVisible = false;
    var isMinimized = false;
    var currentMode = 'auto'; // auto, insert, copy
    
    // 页面加载完成后初始化
    $(function() {
        var savedState = localStorage.getItem('copyTextTool_visible');
        isVisible = savedState === 'true';
        
        createFloatButton();
        createToolPanel();
        
        if (isVisible) {
            showTool();
        }
    });
    
    // 创建浮动按钮
    function createFloatButton() {
        floatButton = $('<div>')
            .attr('id', 'copy-tool-fab')
            .attr('title', '文本复制工具')
            .html('📋')
            .css({
                position: 'fixed',
                bottom: '30px',
                right: '30px',
                width: '56px',
                height: '56px',
                borderRadius: '50%',
                background: 'linear-gradient(135deg, #2196F3 0%, #1976D2 100%)',
                color: 'white',
                fontSize: '24px',
                display: 'flex',
                alignItems: 'center',
                justifyContent: 'center',
                cursor: 'pointer',
                boxShadow: '0 4px 12px rgba(33, 150, 243, 0.4)',
                zIndex: 9998,
                transition: 'all 0.3s ease',
                userSelect: 'none'
            })
            .on('click', toggleTool)
            .hover(
                function() {
                    $(this).css({
                        transform: 'scale(1.1)',
                        boxShadow: '0 6px 16px rgba(33, 150, 243, 0.6)'
                    });
                },
                function() {
                    $(this).css({
                        transform: 'scale(1)',
                        boxShadow: '0 4px 12px rgba(33, 150, 243, 0.4)'
                    });
                }
            )
            .appendTo('body');
    }
    
    // 创建工具面板
    function createToolPanel() {
        var savedPos = localStorage.getItem('copyTextTool_position');
        var position = savedPos ? JSON.parse(savedPos) : defaultPosition;
        
        toolPanel = $('<div>')
            .attr('id', 'copy-text-tool')
            .css({
                position: 'fixed',
                top: position.top,
                right: position.right,
                left: position.left || 'auto',
                width: '340px',
                background: 'white',
                border: '2px solid #2196F3',
                borderRadius: '12px',
                boxShadow: '0 8px 24px rgba(0,0,0,0.15)',
                zIndex: 9999,
                display: 'none',
                fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif'
            });
        
        // 创建标题栏
        var header = $('<div>')
            .attr('id', 'tool-header')
            .css({
                background: 'linear-gradient(135deg, #2196F3 0%, #1976D2 100%)',
                color: 'white',
                padding: '12px 15px',
                borderRadius: '10px 10px 0 0',
                cursor: 'move',
                display: 'flex',
                justifyContent: 'space-between',
                alignItems: 'center',
                userSelect: 'none'
            })
            .appendTo(toolPanel);
        
        // 标题
        $('<div>')
            .css({
                fontWeight: 'bold',
                fontSize: '15px',
                display: 'flex',
                alignItems: 'center',
                gap: '8px'
            })
            .html('<span style="font-size: 18px">📋</span> 文本工具')
            .appendTo(header);
        
        // 控制按钮组
        var controls = $('<div>')
            .css({
                display: 'flex',
                gap: '8px'
            })
            .appendTo(header);
        
        // 最小化按钮
        $('<button>')
            .attr('title', '最小化')
            .html('−')
            .css({
                background: 'rgba(255,255,255,0.2)',
                border: 'none',
                color: 'white',
                width: '24px',
                height: '24px',
                borderRadius: '4px',
                cursor: 'pointer',
                fontSize: '16px',
                display: 'flex',
                alignItems: 'center',
                justifyContent: 'center'
            })
            .on('click', minimizeTool)
            .hover(
                function() { $(this).css('background', 'rgba(255,255,255,0.3)'); },
                function() { $(this).css('background', 'rgba(255,255,255,0.2)'); }
            )
            .appendTo(controls);
        
        // 关闭按钮
        $('<button>')
            .attr('title', '关闭')
            .html('×')
            .css({
                background: 'rgba(255,255,255,0.2)',
                border: 'none',
                color: 'white',
                width: '24px',
                height: '24px',
                borderRadius: '4px',
                cursor: 'pointer',
                fontSize: '20px',
                display: 'flex',
                alignItems: 'center',
                justifyContent: 'center'
            })
            .on('click', hideTool)
            .hover(
                function() { $(this).css('background', 'rgba(255,255,255,0.3)'); },
                function() { $(this).css('background', 'rgba(255,255,255,0.2)'); }
            )
            .appendTo(controls);
        
        // 内容区域
        var body = $('<div>')
            .attr('id', 'tool-body')
            .css({
                padding: '15px'
            })
            .appendTo(toolPanel);
        
        // 模式选择
        var modeSelector = $('<div>')
            .css({
                display: 'flex',
                gap: '8px',
                marginBottom: '12px',
                padding: '8px',
                background: '#f5f5f5',
                borderRadius: '6px'
            })
            .appendTo(body);
        
        $('<div>')
            .text('模式:')
            .css({
                fontSize: '13px',
                color: '#666',
                lineHeight: '28px',
                fontWeight: '500'
            })
            .appendTo(modeSelector);
        
        var modes = [
            { id: 'auto', name: '智能', title: '自动检测:有选中文字则插入,否则复制' },
            { id: 'insert', name: '插入', title: '始终插入到编辑器' },
            { id: 'copy', name: '复制', title: '始终复制到剪贴板' }
        ];
        
        var modeButtons = $('<div>')
            .css({
                display: 'flex',
                gap: '4px',
                flex: '1'
            })
            .appendTo(modeSelector);
        
        modes.forEach(function(mode) {
            $('<button>')
                .attr({
                    'data-mode': mode.id,
                    'title': mode.title
                })
                .text(mode.name)
                .css({
                    flex: '1',
                    padding: '6px 8px',
                    background: mode.id === 'auto' ? '#2196F3' : 'white',
                    color: mode.id === 'auto' ? 'white' : '#666',
                    border: '1px solid #ddd',
                    borderRadius: '4px',
                    cursor: 'pointer',
                    fontSize: '12px',
                    transition: 'all 0.2s'
                })
                .on('click', function() {
                    currentMode = mode.id;
                    modeButtons.find('button').css({
                        background: 'white',
                        color: '#666'
                    });
                    $(this).css({
                        background: '#2196F3',
                        color: 'white'
                    });
                    updateStatus('模式:' + mode.name);
                })
                .appendTo(modeButtons);
        });
        
        // 按钮容器
        var buttonContainer = $('<div>')
            .css({
                display: 'grid',
                gridTemplateColumns: 'repeat(2, 1fr)',
                gap: '8px',
                marginBottom: '10px'
            })
            .appendTo(body);
        
        // 添加功能按钮
        $.each(config, function(name, conf) {
            $('<button>')
                .text(name)
                .css({
                    padding: '12px 8px',
                    background: '#2196F3',
                    color: 'white',
                    border: 'none',
                    borderRadius: '6px',
                    cursor: 'pointer',
                    fontSize: '13px',
                    fontWeight: '500',
                    transition: 'all 0.2s ease'
                })
                .on('click', function() {
                    handleButtonClick(name, conf);
                })
                .hover(
                    function() {
                        $(this).css({
                            background: '#1976D2',
                            transform: 'translateY(-1px)',
                            boxShadow: '0 2px 8px rgba(33, 150, 243, 0.3)'
                        });
                    },
                    function() {
                        $(this).css({
                            background: '#2196F3',
                            transform: 'translateY(0)',
                            boxShadow: 'none'
                        });
                    }
                )
                .appendTo(buttonContainer);
        });
        
        // 状态栏
        $('<div>')
            .attr('id', 'copy-status')
            .css({
                textAlign: 'center',
                color: '#666',
                padding: '10px',
                fontSize: '12px',
                minHeight: '20px',
                background: '#f9f9f9',
                borderRadius: '6px'
            })
            .html('💡 <b>智能模式</b>:选中文字后点击按钮即可包裹')
            .appendTo(body);
        
        toolPanel.appendTo('body');
        
        // 使工具面板可拖动
        makeDraggable(toolPanel[0], header[0]);
    }
    
    // 处理按钮点击
    function handleButtonClick(name, conf) {
        var selectedText = getSelectedText();
        var activeElement = document.activeElement;
        var isInEditor = isEditableElement(activeElement);
        
        // 决定使用哪种模式
        var shouldInsert = false;
        if (currentMode === 'insert') {
            shouldInsert = true;
        } else if (currentMode === 'copy') {
            shouldInsert = false;
        } else { // auto
            shouldInsert = isInEditor;
        }
        
        if (shouldInsert) {
            // 插入模式
            if (selectedText) {
                // 有选中文字:包裹模板
                var text = conf.template.replace('{text}', selectedText);
                insertAtCursor(activeElement, text, true);
                updateStatus('✓ 已插入包裹: ' + name, true);
            } else {
                // 无选中文字:插入模板前缀
                insertAtCursor(activeElement, conf.fallback, false);
                updateStatus('✓ 已插入: ' + name, true);
            }
        } else {
            // 复制模式
            var textToCopy = selectedText ? 
                conf.template.replace('{text}', selectedText) : 
                conf.fallback;
            copyToClipboard(textToCopy);
            updateStatus('✓ 已复制: ' + name, true);
        }
    }
    
    // 获取选中的文本
    function getSelectedText() {
        var activeElement = document.activeElement;
        
        // 检查是否在输入框或文本区域中
        if (activeElement && (activeElement.tagName === 'TEXTAREA' || activeElement.tagName === 'INPUT')) {
            var start = activeElement.selectionStart;
            var end = activeElement.selectionEnd;
            if (start !== end) {
                return activeElement.value.substring(start, end);
            }
        }
        
        // 检查页面选中的文本
        var selection = window.getSelection();
        if (selection && selection.toString()) {
            return selection.toString();
        }
        
        return '';
    }
    
    // 检查是否是可编辑元素
    function isEditableElement(element) {
        if (!element) return false;
        
        var tagName = element.tagName;
        if (tagName === 'TEXTAREA') return true;
        if (tagName === 'INPUT' && element.type === 'text') return true;
        if (element.contentEditable === 'true') return true;
        if (element.classList && element.classList.contains('CodeMirror')) return true;
        
        return false;
    }
    
    // 在光标位置插入文本
    function insertAtCursor(element, text, replaceSelection) {
        if (!element) {
            updateStatus('❌ 未找到输入框', false);
            return;
        }
        
        if (element.tagName === 'TEXTAREA' || element.tagName === 'INPUT') {
            var start = element.selectionStart;
            var end = element.selectionEnd;
            var value = element.value;
            
            if (replaceSelection) {
                // 替换选中的文本
                element.value = value.substring(0, start) + text + value.substring(end);
                element.selectionStart = element.selectionEnd = start + text.length;
            } else {
                // 在光标位置插入
                element.value = value.substring(0, start) + text + value.substring(start);
                element.selectionStart = element.selectionEnd = start + text.length;
            }
            
            // 触发输入事件
            var event = new Event('input', { bubbles: true });
            element.dispatchEvent(event);
            
            // 聚焦回编辑器
            element.focus();
        } else if (element.contentEditable === 'true') {
            // 处理可编辑div (如VisualEditor)
            document.execCommand('insertText', false, text);
        } else {
            updateStatus('❌ 不支持的编辑器类型', false);
        }
    }
    
    // 复制到剪贴板
    function copyToClipboard(text) {
        if (navigator.clipboard && navigator.clipboard.writeText) {
            navigator.clipboard.writeText(text).then(function() {
                // 成功
            }).catch(function() {
                fallbackCopy(text);
            });
        } else {
            fallbackCopy(text);
        }
    }
    
    // 备用复制方法
    function fallbackCopy(text) {
        var $temp = $('<textarea>')
            .val(text)
            .css({
                position: 'fixed',
                opacity: 0,
                top: '-9999px'
            })
            .appendTo('body');
        
        $temp[0].select();
        $temp[0].setSelectionRange(0, 99999);
        
        try {
            document.execCommand('copy');
        } catch(err) {
            updateStatus('❌ 复制失败', false);
        }
        
        $temp.remove();
    }
    
    // 更新状态显示
    function updateStatus(message, success) {
        var icon = success ? '✓' : '❌';
        var color = success ? '#2196F3' : '#f44336';
        
        $('#copy-status')
            .html(icon + ' ' + message)
            .css({
                color: color,
                fontWeight: 'bold'
            });
        
        // 3秒后恢复默认提示
        setTimeout(function() {
            $('#copy-status')
                .html('💡 <b>智能模式</b>:选中文字后点击按钮即可包裹')
                .css({
                    color: '#666',
                    fontWeight: 'normal'
                });
        }, 3000);
    }
    
    // 切换工具显示
    function toggleTool() {
        if (isVisible) {
            hideTool();
        } else {
            showTool();
        }
    }
    
    // 显示工具
    function showTool() {
        if (isMinimized) {
            isMinimized = false;
            $('#tool-body').show();
            toolPanel.css('width', '340px');
        }
        toolPanel.fadeIn(200);
        isVisible = true;
        localStorage.setItem('copyTextTool_visible', 'true');
    }
    
    // 隐藏工具
    function hideTool() {
        toolPanel.fadeOut(200);
        isVisible = false;
        localStorage.setItem('copyTextTool_visible', 'false');
    }
    
    // 最小化工具
    function minimizeTool() {
        if (isMinimized) {
            $('#tool-body').slideDown(200);
            toolPanel.css('width', '340px');
            isMinimized = false;
        } else {
            $('#tool-body').slideUp(200);
            toolPanel.css('width', 'auto');
            isMinimized = true;
        }
    }
    
    // 拖动功能
    function makeDraggable(element, handle) {
        var pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0;
        
        handle.onmousedown = dragMouseDown;
        
        function dragMouseDown(e) {
            e = e || window.event;
            e.preventDefault();
            pos3 = e.clientX;
            pos4 = e.clientY;
            document.onmouseup = closeDragElement;
            document.onmousemove = elementDrag;
            $(element).css('transition', 'none');
        }
        
        function elementDrag(e) {
            e = e || window.event;
            e.preventDefault();
            pos1 = pos3 - e.clientX;
            pos2 = pos4 - e.clientY;
            pos3 = e.clientX;
            pos4 = e.clientY;
            
            var newTop = (element.offsetTop - pos2);
            var newLeft = (element.offsetLeft - pos1);
            
            element.style.top = newTop + "px";
            element.style.left = newLeft + "px";
            element.style.right = "auto";
        }
        
        function closeDragElement() {
            document.onmouseup = null;
            document.onmousemove = null;
            
            var position = {
                top: element.style.top,
                left: element.style.left
            };
            localStorage.setItem('copyTextTool_position', JSON.stringify(position));
        }
    }
    
})(jQuery, mediaWiki);