Rangy:插入词(再次)

时间:2014-02-27 18:15:58

标签: javascript dom rangy

我正在尝试创建一个预先输入wysihtml5富文本编辑器的预先输入代码。 基本上,我需要能够插入人物/主题标签引用,如Twitter / Github / Facebook ... do。

我找到了一些人试图实现同样的事情的代码。

http://jsfiddle.net/A9z3D/

这很好用,除了它只对最后一个单词做建议并且有一些错误。我想要一个像Twitter这样的选择框,而不是使用tab键进行简单的“选择切换”。

为此,我试图检测当前输入的单词。

        getCurrentlyTypedWord: function(e) {
            var iframe = this.$("iframe.wysihtml5-sandbox").get(0);
            var sel = rangy.getSelection(iframe);
            var word;
            if (sel.rangeCount > 0 && sel.isCollapsed) {
                console.debug("Rangy: ",sel);
                var initialCaretPositionRange = sel.getRangeAt(0);
                var rangeToExpand = initialCaretPositionRange.cloneRange();
                var newStartOffset = rangeToExpand.startOffset > 0 ? rangeToExpand.startOffset - 1 : 0;
                rangeToExpand.setStart(rangeToExpand.startContainer,newStartOffset);
                sel.setSingleRange(rangeToExpand);
                sel.expand("word", {
                    trim: true,
                    wordOptions: {
                         includeTrailingSpace: true,
                         //wordRegex: /([a-z0-9]+)*/gi
                         wordRegex: /[a-z0-9]+('[a-z0-9]+)*/gi
                        // wordRegex: /([a-z0-9]+)*/gi
                    }
                });
                word = sel.text();
                sel.removeAllRanges();
                sel.setSingleRange(initialCaretPositionRange);
            } else {
                word = "noRange";
            }
            console.debug("WORD=",word);
            return word;

仅在折叠选区时触发此操作。 注意我必须处理起始偏移的向后移动,因为如果插入符号位于单词的末尾(就像大多数情况下用户键入的情况一样),那么展开函数不会扩展到目前输入的字词。

直到现在这个工作非常好,问题是它使用了具有TextRangeModule的Rangy 1.3的alpha版本。问题是我注意到wysihtml5也在一个不同且不兼容的版本(1.2.2)中使用Rangy(rangy.dom的问题可能已被删除)。

由于Rangy使用全局window.rangy变量,我认为无论如何我都必须使用版本1.2.2。

如何仅使用rangy 1.2.2来完成等效的expand功能?

编辑:顺便说一下,除了使用expand功能之外还有其他解决方案吗?我认为修改当前选择并将其恢复为仅知道当前键入的单词是有点奇怪和苛刻。是否有解决方案不涉及选择当前键入的单词?我的意思是,只要我们知道初始插入符号的折叠范围,就会根据范围进行调整吗?

2 个答案:

答案 0 :(得分:3)

  

由于Rangy使用全局window.rangy变量,我认为无论如何我都必须使用版本1.2.2。

读过Rangy的代码之后,我有了直觉,认为在同一页面加载两个版本的Rangy可能是可行的。我做了谷歌搜索,发现我是对的。 Tim Down(Rangy的创造者)用issue report解释了它。他举了这个例子:

<script type="text/javascript" src="/rangy-1.0.1/rangy-core.js"></script>
<script type="text/javascript" src="/rangy-1.0.1/rangy-cssclassapplier.js"></script>

<script type="text/javascript">
    var rangy1 = rangy;
</script>

<script type="text/javascript" src="/rangy-1.1.2/rangy-core.js"></script>
<script type="text/javascript" src="/rangy-1.1.2/rangy-cssclassapplier.js"></script>

因此,您可以加载您的代码所需的Rangy版本。重命名并在代码中使用此名称,然后加载wysihtml5想要的内容并将版本保留为rangy

否则,必须以忠实复制Rangy 1.3所做的方式自己实施expand并非易事。

这是一个非常原始的代码实现,可以将选择扩展到字边界。此代码将被单词开头或结尾的元素所触发。

var word_sep = " ";

function expand() {
    var sel = rangy.getSelection();
    var range = sel.getRangeAt(0);

    var start_node = range.startContainer;
    if (start_node.nodeType === Node.TEXT_NODE) {
        var sep_at = start_node.nodeValue.lastIndexOf(word_sep, range.startOffset);
        range.setStart(start_node, (sep_at !== -1) ? sep_at + 1 : 0);
    }

    var end_node = range.endContainer;
    if (end_node.nodeType === Node.TEXT_NODE) {
        var sep_at = end_node.nodeValue.indexOf(word_sep, range.endOffset);
        range.setEnd(end_node, (sep_at !== -1) ? sep_at : range.endContainer.nodeValue.length);
    }
    sel.setSingleRange(range);
}

这是一个fiddle。这应该在rangy 1.2.2中起作用。 (它甚至可以在没有笨拙的情况下工作。)

答案 1 :(得分:1)

对于那些感兴趣的人,基于@Louis的建议,我制作了这个JsFiddle,显示了一个wysihtml5集成,以了解当前输入的单词。

它不需要使用rangy 1.3中的expand函数,它仍然是alpha版本。

http://jsfiddle.net/zPxSL/2/

$(function () {

    $('#txt').wysihtml5();

    var editor = $('#txt').data("wysihtml5").editor;

    $(".wysihtml5-sandbox").contents().find("body").click(function(e) {
        getCurrentlyTypedWord();
    });
    $(".wysihtml5-sandbox").contents().find("body").keydown(function(e) {
        getCurrentlyTypedWord();
    });

    function getCurrentlyTypedWord() {
        var iframe = this.$("iframe.wysihtml5-sandbox").get(0);
        var sel = rangy.getIframeSelection(iframe);
        var wordSeparator = " ";
        if (sel.rangeCount > 0) {
            var selectedRange = sel.getRangeAt(0);
            var isCollapsed = selectedRange.collapsed;
            var isTextNode = (selectedRange.startContainer.nodeType === Node.TEXT_NODE);
            var isSimpleCaret = (selectedRange.startOffset === selectedRange.endOffset);
            var isSimpleCaretOnTextNode = (isCollapsed && isTextNode && isSimpleCaret);
            // only trigger this behavior when the selection is collapsed on a text node container,
            // and there is an empty selection (this means just a caret)
            // this is definitely the case when an user is typing
            if (isSimpleCaretOnTextNode) {
                var textNode = selectedRange.startContainer;
                var text = textNode.nodeValue;
                var caretIndex = selectedRange.startOffset;
                // Get word begin boundary
                var startSeparatorIndex = text.lastIndexOf(wordSeparator, caretIndex);
                var startWordIndex = (startSeparatorIndex !== -1) ? startSeparatorIndex + 1 : 0;
                // Get word end boundary
                var endSeparatorIndex = text.indexOf(wordSeparator, caretIndex);
                var endWordIndex = (endSeparatorIndex !== -1) ? endSeparatorIndex : text.length
                // Create word range
                var wordRange = selectedRange.cloneRange();
                wordRange.setStart(textNode, startWordIndex);
                wordRange.setEnd(textNode, endWordIndex);
                console.debug("Word range:", wordRange.toString());
                return wordRange;
            }
        }
    }


});