Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Preventing text in rt tags (furigana) from being selected

I use ruby annotation to add furigana to Japanese text:

<ruby><rb>漢</rb><rt>かん</rt></ruby><ruby><rb>字</rb><rt>じ</rt></ruby>

When I try selecting 漢字 and copying it in Safari or Chrome, the clipboard looks like this:

漢
かん
字

I can't look up the word from OS X's dictionary either.

Is there some way to prevent the furigana from being selected? rt { -webkit-user-select: none; } doesn't seem to work.

like image 727
Lri Avatar asked Nov 18 '12 07:11

Lri


3 Answers

It appears that if you wrap them inside one <ruby> element, like this:

<ruby>
  <rb>漢</rb><rt>かん</rt>
  <rb>字</rb><rt>じ</rt>
</ruby>

Then it'll be possible to select 漢字 without the furiganas selected.


UPDATE:

For kanji-kana mixed text like 間に合わせる, you can either:

  1. Use empty <rt> element, like this:

    <ruby>
        <rb>間</rb><rt>ま</rt>
        <rb>に</rb><rt></rt>
        <rb>合</rb><rt>あ</rt>
        <rb>わせる</rb><r‌​t></rt>
    </ruby>
    
  2. Write some javascript, making use of the Clipboard events* †:

    • HTML:

      <ruby>
        <rb>間</rb><rt>ま</rt>
      </ruby>
      に
      <ruby>
        <rb>合</rb><rt>あ</rt>
      </ruby>
      わせる
      
    • Javascript:

      $(document).on('copy', function (e) {
          e.preventDefault(); // the clipboard data will be set manually later
      
          // hide <rt> elements so that they won't be selected
          $('rt').css('visibility', 'hidden');
      
          // copy text from selection
          e.originalEvent.clipboardData.setData('text', window.getSelection().toString());
      
          // restore visibility
          $('rt').css('visibility', 'visible');
      });
      

Here's a demo page: http://jsfiddle.net/vJK3e/1/

* Tested OK on Safari 6.0.3
† Might require newer browser versions
‡ I add the line of css code rt::selection { display: none; } to prevent the furigana text from beed (visually) selected

like image 169
LET'S DISMANTLE NATO Avatar answered Nov 12 '22 02:11

LET'S DISMANTLE NATO


Here's the vanilla javascript way of doing it:

// hide furigana before sending and reenable after
document.addEventListener('copy', function (e) {
  e.preventDefault();
  var furis = document.getElementsByTagName('rt');
  for (var i = 0; i < furis.length; i++) {
    furis[i].style.display = 'none';
  }
  e.clipboardData.setData('text', window.getSelection().toString());
  for (var i = 0; i < furis.length; i++) {
    furis[i].style.removeProperty('display');
  }
});

As noted above, adding .replace(/\n/g, '') after window.getSelection().toString() will remove any new lines that are somehow still hanging around. .replace(' ', '') might be useful too if you don't want the user to end up with extra spaces either. These may or may not be desirable for your use case.

like image 45
jpc-ae Avatar answered Nov 12 '22 02:11

jpc-ae


Building on Rox Dorentus's accepted answer (and with reference to jpc-ae's helpful Javascript conversion), here is an improvement on the algorithm that doesn't involve hacking the display style of the <rt> elements, which feels fragile to me.

Instead, we build an array of references to all the nodes in the selection, filter for any with the tag <rb>, and return their innerText. I also provide a commented-out alternative in case no <rb> tags are used to wrap up the kanji.

document.addEventListener('copy', function (e) {
  var nodesInRange = getRangeSelectedNodes(window.getSelection().getRangeAt(0));

  /* Takes all <rb> within the selected range, ie. for <ruby><rb>振</rb><rt>ふ</rt></ruby> */
  var payload = nodesInRange.filter((node) => node.nodeName === "RB").map((rb) => rb.innerText).join("");

  /* Alternative for when <rb> isn't used: take all textNodes within <ruby> elements, ie. for <ruby>振<rt>ふ</rt></ruby> */
  // var payload = nodesInRange.filter((node) => node.parentNode.nodeName === "RUBY").map((textNode) => textNode.textContent ).join("");

  e.clipboardData.setData('text/plain', payload);
  e.preventDefault();


  /* Utility function for getting an array of references to all the nodes in the selection area,
   * from: http://stackoverflow.com/a/7784176/5951226 */
  function getRangeSelectedNodes(range) {
    var node = range.startContainer;
    var endNode = range.endContainer;
    if (node == endNode) return [node];
    var rangeNodes = [];
    while (node && node != endNode) rangeNodes.push(node = nextNode(node));
    node = range.startContainer;
    while (node && node != range.commonAncestorContainer) {
      rangeNodes.unshift(node);
      node = node.parentNode;
    }
    return rangeNodes;

    function nextNode(node) {
      if (node.hasChildNodes()) return node.firstChild;
      else {
        while (node && !node.nextSibling) node = node.parentNode;
        if (!node) return null;
        return node.nextSibling;
      }
    }
  }

});
like image 1
Jamie Birch Avatar answered Nov 12 '22 02:11

Jamie Birch