---
# System prepended metadata

title: 型別推斷 (Type Inference)
tags: [programming, ' typescript']

---

---
title: 型別推斷 (Type Inference) # 標題
description:  型別推斷 (Type Inference) # 描述
image: # 封面
tags: programming, typescript # 內部標籤
robots: index, nofollow # [no]index, [no]follow
lang: zh # language
dir: ltr # left to right or right to left
breaks: # newline style
GA: UA-168653916-1 # google analysis
disqus: hackmd
slideOptions: 
 transition: fade  # https://revealjs.com/transitions/
 theme: back 
---

# 型別推斷 (Type Inference)

## 起因

你是否曾經遇到難以結合的類別
既不想另開類別, 也不想用object, 甚至自廢武功用dynamic
而在 TypeScript 中, 我想應該很難再遇到這樣的困境了
而一切都是從下面這段code 開始說起
[原始碼傳送門](https://github.com/voodoocreation/ts-deepmerge/blob/master/src/index.ts#L5)

```typescript
type TUnionToIntersection<U> =
  (U extends any ? ((k: U) => void) : never) extends
  ((k: infer I) => void ? I : never);
function merge<T extends IObject[]>(...objects: T): TUnionToIntersection<T[number]>;
```

## 在開始之前

這篇文會提到 TypeScript 中
關於 type 宣告上的各種進階手法
絕對都不會超過官方文件
每一個部分都會有官網說明來進行對照
但以我個人而言 是到現在才初步了解了那些寫法所代表的意思
如果你也對 TypeScript 的型別定義有興趣且剛好苦手於此
也許這篇文章對你會有點幫助
本篇文章會提到的東西接為下面連結中的內容
[TypeScript AdvancedType](https://www.typescriptlang.org/docs/handbook/advanced-types.html)

因為c# 的型別推斷沒什麼好講的
畢竟已經做到全自動且開發上無感
而TypeScript則不一樣
可以主動去定義外 也涵蓋在 c# 上情境的範圍
當然主要還是因為發現有趣的事情

## 開始囉

### 剛剛那段code的效果是什麼

剛剛那段code原本實現的事情是 js 物件的深層合併
但重點不是他如何合併的
因為合併並不困難
困難的是, 如何定義出合併後的型別
為什麼這件事情很困難
我們可以參考一下 EF Core 的 Include 案例
他也做了類似的事情 但當你Include之後
他是不讓你再當下進行Select
因為他根本沒有辦法提供正確的型別給使用者
而在TypeScript中可以透過type定義輕而易舉地達成
像是這樣

```typescript
type Merge<T, T2> = T & T2;
const a: Merge<{a: number}, { b: string }> = { a: 1, b: 's' };
```

但這種類型的困境不只有這樣子而已
如果我們想要做不管使用者帶幾個都要能夠合併出正確的結果的話
那就是更困難的事情
而前面提到的那段code 就是可以完美實現這件事情的方式

```typescript
const mobj = merge({ a: 1 }, { b: 2 }, { c: '3' });
// 此時基於型別可推斷的情況下 可以省略不寫
// 所以下面列出上述可忽略的部分實際被推斷的結果
// const mobj: {a: 1} & {b: 2} & {c: '3'} = merge<[{a: 1}, {b: 2}, {c: '3'}]>({ a: 1 }, { b: 2 }, { c: '3' }): {a: 1} & {b: 2} & {c: '3'}
// 也就是說 此時泛型別為 [{a: 1}, {b: 2}, {c: '3'}]
// 並且回傳型別型別為 {a: 1} & {b: 2} & {c: '3'}
// 所以 mobj 在merge 之後仍然可提供合併後完整的強行別
```

### type 定義是什麼

在 TypeScript 中因為是作為弱型別js 的強行別超集
那型別當然就很重要
而這裡的type所定義的事情
可以想像成js 中各種類型物件的接口描述
以及提供這些特定類型的接口一個新的別名
他與介面(interface)極為相似
但與介面的差別在於
type 定義之後是不能另行擴展的
[官方解釋](https://www.typescriptlang.org/docs/handbook/advanced-types.html#interfaces-vs-type-aliases)

### 在 TypeScript 中的型別推斷

在 c# 中的型別推斷是自動且規律的
因為 c# 本身就是與型別系統相依
但是在 TypeScript 上因為最後會與JS脫鉤的緣故
TypeScript 就比較不會受限於型別系統的嚴謹規範
再加上Function Programming的技術融入
所以我們可以更主動去定義型別該如何推斷
下面會依序介紹最後解釋一開始提到的那段語法所需要用到的語法

## TypeScript AdvencedType 語法

### extends

extends 在我們常用的語言上的概念是繼承
而型別與型別之間的關係 也就是繼承關係
所以在這裡我們可以把 T extends U
視為 T 型別是否繼承 U
或是 更簡單一點的想法是 T 是不是 U
透過一個想法上的改變 把 extends 當作一個判斷語句
所以 當我們說
```T extends U ? X : Y```
意思就是 T 型別如果是 U 型別
則回傳 X 型別否則回傳 Y 型別

[官方網站解釋](https://www.typescriptlang.org/docs/handbook/advanced-types.html#conditional-types)

### keyof

在大多數的語言中所指的物件多半都是 Key Value Pair的集合體
在 JS 中也不例外
所以 keyof T 的意思就是 T 物件中的 Key
例如

```typescript
type Keys = keyof { a: 1, b: {c: 2} };
const keys: Keys = 'a'; // or 'b' Keys 此時為 'a' | 'b';
type Keys2 = keyof ['a', 'b', 'c'];
const keys2: Keys2 = 1 // or 2 or 3 or 其他array 的 member name (像是 'length' ...etc)
```

[官方網站解釋](https://www.typescriptlang.org/docs/handbook/advanced-types.html#index-types)

### P in keyof T, T[P], { [P in keyof T]: T[P] }

這裡 in keyof 的意思是指 P 為 T 中所有的成員的名稱的名稱
而 因為 P 是 T 所有的成員名稱 所以 T[P] 就是指 T 的成員 P 的型別
而這裡之所以用 T[P] 而不是指定一個型別
就表示 T[P] 的結果可能為不同的型別
例如內建的 ```Partial<T>``` 如下:

```typescript
type Partial<T> = { [P in keyof T]?: T[P]; };
```

這個意思就是指 ```Partial<T>``` 型別代表一個物件 其內容中的成員名稱是 T 所有的成員名稱
其成員型別與 T 的成員型別也相同 而Partial 在每一個 成員名稱後面都加上了'?'已表示此成員可選擇性存在
所以稱之為 T 的一部分(Partial)

[官方網站解釋](https://www.typescriptlang.org/docs/handbook/advanced-types.html#mapped-types)

### 陣列型別中的 T[number]

這是一個非常重要的語法 也是讓難以實現的事情實現的關鍵之一
當 T 為 Array的時候 往往我們很難去在往下追朔其型別
(因為在強行別系統中所有elemment 都是相同的型別)
而在TypeScript 我們可以透過 T[number] 來對於陣列中的每一個型別一一往下探究
例如:

```typescript
type Values<T> = T extends { value: any }[] ? T[number]['value'] : never;
```

這個意思是指 T 如果是 { value: any }[] 這個陣列型別
那 ```Values<T>``` 則是作為 array 的 T 中的每一個元素的成員 value 的型別

[官方網站解釋](https://www.typescriptlang.org/docs/handbook/advanced-types.html#distributive-conditional-types)

### any, void, never

這四個型別各自代表如下

- any: 表示任何型別 包含 function, undefined / null, 值型別, 物件, 陣列 等  
  - ```function fn<T = any>(): T { }```: 此為fn 方法的泛行參數預設為any 型別 如果使用者不寫的話就會以 any 來執行
  - ```T extends U ? X : any```: 如果 T 是 U 則回傳 X 型別 否則回傳 any
- void: void 表示空型別 通常表示沒有回傳值的函數的執行結果
  - ```function fn<T = void>(): T { }```: 此為fn 方法的泛行參數預設為void 型別  
    如果使用者不寫的話此函數就是無回傳值函數 如果有帶泛行參數則表示會回傳指定型別的函數
  - ```T extends U ? X : void```: 如果 T 是 U 則回傳 X 型別 否則回傳 any
- never: never 的意思就是不曾做過 不曾有過 或是不存在的型別
  - ```function fn<T = never>(): T { }```: 意思就是預設參數型別不曾有過  
    所以如果真的去運行 fn 就會是 undefined(不曾定義過)
  - ```T extends U ? X : never```: 的意思就是 T 如果是 U 則回傳 X 型別 否則回傳 never 可以想成此路不通  
    所以會引發語法錯誤

上述三個型別尤其 never 非常重要, 可以用來約束推斷導向某一個期望的方向

### infer T

infer 顧名思義就是推斷(inference)
通常只會接在 extends 之後 
先來看一個範例會比較好解釋
例如:

```typescript
type Instance<T> = T extends new (...args: any[]) => infer R ? R : never
```

上述的語法可以這樣解釋 ```Instance<T>``` 為 
如果 T 是 new (...args: any[]) => infer R 時
則回傳 R 否則 報錯 (never)
而型別 R 為推斷結果的型別
這裡的推斷結果等同於 
前面提到的 ```{ [P in keyof T ]: T[P] }``` 中的 T[P] 指的是推斷T的成員名稱為 P的型別
所以這裡的 infer R 的意思就是指 
如果條件為真則回傳 new (...args: any[]) => R 中原本的回傳型別 R

所以, 當我們使用了 infer 之後
該次判斷的回傳結果可以用我們定義 infer 的變數所在位置的那個型別
即便我們根本無法明確標示他的型別
而這件事情極為重要
在接下來馬上就會用到

[官方網站解釋](https://www.typescriptlang.org/docs/handbook/advanced-types.html#distributive-conditional-types)

### 合併 (Union) 與相交 (intersection)

如何定義出多個型別的相交型別與合併型別?
也就是: ```[A,B,C....] => A | B | C |... or A & B & C &...```
如果是相交型別的話不會太困難 例如:

```typescript=
type Intersection<T> = T extends Array<infer E> ? E : never
```

依照官方文件表示 如果在一個共變數變量上的型別會以相交(也就是 or '|')來彼此進行運算
共變量變數的話 可以暫時理解成 任何非函數參數的變數
與共變量變數成對的名為 反變量變數 可以暫時理解成作為函數參數的變數
馬上來看一下什麼是反變量變數以及如何做出合併型別

```typescript
type Union<T> = T extends Array<infer E> 
  ? ((E extends any 
      ? ((arg: E) => void) 
      : never
    ) extends (
    (arg: infer I) => void)
      ? I 
      : never
    ) 
  : never;
```

這裡雖然比較複雜, 但可以讓我們重新理解一下之前看到的用法
首先是```T extends Array<infer E>```
可以描述為如果T 是 ```Array<infer E>``` 為真的話
那就回傳 E 所代表的型別 所以使用T的時候要帶入一個陣列(T[])或組元([T,T2])型別

再來是 ```T extends any``` 
就是 T 是 any 的話而這個必然為真
所以一定會回傳 ```(args: T) => void``` 這個型別
此時的T 就從原本的共變數變量變成了反變數變量 (從一般變數變成函數參數)

接著後面在一次判斷 ```((args: T) => void) extends (args: infer I) => void```
而這裡也可以很清楚的看到extends 型別可以是右邊的型別
而I要推斷的位置就是T

因為一開頭已經把一個Array型別變成一個單一的型別
這會導致最後Array中的每一個元素的型別要合併變成一個型別
而在過程中被推斷的型別E 又從共變數變成反變數
因此依照官方文件上的說明 反變數的合併方式為 and (也就是 '&')
因此這個結果就會變成把陣列中的所有型別都進行 and 的運算

我嘗試了幾種可能的類似寫法
幾乎只有這一種狀況才有可能做到合併
至於為何合併僅限定於反變數的原因

.
..
...目前想不到任何可能的關聯性 XD
希望之後有辦法找到為何這樣定義 

[官方網站解釋](https://www.typescriptlang.org/docs/handbook/advanced-types.html#distributive-conditional-types)


## 最後

該是來挑戰最後大魔王的時候了

```typescript
type TUnionToIntersection<U> =
  (U extends any ? ((k: U) => void) : never) extends
  ((k: infer I) => void ? I : never);
function merge<T extends IObject[]>(...objects: T): TUnionToIntersection<T[number]>;
```

解釋:
```TUnionToIntersection<U>```為
當U 是 any 時, 則回傳 ```((k: U) => void)``` 否則報錯
當上面判斷的回傳結果為 ```((k: infer I) => void)``` 時 則回傳 推斷出來的型別 I

以及 merge 函數為 泛型參數T 是 IObject 的陣列 參數為 REST 參數 (c# 的 params)
此時的 T 是陣列型別
最後 merge 函數的回傳值為最後 merge 函數的回傳值為 ```TUnionToIntersection<T[number]>```
當透過上述定義的 TUnionToIntersection 型別推斷方式依序如下
T[number] 是 any 所以 回傳 (k: T[number]) => void
以及 這個回傳如果是 (k: infer I) => void 則回傳推斷的 I 型別
因此 merge 函數就會依照他所帶入的參數數量 而逐步改變該函數的泛型型別中陣列中的型別個數
但不管事幾個 都是一個泛行參數
並且此泛行參數因為可透過期待入的函數參數型別推斷 所以可以不寫
在來回船型別因為 T[number] 的緣故會把每一個泛行參數中的陣列型別
一一帶入到 T一一帶入到 TUnionToIntersection 型別中
並且用 and 將彼此型別合併
所以在回傳的時候泛行參數中的陣列型別內的所有型別都在後面被展開後取聯集
並且同樣因為型別可被推斷 所以可以不寫
因此 當我們調用 merge 的時候 無論要merge幾個 object 我們總是可以得到最後最正確的強型別

[關於衍生的共變數與反變數 來自官網上的對外連結參考資料](https://www.stephanboyer.com/post/132/what-are-covariance-and-contravariance)


<style>
  /*--------------- view ---------------*/
  body[style],
  body[style*="background-color: white;"] {
    background-color: #1e1e1e !important;
  }

  body {
    color: #abb2bf;
  }

  .ui-view-area,
  .markdown-body,
  .ui-content {
    background: #1e1e1e;
    color: #abb2bf;
  }

  h1,
  h2,
  h3,
  h4,
  h5,
  h6,
  p {
    color: #ddd;
  }

  hr {
    border-color: #6d6d6d;
  }

  /* form */
  .form-control {
    background: #333;
    color: #fff;
  }

  .form-control::placeholder,
  .form-control::-webkit-input-placeholder,
  .form-control:-moz-placeholder,
  .form-control::-moz-placeholder,
  .form-control:-ms-input-placeholder {
    color: #eee;
  }

  /*--------------- navbar ---------------*/
  .header {
    background-color: #0e0e0e;
    border-color: #0e0e0e;
  }

  .navbar {
    background-color: #0e0e0e;
    border-color: #0e0e0e;
  }

  .navbar a {
    color: #eee !important;
  }

  .navbar .btn-group label {
    background-color: #0e0e0e;
    color: #eee;
    border-color: #555;
  }

  .navbar .btn-group label.btn-default:focus,
  .navbar .btn-group label.btn-default:hover {
    background-color: #2a2a2a;
    color: #eee;
    border-color: #555;
  }

  .navbar .btn-group label.active {
    background-color: #555;
    color: #eee;
    border-color: #555;
  }

  .navbar .btn-group label.active:focus,
  .navbar .btn-group label.active:hover {
    background-color: #555;
    color: #eee;
    border-color: #555;
  }

  .navbar-default .btn-link:focus,
  .navbar-default .btn-link:hover {
    color: #eee;
  }

  .navbar-default .navbar-nav>.open>a,
  .navbar-default .navbar-nav>.open>a:focus,
  .navbar-default .navbar-nav>.open>a:hover {
    background-color: #555;
  }

  .dropdown-header {
    color: #aaa;
  }

  .dropdown-menu {
    background-color: #222;
    border: 1px solid #555;
    border-top: none;
  }

  .dropdown-menu>li>a {
    color: #eee;
  }

  .dropdown-menu>li>a:focus,
  .dropdown-menu>li>a:hover {
    background-color: #555555;
    color: #eee;
  }

  .dropdown-menu .divider {
    background-color: #555;
  }

  .header .open .dropdown-menu {
    background-color: #202020;
  }

  .navbar .announcement-popover {
    background: #4F4F4F;
  }

  .navbar .announcement-popover .announcement-popover-header {
    background: #2e2e2e;
    border-bottom: 1px solid #2e2e2e;
  }

  .navbar .announcement-popover .announcement-popover-body {
    background: #4F4F4F;
    color: #eee;
  }

  .navbar .announcement-popover .announcement-popover-footer {
    background: #4F4F4F;
  }

  .navbar .announcement-area .caption.inverse {
    color: #eee;
  }

  .label-warning {
    background-color: #ffc107;
    color: #212529;
  }

  /*--------------- history / recent ---------------*/
  .list.row-layout li .item {
    border-color: #696c7d;
  }

  .list.row-layout li:nth-last-of-type(1) .item {
    border-bottom: none;
  }

  .list li .item {
    background: #1c1c1c;
  }

  .list li:hover .item,
  .list li:focus .item {
    background: #404040;
  }

  .list li .item h4 {
    color: #fff;
  }

  .list li p {
    color: #ccc;
  }

  .list li p i {
    font-style: normal;
  }

  .list li .item .content .tags span {
    background: #555;
  }

  .list li .item.wide .content .title a,
  .list li .item.wide .content .title a:focus,
  .list li .item.wide .content .title a:hover {
    color: #ddd;
  }

  .ui-item {
    color: #fff;
    opacity: 0.7;
  }

  .ui-item:hover,
  .ui-item:focus {
    opacity: 1;
    color: #fff;
  }

  .list li .item.wide hr {
    border-color: #6d6d6d;
  }

  .overview-widget-group .btn,
  .multi-select-dropdown-menu .ui-dropdown-label,
  .multi-select-dropdown-menu .dropdown-options,
  .form-control {
    border-color: #6d6d6d;
  }

  .multi-select-dropdown-menu .dropdown-options .ui-option:hover {
    background-color: #4d4d4d;
    color: #eee;
  }

  #overview-control-form #overview-keyword-input-container .select2-container {
    background-color: #3e4045 !important;
  }

  #overview-control-form #overview-keyword-input-container .select2-container .select2-choices {
    background-color: #3e4045;
  }

  .search {
    background-color: #3e4045;
    color: #eee;
  }

  .btn.btn-gray {
    background: #1b1b1b;
  }

  .btn.btn-gray:hover {
    background: #4d4d4d;
    color: #eee;
  }

  .search::placeholder,
  .search::-webkit-input-placeholder,
  .search:-moz-placeholder,
  .search::-moz-placeholder,
  .search:-ms-input-placeholder {
    color: #eee;
  }

  .btn.btn-gray {
    border-color: #6d6d6d;
    background: #333;
    color: #eee;
  }

  .select2-default {
    color: #eee !important;
  }

  .select2-results .select2-highlighted {
    background: #4d4d4d;
    color: #eee;
  }

  .select2-container-multi .select2-choices {
    background: #3e4045;
  }

  .select2-container-multi .select2-choices .select2-search-choice {
    background: #131313;
    color: #eee;
    border-color: #555;
    box-shadow: none;
  }

  .btn-default,
  .btn-default:focus {
    color: #eee;
    background-color: #2e2e2e;
    border-color: #6a6a6a;
  }

  .btn-default.active.focus,
  .btn-default.active:focus,
  .btn-default.active:hover,
  .btn-default:active.focus,
  .btn-default:active:focus,
  .btn-default:active:hover,
  .open>.dropdown-toggle.btn-default.focus,
  .open>.dropdown-toggle.btn-default:focus,
  .open>.dropdown-toggle.btn-default:hover {
    background: #737373;
  }

  .btn-default:hover {
    color: #fff;
    background-color: #7d7d7d;
    border-color: #6a6a6a;
  }

  .overview-widget-group .btn.active {
    background-color: #6a6a6a;
    color: #eee;
  }

  .overview-widget-group .btn:hover {
    background-color: #7d7d7d;
    color: #eee;
    border-color: #636363;
  }

  .overview-widget-group .slider.round {
    border-color: #ccc;
  }

  .overview-widget-group .slider.round:before {
    border-color: #ccc;
  }

  .overview-widget-group input:checked+.slider {
    background-color: #ccc;
  }

  .ui-category-description-icon a {
    color: #eee;
  }

  .item .ui-history-pin.active {
    color: #f00;
  }

  .ui-history-close {
    color: #eee;
    opacity: 0.5;
  }

  .pagination>li>a,
  .pagination>li>span {
    color: #eee;
    background-color: #2e2e2e;
    border-color: #6a6a6a;
  }

  .pagination>li>a:hover {
    color: #fff;
    background-color: #7d7d7d;
    border-color: #6a6a6a;
  }

  .pagination>.disabled>a,
  .pagination>.disabled>a:focus,
  .pagination>.disabled>a:hover,
  .pagination>.disabled>span,
  .pagination>.disabled>span:focus,
  .pagination>.disabled>span:hover {
    color: #eee;
    background-color: #2e2e2e;
    border-color: #6a6a6a;
  }

  .pagination.dark>li>a,
  .pagination.dark>li>span {
    color: #aaa;
  }

  /*--------------- settings ---------------*/
  .section .form-horizontal .form-group .btn-default {
    font-size: 16px;
    border-color: #6d6d6d;
    background-color: #333;
    color: #FFF;
  }

  .section .form-horizontal .form-group .btn-default:hover,
  .section .form-horizontal .form-group .btn-default:focus {
    background-color: #737373;
    color: #FFF;
  }

  .section .form-horizontal .form-control:focus {
    border-color: #bbb;
  }

  /*--------------- share view ---------------*/
  #notificationLabel,
  .ui-infobar .btn.ui-edit {
    color: #eee;
    border-color: #6a6a6a;
  }

  .ui-infobar__user-info li {
    color: #bbb;
  }

  footer {
    background: #101010;
    color: #bbb;
    border-top: 1px solid #454545;
  }

  footer a {
    color: #bbb;
  }

  /*--------------- doc view ---------------*/
  .markdown-body h1,
  .markdown-body h2,
  .markdown-body h3,
  .markdown-body h4,
  .markdown-body h5,
  .markdown-body h6,
  .markdown-body hr,
  #doc>h1 {
    color: #ddd;
    border-color: #777 !important;
  }

  .h1 .small,
  .h1 small,
  .h2 .small,
  .h2 small,
  .h3 .small,
  .h3 small,
  .h4 .small,
  .h4 small,
  .h5 .small,
  .h5 small,
  .h6 .small,
  .h6 small,
  h1 .small,
  h1 small,
  h2 .small,
  h2 small,
  h3 .small,
  h3 small,
  h4 .small,
  h4 small,
  h5 .small,
  h5 small,
  h6 .small,
  h6 small {
    color: #ddd;
  }

  .markdown-body p {
    color: #ddd;
  }

  .markdown-body a {
    color: #7bf;
  }

  .markdown-body a 
  
  
  
  
  
  
  
  
  
  
  {
    color: #7bf !important;
  }

  .markdown-body ul li,
  .markdown-body ol li {
    color: #ddd;
  }

  .markdown-body blockquote {
    color: #ddd;
    border-left-color: #777;
    font-size: 16px;
  }

  .markdown-body code,
  code {
    color: #dfdfdf !important;
    background-color: #424a55;
  }

  .markdown-body pre {
    background-color: #1e1e1e;
    border: 1px solid #555 !important;
    color: #dfdfdf;
  }

  blockquote .small,
  blockquote footer,
  blockquote small {
    color: #bbb;
  }

  .mark,
  mark {
    background-color: rgba(255, 255, 0, 0.32) !important;
    color: #ddd;
    margin: .1em;
    padding: .1em .2em;
  }

  /* Todo list */
  .task-list-item-checkbox {
    margin: 0.18em 0 0.2em -1.3em !important;
  }

  .task-list-item input[type=checkbox] {
    -webkit-appearance: none;
    -moz-appearance: none;
    appearance: none;
    position: relative;
    top: -1px;
    margin: 0 1rem 0 0;
    cursor: pointer;
  }

  .task-list-item input[type=checkbox]::before {
    -webkit-transition: all 0.1s ease-in-out;
    -moz-transition: all 0.1s ease-in-out;
    transition: all 0.1s ease-in-out;
    content: "";
    position: absolute;
    left: 0;
    z-index: 1;
    width: 16px;
    height: 16px;
    border: 2px solid #F44336;
  }

  .task-list-item input[type=checkbox]:checked::before {
    -webkit-transform: rotate(-48deg);
    -moz-transform: rotate(-48deg);
    -ms-transform: rotate(-48deg);
    -o-transform: rotate(-48deg);
    transform: rotate(-48deg);
    height: 9px;
    border-color: #00E676;
    border-top-style: none;
    border-right-style: none;
  }

  .task-list-item input[type=checkbox]::after {
    content: "";
    position: absolute;
    top: -0.125rem;
    left: 0;
    width: 16px;
    height: 16px;
    background: #333;
    cursor: pointer;
  }

  /* table */
  .markdown-body table tr {
    background-color: #1e1e1e;
    border-top: none;
    border-bottom: 1px solid rgba(255, 255, 255, 0.3);
  }

  .markdown-body table tr:first-child {
    border-top: 1px solid rgba(255, 255, 255, 0.2);
  }

  .markdown-body table tr:nth-child(2n) {
    background-color: #333;
  }

  .markdown-body table tr th {
    color: #64B5F6;
  }

  .markdown-body table th,
  .markdown-body table td {
    border: none;
  }

  .markdown-body table tr th:first-child,
  .markdown-body table tr td:first-child {
    border-left: 1px solid rgba(255, 255, 255, 0.1);
  }

  .markdown-body table tr th:last-child,
  .markdown-body table tr td:last-child {
    border-right: 1px solid rgba(255, 255, 255, 0.1);
  }

  .markdown-body table tr td {
    color: #ddd;
  }

  .markdown-body pre.flow-chart,
  .markdown-body pre.sequence-diagram,
  .markdown-body pre.graphviz,
  .markdown-body pre.mermaid,
  .markdown-body pre.abc {
    background-color: #fff !important;
  }

  /* alert */
  .alert h1,
  .alert h2,
  .alert h3,
  .alert h4,
  .alert h5,
  .alert h6,
  .alert p,
  .alert ul li,
  .alert ol li {
    color: #31708f;
  }

  .alert a {
    color: #002752;
    font-weight: 700;
  }

  .alert h1:first-child,
  .alert h2:first-child,
  .alert h3:first-child,
  .alert h4:first-child,
  .alert h5:first-child,
  .alert h6:first-child {
    margin-top: 0;
  }

  .markdown-body .alert>p {
    margin-top: 0px;
    margin-bottom: 10px;
  }

  .markdown-body .alert>ul,
  .markdown-body .alert>ol {
    margin-bottom: 16px;
  }

  .markdown-body .alert>*:last-child {
    margin-bottom: 0;
  }

  .alert-warning {
    background-color: #fff3cd;
    border-color: #ffeeba;
  }

  /* scroll bar */
  .ui-edit-area .ui-resizable-handle.ui-resizable-e {
    background-color: #303030;
    border: 1px solid #303030;
    box-shadow: none;
  }

  /* info bar */
  .ui-infobar {
    color: #999;
  }

  /* permission */
  .permission-popover-btn-group .btn.focus,
  .permission-popover-btn-group .btn:active,
  .permission-popover-btn-group .btn:focus,
  .permission-popover-btn-group .btn.active {
    background-color: #6a6a6a !important;
    color: #eee !important;
    border-color: #555 !important;
  }

  .permission-popover-btn-group .btn:hover,
  .permission-popover-btn-group .btn.active:hover {
    background-color: #7d7d7d !important;
    color: #eee !important;
    border-color: #636363 !important;
  }

  .ui-delete-note a:hover,
  .ui-delete-note a:focus,
  .ui-delete-note a:active {
    background-color: #dc3545 !important;
  }

  .ui-invitee-invite {
    border-color: #6a6a6a !important;
  }

  .ui-invitee-invite:hover,
  .ui-invitee-invite:focus {
    background-color: #737373;
    color: #eee !important;
  }

  .ui-invitee.ui-invitee-list .ui-invitee-remove,
  .ui-invitee.ui-invitee-list .ui-invitee-remove:hover,
  .ui-invitee.ui-invitee-list .ui-invitee-remove:focus,
  .ui-invitee.ui-invitee-list .ui-invitee-remove:active {
    background-color: #dc3545;
    border: 1px solid #dc3545;
  }

  .select2-container {
    background: #202020;
  }

  .select2-container-multi .select2-choices .select2-search-field input {
    color: #eee;
  }

  .select2-container-multi .select2-choices .select2-search-field input.select2-active {
    color: #000;
  }

  .select2-drop {
    background: #202020;
    color: #eee;
  }

  .select2-results .select2-no-results,
  .select2-results .select2-searching,
  .select2-results .select2-ajax-error,
  .select2-results .select2-selection-limit {
    background: #202020;
  }

  /* table of contents block*/
  .ui-toc-dropdown {
    width: 42vw;
    max-height: 90vh;
    overflow: auto;
    text-align: inherit;
  }

  /* table of contents text*/
  .ui-toc-dropdown .nav>li>a {
    font-size: 14px;
    font-weight: bold;
    color: #ddd;
  }

  /* table of contents text: active*/
  .ui-toc-dropdown .nav>.active:focus>a,
  .ui-toc-dropdown .nav>.active:hover>a,
  .ui-toc-dropdown .nav>.active>a {
    color: #7bf;
    border-left-color: #7bf;
  }

  /* table of contents text: focus, hover*/
  .ui-toc-dropdown .nav>li>a:focus,
  .ui-toc-dropdown .nav>li>a:hover {
    color: #7bf;
    border-left-color: #7bf;
  }

  /* drop down floating table of contents */
  .ui-toc-dropdown.dropdown-menu {
    background: #333;
  }

  .toc-menu a {
    color: #ddd;
  }

  .toc-menu a:focus,
  .toc-menu a:hover {
    color: #7bf;
  }


  /*--------------- editor ---------------*/
  .cm-m-markdown {
    color: #ddd;
  }

  .cm-s-one-dark .cm-header,
  .cm-m-xml.cm-attribute {
    color: #ffa653;
  }

  .cm-s-one-dark .cm-string,
  .cm-s-one-dark .cm-variable-2 {
    color: #7bf;
  }

  .cm-m-markdown.cm-variable-3 {
    color: #ff7e7e;
  }

  .cm-s-one-dark .cm-link {
    color: #b0ee83;
  }

  .cm-s-one-dark .CodeMirror-linenumber {
    color: #666;
  }

  .cm-strong {
    color: #f4511e;
  }

  .cm-s-one-dark .cm-comment {
    color: #a9a9a9;
  }

  .cm-matchhighlight {
    color: #ffea00;
  }

  .cm-positive {
    color: #11bf64;
  }

  .cm-negative {
    color: #ff3e3e;
  }

  .dropdown-menu.CodeMirror-other-cursor {
    border: 2px solid #4d4d4d;
    background-color: #202020;
  }

  .dropdown-menu.CodeMirror-other-cursor li a {
    color: #ececec;
  }

  /*--------------- book mode ---------------*/
  .topbar {
    background: #1e1e1e;
  }

  .btn.focus,
  .btn:focus,
  .btn:hover {
    color: #aaa;
  }

  .summary {
    background: #1e1e1e;
  }

  .summary,
  .toolbar {
    background: #1e1e1e !important;
    border-color: #4d4d4d !important;
  }

  .toolbar i {
    color: #fff;
  }

  .summary h1,
  .summary h2,
  .summary h3 .summary hr {
    color: #ddd;
    border-color: #777 !important;
  }

  .summary .nav>li>a {
    color: #7bf;
  }

  .summary .nav-pills>li.active>a,
  .summary .nav-pills>li.active>a:focus,
  .summary .nav-pills>li.active>a:hover {
    color: #ff9100;
  }

  .ui-summary-search {
    font-size: 16px;
    border: 1px solid #6D6D6D;
    background-color: #333;
    color: #FFF;
  }

  .summary h1,
  .summary h2,
  .summary h3,
  .summary h4,
  .summary h5,
  .summary h6 {
    border-color: #454545;
  }

  /* fix body background color to dark */
  div[class$=container-mask] {
    background: #1e1e1e;
    z-index: 1;
    display: block;
  }

  /* notification */
  .dropdown.ui-notification .ui-notification-label,
  .dropdown.ui-invitee .ui-invitee-label {
    color: #eee;
    border-color: #6a6a6a;
  }

  .ui-notification .dropdown-menu {
    border-top: 1px solid #555;
  }

  /*--------------- help ---------------*/
  .modal-header {
    background-color: #2a2a2a;
  }

  .panel-default {
    border-color: #6d6d6d;
  }

  .panel-default>.panel-heading {
    background-color: #2a2a2a;
    color: #eee;
    border-color: #6d6d6d;
  }

  .panel-body {
    background: #2e2e2e;
  }

  .panel-body a {
    color: #7bf;
  }

  .table>tbody>tr>td,
  .table>tbody>tr>th,
  .table>tfoot>tr>td,
  .table>tfoot>tr>th,
  .table>thead>tr>td,
  .table>thead>tr>th {
    border-color: #6d6d6d;
  }

  /*--------------- comment ---------------*/
  .ui-comment-container .ui-comment-header {
    background-color: #2a2a2a;
    color: #eee;
    border-color: #6d6d6d;
  }

  .ui-comment-container {
    background-color: #2e2e2e;
    border-color: #6d6d6d;
  }

  .ui-comment-container .ui-comments-container .ui-comment .comment-author {
    color: #eee;
  }

  .ui-comment-container .ui-comments-container .ui-comment .timestamp {
    color: #aaa;
  }

  .ui-comment-container .ui-comments-container .ui-comment .comment-content {
    color: #eee;
  }

  .ui-comment-container .ui-comments-container .ui-comment .comment-menu {
    color: #eee;
  }

  .ui-comment-container .ui-comments-container .ui-comment .comment-menu .comment-dropdown-menu {
    background: #222;
    color: #eee;
    border-color: #555;
  }

  .ui-comment-container .ui-comments-container .ui-comment .comment-menu .comment-dropdown-menu>div:hover {
    background-color: #555555;
    color: #eee;
  }

  .ui-comment-container .ui-comments-container .ui-comment .comment-menu:hover,
  .ui-comment-container .ui-comments-container .ui-comment .comment-menu:active,
  .ui-comment-container .ui-comments-container .ui-comment .comment-menu.active {
    background-color: #737373;
    color: #eee;
  }

  .ui-comment-container .ui-comment-input-container {
    background-color: #3c3c3c;
  }

  .ui-comment-container textarea {
    background-color: #3e4045;
    color: #eee;
    border: 1px solid #6d6d6d;
  }

  .ui-comment-container textarea::placeholder,
  .ui-comment-container textarea::-webkit-input-placeholder,
  .ui-comment-container textarea:-moz-placeholder,
  .ui-comment-container textarea::-moz-placeholder,
  .ui-comment-container textarea:-ms-input-placeholder {
    color: #eee;
  }

  @keyframes highlight {
    0% {
      background-color: #3c3c3c;
    }

    30% {
      background-color: #3c3c3c;
    }

    100% {
      background-color: transparent;
    }
  }

  /*--------------- template ---------------*/
  .template-content .modal-header {
    background: #2a2a2a;
  }

  .template-content .close {
    color: #fff;
  }

  .template-content .modal-title {
    color: #eee;
  }

  .template-content .ui-templates-container {
    border-color: #6d6d6d;
  }

  .ui-templates-container .ui-create-template-btn {
    background: #446fab;
    color: #fff;
  }

  .ui-template-list-filter .ui-template-list-filter-label,
  .ui-template-list-filter .ui-template-list-filter-label:hover {
    color: #eee;
  }

  .ui-template-list .list-group-item.active {
    background: #4d4d4d;
  }

  .ui-template-list .list-group-item.active:focus {
    background: #4d4d4d !important;
  }

  .list-group-item.active,
  .list-group-item.active:focus,
  .list-group-item.active:hover {
    color: #eee;
  }

  .ui-template-list .list-group-item .list-group-item-heading {
    color: #eee;
  }

  .ui-template-list .list-group-item.active .list-group-item-heading {
    color: #eee;
  }

  .ui-template-list .list-group-item:hover {
    background: #4d4d4d !important;
  }

  .ui-template-item-menu {
    color: #eee !important;
  }

  .ui-template-list .list-group-item {
    color: #fff;
  }

  .ui-template-list .list-group-item .dropdown-container.open {
    background-color: #2a2a2a;
  }

  .ui-template-list .list-group-item .dropdown-container:hover {
    background-color: #2a2a2a !important;
  }

  .template-menu .more-template {
    border-color: #6d6d6d;
  }

  .template-menu .more-template:hover {
    color: #eee;
    border-color: #6d6d6d;
  }

  /*--------------- code mirror ---------------*/
  .modal-content {
    background: #1f2226;
  }

  .modal-header {
    border-bottom: 1px solid #46484f;
  }

  .modal-footer {
    border-top: 1px solid #46484f;
  }

  a.list-group-item {
    background: #1f2226;
    color: #ddd;
    border: 1px solid #46484f;
  }

  a.list-group-item .list-group-item-heading {
    color: #ddd;
  }

  a.list-group-item:focus,
  a.list-group-item:hover {
    background: #434651;
    color: #ddd;
  }

  button.close {
    color: #ddd;
    opacity: .5;
  }

  .close:focus, .close:hover {
    color: #fff;
    opacity: .8;
  }

  .CodeMirror {
    background: #1f2226;
  }

  .CodeMirror-gutters {
    background: #1f2226;
    border-right: 1px solid rgba(204, 217, 255, 0.1);
  }

  .cm-s-default .cm-comment {
    color: #888;
  }

  .cm-s-default .cm-quote {
    color: #ddd;
  }

  .cm-s-default .cm-header {
    color: #ffa653;
  }

  .cm-s-default .cm-link {
    color: #b0ee83;
  }

  .cm-s-default .cm-string,
  .cm-s-default .cm-variable-2 {
    color: #7bf;
  }

  .cm-s-default .cm-def {
    color: #c678dd;
  }

  .cm-s-default .cm-number,
  .cm-s-default .cm-attribute,
  .cm-s-default .cm-qualifier,
  .cm-s-default .cm-plus,
  .cm-s-default .cm-atom {
    color: #eda35e;
  }

  .cm-s-default .cm-property,
  .cm-s-default .cm-variable,
  .cm-s-default .cm-variable-3,
  .cm-s-default .cm-operator,
  .cm-s-default .cm-bracket {
    color: #f76e79;
  }

  .cm-s-default .cm-keyword,
  .cm-s-default .cm-builtin,
  .cm-s-default .cm-tag {
    color: #98c379;
  }

  .modal-title {
    color: #ccc;
  }

  .modal-body {
    color: #ccc !important;
  }

  div[contenteditable]:empty:not(:focus):before {
    color: #aaa;
  }

  .CodeMirror pre {
    color: #ddd;
  }

  .CodeMirror pre span[style^="background-color: rgb(221, 251, 230)"] {
    background-color: #288c27 !important;
  }

  .CodeMirror pre span[style^="background-color: rgb(249, 215, 220)"] {
    background-color: #a52721 !important;
  }

  /*------- code highlight: Visual Stutdio Code theme for highlight.js -------*/
  .hljs {
    background: #1E1E1E;
    color: #DCDCDC;
  }

  .hljs-keyword,
  .hljs-literal,
  .hljs-symbol,
  .hljs-name {
    color: #569CD6;
  }

  .hljs-link {
    color: #569CD6;
    text-decoration: underline;
  }

  .hljs-built_in,
  .hljs-type {
    color: #4EC9B0;
  }

  .hljs-number,
  .hljs-class {
    color: #B8D7A3;
  }

  .hljs-string,
  .hljs-meta-string {
    color: #D69D85;
  }

  .hljs-regexp,
  .hljs-template-tag {
    color: #d16969;
  }

  .hljs-title {
    color: #dcdcaa;
  }

  .hljs-subst,
  .hljs-function,
  .hljs-formula {
    color: #DCDCDC;
  }

  .hljs-comment,
  .hljs-quote {
    color: #57A64A;
  }

  .hljs-doctag {
    color: #608B4E;
  }

  .hljs-meta,
  .hljs-meta-keyword,
  .hljs-tag {
    color: #9B9B9B;
  }

  .hljs-variable,
  .hljs-template-variable {
    color: #BD63C5;
  }

  .hljs-params,
  .hljs-attr,
  .hljs-attribute,
  .hljs-builtin-name {
    color: #9CDCFE;
  }

  .hljs-section {
    color: gold;
  }

  .hljs-emphasis {
    font-style: italic;
  }

  .hljs-strong {
    font-weight: bold;
  }

  /*
  .hljs-code {
    font-family:'Monospace';
  }
  */

  .hljs-bullet,
  .hljs-selector-tag,
  .hljs-selector-id,
  .hljs-selector-class,
  .hljs-selector-attr,
  .hljs-selector-pseudo {
    color: #D7BA7D;
  }

  .hljs-addition {
    background-color: #155a36;
    color: #dfdfdf;
    display: inline-block;
    width: 100%;
  }

  .hljs-deletion {
    background-color: #872e2e;
    color: #dfdfdf;
    display: inline-block;
    width: 100%;
  }

  /*---------- code highlight: Visual Stutdio Code theme for Prism.js ----------*/
  code[class*="language-"],
  pre[class*="language-"] {
    color: #DCDCDC;
  }

  :not(pre)>code[class*="language-"],
  pre[class*="language-"] {
    background: #1E1E1E;
  }

  .token.comment,
  .token.block-comment,
  .token.prolog,
  .token.cdata {
    color: #57A64A;
  }

  .token.doctype,
  .token.punctuation {
    color: #9B9B9B;
  }

  .token.tag,
  .token.entity {
    color: #569CD6;
  }

  .token.attr-name,
  .token.namespace,
  .token.deleted,
  .token.property,
  .token.builtin {
    color: #9CDCFE;
  }

  .token.function,
  .token.function-name {
    color: #dcdcaa;
  }

  .token.boolean,
  .token.keyword,
  .token.important {
    color: #569CD6;
  }

  .token.number {
    color: #B8D7A3;
  }

  .token.class-name,
  .token.constant {
    color: #4EC9B0;
  }

  .token.symbol {
    color: #f8c555;
  }

  .token.rule {
    color: #c586c0;
  }

  .token.selector {
    color: #D7BA7D;
  }

  .token.atrule {
    color: #cc99cd;
  }

  .token.string,
  .token.attr-value {
    color: #D69D85;
  }

  .token.char {
    color: #7ec699;
  }

  .token.variable {
    color: #BD63C5;
  }

  .token.regex {
    color: #d16969;
  }

  .token.operator {
    color: #DCDCDC;
    background: transparent;
  }

  .token.url {
    color: #67cdcc;
  }

  .token.important,
  .token.bold {
    font-weight: bold;
  }

  .token.italic {
    font-style: italic;
  }

  .token.entity {
    cursor: help;
  }

  .token.inserted {
    color: green;
  }

  /*---------- code highlight: dark theme for Gist ----------*/
  .gist .gist-file {
    border: 1px solid #555;
  }

  .gist .gist-data {
    background-color: #1e1e1e;
    border-bottom: 1px solid #555;
  }

  .gist .gist-meta {
    background-color: #424a55;
    color: #eee;
  }

  .gist .gist-meta a {
    color: #eee;
  }

  .gist .highlight {
    color: #eee;
    background-color: #1e1e1e;
  }

  .gist .blob-num {
    color: #afafaf;
  }

  .gist .blob-code-inner {
    color: #dfdfdf;
  }

  .pl-mb {
    color: #fff !important;
  }

  .pl-c {
    color: #57A64A !important;
  }

  /* comment */
  .pl-ent {
    color: #569CD6 !important;
  }

  /* entity */
  .pl-e {
    color: #9CDCFE !important;
  }

  .pl-en {
    color: #4EC9B0 !important;
  }

  /* entity attribute */
  .pl-smi {
    color: #9CDCFE !important;
  }

  .pl-k {
    color: #569cd6 !important;
  }

  .pl-c1,
  .pl-s .pl-v {
    color: #4EC9B0 !important;
  }

  .pl-pds,
  .pl-s,
  .pl-s .pl-pse .pl-s1,
  .pl-sr,
  .pl-sr .pl-cce,
  .pl-sr .pl-sra,
  .pl-sr .pl-sre,
  .pl-s .pl-s1 {
    color: #D69D85 !important;
  }

  .pl-s .pl-s1 .pl-pse {
    color: #c5dbff !important;
  }

  /* strings */
  .diff-table .pl-c,
  .diff-table .pl-ent,
  .diff-table .pl-e,
  .diff-table .pl-en,
  .diff-table .pl-pds,
  .diff-table .pl-s,
  .diff-table .pl-s .pl-s1,
  .diff-table .pl-s .pl-pse .pl-s1,
  .diff-table .pl-sr,
  .diff-table .pl-sr .pl-cce,
  .diff-table .pl-sr .pl-sra,
  .diff-table .pl-sr .pl-sre,
  .diff-table .pl-k,
  .diff-table .pl-smi,
  .diff-table .pl-c1,
  .diff-table .pl-v {
    color: #eee !important;
  }
</style>