wolai.com Command+Enter 换行插件

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

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(Tôi đã có Trình quản lý tập lệnh người dùng, hãy cài đặt nó!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

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

})();