diff --git a/README.md b/README.md index ec866206..74288e0e 100644 --- a/README.md +++ b/README.md @@ -44,7 +44,7 @@ Quick Reference 非常感谢一直以来支持我开源项目的朋友们!如果您认可我的工作,欢迎通过 [赞助](https://wangchujiang.com/#/sponsor) 我或下载并使用我开发的 [macOS 应用](https://wangchujiang.com/#/app) 来支持我。以下是我个人独立开发的 macOS 应用列表:

- Keyzer + Keyzer Vidwall Hub VidCrop Vidwall @@ -147,6 +147,7 @@ Quick Reference [Kubernetes](./docs/kubernetes.md) [LaTeX](./docs/latex.md) [Laravel 8](./docs/laravel.md) +[Leaf](./docs/leaf.md) [Lua](./docs/lua.md) [Markdown](./docs/markdown.md) [MATLAB](./docs/matlab.md) diff --git a/appicon/keyzer.png b/appicon/keyzer.png new file mode 100755 index 00000000..5f3fbfa7 Binary files /dev/null and b/appicon/keyzer.png differ diff --git a/assets/leaf.svg b/assets/leaf.svg new file mode 100644 index 00000000..0f80d39b --- /dev/null +++ b/assets/leaf.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/leaf.md b/docs/leaf.md new file mode 100644 index 00000000..9e675ec9 --- /dev/null +++ b/docs/leaf.md @@ -0,0 +1,474 @@ +Leaf 备忘清单 +==== + +[Leaf](https://github.com/vapor/leaf) 是 [Vapor](https://github.com/vapor/vapor) 的轻量级模板引擎,用于在服务端生成动态 HTML 页面。 + +入门 +---- + +### Leaf + +`Leaf` 是一种强大的模板语言,其语法受 `Swift` 启发。 + +- [Leaf 模板语言官方文档](https://docs.vapor.codes/zh/leaf/getting-started/) _(vapor.codes)_ +- [**LeafKit**:Swift 模板引擎库](https://github.com/vapor/leaf-kit) _(github.com)_ +- [**Leaf**:LeafKit 的 Vapor 集成模板系统](https://github.com/vapor/leaf) _(github.com)_ + +### Package + + +```swift +// swift-tools-version:5.2 +import PackageDescription + +let package = Package( + name: "MyApp", + platforms: [ .macOS(.v10_15) ], + dependencies: [ /// 添加其它依赖 + .package(url: "https://github.com/vapor/leaf.git", from: "4.0.0"), + ], + targets: [ + .target(name: "App", dependencies: [ .product(name: "Leaf", package: "leaf") ]), + ] +) +``` + +### 配置 + +```swift +import Vapor +import Leaf +``` + +设置工作目录 + +```swift +app.directory.workingDirectory = "...." +``` + +设置模板目录 + +```swift +app.directory.viewsDirectory = "...." +``` + +设置模板引擎 + +```swift +app.views.use(.leaf) +``` + +### 目录结构 + +``` +VaporApp +├── Package.swift +├── Resources +│ ├── Views +│ │ └── hello.leaf +├── Public +│ ├── images (images 资源) +│ ├── styles (css 资源) +└── Sources + └── ... +``` + +- Views 文件夹来存储 `.leaf` 文件 +- 配置 [`FileMiddleware`](https://api.vapor.codes/vapor/documentation/vapor/filemiddleware/) 提供静态文件 + +```swift +app.middleware.use(FileMiddleware( + publicDirectory: + app.directory.publicDirectory +)) +``` + +### 渲染视图 + +```swift +app.get("hello") { + req -> EventLoopFuture in + return req.view.render("hello", [ + "name": "Leaf" + ]) +} +// 或 +app.get("hello") { + req async throws -> View in + return try await req.view.render( + "hello", ["name": "Leaf"] + ) +} +``` + +在 `hello.leaf` 模板中使用 `name` + +``` +Hello, #(name)! +``` + +打开浏览器访问 `/hello` 显示 `Hello, Leaf!`。 + +## Leaf 概述 + +### 模板语法 + +一个基本的 `Leaf` 标签使用示例 + +```swift +There are #count(users) users. +``` + +可以使用冒号和结束标签为某些标签提供可选的正文。 + +- 标记 `#`:这表示 leaf 解析器开始寻找的标记。 +- 名称 `count`:标签的标识符。 +- 参数列表 (`users`):可以接受零个或多个参数。 + +### 内置标签示例 + +```html +#(variable) +#extend("template"): 添加到模板中!#endextend +#export("title"): 欢迎使用 Vapor #endexport +#import("body") +#count(friends) +#for(friend in friends): +

  • #(friend.name)
  • +#endfor +``` + +### 表达式 + +- `+` +- `%` +- `>` +- `==` +- `||` + + +```leaf +#if(1 + 1 == 2): + Hello! +#endif + +#if(index % 2 == 0): + This is even index. +#else: + This is odd index. +#endif +``` + +### 上下文 + + +Leaf 推荐用 `Encodable` 结构体传数据,数组需包装,`[String: Any]` 不支持。 + +```swift +struct WelcomeContext: Encodable { + var title: String + var numbers: [Int] +} +return req.view.render("home", + WelcomeContext( + title: "Hello!", + numbers: [42, 9001] + ) +) +``` + +`title` 和 `numbers` 将暴露给 `Leaf` 模板,就可以在标签中使用这些变量。 + +```html +

    #(title)

    +#for(number in numbers): +

    #(number)

    +#endfor +``` + +### 条件 + + +变量是否存在 + +```html +#if(title): + The title is #(title) +#endif +``` + +比较 + +```html +#if(title == "Welcome"): + This is a friendly web page. +#endif +``` + +使用另一个标签作为判断条件的一部分,内部标签应该省略 `#` + +```html +#if(count(users) > 0): + You have users! +#else: + There are no users yet :( +#endif +``` + +### #elseif + +```html +#if(title == "Welcome"): + Hello new user! +#elseif(title == "Welcome back!"): + Hello old user +#else: + Unexpected page! +#endif +``` + +### 循环 + +```swift +struct SolarSystem: Codable { + let planets = ["Venus", "Earth", "Mars"] +} +return req.view.render( + "solarSystem", SolarSystem() +) +``` + +在 `Leaf` 中循环它们: + +```html + +``` + +### 模板示例 + + +入口页面,通过 `#extend("main")` 将 `mainleaf` 模板的内容复制到当前模板中使用 + +```html +#extend("main"): + #export("body"): +

    Welcome to Vapor!

    + #endexport +#endextend +``` + +在公共模板 `main.leaf` 中 + +```html + + + #(name) + + #import("body") + +``` + +呈现如下内容: + +```html + + + Leaf + + +

    Welcome to Vapor!

    + + +``` + +### 扩展模板 + + +在模板中使用 `#export` 存储名为 `body` 的一些 HTML + +```html +#export("body"): +

    Welcome to Vapor!

    +#endexport +``` + +使用 `#import` 获取传递给 `#extend` 标签的内容 + +```html + + #import("body") + +``` + +### #count + +```html +Your search matched #count(matches) pages. +``` + +`#count` 标签返回数组中的项目数量 + +### #lowercased + +```html +#lowercased(name) +``` + +`#lowercased` 标签将字符串转成小写字母。 + +### #capitalized + +```html +#capitalized(name) +``` + +`#capitalized` 标签将字符串中每个单词的首字母大写,其他字母小写。 + +### #contains + +```html +#if(contains(planets, "Earth")): + Earth is here! +#else: + Earth is not in this array. +#endif +``` + +`#contains` 标签接受一个数组和一个值作为其两个参数,如果参数一中的数组包含参数二中的值,则返回 true。 + +### #date + + +`#date` 标签将日期格式化为可读的字符串。默认情况下,它使用 ISO8601 格式。 + +```swift +render(..., ["now": Date()]) +``` + +模板中使用 + +``` +The time is #date(now) +``` + +你可以传自定义日期格式作为第二参数,详见 [Swift DateFormatter](https://developer.apple.com/documentation/foundation/dateformatter)。 + +``` +The date is #date(now, "yyyy-MM-dd") +``` + +### #unsafeHTML + +标签就像一个变量标签 - 例如 `#(variable)`。 + +``` +The time is #unsafeHTML(styledTitle) +``` + +它不会转义任何 `variable` 可能包含的 HTML 标签 + +### #dumpContext + +`#dumpContext` 标签将整个上下文渲染为可读的字符串。使用此标记来调试作为上下文提供给当前渲染的内容。 + +``` +Hello, world! +#dumpContext +``` + +自定义标签 +--- + +### LeafTag + + +创建一个名为 `NowTag` 的类并遵循 `LeafTag` 协议 + +```swift +struct NowTag: LeafTag { + func render(_ ctx: LeafContext) throws -> LeafData { + ... + } +} +``` + +实现 `render(_:)` 方法。传递给该方法的 `LeafContext` 参数包含了我们需要的所有内容。 + +```swift +enum NowTagError: Error { + case invalidFormatParameter + case tooManyParameters +} + +struct NowTag: LeafTag { + func render(_ ctx: LeafContext) throws -> LeafData { + let formatter = DateFormatter() + switch ctx.parameters.count { + case 0: formatter.dateFormat = "yyyy-MM-dd HH:mm:ss" + case 1: + guard let string = ctx.parameters[0].string else { + throw NowTagError.invalidFormatParameter + } + + formatter.dateFormat = string + default: + throw NowTagError.tooManyParameters + } + + let dateAsString = formatter.string(from: Date()) + return LeafData.string(dateAsString) + } +} +``` + +### 配置标签 + +实现了 `NowTag`,告诉 `Leaf` 并设置标签名称为 `#now` + +```swift +app.leaf.tags["now"] = NowTag() +``` + +现在可以在 Leaf 中使用我们的自定义标签 `#now` 了 + +```html +The time is #now() +``` + +### 上下文属性 + +#### `parameters`: 包含标签参数的数组 + +```swift +struct NowTag: LeafTag { + func render( + _ ctx: LeafContext + ) throws -> LeafData { + /// ctx.parameters + } +} +``` + +#### 使用 Data,`render(_:_:)` 方法作为上下文视图的数据 + +```swift +return try await req.view.render( + "home", ["name": "John"] +) +``` + +```swift +struct NowTag: LeafTag { + func render( + _ ctx: LeafContext + ) throws -> LeafData { + let name = ctx.data["name"]?.string + } +} +``` + +`LeafContext` 包含两个重要的属性