【问题标题】:How to make range offset to work with HTML elements in a multiple line contenteditable div?如何使范围偏移以使用多行内容可编辑 div 中的 HTML 元素?
【发布时间】:2017-10-28 00:38:16
【问题描述】:

我的代码在插入符号定位、内容可编辑 div 和 HTML 标记方面存在一些问题。

我正在努力实现的目标

我想要一个内容可编辑的 div,它允许通过键入某种快捷方式来插入换行符和多个 HTML 标记 - 在我的例子中是双左括号“{{”。

我目前取得的成就

div 允许使用单个 HTML 标记,并且仅适用于单行文本。

问题

1) 当我用回车键换行时,{{ 不再触发标签显示。我假设您必须以某种方式使脚本在创建范围时考虑换行符(节点?)。

2) 如果您已经有一个可见的 HTML 标记,则不能再插入另一个。相反,您会在浏览器的控制台中收到以下错误。

Uncaught DOMException: Failed to execute 'setStart' on 'Range': The offset 56 is larger than the node's length (33).

我注意到范围偏移变为 0(或从 HTML 标记的末尾开始),这可能是这里问题的罪魁祸首。

下面是我目前的代码...

一切都是在键盘或鼠标点击时触发的。

var tw_template_trigger = '{{';
var tw_template_tag = '<span class="tw-template-tag" contenteditable="false"><a href="#" class="tw-template-tag-remove"><i class="tw-icon tw-icon-close"></i></a>Pick a tag</span>';

$('.tw-post-template-content').on( 'keyup mouseup', function() {

    // Basically check if someone typed {{ 
    // if yes, attempt to delete those two characters
    // then paste tag HTML in that position
    if( checkIfTagIsTriggered( this ) && deleteTagTrigger( this ) ) {
        pasteTagAtCaret();
    }   

});


function pasteTagAtCaret(selectPastedContent) {

    // Then add the tag
    var sel, range;
    if (window.getSelection) {
        // IE9 and non-IE
        sel = window.getSelection();
        if (sel.getRangeAt && sel.rangeCount) {
            range = sel.getRangeAt(0);
            range.deleteContents();

            // Range.createContextualFragment() would be useful here but is
            // only relatively recently standardized and is not supported in
            // some browsers (IE9, for one)
            var el = document.createElement("div");
            el.innerHTML = tw_template_tag;
            var frag = document.createDocumentFragment(), node, lastNode;
            while ( (node = el.firstChild) ) {
                lastNode = frag.appendChild(node);
            }
            var firstNode = frag.firstChild;
            range.insertNode(frag);

            // Preserve the selection
            if (lastNode) {
                range = range.cloneRange();
                range.setStartAfter(lastNode);
                range.collapse(true);
                sel.removeAllRanges();
                sel.addRange(range);
            }
        }
    } else if ( (sel = document.selection) && sel.type != "Control") {
        // IE < 9
        var originalRange = sel.createRange();
        originalRange.collapse(true);
        sel.createRange().pasteHTML( tw_template_tag );
    }

}

function checkIfTagIsTriggered(containerEl) {

    var precedingChar = "", sel, range, precedingRange;
    if (window.getSelection) {
        sel = window.getSelection();
        if (sel.rangeCount > 0) {
            range = sel.getRangeAt(0).cloneRange();
            range.collapse(true);
            range.setStart(containerEl, 0);
            precedingChar = range.toString().slice(-2);
        }
    } else if ( (sel = document.selection) && sel.type != "Control") {
        range = sel.createRange();
        precedingRange = range.duplicate();
        precedingRange.moveToElementText(containerEl);
        precedingRange.setEndPoint("EndToStart", range);
        precedingChar = precedingRange.text.slice(-2);
    }

    if( tw_template_trigger == precedingChar )
        return true;

    return false;

}

function deleteTagTrigger(containerEl) {

    var preceding = "",
        sel,
        range,
        precedingRange;
    if (window.getSelection) {
        sel = window.getSelection();
        if (sel.rangeCount > 0) {
            range = sel.getRangeAt(0).cloneRange();
            range.collapse(true);
            range.setStart(containerEl, 0);
            preceding = range.toString();
        }
    } else if ((sel = document.selection) && sel.type != "Control") {
        range = sel.createRange();
        precedingRange = range.duplicate();
        precedingRange.moveToElementText(containerEl);
        precedingRange.setEndPoint("EndToStart", range);
        preceding = precedingRange.text;
    }

    // First Remove {{
    var words = range.toString().trim().split(' '),
    lastWord = words[words.length - 1];

    if (lastWord && lastWord == tw_template_trigger ) {

        /* Find word start and end */
        var wordStart = range.toString().lastIndexOf(lastWord);
        var wordEnd = wordStart + lastWord.length;

        range.setStart(containerEl.firstChild, wordStart);
        range.setEnd(containerEl.firstChild, wordEnd);

        range.deleteContents();
        range.insertNode(document.createTextNode(' '));
        // delete That specific word and replace if with resultValue

        return true;

    }

    return false;

}

我注意到这两行导致第二期的浏览器错误

range.setStart(containerEl.firstChild, wordStart);
range.setEnd(containerEl.firstChild, wordEnd);

理论上,我知道问题出在哪里。我相信这两个问题都可以通过使范围创建脚本使用父节点而不是子节点以及循环遍历换行符所在的文本节点来解决。但是,我现在不知道如何实现它。

你能指出我正确的方向吗?

编辑

我实际上已经设法上传了一个演示到目前为止的进度,以使其更加清晰。

Demo

【问题讨论】:

    标签: javascript html range contenteditable caret


    【解决方案1】:

    我自己解决了这个问题并将所有功能合并为一个。整洁的!下面是最终代码。经过进一步考虑,我删除了按 Enter 键的功能。

    希望对某人有所帮助

        var tw_template_trigger = '{{';
        var tw_template_tag = '<span class="tw-template-tag" contenteditable="false">Pick a tag</span>';
    
        $(".tw-post-template-content").keypress(function(e){ return e.which != 13; });
    
        $('.tw-post-template-content').on( 'keyup mouseup', function() {
            triggerTag( this ); 
        });
    
        function triggerTag(containerEl) {
    
            var sel,
                range,
                text;
    
            if (window.getSelection) {
                sel = window.getSelection();
                if (sel.rangeCount > 0) {
                    range = sel.getRangeAt(0).cloneRange(); // clone current range into another variable for manipulation#
                    range.collapse(true);
                    range.setStart(containerEl, 0);
                    text = range.toString();
                }
            }
    
            if( text && text.slice(-2) == tw_template_trigger ) {
                range.setStart( range.endContainer, range.endOffset - tw_template_trigger.length);
                range.setEnd( range.endContainer, range.endOffset );
                range.deleteContents();
                range.insertNode(document.createTextNode(' '));
    
                //
    
                var el = document.createElement("div");
                el.innerHTML = tw_template_tag;
                var frag = document.createDocumentFragment(), node, lastNode;
                while ( (node = el.firstChild) ) {
                    lastNode = frag.appendChild(node);
                }
                var firstNode = frag.firstChild;
                range.insertNode(frag);
    
                // Preserve the selection
                if (lastNode) {
                    range = range.cloneRange();
                    range.setStartAfter(lastNode);
                    range.collapse(true);
                    sel.removeAllRanges();
                    sel.addRange(range);
                }
    
                return true;
            }
    
            return false;
    
        }
    

    【讨论】:

    • 非常感谢,对我来说这个答案是迄今为止最令人满意的堆栈溢出问题。
    猜你喜欢
    • 1970-01-01
    • 2021-03-09
    • 1970-01-01
    • 1970-01-01
    • 2013-10-03
    • 2010-11-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多