如何在contentEditable span中进行纯文本粘贴而不会破坏撤消功能?

11

这是一个非常具体的问题,但我已经有一个解决方案可以将纯文本粘贴到<span contentEditable="true">中,方法是使用一个隐藏的textarea,看起来效果非常好,但它会破坏浏览器的撤销功能。一开始我不担心跨浏览器的解决方案;我只关心Chrome。我采用的方法大致如下:

$('.editable').live('paste', function()
{
    var $this = $(this);

    //more code here to remember caret position, etc

    $('#clipboard').val('').focus(); //put the focus in the hidden textarea so that, when the paste actually occurs, it's auto-sanitized by the textarea

    setTimeout(function() //then this will be executed immediately after the paste actually occurs
    {
        $this.focus();
        document.execCommand('insertHTML', true, $('#clipboard').val());
    });
});

这个方法可以正常工作——无论我复制什么内容,它都会在进入我的contentEditable区域之前被转换为纯文本。但是当我尝试撤销操作时:

  • 第一次撤销只能撤销刚才的粘贴操作。
  • 第二次撤销就会尝试撤销对#clipboard的更改,并将焦点从我的contentEditable中移开。

我已经尝试了各种方法,希望能阻止浏览器尝试撤销对#clipboard的更改,例如在没有使用它时切换display:none,切换readonlydisabled状态,在上面的事件结束时销毁它并在开始时重新创建它,以及其他各种方法,但是似乎都没有起作用。

这是一种糟糕的清理数据的方式吗?这是我真正设法让其正常运行的第一件事情。尝试在粘贴后清理标记没有起作用,因为有些东西(整个HTML文档)在粘贴时会崩溃浏览器,我想避免这种情况。

是否有任何方法可以使#clipboard不可撤销,或者有其他建议如何让它正常工作吗?

编辑

我通过添加以下一行代码,成功改善了某些情况:

$('#clipboard').val('');

execCommand代码行后面。这似乎完全中和了撤销功能:插入符不再离开contentEditable字段,但是没有任何撤销操作。有点进步,但我仍在寻找一个合适的解决方案。


为什么要重复造轮子呢?既然你已经在使用jQuery,为什么不直接使用像markItUp!TinyMCE这样的插件呢? - Matt Ball
@Matt - 两者都不是我要的。markItUp!使用文本区域--没有contentEditable--而TinyMCE允许粘贴各种标记。我不想制作富文本编辑器--事实上恰恰相反。我想制作一个纯文本的contentEditable元素。只需使用一个“文本区域”,对吧?虽然很想这样做,但我需要内容围绕页面中的其他元素换行。 - Ian Henry
2
@kirilloid:并不完全是这样。在<textareas>contenteditable元素中处理选择和更新内容的机制是如此不同,以至于在两者之间切换需要进行完全重写。 - Tim Down
3
我觉得你可能需要创建自己的撤销机制。 - Tim Down
小更新:'live'方法在jQuery 1.7中已被弃用,对于后续版本,请改用"on()"方法。 - ılǝ
显示剩余5条评论
4个回答

1

CodeMirror 1通过在文本粘贴后剥离格式来实现此功能。CodeMirror 2通过实际上有一个不可见的文本区域处理所有内容,并手动呈现文本和光标来实现此功能。

CodeMirror的网站更详细地描述了它的工作原理:http://codemirror.net/internals.html

除此之外,还有CodeMirror源代码。您可以自行决定CodeMirror 1或CodeMirror 2的方法是否更适合您的目的。 :)


0

你试过了吗?

setTimeout(function() //then this will be executed immediately after the paste actually occurs
{
    $this.focus();
    document.execCommand('insertHTML', true, $('#clipboard').val());
    var t = document.body.innerHTML;
    document.execCommand("undo");
    document.body.innerHTML = t;
});

我认为它可以帮助你。 但我认为你必须使用事件对象。不幸的是,由于安全原因可能会出现问题。


0

在onpaste中:

  1. 存储当前选择。

    var sel = window.getSelection();
    var range  = selObj.getRangeAt(0).cloneRange;
    // 在某处存储范围对象。
    
  2. 修改选择对象以指向您的隐藏文本区域。

  3. 设置延迟为0的超时(立即发生粘贴事件后)。

  4. 在超时函数中,从隐藏的文本区域获取数据,然后:

    var sel = window.getSelection();
    sel.removeAllRanges();
    var range = // 从之前恢复范围对象。
    sel.addRange(range);
    
    document.execCommand("insertHTML", false, /* 在此处插入您的文本区域内容 */);
    

现在,如果你想要对实际的HTML内容进行这样的操作,那么你会感到非常困难...


笔误:应该是 cloneRange(),而不是 cloneRange。此外,在某些浏览器中(我记不清具体情况),在 paste 事件中这种方法无法正常工作:有些浏览器在触发 paste 事件后就太晚了,无法重定向粘贴操作。但你可以在按键事件(如 Ctrl-V 等)中可靠地使用它。另一个问题是,“InsertHTML”命令在 IE 中无法正常工作(尽管你可以使用选择 TextRange 的 pasteHTML() 方法)。最后,你可以通过将粘贴操作重定向到另一个可编辑元素并提取其 innerHTML 来获取已粘贴的 HTML。 - Tim Down
此外,在看了问题之后,我怀疑这种技术恰恰是问题提问者已经在做的。 - Tim Down

-2

插入一个<pre contenteditable="true">...</pre>。我记得这正是你想要的。 (不幸的是,我还不能加入评论,但我想这是一种回答的尝试。)


不幸的是,这没有任何区别。我仍然可以将任何内容粘贴到<pre>块中,它看起来就像<span>一样。 - Ian Henry

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