Javascript 如何使范围偏移在多行contenteditable div中使用HTML元素?

Javascript 如何使范围偏移在多行contenteditable div中使用HTML元素?,javascript,html,range,contenteditable,caret,Javascript,Html,Range,Contenteditable,Caret,我的代码中有一些关于插入符号定位、内容可编辑div和HTML标记的问题 我正在努力实现的目标 我希望有一个内容可编辑的div,它允许通过键入某种快捷方式插入换行符和多个HTML标记——在我的例子中是左双括号“{{” 我迄今为止所取得的成就 div允许一个HTML标记,并且只能在一行文本中工作 问题 1) 当我用return键断行时,{{不再触发标记显示。我假设在创建范围时,您必须以某种方式使脚本考虑断行(节点?) 2) 如果已经有一个HTML标记可见,则无法插入另一个HTML标记。相反,在浏览器

我的代码中有一些关于插入符号定位、内容可编辑div和HTML标记的问题

我正在努力实现的目标

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

我迄今为止所取得的成就

div允许一个HTML标记,并且只能在一行文本中工作

问题

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

2) 如果已经有一个HTML标记可见,则无法插入另一个HTML标记。相反,在浏览器控制台中会出现以下错误

Uncaught DOMException:未能在“范围”上执行“setStart”:偏移量56大于节点的长度(33)。

我注意到范围偏移量变为0(或以HTML标记结尾开始),这可能是问题的根源

下面是我到目前为止的代码

一切都会在keyup或mouseclick上触发

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;

}
理论上,我知道问题是什么。我相信这两个问题都可以通过使范围创建脚本使用父节点而不是子节点来解决,并且还可以通过文本节点循环哪些换行符。但是,我现在不知道如何实现它

你能给我指一下正确的方向吗

编辑

事实上,我已经上传了一个演示,其中有到目前为止的进展,以使其更加清晰


我自己解决了这个问题,并将所有函数合并为一个。整洁!下面是最终代码。在进一步考虑之后,我取消了按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;

    }
var-tw_-template_-trigger='{{};
var tw_template_tag='Pick a tag';
$(“.tw发布模板内容”).keypress(函数(e){returne e.which!=13;});
$('.tw发布模板内容')。在('keyup mouseup',function()上{
triggerTag(本);
});
函数触发器标签(containerell){
var sel,
范围
文本;
if(window.getSelection){
sel=window.getSelection();
如果(选择范围计数>0){
range=sel.getRangeAt(0.cloneRange();//将当前范围克隆到另一个变量中进行操作#
范围。塌陷(真);
range.setStart(containerell,0);
text=range.toString();
}
}
if(text&&text.slice(-2)==tw\u模板\u触发器){
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_模板_标记;
var frag=document.createDocumentFragment(),节点,lastNode;
while((node=el.firstChild)){
lastNode=frag.appendChild(节点);
}
var firstNode=frag.firstChild;
range.insertNode(frag);
//保留所选内容
如果(最后一个节点){
range=range.cloneRange();
range.setStartAfter(lastNode);
范围。塌陷(真);
选择removeAllRanges();
选择添加范围(范围);
}
返回true;
}
返回false;
}

非常感谢,对我来说,这个答案是迄今为止最令人满意的堆栈溢出事件。
    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;

    }