textarea 控件添加 Markdown 编辑器常用的几个快捷键

textarea 控件上添加了几个简单的快捷键,以方便编辑博客内容。功能比较简陋,仅实现了最简单的功能。

所有操作均支持通过 Ctrl + z 快捷键撤消。

具体快捷键如下:

  • Ctrl + i :斜体,在选中范围前后插入 * ,再次点击快捷键删除 *
  • Ctrl + b :粗体,在选中范围前后插入 ** ,再次点击删除 **
  • ` :行内代码,在选中范围前后插入 ` 字符,再次点击删除。
  • Tab :缩进,插入四个空格,支持多行。
  • Shift + Tab :取消缩进,整行取消缩进至多 4 个空格,支持多行。
  • Alt + ↑ & Alt + ↓ :上移或下移。
  • Ctrl + / :添加 HTML 备注标签 <!-- -->
$("#Content").on(
    'keydown',
    function (e) {
        const start = this.selectionStart;
        const end = this.selectionEnd;
        if (e.shiftKey && !e.ctrlKey && !e.altKey && e.code == "Tab") {
            // Shift + TAB
            e.preventDefault();

            let strBefore = this.value.slice(0, start);
            let curLineStart = strBefore.includes('\n') ? strBefore.lastIndexOf('\n') : 0;
            let strBetween = this.value.slice(curLineStart, end);
            let newStr = strBetween.replace(/\n {1,4}/g, '\n');
            if (strBetween == newStr) {
                return false;
            }
            let newStart = start;
            let newEnd = end - strBetween.length + newStr.length;

            this.setSelectionRange(curLineStart, end);
            document.execCommand("insertText", false, newStr);
            this.setSelectionRange(newStart, newEnd);
        } else if (!e.shiftKey && !e.ctrlKey && !e.altKey && e.code == "Tab") {
            // TAB
            e.preventDefault();

            if (start === end) {
                document.execCommand('insertText', false, "    ");
            } else {
                let strBefore = this.value.slice(0, start);
                let curLineStart = strBefore.includes('\n') ? strBefore.lastIndexOf('\n') + 1 : 0;
                let strBetween = this.value.slice(curLineStart, end);
                let newStr = "    " + strBetween.replace(/\n/g, '\n    ');
                let lineBreakCount = strBetween.split('\n').length;
                let newStart = start;
                let newEnd = end + lineBreakCount * 4;

                this.setSelectionRange(curLineStart, end);
                document.execCommand("insertText", false, newStr);
                this.setSelectionRange(newStart, newEnd);
            }
        } else if (!e.shiftKey && e.ctrlKey && !e.altKey && e.keyCode === 73) {
            // Ctrl + i
            e.preventDefault();

            if (start === end) {
                document.execCommand('insertText', false, "**");
                this.setSelectionRange(start + 1, start + 1);
            }
            else {
                let strBetween = this.value.slice(start, end);
                if (strBetween.length > 1 && strBetween.indexOf('*') == 0 && strBetween.lastIndexOf('*') == strBetween.length - 1) {
                    let newStr = strBetween.substr(1, strBetween.length - 2);
                    let newStart = start;
                    let newEnd = end - 2;

                    this.setSelectionRange(start, end);
                    document.execCommand("insertText", false, newStr);
                    this.setSelectionRange(newStart, newEnd);
                } else {
                    let newStr = "*" + strBetween + "*";
                    let newStart = start;
                    let newEnd = end + 2;

                    this.setSelectionRange(start, end);
                    document.execCommand("insertText", false, newStr);
                    this.setSelectionRange(newStart, newEnd);
                }
            }
        } else if (!e.shiftKey && e.ctrlKey && !e.altKey && e.keyCode === 66) {
            // Ctrl + B
            e.preventDefault();

            if (start === end) {
                document.execCommand('insertText', false, "****");
                this.setSelectionRange(start + 2, start + 2);
            } else {
                let strBetween = this.value.slice(start, end);
                if (strBetween.length > 1 && strBetween.indexOf('**') == 0 && strBetween.lastIndexOf('**') == strBetween.length - 2) {
                    let newStr = strBetween.substr(2, strBetween.length - 4);
                    let newStart = start;
                    let newEnd = end - 4;

                    this.setSelectionRange(start, end);
                    document.execCommand("insertText", false, newStr);
                    this.setSelectionRange(newStart, newEnd);
                } else {
                    let newStr = "**" + strBetween + "**";
                    let newStart = start;
                    let newEnd = end + 4;

                    this.setSelectionRange(start, end);
                    document.execCommand("insertText", false, newStr);
                    this.setSelectionRange(newStart, newEnd);
                }
            }
        } else if (!e.shiftKey && !e.ctrlKey && !e.altKey && e.keyCode === 192) {
            // '`'
            e.preventDefault();

            if (start === end) {
                document.execCommand('insertText', false, "`");
                this.setSelectionRange(start + 1, start + 1);
            } else {
                let strBetween = this.value.slice(start, end);
                if (strBetween.length > 1 && strBetween.indexOf('`') == 0 && strBetween.lastIndexOf('`') == strBetween.length - 1) {
                    let newStr = strBetween.substr(1, strBetween.length - 2);
                    let newStart = start;
                    let newEnd = end - 2;

                    this.setSelectionRange(start, end);
                    document.execCommand("insertText", false, newStr);
                    this.setSelectionRange(newStart, newEnd);
                } else {
                    let newStr = "`" + strBetween + "`";
                    let newStart = start;
                    let newEnd = end + 2;

                    this.setSelectionRange(start, end);
                    document.execCommand("insertText", false, newStr);
                    this.setSelectionRange(newStart, newEnd);
                }
            }
        } else if (!e.shiftKey && !e.ctrlKey && !e.altKey && e.keyCode === 219) {
            // '['
            e.preventDefault();

            if (start === end) {
                document.execCommand('insertText', false, "[]");
                this.setSelectionRange(start + 1, start + 1);
            } else {
                let strBetween = this.value.slice(start, end);
                let newStr = "[" + strBetween + "]";
                let newStart = start;
                let newEnd = end + 2;

                this.setSelectionRange(start, end);
                document.execCommand("insertText", false, newStr);
                this.setSelectionRange(newStart, newEnd);
            }
        } else if (e.shiftKey && !e.ctrlKey && !e.altKey && e.keyCode === 57) {
            // '('
            e.preventDefault();

            if (start === end) {
                document.execCommand('insertText', false, "()");
                this.setSelectionRange(start + 1, start + 1);
            } else {
                let strBetween = this.value.slice(start, end);
                let newStr = "(" + strBetween + ")";
                let newStart = start;
                let newEnd = end + 2;

                this.setSelectionRange(start, end);
                document.execCommand("insertText", false, newStr);
                this.setSelectionRange(newStart, newEnd);
            }
        } else if (!e.shiftKey && !e.ctrlKey && e.altKey && e.keyCode === 38) {
            // ALT + ↑
            e.preventDefault();

            const isFirstLine = this.value.lastIndexOf('\n', start) === -1;
            if (isFirstLine) {
                return;
            }
            const curAreaStart = isFirstLine ? 0 : this.value.lastIndexOf('\n', start - 1) + 1;
            const isLastLine = this.value.indexOf('\n', end) === -1;
            const curAreaEnd = isLastLine ? this.value.length : this.value.indexOf('\n', end);
            const isNewFirstLine = this.value.lastIndexOf('\n', curAreaStart - 2) === -1;
            const newAreaStart = isNewFirstLine ? 0 : this.value.lastIndexOf('\n', curAreaStart - 2) + 1;
            if (isNewFirstLine) {
                let strBetween = this.value.slice(curAreaStart, curAreaEnd + 1);
                if (isLastLine) {
                    strBetween += "\n";
                }
                this.setSelectionRange(isLastLine ? curAreaStart - 1 : curAreaStart, curAreaEnd + 1);
                document.execCommand('insertText', false, "");
                this.setSelectionRange(newAreaStart, newAreaStart);
                document.execCommand('insertText', false, strBetween);
            } else {
                let strBetween = this.value.slice(curAreaStart - 1, curAreaEnd);
                this.setSelectionRange(curAreaStart - 1, curAreaEnd);
                document.execCommand('insertText', false, "");
                this.setSelectionRange(newAreaStart - 1, newAreaStart - 1);
                document.execCommand('insertText', false, strBetween);
            }

            this.setSelectionRange(newAreaStart + start - curAreaStart, newAreaStart - curAreaStart + end);
        } else if (!e.shiftKey && !e.ctrlKey && e.altKey && e.keyCode === 40) {
            // ALT + ↓
            e.preventDefault();

            const isFirstLine = this.value.lastIndexOf('\n', start) === -1;
            const curAreaStart = isFirstLine ? 0 : this.value.lastIndexOf('\n', start - 1) + 1;
            const isLastLine = this.value.indexOf('\n', end) === -1;
            if (isLastLine) {
                return;
            }
            const curAreaEnd = this.value.indexOf('\n', end);
            const isNewLastLine = this.value.indexOf('\n', curAreaEnd + 1) === -1;
            const newAreaStart = isNewLastLine ? this.value.length : this.value.indexOf('\n', curAreaEnd + 1) + 1;
            if (isNewLastLine) {
                let strBetween = this.value.slice(curAreaStart - 1, curAreaEnd);
                this.setSelectionRange(newAreaStart, newAreaStart);
                document.execCommand('insertText', false, strBetween);
                this.setSelectionRange(curAreaStart - 1, curAreaEnd);
                document.execCommand('insertText', false, "");
                this.setSelectionRange(newAreaStart + start - curAreaEnd, newAreaStart - curAreaEnd + end);
            } else {
                let strBetween = this.value.slice(curAreaStart, curAreaEnd + 1);
                this.setSelectionRange(newAreaStart, newAreaStart);
                document.execCommand('insertText', false, strBetween);
                this.setSelectionRange(curAreaStart, curAreaEnd + 1);
                document.execCommand('insertText', false, "");
                this.setSelectionRange(newAreaStart + start - curAreaEnd - 1, newAreaStart - curAreaEnd - 1 + end);
            }
        } else if (!e.shiftKey && e.ctrlKey && !e.altKey && e.keyCode === 191) {
            // Ctrl + /
            e.preventDefault();

            let curAreaStart = start;
            let curAreaEnd = end;

            if (start === end) {
                const isFirstLine = this.value.lastIndexOf('\n', start) === -1;
                curAreaStart = isFirstLine ? 0 : this.value.lastIndexOf('\n', start - 1) + 1;
                const isLastLine = this.value.indexOf('\n', end) === -1;
                curAreaEnd = isLastLine ? this.value.length + 1 : this.value.indexOf('\n', end);
            }

            let strBetween = this.value.slice(curAreaStart, curAreaEnd);
            console.log(strBetween);
            if (strBetween.indexOf("<!--") === 0 && strBetween.lastIndexOf("-->") === strBetween.length - 3) {
                this.setSelectionRange(curAreaStart, curAreaEnd);
                document.execCommand('insertText', false, strBetween.substr(4, strBetween.length - 7));
                this.setSelectionRange(curAreaStart, curAreaEnd - 7);
            } else {
                this.setSelectionRange(curAreaStart, curAreaEnd);
                document.execCommand('insertText', false, "<!--" + strBetween + "-->");
                this.setSelectionRange(curAreaStart, curAreaEnd + 7);
            }
        } else {
            return true;
        }
    }
)