wolai.com Command+Enter 换行插件

在 wolai.com 中添加 Command+Enter 换行功能,类似 VSCode

// ==UserScript==
// @name         wolai.com Command+Enter 换行插件
// @namespace    http://tampermonkey.net/
// @version      1.0
// @description  在 wolai.com 中添加 Command+Enter 换行功能,类似 VSCode
// @author       You
// @match        https://wolai.com/*
// @match        https://*.wolai.com/*
// @license      MIT
// @grant        none
// @run-at       document-idle
// ==/UserScript==

(function() {
    'use strict';

    // 等待页面加载完成
    function waitForElement(selector, callback, timeout = 10000) {
        const startTime = Date.now();
        
        function check() {
            const element = document.querySelector(selector);
            if (element) {
                callback(element);
            } else if (Date.now() - startTime < timeout) {
                setTimeout(check, 100);
            }
        }
        
        check();
    }

    // 将光标移动到行尾并插入换行
    function moveToEndOfLineAndEnter(element) {
        if (element.tagName === 'TEXTAREA' || element.tagName === 'INPUT') {
            // 处理 textarea 和 input 元素
            const start = element.selectionStart;
            const value = element.value;
            
            // 找到当前行的结束位置
            let lineEnd = start;
            while (lineEnd < value.length && value[lineEnd] !== '\n') {
                lineEnd++;
            }
            
            // 将光标移动到行尾并插入换行
            element.selectionStart = element.selectionEnd = lineEnd;
            element.value = value.slice(0, lineEnd) + '\n' + value.slice(lineEnd);
            element.selectionStart = element.selectionEnd = lineEnd + 1;
            
        } else if (element.getAttribute('contenteditable') !== null) {
            // 处理 contenteditable 元素
            const selection = window.getSelection();
            
            if (selection.rangeCount === 0) return;
            
            const range = selection.getRangeAt(0);
            const currentNode = range.startContainer;
            
            // 找到当前行的末尾
            let endNode = currentNode;
            let endOffset = range.startOffset;
            
            // 如果当前节点是文本节点,移动到文本末尾
            if (currentNode.nodeType === Node.TEXT_NODE) {
                endOffset = currentNode.textContent.length;
            } else {
                // 如果是元素节点,找到最后一个子节点
                const walker = document.createTreeWalker(
                    element,
                    NodeFilter.SHOW_TEXT,
                    null,
                    false
                );
                
                // 定位到当前位置
                walker.currentNode = currentNode.nodeType === Node.TEXT_NODE ? currentNode : 
                                   currentNode.firstChild || currentNode;
                
                // 向前遍历直到找到换行或到达容器边界
                let node = walker.currentNode;
                while (node) {
                    if (node.nodeType === Node.TEXT_NODE) {
                        const text = node.textContent;
                        // 检查是否有换行符
                        const newlineIndex = text.indexOf('\n', 
                            node === currentNode ? range.startOffset : 0
                        );
                        
                        if (newlineIndex !== -1) {
                            endNode = node;
                            endOffset = newlineIndex;
                            break;
                        } else {
                            endNode = node;
                            endOffset = text.length;
                        }
                    }
                    
                    // 检查是否遇到块级元素
                    let nextNode = walker.nextNode();
                    if (!nextNode) break;
                    
                    const parentElement = nextNode.parentElement;
                    if (parentElement && window.getComputedStyle(parentElement).display === 'block') {
                        break;
                    }
                    
                    node = nextNode;
                }
            }
            
            // 将光标移动到行尾
            const newRange = document.createRange();
            newRange.setStart(endNode, endOffset);
            newRange.collapse(true);
            
            selection.removeAllRanges();
            selection.addRange(newRange);
            
            // 模拟按下 Enter 键
            const enterEvent = new KeyboardEvent('keydown', {
                key: 'Enter',
                code: 'Enter',
                keyCode: 13,
                which: 13,
                bubbles: true,
                cancelable: true
            });
            
            element.dispatchEvent(enterEvent);
            
            // 如果模拟 Enter 键没有效果,手动插入换行
            setTimeout(() => {
                const currentSelection = window.getSelection();
                if (currentSelection.rangeCount > 0) {
                    const currentRange = currentSelection.getRangeAt(0);
                    
                    // 创建换行
                    const br = document.createElement('br');
                    const div = document.createElement('div');
                    div.appendChild(document.createElement('br'));
                    
                    try {
                        currentRange.insertNode(div);
                        currentRange.setStartAfter(div);
                        currentRange.collapse(true);
                        currentSelection.removeAllRanges();
                        currentSelection.addRange(currentRange);
                    } catch (e) {
                        // 备用方案:插入 br 标签
                        currentRange.insertNode(br);
                        currentRange.setStartAfter(br);
                        currentRange.collapse(true);
                        currentSelection.removeAllRanges();
                        currentSelection.addRange(currentRange);
                    }
                }
            }, 10);
        }
    }

    // 查找可能的编辑器元素
    function findEditorElements() {
        const selectors = [
            '[contenteditable="true"]',
            '[contenteditable]',
            '.editor',
            '.content-editable',
            '.rich-editor',
            '.notion-editor',
            '.wolai-editor',
            'textarea',
            'input[type="text"]',
            '[role="textbox"]',
            '.ql-editor', // Quill editor
            '.CodeMirror', // CodeMirror
            '.monaco-editor' // Monaco editor
        ];

        const elements = [];
        selectors.forEach(selector => {
            const found = document.querySelectorAll(selector);
            elements.push(...Array.from(found));
        });

        return elements;
    }

    // 键盘事件处理
    function handleKeydown(event) {
        // 检查是否是 Command+Enter (Mac) 或 Ctrl+Enter (Windows/Linux)
        const isMac = navigator.platform.toUpperCase().indexOf('MAC') >= 0;
        const commandKey = isMac ? event.metaKey : event.ctrlKey;
        
        if (commandKey && event.key === 'Enter') {
            event.preventDefault();
            event.stopPropagation();
            
            const target = event.target;
            const activeElement = document.activeElement;
            
            // 确保焦点在可编辑元素上
            const editableElement = target.getAttribute('contenteditable') !== null ? target : 
                                   activeElement.getAttribute('contenteditable') !== null ? activeElement : 
                                   target.tagName === 'TEXTAREA' ? target :
                                   activeElement.tagName === 'TEXTAREA' ? activeElement :
                                   target;
            
            if (editableElement) {
                moveToEndOfLineAndEnter(editableElement);
            }
        }
    }

    // 初始化插件
    function init() {
        console.log('wolai.com Command+Enter 换行插件已加载');
        
        // 为整个文档添加键盘事件监听
        document.addEventListener('keydown', handleKeydown, true);
        
        // 动态监听新添加的编辑器元素
        const observer = new MutationObserver(function(mutations) {
            mutations.forEach(function(mutation) {
                if (mutation.type === 'childList') {
                    mutation.addedNodes.forEach(function(node) {
                        if (node.nodeType === Node.ELEMENT_NODE) {
                            // 检查新添加的元素是否包含编辑器
                            const editors = findEditorElements();
                            editors.forEach(editor => {
                                if (!editor.hasAttribute('data-command-enter-enabled')) {
                                    editor.setAttribute('data-command-enter-enabled', 'true');
                                }
                            });
                        }
                    });
                }
            });
        });

        observer.observe(document.body, {
            childList: true,
            subtree: true
        });

        // 标记现有的编辑器元素
        setTimeout(() => {
            const editors = findEditorElements();
            editors.forEach(editor => {
                editor.setAttribute('data-command-enter-enabled', 'true');
            });
            console.log(`找到 ${editors.length} 个编辑器元素`);
        }, 1000);
    }

    // 等待页面完全加载后初始化
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', init);
    } else {
        init();
    }

})();