iT邦幫忙

2022 iThome 鐵人賽

DAY 18
0

學會運用瀏覽器外掛的【頁面內容】【彈出頁面】【背景服務】,
瀏覽器外掛對你來說,應該已經不算難事,
終於可以把它做為你口袋裡的一個好用工具了。

目前我們的翻譯工具,也算是來到相當好用的程度,

git clone https://github.com/betterTrans/betterTranslation.git

但我們當然不會只滿足於此囉。

單純的翻譯修改,其實還是有很多重複的工作。
比方說,如果遇到已經翻譯過的句子,
出現在文章的其他位置,
或許就應該自動套入之前的翻譯,
而不需要再重新翻譯一次。

就算不是自動套入,也應該以某種方式出現在提示中,
讓我們很方便加以引用。

再舉個例子,
當我們看到一些像是「內存」這樣的大陸用語,
想換成更道地的「記憶體」,
修改起來雖然很簡單,
但如果同樣的用詞在同一頁出現好幾十次甚至上百次,
我們就要做幾十次、上百次的重複工作。

這種情況一般最常用的就是「搜尋/取代」功能,
這功能雖然好用,但也可能製造出新的問題。
舉例來說,
我們若把「內存」全部無腦替換成「記憶體」,
就會遇到「海內存知己」變成「海記憶體知己」
這類奇怪的結果。

如果在替換的時候,程式可以自動參考原文、
(比如前一句原文肯定沒有 memory 所以不用修改)
或是根據詞語在句中不同的詞性與角色,
判斷是否應該進行替換,
甚至根據這些額外的「線索」,做出更聰明的修改,
那不就太好了嗎?

這就是所謂的翻譯輔助工具應該做到的其中一種功能。
而目前我們這個很單純的翻譯界面,還看不到這樣的功能。
我們需要一個好用的界面,來引入這些好用的功能。

今天我們就先來製作一個簡單的面板功能,
之後再用它來製作【句子面板】、【詞語面板】之類的界面,
除了可用來呈現一些有用的資訊,
也可以協助我們更聰明的編輯翻譯內容。

之前我們所用的翻譯編輯界面,
主要就是把各個包在 sent 標籤裡的句子,
變成一個 textarea 文字輸入框,
這樣的做法雖然直觀,但有以下幾個缺點:

  • 會破壞排版美觀性
  • 編輯的位置會一直改變
  • 要顯示其他有用的資訊或功能很不方便

只要另外建立一個【句子面板】,就能解決這些問題。

其實除了【句子面板】之外,我還想建立一個【詞語面板】。
句子面板用來放句子,所以需要比較寬一點的寬度,
我想把它放在頁面的下方。
詞語面板可用來顯示詞語相關資訊,
我打算放在頁面的右邊。

我希望這兩個面板,都可以用滑動的方式,
從下面和右邊滑動進來,
不需要的時候,也可以滑動出去隱藏起來。

若想做到滑動面板的功能,
其實可以嘗試尋找現成的 JS 專案,
但由於我想客製出自己所需的面板,
相應的程式碼也不困難,
所以,今天就來嘗試看看怎麼做吧。

建立面板

首先,我們在 js 目錄下建立一個 panels.js,
並在 css 目錄下建立一個 panels.css,
然後記得在 manifest.json 裡宣告一下。

"content_scripts": [ {
  ...
  "css": [
    ...
    "css/panels.css"
  ],
  "js": [
    ...
    "js/panels.js"
  ]
} ],

這兩個檔案,
主要就是用來整合面板相關的所有程式碼。

一開始我們先在 panels.js 裡,
建立一個叫做 createPanel() 的函式:

function createPanel(id, position = 'bottom', display=true, size = '300px', background_color = '', z_index = 0, opacity = 0) {
  ...
}

這個函式會先建立一個 id 為 bt_panels 的 div 區塊,
掛進原始網頁的末尾處,以做為放置其他面板的主要區塊。
後續我們所有的面板,全都會放在這個區塊中。

  panels = document.createElement("div");
  panels.id = "bt_panels"
  document.body.append(panels)

接著再根據函式所設定的參數,建立相應的面板。
除了最重要的 id 參數之外,
我們還會在 class 屬性中,設定 bt_panel 做為其類別。

  panel = document.createElement("div");
  panel.id = id;
  panel.classList.add('bt_panel')

在這樣的做法下,
我們就可以在 panels.css 檔案中,
針對 bt_panel 這個類別,定義一些面板通用的 css 設定。

div.bt_panel {
  position: fixed;  // 脫離原內容位置,以便直接定義區塊位置
  ...
}

這個函式也會根據 position 位置參數,
決定面板所要擺放的位置。
做法上也是設定 class 屬性:

  panel.classList.add(position)

然後在 css 檔案中定義相應的 css 設定。

/*  上面板  */
div.bt_panel.top { ... }

/*  下面板  */
div.bt_panel.bottom {
  bottom: 0;  // 靠到畫面下緣
  left: 0;  // 靠到畫面左緣
  width: 100%;  // 延展至全寬
}

/*  左面板  */
div.bt_panel.left { ... }

/*  右面板  */
div.bt_panel.right { ... }

面板的大小,則是直接根據 size 參數,
參考面板的不同位置進行設定:

  if (position === 'left' || position === 'right') {
    panel.style.width =  size;
  }
  else if (position === 'top' || position === 'bottom') {
    panel.style.height = size;
  }

其他設定陸續設好之後,
就可以把面板掛進一開始建立的那個主面板裡頭了。

  panels.appendChild(panel);

有了這個函式,
我們就可以透過 window.load 的方式,
直接調用這個函式,
這樣就可以看到所建立的面板了:

window.addEventListener('load', (e)=>{

  // 建立一個【句子面板】和一個【單詞面板】
  sent_panel = createPanel("bt_sent_panel", "bottom")
  token_panel = createPanel("bt_token_panel", "right")

}

由於我們主要是利用 class 類別,搭配相應的 css 設定,
來定義面板的呈現方式,
因此這裡稍微簡單說明一下相關 css 設定所產生的一些效果:

  • position: fixed; 可以讓 div 脫離原本的內容,而不是直接出現在頁面內容的最後面
  • bottom: 0; 可以讓面板黏在下方,top: 0; 黏在上方,right: 0; 黏在右面,left: 0; 黏在左邊
  • width、height 可設定面板大小
  • z-index: 999;、9999 可以讓面板浮在內容上方(數值越大越上面)
  • overflow-y: auto; 此設定可以在內容過多時使用捲動功能

滑動面板

接著我們來實現面板的滑動功能。
我們會建立 slideInPanel() 和 slideOutPanel() 兩個函式,
負責面板的滑入與滑出。

面板滑入滑出的功能,
主要是靠 transform 和 transition-duration 這兩個 css 設定。
transform 可搭配 translate(x, y) 的設定值,
負責「轉移」面板的位置;
transition-duration 則是設定轉移所花費的時間。
舉例來說,
如果想用 0.3 秒的時間,把面板往左滑動 100 px,
可以採用下面的做法:

  panel.style['transition-duration'] = '300ms';
  // panel.style.transitionDuration = '300ms';  // 這種設定方式也可以
  panel.style.transform = 'translate('-100px', 0)'

以 slideOutPanel() 這個把面板滑出去的函式為例,
我們首先會用 on/off 這兩個不同的 class 值,
來設定面板「滑進來/滑出去」的不同狀態,
然後再根據面板所在的 position 位置與大小,
決定要往哪個方向滑動,滑動多少距離(movement)。

  if (panel.classList.contains('top')) {
    movement = `0px, -${panel.offsetHeight+20}px`;
  } else if (panel.classList.contains('bottom')) {
    movement = `0px, ${panel.offsetHeight+20}px`;
  } else if (panel.classList.contains('right')) {
    movement = `${panel.offsetWidth+20}px,  0px`;
  } else if (panel.classList.contains('left')) {
    movement = `-${panel.offsetWidth+20}px, 0px`;
  }

  panel.style.transform = 'translate(' + movement + ')'

至於 slideInPanel() 要把面板滑進來,
就不用那麼麻煩,只要滑回原來的位置即可:

  panel.style.transform = 'translate(0px, 0px)'

顯示面板時,網頁原本的內容會被遮擋住,
如果不希望面板遮擋住內容,
可以針對原本網頁的 body 標籤,設定相應的 margin 值。

if (!overlay) {
  console.log(position)
  if (panel.classList.contains('top')) {
    document.body.style['margin-top'] = (panel.offsetHeight + 10) + 'px';
  } else if (panel.classList.contains('right')) {
    document.body.style['margin-right'] = (panel.offsetWidth + 10) + 'px';
  } else if (panel.classList.contains('bottom')) {
    document.querySelector("#bt_panels").style.height = (panel.offsetHeight + 10) + 'px';
  } else if (panel.classList.contains('left')) {
    document.body.style['margin-left'] = (panel.offsetWidth + 10) + 'px';
 }
}

這裡特別說明一下的是,
設定 margin-bottom 不一定有用,
但既然我們的 #bt_panels 主面板一定是 body 最後一個 div,
就用它的 height 來做設定,
顯示正確的機率會更高一點。

最後我們可以再寫一個 togglePanel(),
負責面板的開關切換。

  if (panel.classList.contains('on')) {
    slideOutPanel(id, position);
  } else {
    slideInPanel(id, position);
  }

使用面板

至此,面板功能就相當完善了。
我們可以設定一個測試用的快速鍵(Alt+9),
試著切換剛才所建立的面板:

hotkey_handlers = {
  ...
  'Alt9': Alt9,
  ...
}
...
function Alt9(){
  togglePanel('bt_sent_panel')
  togglePanel('bt_token_panel')
}

還記得嗎?如果想讓使用者自定義快速鍵,
manifest.json 的 commands 裡還要再添加一個項目:

  "Alt9": {
    "description": "測試新功能"
  },

好啦!今天的任務總算完成。
有了這個面板功能,
隨後我們就可以在面板放入其他想要的功能了。

接下來我們打算運用 Vue3,
來實現面板中的各項功能。
明天請拭目以待囉。


上一篇
全鍵盤操作才是王道!commands 自定義快速鍵
下一篇
運用 Vue3 製作方便的句子面板
系列文
譯者會消失嗎?Maybe, but not today —— 你,才是更好的翻譯師30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言