如何设置光标/插入符位置最佳?

36

如果我要向TinyMCE占用的textarea中插入内容,那么设置光标位置的最佳方法是什么?

我正在使用tinyMCE.execCommand("mceInsertRawHTML", false, content);插入内容,并希望将光标位置设置为内容的末尾。

无论是document.selection还是myField.selectionStart都无法解决此问题,我感觉这将由TinyMCE支持(通过我在其论坛上找不到的某些东西)或者是一个非常丑陋的hack。

后来:更好的是; 我刚刚发现,在WordPress中加载TinyMCE时,它会将整个编辑器加载到内嵌的iframe中。

后来 (2): 我可以使用document.getElementById('content_ifr').contentDocument.getSelection();将选择作为字符串获取,但不能将其用于getRangeAt(0)。 一步一步取得进展。


这里有一个更好的答案:https://dev59.com/R1zUa4cB1Zd3GeqP6cZW#7977681 - Rémi Becheras
12个回答

32

在这个问题上我花费了超过15个小时(我很专注),我找到了一个部分解决方案,可以在Firefox和Safari中使用,但在IE中无法使用。目前来说,对我已经足够了,尽管我可能会继续研究它。

解决方案:在当前插入光标位置插入HTML时,最好使用以下函数:

tinyMCE.activeEditor.selection.setContent(htmlcontent);

在Firefox和Safari中,此函数将在WordPress使用的TinyMCE编辑器的iframe中的当前插入光标位置插入内容。在IE 7和8中的问题是,该函数似乎会将内容添加到页面顶部而不是iframe(即完全忽略文本编辑器)。为了解决这个问题,我添加了一个基于此代码的条件语句,将使用此函数代替IE:

tinyMCE.activeEditor.execCommand("mceInsertRawHTML", false, htmlcontent);

然而,第二个函数的问题是,在调用后,插入光标位置被设置为文章区域的开头(没有希望根据浏览器范围等进行调用)。在接近结尾的某处,我发现这个函数能够通过第一个函数来恢复插入内容的结尾处的插入光标位置:

tinyMCE.activeEditor.focus();

此外,它可以将插入光标位置恢复到插入内容的结尾,而无需计算插入文本的长度。缺点是它仅适用于第一个插入函数,在IE 7和IE 8中似乎会引起问题(这可能更多是WordPress的错误而不是TinyMCE的错误)。

我知道这个答案有点啰嗦,如有需要请随时提出澄清问题。


30

首先,你需要在要创建的内容末尾添加一个标签。

var ed = tinyMCE.activeEditor;

//add an empty span with a unique id
var endId = tinymce.DOM.uniqueId();
ed.dom.add(ed.getBody(), 'span', {'id': endId}, '');

//select that span
var newNode = ed.dom.select('span#' + endId);
ed.selection.select(newNode[0]);

这是 TinyMCE 开发人员在编写 Selection.js 时使用的一种策略。阅读底层源代码对解决此类问题非常有帮助。


这个解决方案在默认的WordPress编辑器中没有起作用,所以我在span标签内添加了 :'<span id="caret_pos_holder"> </span>',它起到了帮助作用。谢谢! - Andrius
@Garry Tan,我编辑了这段代码片段,对我来说可以正常工作。谢谢! - Evgeny
2
最好在最后添加<br data-mce-bogus='1' />,而不是 ,因为在保存tinymce时,它不会计算br--bogus,并且您也无法在tinymce中选择空格。 - Vishal Sharma
使用 data-mce-bogus='1' 时,我的 span 会填充零宽度空格,选中后不会显示实心黑色插入符/光标,而是蓝色(Mac)选定空间。我没有使用 BR,因为这样更糟糕。当被脚本选中时,会显示整个蓝色行,所以用户的第一反应是再次点击,以查看插入符/光标,然后再输入。如果您没有单击离开,则最终会在键入时将其删除。我必须找到一种方法来删除填充,否则用户可能会在我的 span 内部输入,而不是在其上输入,并且获取他们的内容被剥离? - sergio
这对我来说非常有效。最终我使用了 tinyMCEPopup.editor 而不是 tinyMCE.activeEditor,因为我在对话框 js 中工作,但我有一种隐隐的感觉它们是可以互换的。 - wolffer-east

16

将光标移动到末尾非常简单,我无法相信其他在线资源上发布的那些糟糕的补丁。Spocke先生的答案一开始并不有用,但最终API文档给了我答案。“选择所有内容然后折叠选择”:

ed.selection.select(ed.getBody(), true); // ed is the editor instance

ed.selection.collapse(false);

希望这能帮助其他人,因为这个线程是谷歌上最先出现的之一。


2
如果最后一个元素是 ul 或 ol,则此操作不会将插入符号放置在其后面。 - rpilkey

8

我发现最好的方法是使用临时id插入内容,然后通过在advlink插件中找到的一些技巧来使其获得焦点。

希望这可以帮助您。

var ed = tinyMCE.activeEditor;

ed.execCommand('mceInsertContent', false, '<a id="_mce_temp_rob" href="http://robubu.com">robubu.com</a>');

//horrible hack to put the cursor in the right spot
ed.focus(); //give the editor focus
ed.selection.select(ed.dom.select('#_mce_temp_rob')[0]); //select the inserted element
ed.selection.collapse(0); //collapses the selection to the end of the range, so the cursor is after the inserted element
ed.dom.setAttrib('_mce_temp_rob', 'id', ''); //remove the temp id

如果您正在从弹出窗口执行此操作,我认为您还需要添加以下内容:
tinyMCEPopup.storeSelection();

在关闭弹出窗口之前。

对于任何试图将来自Wordpress自定义元框的内容插入到TinyMCE编辑器中的人,这是可行的解决方案。我尝试了很多其他脚本,包括本页上的另一个答案Gary Tan提供的方案,当安装自定义TinyMCE按钮时它对我有效,但在这种情况下却不起作用。


当在WordPress的限制范围内工作时,这个很有效。 - Cleanshooter

6
正确的解决方案是使用tinyMCE api tinymce.dom.Selection。 http://www.tinymce.com/wiki.php/API3:class.tinymce.dom.Selection 语法大致如下:
var rng = tinymce.DOM.createRng();  // the range obj
var newNode = editor.dom.select(...    // find the correct selector so that newNode is the element of your editor text
rng.setStart(newNode.firstChild, 0); // 0 is the offset : here it will be at the beginning of the line.
rng.setEnd(newNode.firstChild, 0);
editor.selection.setRng(rng);

这个很好用,我自己也在使用。


2
请问您能否澄清一下...的含义?可以举个例子吗? - Evgeny

4

我正在当前插入HTML内容的插入点(而不停留在该HTML父元素中),如下所示:

editor.insertContent( '<div>My html inserted code here...</div>' + '&nbsp;' , {format: 'html'} );

在结尾处留出空白,这将确保进一步输入的文本被放置在插入的 div 元素之外。


这解决了我的问题,即插入的HTML元素在每个换行符后都重复出现。 - Cerebres

3

因此,以下是我们项目的解决方案。我们的目标是在用户输入时“即时”将(+)/(-)/(?) 字符串更改为相应的图像。

问题在于:每次将字符串更改为图像后,光标都会跳到图像的开头,而不是预期的结尾。

我们添加了大量代码,将光标移到图像的末尾,因此用户可以连续输入而无需中断“跳跃”的光标。

关键部分:不要使用editor.getBody,有一种更优雅的方法来创建临时的span元素,并在必要操作完成后立即删除它。

if (value.match(/\([+?-]\)/)) {
        // set current cursor via span
        editor.selection.setContent('<span id="temp-span"/>');

        // refresh Content with new span
        value = editor.getContent();

        // changing (+)/(-)/(?) to the appropriate image "on the fly"
        value = value.replace('(+)', ' <img src="https://url_here/static/task_manager/img/add.png">&nbsp;');
        value = value.replace('(-)', ' <img src="https://url_here/static/task_manager/img/forbidden.png">&nbsp;');
        value = value.replace('(?)', ' <img src="https://url_here/static/task_manager/img/help_16.png">&nbsp;');
        if (this.state.editor) {
            editor.setContent(value);

            // move cursor to the latter span
            var newNode = editor.dom.select('span#temp-span');
            editor.selection.select(newNode[0]);
            editor.selection.setContent('');
        }
        // and refreshing content again w/o span
        value = editor.getContent();
    }

1
在当前选择/插入符位置插入一个DOM节点。
tinyMCE.activeEditor.selection.setNode(tinyMCE.activeEditor.dom.create('img', {src : 'some.gif', title : 'some title'}));

1

我来发表一下自己的看法。这些回答都没有解决我的问题。我不是在插入HTML元素,而是一段文本(例如[code] [/ code]),需要让光标移动到两者之间。

借鉴了@robyates答案的一部分,我在两个 [code] 标记之间放置了一个临时的HTML元素,将焦点放在它上面,然后完全删除了HTML元素。 这是我的代码:

setup : function(ed) {
    // Add a custom button
    ed.addButton('codeblock', {
        title : 'Code Block',
        text : '[code/]',
        icon: false,
        onclick : function() {
            // Add you own code to execute something on click
            ed.focus();
            //ed.selection.setContent('[code][/code]');
            ed.execCommand('mceInsertContent', false, '[code]<span id="_cursor" />[/code]');

            ed.selection.select(ed.dom.select('#_cursor')[0]); //select the inserted element
            ed.selection.collapse(0); //collapses the selection to the end of the range, so the cursor is after the inserted element
            ed.dom.remove('_cursor'); //remove the element
        }
    });
}

1

我从这里抄袭了这个。

function setCaretTo(obj, pos) { 
    if(obj.createTextRange) { 
        /* Create a TextRange, set the internal pointer to
           a specified position and show the cursor at this
           position
        */ 
        var range = obj.createTextRange(); 
        range.move("character", pos); 
        range.select(); 
    } else if(obj.selectionStart) { 
        /* Gecko is a little bit shorter on that. Simply
           focus the element and set the selection to a
           specified position
        */ 
        obj.focus(); 
        obj.setSelectionRange(pos, pos); 
    } 
} 

2
我认为最大的问题是TinyMCE将内容加载到WordPress的iframe中。通常这样的事情是可行的,但是getSelection()只返回一个字符串而不是选择内容。我正在使用.selectionStart来操作HTML编辑器使用的文本区域,但它对于TinyMCE iframe无效。 - Daniel Bachhuber

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接