>[danger] **棄用提醒:**
> *由于看云對于免費用戶的限制愈發嚴苛,本文檔已經遷移至語雀。本文檔將不做維護。*
> **語雀地址**:[https://www.yuque.com/a632079/nodebb](https://www.yuque.com/a632079/nodebb)
*****
# 主題制作
## 導言

NodeBB 作為一個比較成熟的 BBS 開源程序,當然具備了自定義主題的功能。NodeBB 采用 **Express** 作為其 Web 服務底層,模板引擎則采用自寫的 **benchpressjs** (還單獨寫了 rust 實現的編譯加速版),性能十分出色。
和 DiscuzX 類似, NodeBB 采用了一種替換式的方案實現了主題的更換。換句話說, 你只需要在自己的主題目錄中重寫與渲染路徑對應的模板文件即可使渲染結果生效。
**在開始之前,你需要知道**: NodeBB 初版采用 Bootstrap 作為其前端 UI 基礎,這也是當今 Persona 主題的前身。而對于 Bootstrap, 我們都應了解: 這是一個非常優秀的前端框架,但是其傳染性也十分的強。通常,這會限制我們在主題中的自由發揮。雖然 NodeBB 支持主題使用其他的框架,但是我們**必須**實現 bootstrap 3 的柵格引擎以適配第三方插件的自帶模板。
[TOC]
## 了解 NodeBB 主題的內在原理
NodeBB 主題其實就是一種特殊的插件。它具備插件的所有特性(有關插件的特性,請參考前一章節)。與插件最大的不同, 它具備了模板替換的特性。
NodeBB 將主題劃分為兩種: **原生主題** 和 **子主題**。
所謂**原生主題**就是如 **persona** 這種,完整實現了 NodeBB 路由所需的所有模板的主題。
而**子主題**必須是基于**原生主題**構建的,子主題可以只修改特定的模板文件,缺省其他文件,使 NodeBB 自動調用回滾機制,在渲染時調用原生主題的模板文件。我們將在稍后詳細講解子主題。
>[info] **子主題** 和 **原生主題** 之間的關系和子類和母類的關系很像。子主題繼承了原生主題的所有,包括 scripts,靜態資源等等。
### NodeBB 中主題識別的方式
和插件一樣,NodeBB 為了鼓勵主題的流通,使用 npm 作為其主題的存儲媒介。這意味著大部分主題可以很簡單得通過如下方式安裝:
```
$ yarn add nodebb-theme-name
```
NodeBB 要求主題必須以 `nodebb-theme-`作為前綴,并且至少以下兩個文件:
* `theme.json`
* `theme.less`
否則, 你的主題將不會被 NodeBB 識別。
`theme.less` 是你主題的入口樣式。它是主題重要的樣式文件。它通常由 less 編寫, 你可以在這里使用任何的 less 語法。并且子主題可以修改父主題的 less 變量。
>[warning] 建議將`theme.less` 當成單純的入口文件。將樣式按模塊拆分,再使用`@import`引入。而不是一股腦的全塞這兒。
### 配置 `theme.json`
和 `plugin.json` 類似, `theme.json`中包含主題的配置信息。以下是一個例子:
```
{
"id": "nodebb-theme-persona",
"name": "Persona",
"description": "The default theme for NodeBB. Uses a standard approach to forum design.",
"url": "https://github.com/psychobunny/nodebb-theme-persona",
"screenshot": "screenshot.png"
}
```
* `id` 主題的標識。通常建議以 `nodebb-theme-name` 的形式命名
* `name` 主題的名稱。例如:“仿 MiUI 社區主題”
* `description` 主題的描述。簡單講解主題的作用,功能。
* `screenshot` 主題預覽圖片。值應是一個和`theme.json`同目錄的文件名。圖像最好比例為 1.48:1
* `url` 可以鏈接到主題的主頁,介紹頁。
* `templates` 該項可選,主題文件夾名。值應是一個相對路徑的文件夾。默認為:“templates”
* `baseTheme` 該項可選,父主題標識。如果未定義,將以 `nodebb-theme-persona` 或者當前選擇的主題作為父主題。更多細節請參考 子主題 一節。
## 子主題
### CSS / LESS
子主題可以直接合并原生主題的 CSS/LESS 資源。
當你指定主題 `nodebb-theme-persona` 作為子主題的父主題時,像 `topic.less` 這些 `persona` 中存在的 CSS/LESS 資源將自動合并到你的子主題中。
### 模板
和前文所述一樣, 子主題將自動引入父主題的所有模板文件。所以,你無需定義所有的模板文件,只需要創建必要的主題文件。
>[success] 需要注意的是: `nodebb-theme-persona` 默認是所有主題的父主題。因此,你也可以在“原生主題”中享受自動繼承的便利。
## 有關 **皮膚** 的說明

NodeBB 在線丟人。 主題目前還不支持自定義皮膚哦!
## 模板引擎
### 原理
每個頁面都有相對應的 API 接口,主題文件以及 i18n 文件。
例如,當你訪問 `/topic/351/nodebb-wiki` 時,NodeBB會返回三個資源。API 返回 `/api/topic/351/nodebb-wiki` 相對應的模板文件(本例中為:`topic.tpl`),以及相對應的語言文件(本例中為:`topic.json`)
> 頁面名稱是和模板相對應的。 例如: `/topic/xyz` 對應 `topic.tpl`
你可以簡單的再路由前加上 `/api` 前綴以查閱其 JSON 返回。這些數據便是模板處理的關鍵數據。
### 基礎
如前節所說, NodeBB 渲染模板的實質是對 API 返回數據的渲染。因此,你可以在模板中使用任何能在 API 中得到的數據。
以 `/api/topic` 為例,你可以在模板中這樣打印值:
```
{topic_name}
```
訪問對象中的值:
```
{privileges.read}
```
并且你可以像這樣遍歷數組:
```
<!-- BEGIN posts -->
{posts.content}
<!-- END posts -->
```
這會基于數組 posts ,生成 X 個塊。
### 流程控制
NodeBB 的模板系統實現了一些基本邏輯。繼續我們剛才的例子,你可以這樣控制流程:
```
<!-- IF unreplied -->
This thread is unreplied!
<!-- ENDIF unreplied -->
```
或者這樣:
```
<!-- IF !disableSocialButtons -->
<button>Share on Facebook</button>
<!-- ELSE -->
Sharing has been disabled.
<!-- ENDIF !disableSocialButtons -->
```
我們可以像這樣,檢測數組的長度:
```
<!-- IF posts.length -->
There be some posts
<!-- ENDIF posts.length -->
```
當遍歷數組時,我們可以使用 `@first` 和 `@last` 確定引索:
```
<!-- BEGIN posts -->
<!-- IF @first -->
<h1>Main Author: {posts.username}</h1>
<!-- ENDIF @first -->
{posts.content}
<!-- IF @last -->
End of posts. Click here to scroll to the top.
<!-- ENDIF @last -->
<!-- END posts -->
```
想要更深入得了解模板引擎?[https://github.com/benchpressjs/benchpressjs](https://github.com/benchpressjs/benchpressjs)
### 向客戶端 JS 傳遞模板參數
除了 Socket.IO,我們還擁有兩種方法能向客戶端 JS 傳遞參數。
#### 通過 jQuery.get
如果我們需要獲得其他頁面的數據, 我們可以使用 `$.get` 請求 API 接口獲得數據。例如,我們可以像這樣獲得一個用戶的數據:
~~~
$.get(RELATIVE_PATH + '/api/user/psychobunny', {}, function(user) {
console.log(user)
});
~~~
API 返回的數據大致為這樣:[https://community.nodebb.org/api/user/psychobunny](https://community.nodebb.org/api/user/psychobunny)
#### 通過模板參數
在 `topic.tpl` 中, 我們像這樣添加了一個不可見(hidden)的輸入框(input):
```
<input type="hidden" template-variable="pageCount" value="{pageCount}" />
```
而在 JS 中,我們只需這樣,即可獲得值:
```
ajaxify.variables.get('pageCount');
```
這是讓 JS 知道模板中重要變量的理想方式。
### 國際化
模板引擎被設計為和國際化系統相耦合,變量可以嵌入語言字符串中。讓我們以[該 API 請求](https://community.nodebb.org/api/register) 和 [語言文件](https://community.nodebb.org/assets/language/zh-CN/register.json) 為例:
```
[[register:help.username_restrictions, {minimumUsernameLength}, {maximumUsernameLength}]]
```
他將會翻譯文本:
```
A unique username between %1 and %2 characters
```
成
```
全局唯一的用戶名,長度 2 到 12 個字。
```
### 進階
#### 從客戶端動態獲取并渲染模板
模板引擎根據需要延遲加載模板并緩存它們。如果你的代碼需要按需加載模板(或是部分按需),可以這樣加載:
```
ajaxify.loadTemplate('myTemplate', function(myTemplate) { // 獲得模板內容
const html = templates.parse(myTemplate, myData) // 渲染成 HTML
})
```
你還可以訪問模板中獨立的塊。這在更新`<ul>`塊中的`<li>`部分時,十分有用。
```html
Some stuff here...
<!-- BEGIN posts -->
We just want to pull this block only.
<!-- END posts -->
... some stuff here
```
```javascript
ajaxify.loadTemplate('myTemplate', function(myTemplate) {
const block = templates.getBlock(myTemplate, 'posts')
const html = templates.parse(block, myData)
})
```
#### 在服務端渲染模板
與大多數模板引擎一樣,服務端中模板引擎與 Express 深度耦合。 只需要使用 `app.render` 或 `res.render` 即可渲染模板。
```
res.render('myTemplate', myData)
app.render('myTemplate', myData, function(err, parsedTemplate) {
console.log(parsedTemplate)
});
```
>[info] `res.render` 會將渲染的結果直接發送給瀏覽器。而 `app.render` 則是渲染后將結果傳遞給回調方法。
## 使用工具包快速開始
NodeBB 官方提供了一個[主題工具包](https://github.com/nodebb/nodebb-theme-quickstart)方便快速開始一個主題。這個工具包由于是基于ES5,回調風格編寫的,所以我們不推薦使用。
我們在保持與官方工具包風格大致一致的情況下,與插件一樣,我們也提供了一個主題工具包:[https://github.com/nodebb-china/nodebb-theme-quickstart](https://github.com/nodebb-china/nodebb-theme-quickstart)。
接下來的涉及工具包的內容,我們將使用我們自己的工具包作為例子講解。
嗯,為了方便開發,和官方工具包一樣,我們的主題工具包是一個基于 **主題:Persona** 的子主題,它會自動從 **主題:Persona** 補全所缺省的模板文件。
當然,我們也可以完整實現一個 NodeBB 主題。只需要簡單克隆 [Persona 倉庫](https://github.com/nodebb/nodebb-theme-persona
) ,修改相關信息, 對照結構完成主題就好了。是不是很簡單呢?
更多相關工具包的使用說明,我們將在稍后講解。
>[info] 編寫: a632079
維護: PA Team
審核: PA Team
最后更新: 2019.12.09
- 序
- 贊助
- 導言
- 安裝
- 通過操作系統
- Windows + Mongodb/Redis
- Ubuntu/Debian + Redis/Mongodb
- CentOS + Redis
- CentOS + Mongodb
- FreeBSD/OpenBSD + Redis
- Arch Linux + Redis
- OSX + Redis
- 通過云服務
- 通過主機面板安裝
- AppNode
- CPanel
- 寶塔
- 使用
- FAQ
- 高級
- 運行 NodeBB
- 配置 Config.json
- 配置 Nginx
- 配置 MongoDB
- 更新 NodeBB
- 設置 Widgets
- 安裝 Yarn
- 更新 MongoDB
- 數據庫備份與恢復
- 重置管理員密碼
- 讓 NodeBB 支持搜索
- 優化
- 優化配置,提升NodeBB處理能力
- Google字體庫 -> 360公共前端庫
- Google字體庫 -> 中科大鏡像
- 海外VPS提升NodeBB訪問速度
- 通過 NodeBB API 自動發帖
- 開發
- 準備
- 常用方法 & 變量
- 插件制作
- 使用工具包編寫一個插件
- 主題制作
- 使用工具包編寫一個主題
- 部件制作
- 國際化
- 鉤子(hook)使用說明