# 所見即所得 - 專案功能盤點
###### tags: `WYSIWYG`
<style>
.note{
color: Teal
}
.note-tag{
color: CadetBlue;
}
.level1{
color: Maroon;
}
.level2{
color: IndianRed;
}
.level3{
color: LightCoral;
}
.helpfunc{
color: green;
}
.idontknow{
color: Crimson;
font-weight: bolder;
}
</style>
下圖為目前的檔案架構


## <span class="level1">background</span>
邏輯處理中心,負責 Chrome Extension 大部分的事件處理
### <span class="level2">background.js</span>
#### <span class="level3">chrome.runtime.onInstalled</span>
會先去判斷被觸發的原因是哪種
* 第一次安裝
先確認一下使用者這邊有沒有先前儲存過的資料,
如果有的話就使用它,沒有的話就初始化資料為空值

* 更新
目前並沒做任何處理
#### <span class="level3">chrome.tabs.onUpdated</span>
判斷 tab 的變動原因
* 如果狀態是 loading ,它就會去 storage 去抓 validList 的資料
(<span class="idontknow">目前還不確定 validList 在幹嘛</span>)
(感覺是在對 URL, 如果有對到就讓它執行content.script)

#### <span class="level3">chrome.runtime.onMessage</span>
會先看 send 過來的 request,它的子結點 action 是不是 array,是的話就繼續
再來看 action 中有沒有 "save" ,有的話就用 chrome.storage 來儲存進去

#### <span class="level3">chrome.commands.onCommand</span>
設定來處理 ChromeExtension 的快捷鍵處理事項
目前有兩種快捷鍵功能
* 開啟關閉 Edit Mode

* 開啟 devPannel 小視窗

### <span class="level2">backgroundRequestManage.js</span>
#### <span class="level3">CloseLanguageToolTab</span>
一個關閉 chrome tab 的 function,
TABID_TO_CLOSR 已經在 [createLanguageToolTab](#createLanguageToolTab) 中被定義

#### <span class="level3" id="GetUserGoogleInfoByToken">GetUserGoogleInfoByToken</span>
它是一個非同步的 code,它依序做以下的操作
1. fetch API 到這 google identitytoolkit
2. 可以獲得3筆使用者資料(displayName, email, photoURL),
並使用 chrome.storage 來儲存
3. 使用 chrome.runTime.sendMessage 來傳出一個 action : `getUserInfoDone`
#### <span class="level3">chrome.storage.sync.get</span>
首先先去取得 chrome storage 中名為 `language` 的值,
(<span class="idontknow">這個值好像跟 popup 的 script 有關,還不確定在幹嘛</span>)
並設定為變數 `MAINPAGE_LANGUAGE` 的值

#### <span class="level3">chrome.runtime.onMessage</span>
這邊的 runtime.onMessage 負責監聽兩個 action,分別是
* <span id="createLanguageToolTab">createLanguageToolTab</span>
看註解是說:
當偵測到 devPannel 的 token 是 invalid 的時候,
會開一個新的 tab 去開 Language Tool 的頁面,
並且將新的 tabid 記錄於變數 `TABID_TO_CLOSE` 中

* changeLocalLanguage
看註解是說:
當 user 改變 popup 裡的 language 值時,
會更新變數 `MAINPAGE_LANGUAGE` 的語言值

#### <span class="level3">chrome.webRequest.onBeforeRequest</span>
在 Request 發出後會去判斷以下資訊
* Method 是不是 Post
* initiator 是不是來自 Language Tool
* requestbody 看有沒有資訊
* requestbody.raw 看有沒有資訊
如果上述判斷皆可成立,就可已從 request body 中獲得 ID_Token,
並將其存放於 [GetUserGoogleInfoByToken](#GetUserGoogleInfoByToken) 這個 POST request 的 body 中
以獲得 user 的 display name, photo url 及 email 的資訊

#### <span class="level3">chrome.webRequest.onBeforeSendHeaders</span>
<span class="idontknow">好像跟設改變語系有關,但還不知道他在幹麻</span>

### <span class="level2">backgroundsendKeysURL.js</span>
#### <span class="level3" id="ReportKeyUrlMap">ReportKeyUrlMap</span>
使用 chrome.storage.get 取得 email 及 token
接著會去打以下這個 API
`http://${DOMAIN}/api/v3/modules/${module_key[0]}/translations/${module_key[1]}/paths/add`
Method 是 PATCH,
header 的資訊也有另外處理
<span class="idontknow">目前還不知道在幹麻 QQ</span>
#### <span class="level3">chrome.runtime.onMessage</span>
這邊的 runtime.onMessage 負責監聽一個 action,
* sendKeysUrl
把它的 request body 裡的 module_key 去 run [ReportKeyUrlMap](#ReportKeyUrlMap) 這個 function

## <span class="level1">popup</span>
### <span class="level2">popup.html</span>
主要有以下部份

* **Google 帳號資訊**
* 比較要注意的是它在未登入時的登入按鈕,它綁定的事件是 getLoginData,
會使用 chrome.runtime 發送一個 message 來通知 background.js,
它所綁的事件為 `createLanguageToolTab`


* **加入網址**

* addSiteBtnClick
將目前的 domain 加入至 `this.validSite.validList` 這個變數當中
然後使用 chrome.runtime.sendMessage 去發出 `save` 任務給 background.js
最後再針對當前的 tab 做 reload 的動作

* deleteSiteBtnClick
將目前的 domain 從`this.validSite.validList` 這個變數當中刪除
然後使用 chrome.runtime.sendMessage 去發出 `save` 任務給 background.js
最後再針對當前的 tab 做 reload 的動作

* **切換閱覽及編輯模式**
主要重點是 `changeMode` 這個 function,
它會在 popup.js 使用 chrome.tabs.sendMessage 發送 `changeMode` 這個 action 給 content.js


* **切換頁面語系**
主要做的有三個動作
* 發送 `save` 這個 action,
所帶 data 為 `{ language: this.language.selected }`
* 發送 `changeLocalLanguage` 這個 action,
所帶 data 為 `{ language: this.language.selected }`
* 更新頁面的 URL 的 query Selector


### <span class="level2">popup.js</span>
所使用的前端框架為 Vue,
以下是他的架構
#### <span class="level3">chrome.runtime.onMessage</span>
一開始會先取得所需要的資訊
使用 chrome.storage.sync.get 來取得以下資訊
* editorMode
* email
* token
* language
* validList
* displayName
* photoUrl

**Data 架構**:
```json
{
"profile":{
"name":"",
"email":"",
"img":"",
"valid":false
},
"controller":{
"token":"",
"editorMode":true
},
"validSite":{
"domain":"",
"validList":[
"https://sms.qa.91dev.tw",
"https://sms.qa8.91dev.tw",
"https://getbootstrap.com"
]
},
"language":{
"selected":"zh-TW",
"options":[
{
"value":"zh-TW",
"text":"中文"
},
{
"value":"zh-HK",
"text":"香港中文"
},
{
"value":"en-US",
"text":"英文"
},
{
"value":"ms-MY",
"text":"馬來文"
},
{
"value":"kok-IN",
"text":"火星文"
}
]
}
}
```
**mounted (概念類似 React 的 componentDidMount)**
首先先使用 chrome.storage.sync.get 來取得以下資訊
* token
* language
* validList
* editorMode
再來使用 `chrome.tab.query` 來取得 Domain Name 的樣子
(但它的 regular expression 好像怪怪的)
並將 Domain Name 存放於 this.validSite.domain 這個變數來做使用
最後再跑 `loadIframe` 這個 function
#### <span class="level3" id="loadIframe">loadIframe</span>
先使用 querySelector 找到 class 名為 `language-tool` 的 element,
並在其注入 tag 為 iframe 的 element ,
並且在該 element 加以下特性,
* frameborder: 0
* style.height: 0
* <span class="idontknow">使用 setTimeout,加入 src 這個屬性</span>

## <span class="level1">content</span>
### <span class="level2">監聽 DOM 結構的變動</span>
#### **<span class="level3">MutationObserver</span>**
MutationObserver 它是一個 WebAPI,設計用來監控 DOM tree 結構的變化
建構式:
```javascript
new MutationObserver(
function callback
);
```
一但有變動,裡面的 callback 就會被呼叫

#### **<span class="level3">setObserver</span>**
有一個全域變數叫做 updateCounter 來計時用,這邊賦予它值為 5,
接下來用 setInterval 來計時,並以 `checkAfterUpdated` 這個變數來存取它,
每 100 ms 為一個循環,一共跑 5 次
時間一到,
先將 mutationObserver 關閉一下,
並清除這個 setInterval, 再重新將 `checkAfterUpdated` 賦值為 null,
接下來就是把 iFrame 放到畫面上,並以 `module_key_list` 這個變數來存這資料,
再用 chrome 的 sendmessage 發送 `sendKeysUrl` 這個 action 並帶著 module_key 這個 Data
最後重新開啟 mutationObserver.observe 去觀看 iFrame 的變化
***補:***
[.observe](https://developer.mozilla.org/zh-TW/docs/Web/API/MutationObserver#observe()) 是 mutationObserver 的一個 Instance methods,
它的結構如下, Node 為想要監控的節點,MutationObserverInit 則是一些監控的設定,
可以決定說什麼樣的事件才會回傳,是否監控子結構等
```javascript
void observe(
Node target,
MutationObserverInit options
);
```

### <span class="level2">初始化 iframe</span>
#### **<span class="level3">initIframe()</span>**
這邊就是在製作一個 iframe 的 element,
* iframe 的 src Attribute 是看當前所在的 URL,
會先判斷目前的 URL 是不是具有 query string,
如果有 query string 有 lang 的話,就把當前的 lang 替換成 `kok-IN` (火星文),
沒有的話就是保持當前 URL
* 預設是不希望將 iframe 呈現在畫面上,
所以把他的 height 調成 0
* 當 iFrame 是 onload 的狀態,就執行以下處理
* <span class="idontknow">setObserver()</span>
* <span class="idontknow">mutationObserver()</span>
* 一切處理完之後,就把 iFrame 這個 element 放在 body 的最底端

#### **<span class="level3">parseIframeArea()</span>**
先準備好 3 變數
* base : iFrame 的 document body
* all : iFrame 的 innerHTML
* reg : 三種 regular expression
* `"\s*\[[^\[\]\(\{\"\']+\]\s*"` : "[key]"
* `\s*\[[^\[\]\(\{\"\']+\]\s*` : [key]
* `\s*\[\[[^\[\]\(\{\"\']+\]\]\s*` : [[key]]
接下來是製作 virtualDOM
去尋找有對應到的 key 值,多語系 kok-IN 的值為 [key] 的形式
簡單來說這邊 漏漏登 的 code 就是在有多語系的地方做標記

### <span class="level2">針對 body 監聽的事件</span>
#### **<span class="helpfunc">validTarget()</span>**
判斷目前的 target element 是不是具有 `have-key` 這個 class

#### **<span class="level3">mouseover</span>**
為 `pointElement` 這個變數指定為 `e.target`
先判斷是不是在編輯模式,是用 selected 這個變數做確認,
滑鼠移入時,加上 `is-selected` 這個 class

#### **<span class="level3">mouseout</span>**
先判斷是不是在編輯模式,是用 selected 這個變數做確認,
滑鼠移出時,移除 `is-selected` 這個 class

#### **<span class="helpfunc">checkFakeButton()</span>**
判斷這個 element 是不是 button,有三種可能形式
`button` , `submit`, `navbar-toggle`
如果是其中之一,則回傳 true

#### **<span class="helpfunc">getDomIndexString()</span>**
取得它在整個 DOM 結構中所在的位置
<span class="idontknow">雖然覺得有點怪</span>
回傳的格式為 `body>${domindex}`, 似乎是一個奇妙的遞迴~

#### **<span class="level3">click</span>**
* 先判斷是不是在編輯模式,是用 selected 這個變數做確認
* 判斷是不是 button, submit 或 navbar-toggle 其中一種,
如果不是的話,對它使用 `e.preventDefault()`
* 判斷是不是 valid target : class 是不是有 `have-key` 這屬性
是的話,先看他是不是 a 連結,是的話對它使用 `e.preventDefault()`
再來將 e.target 存放在 `editorElement` 這個全域變數中
接著為 `updateDevPage` 這個 chrome.sendmessagfe 的動作準備
這是要傳送到 devPannel 的 content.js
### <span class="level2">切換編輯及閱覽模式</span>
**chrome.runtime.onMessage**
這支監控的任務為 `changeMode`,
#### <span class="level3">switchMode</span>
#### <span class="level3">showMode</span>
## <span class="level1">devPanel</span>
## <span class="note">Language Tool API 筆記</span>
### 取得單一 key 值多語系資料
METHODS : `GET`
URL : `http://translation.qa.91dev.tw/api/v3/modules/[module_name]?key=[module_key]`
## <span class="note">Chrome extension API 筆記</span>
### <span class="note-tag">chrome.runtime</span>
它可以取得我們 background page 的資訊 (manifest.json)
也可以用來監控我們 extension life cycle 中的事件 及 取得我們 URL 的資訊
* [**onInstalled**](https://developer.chrome.com/extensions/runtime#event-onInstalled)
只有在 Extension **第一次安裝**, **Extension 更新版本** 或 **Chrome 更新** 這 3 種狀況才會觸發
* [**onMessage**](https://developer.chrome.com/extensions/runtime#event-onMessage)
當接受到 Message 時會被觸發,有兩個管道可以接受到 Message
* From other extension (PopUp or devPannel) : `runtime.sendMessage`
* From content script : `tabs.sendMessage`
### <span class="note-tag">chrome.storage</span>
它是設計用來處理 extension 的 storage 需求,
可以針對 user data 做 取得, 儲存 及 追蹤變化 等操作
類似於 localstorage 的存在,
但它的儲存格式為 json,不是用 string
* [**sync**](https://developer.chrome.com/apps/storage#property-sync)
使用 storage.sync 會將 user data 儲存到使用者所登入的 chrome browser 裡
如果使用者現在的 Chrome 是離線狀態,會先將資料儲存在 local 端,一旦恢復連線就會同步
### <span class="note-tag">chrome.tabs</span>
它是來處理 chrome browser 裡的 tab 操作,
可以透過這個 API 來 新增修改 瀏覽器中的 tab
* [**onUpdated**](https://developer.chrome.com/apps/storage#property-sync)
當我們的 tab 有更動的時候就會被觸發,它有三個變數可以做使用
* tabId : tab ID (int)
* changeInfo : 變動的資訊 (它是一個 ogject)
* tab : 詳細的 tab 資訊 (它是一個 object)
### <span class="note-tag">chrome.commands</span>
它可以幫助我們使用鍵盤組合鍵來做相對應的動作,
而組合鍵的設定都在 manifest.json 中處理
### <span class="note-tag">chrome.webRequest</span>
用來監控發出的 request ,也可以對它攔截修改
下方是整個 request 的生命週期,
可以依照對應的 API 在想攔截的點做操作

onBeforeRequest
onBeforeSendHeaders