Javascript保留格式翻譯選區內容及預覽(完結)

談笑斗酒發表於2019-11-10

轉載自道招網Javascript保留格式翻譯選區內容及預覽(完結)

前面的一篇文章Javascript保留格式翻譯選區內容及預覽(一),我們已經知道了,怎麼獲取選中的文字,然後交個介面返回,現在我們接著繼續將這麼講翻譯好的詞語替換到原文的對應位置。 首先我們在選擇文字並進行翻譯時,我們的選區還在的,如果後續操作有存在丟掉選區的操作的話,我們需要先將選區暫存起來。

怎麼儲存選區

首先儲存選區的前提是:選區還存在。 怎麼說 ? 如果我們選中的詞語是abc,如果abc都被替換了,之前儲存的選區資訊也無法使用了,或者不準確了,就不要使用了。

const ranges = [];
for (let i = 0; i < window.getSelection().rangeCount; i++) {
      const range = window.getSelection().getRangeAt(i);
      ranges.push(range.cloneRange()); // clone後確保ranges可以用來重試
}
我們現在就把選區資訊儲存在`ranges`陣列裡面了
複製程式碼

怎麼恢復選區

既然提到儲存選區,自然要知道怎麼恢復選區了

// 恢復指定選區前移除所有可能存在選區
window.getSelection().removeAllRanges();
ranges.forEach(range => window.getSelection().addRange(range));
複製程式碼

替換原文

替換原文的前提是保持原文處於選中狀態,也就是保留選區,現在這一步上面已經做到了,再加上第一篇提到的獲取的選區的節點的最近一層的共同父節點commonAncestorContainer。現在我們正式開始進行替換了。 我們用兩個方法完成替換,一個方法(traversalParent)是查詢需要可能需要替換的dom節點。另一個方法(traversalReplace)進行替換。

尋找可能需要替換的dom節點

commonAncestorContainer裡面是包含我們需要替換的dom節點的,但是裡面可能還有很多無關節點,所以我們必須進行排除。 這就需要window.getSelection().containsNode方法,它能幫助我們判斷某個節點是不是在選區中。 從MDN上看看它的api

file
我們可以判斷指定節點是否全部或者部分包含在選區內。 如果dom全部內容都在選區是最簡單的,直接全量替換了;如果只有不分在選區裡面就麻煩點,比如原來的dom結構是<div>你們好</div>,但是使用者只選中了“好”字,這時我們就不應該把“你們”給替換了。這部分工作就需要在我們的traversalReplace方法裡面實現了。 window.getSelection().anchorNodewindow.getSelection().focusNode分別能讓我們知道選區的起始和結尾的節點。因為我們是人工選擇文字節點,所以我們這裡的起始、結尾節點極大概率是文字節點或者是包含文字的元素節點(只是我們看不見而已,這時就屬於全部包含了,反而簡單)。 文字節點的data屬性會返回該文字的文字內容。 與anchorNodefocusNode對應的window.getSelection().anchorOffsetwindow.getSelection().focusOffset可以告知我們選擇的文字在該文字節點的位置。 我們替換的的原文時就可以用 原文前內容+ 翻譯結果 + 原文結尾內容來替換了。 這兩個方法的程式碼如下

traversalParent(parent, isPreviewMode = false) {
    let hit = 0;
    let isReached = false;
    const childNodes = parent.childNodes;
    if (childNodes && childNodes.length > 0) {
      for (let j = 0; j < parent.childNodes.length; j++) {
        const node = parent.childNodes[j];
        if (window.getSelection().containsNode(node)) {
          isReached = true;
          hit++;
          traversalReplace(node, false, win)
        } else if (window.getSelection().containsNode(node, true)) {
          isReached = true;
          hit++;
          traversalReplace(node, true, win)
        } else {
          if (isReached) {
            break;
          }
        }
      }
    } else if (parent.nodeType === 3) {
      traversalReplace(parent, false, window);
    }
  }

  traversalReplace(node, allowPartialContainment, win) {
    const childNodes = node.childNodes;
    if (childNodes && childNodes.length > 0) {
      let isReached = false;
      for (let i = 0; i < childNodes.length; i++) {
        const item = childNodes[i];
        // node部分包含時,再遍歷它的子節點時,要繼續判斷子節點是否包含
        if (allowPartialContainment && !window.getSelection().containsNode(item, true)) {
          if (isReached) {
            break;
          }
        } else {
          isReached = true;
          traversalReplace(item, allowPartialContainment, win);
        }
      }
    } else if (node.nodeType === 3) {
      const data = node.data || '';
      if (data.trim().length < 1) {
        return;
      }
      let targetText = '';
      const translatedText = this.translatedWords[this.traversalIndex++] || '';
      if (node === window.getSelection().anchorNode) {
        // 存在node既是anchorNode又是focusNode的情況
        const tailText = node === window.getSelection().focusNode ? node.data.slice(window.getSelection().focusOffset) : '';
        targetText = node.data.slice(0, window.getSelection().anchorOffset) + translatedText + tailText;
      } else if (node === window.getSelection().focusNode) {
        targetText = translatedText + node.data.slice(window.getSelection().focusOffset);
      } else {
        targetText = translatedText;
      }
      // 此操作後會改變原來選中該node的Range的startOffset,endOffset等值。皮之不存毛將焉附
      node.data = targetText;
    }
  }
複製程式碼

具體請通過npm安裝translate_selection

相關文章