在特定位置插入字符但保留标记?

7

更新 #2

进行了更多测试。当我使用伪空间符时,代码看起来运行良好,但正则表达式最终失败。具体而言,以下情况有效:

  1. 选择 a 标签上方或下方的单词
  2. 仅选择直接在 a 标签上方或下方的一行
  3. 选择 a 标签上方/下方多行
  4. 选择任何 a 标签下方的多行

以下情况无效:

  1. 选择 a 标签上方的行/多行,然后选择 a 标签下方的行/多行

当“不起作用”时,将从 DOM 中删除 a 标签空间符。这可能是正则表达式的问题...

基本上,当您选择围绕 a 标签的文本时,它会失败。


更新:

我不需要将每行都包装在一个 p 标签中,我可以使用内联元素,例如 aspanlabel 标签,带有 display:inline-block 和高度+宽度以充当新的行元素(<br />)。这应该使修改代码更容易,因为唯一需要更改的部分是在边界之间获取文本的位置。我只需更改那个部分 selectedText.textContent,就可以检索在边界内的 HTML 而不仅仅是文本。


我正在创建一个 Phonegap,需要用户选择文本。但是,我需要对文本选择进行精细控制,而不能再将整个文本内容放入样式化的 p 标签中。相反,我需要用类似于 <a class="space"></a> 的东西来表示换行,以便可以精确地突出显示正确单词。当我的文本如下时:

<p class="text">This is line one

Line two

Line three
</p>

如果有 .text{ white-space:pre-wrap },下面的代码允许我选择单词,然后用 span 元素包裹文本以显示高亮文本:

$("p").on("copy", highlight);

function highlight() {
    var text = window.getSelection().toString();
    var selection = window.getSelection().getRangeAt(0);
    var selectedText = selection.extractContents();
    var span = $("<span class='highlight'>" + selectedText.textContent + "</span>");
    selection.insertNode(span[0]);
    if (selectedText.childNodes[1] != undefined) {
        $(selectedText.childNodes[1]).remove();
    }
    var txt = $('.text').html();
    $('.text').html(txt.replace(/<\/span>(?:\s)*<span class="highlight">/g, ''));
    $(".output ul").html("");
    $(".text .highlight").each(function () {
        $(".output ul").append("<li>" + $(this).text() + "</li>");
    });
    clearSelection();
}

function clearSelection() {
    if (document.selection) {
        document.selection.empty();
    } else if (window.getSelection) {
        window.getSelection().removeAllRanges();
    }
}

这段代码非常好用,但是当每行之间由一个间隔标签分隔时就无法正常工作。新的文本看起来像这样:

<p class="text">
    Line one
    <a class="space"></a>
    Line two
    <a class="space"></a>
    Line three
</p>

当我修改上面的代码以便让换行用<a class="space"></a> 表示时,代码就失败了。它只检索文本选择部分的文本,而不是HTML (selectedText.textContent)。我不确定正则表达式是否也会用一个 a 元素作为新行而失败。这个 a 元素可以是一个 spanlabel, 或者任何通常内联定位的元素,以欺骗iOS允许我选择字母而不是块元素。
有没有办法改变代码以保留新行元素呢?

jsFiddle: http://jsfiddle.net/charlescarver/39TZ9/3/

期望输出应该像这样:

如果高亮显示文本“第一行”:

<p class="text">
    <span class="highlight">Line one</span>
    <a class="space"></a>
    Line two
    <a class="space"></a>
    Line three
</p>

如果高亮显示文本“Line one Line two”:
<p class="text">
    <span class="highlight">Line one
    <a class="space"></a>
    Line two</span>
    <a class="space"></a>
    Line three
</p>

当然,不仅可以突出显示整行文本,还可以突出显示不同部分和单个字母。

这不是问题,因为它被插入为双引号。我只使用单引号,这样我就不必在双引号内转义双引号。 - Charlie
我更新了一个例子。 - Charlie
1
修订版 http://fiddle.jshell.net/s5UnH/1/ - charlietfl
1
每次单击“复制”按钮都会删除书框,因此只会留下一个。在其他“highlight”类中解决书框的问题可能会变得有些棘手,但我认为使用jQuery创建逻辑不太难。 - charlietfl
其实我并没有计划做更多的事情...覆盖所有场景并重构代码以使其更紧凑需要相当长的时间。 - charlietfl
显示剩余15条评论
2个回答

0

这里有一个解决方案,支持您所有需求的功能:

HTML

<p class="text">
    First Line
    <a class="space"></a>
    <a class="space"></a>
    Second Line
    <span class="space"></span>
    Third Line
    <label class="space"></label>
    Forth Line
</p>
<ul class="output"></ul>

CSS

.space {
    display: inline-block;
    width: 100%;
}
.highlighting {
    background-color: green;
}

JavaScript

var text,
    output,
    unwrapContents,
    mergeElements,
    clearSelection,
    clearHighlighting,
    mergeHighlighting,
    handleCopy;

unwrapContents = function unwrapContents(element) {
    while(element.firstChild !== null) {
        element.parentNode.insertBefore(element.firstChild, element);
    }
    element.parentNode.removeChild(element);
};

mergeElements = function mergeElements(startElement, endElement) {
    var currentElement;
    endElement = endElement.nextSibling;
    while((currentElement = startElement.nextSibling) !== endElement) {
        startElement.appendChild(currentElement);
    }
};

clearSelection = function clearSelection() {
    if (document.selection) {
        document.selection.empty();
    } else if (window.getSelection) {
        window.getSelection().removeAllRanges();
    }
};

clearHighlighting = function clearHighlighting(target, exception) {
    $('.highlighting', target).each(function(index, highlighting) {
        if(highlighting !== exception) {
            unwrapContents(highlighting);
        }
    });
    target.normalize();
};

mergeHighlighting = function mergeHighlighting() {
    var i, j;
    // Remove internal highlights
    $('.highlighting', text).filter(function() {
        return this.parentNode.className === 'highlighting';
    }).each(function(index, highlighting) {
        unwrapContents(highlighting);
    });
    text.normalize();
    // Merge adjacent highlights
    first:
    for(i=0; i<text.childNodes.length-1; i++) {
        if(text.childNodes[i].className === 'highlighting') {
            for(j=i+1; j<text.childNodes.length; j++) {
                if(text.childNodes[j].className === 'highlighting') {
                    mergeElements(text.childNodes[i], text.childNodes[j--]);
                    unwrapContents(text.childNodes[i].lastChild);
                } else {
                    switch(text.childNodes[j].nodeType) {
                        case 1:
                            if(text.childNodes[j].className !== 'space') {
                                continue first;
                            }
                            break;
                        case 3:
                            if(text.childNodes[j].textContent.trim() !== '') {
                                continue first;
                            }
                            break;
                    }
                }
            }
        }
    }
};

handleCopy = function handleCopy() {
    var range,
        highlighting,
        item;

    // Highlighting
    range = window.getSelection().getRangeAt(0);
    highlighting = document.createElement('span');
    highlighting.className = 'highlighting';
    highlighting.appendChild(range.cloneContents());
    range.deleteContents();
    range.insertNode(highlighting);

    // Output
    item = document.createElement('li');
    item.innerHTML = highlighting.innerHTML;
    clearHighlighting(item);
    output.appendChild(item);

    // Cleanup
    mergeHighlighting();
    clearSelection();
};

$(function(){
    text = $('.text')[0];
    output = $('.output')[0];
    $(text).on('copy', handleCopy);
});

这里有一个可工作的例子 http://jsbin.com/efohit/3/edit


不是真的。这并没有包括在重叠部分合并亮点的选项,并且每次只在屏幕上显示一个亮点。 - Charlie
@Charlie,我更新了实现。现在相邻的、重叠的和嵌套的高亮已经合并了。 - Vadim
这与我的代码相比如何?我的代码因某种原因被投了反对票。 - Charlie

-1

好的,我想出了一个解决方案,而且非常简单明了。

如果.text看起来像这样:

<p class="text">Line one
<a class="space"></a>Line two
<a class="space"></a>Line three</p>

如果保留与上述相同的准确标记和换行符,则可以找到每个 \n 并将其替换为间隔元素。
if (textStr.indexOf("\n") >= 0) {
    textStr = textStr.replace(/\n/g, "\n<a class='space'></a>");
}

这种方法并不通用,如果有多个换行符、标签不同等情况就会失败。因此,我鼓励任何有更好方法的人来回答这个问题!这并不难,我已经想出来了。


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