feat: Tiptap替换为Vditor + MD存储重构 + Shortcode卡片扩展

- 编辑器:Tiptap 替换为 Vditor 3.11.2(wysiwyg 模式,本地托管 5.8MB,去 CDN)
- 存储:Body 字段从 HTML 改为 Markdown,去除 BodyHTML,新增 Excerpt 列表摘要
- 安全:去除 bluemonday 依赖(MD 纯文本无 XSS 风险),go.mod 已清理
- Shortcode:新增 [zone:type:params] 扩展语法,支持活动/游戏/投票/资源卡片
- 图片上传:POST /api/posts/upload-image 直接返回 Vditor 原生响应格式
- 草稿:localStorage 键名改为 draft_post_body_md,与旧 HTML 草稿隔离
- 详情页:Vditor.method.min.js 客户端 MD → HTML 渲染,去 highlight.js CDN
- 样式:去 Tiptap 样式 ~190 行,精简工具栏 CSS,新增卡片样式
- 文档:vditor-migration-plan.md 完整记录迁移决策、架构、用法
This commit is contained in:
2026-05-30 15:56:22 +08:00
parent c9808ab9b2
commit 6ec12010f3
121 changed files with 8267 additions and 613 deletions

View File

@ -0,0 +1,319 @@
# Vditor 整合 + MD 存储迁移方案
> **状态:已实施 ✓** `2026-05-30`
## 决策与结果
| 决策 | 结论 | 结果 |
|------|------|------|
| 新增 API | ❌ 不新增 | 仅改 `POST /api/posts/upload-image` 响应格式 |
| 桥接/转换层 | ❌ 不做 | API 直接返回 Vditor 原生格式,前端零适配 |
| 存储格式 | HTML → Markdown | MD 更小、更灵活、更可移植 |
| 向后兼容 | ❌ 不考虑 | 开发阶段,已有帖子数据量小 |
| 依赖方式 | 本地托管 | 从 npm registry 下载 dist5.8MB(仅必需插件) |
---
## 最终数据流
```
编辑器: Vditor(wysiwyg) → vditor.getValue() → MD 纯文本
存储: 后端直接存 MD纯文本无 XSS 风险,无需 sanitize
└─ generateExcerpt() 生成 plain text 摘要
显示: Vditor.preview() 客户端 MD → HTML + github-dark 代码主题
```
---
## 变更文件清单11 个)
| 文件 | 改动 | 详情 |
|------|------|------|
| `internal/model/post.go` | 修改 | `Body` → MD 存储,去 `BodyHTML`,加 `Excerpt`varchar 500 |
| `internal/service/post_service.go` | 重写 | 去 bluemonday`generateExcerpt()`(纯 Go regex无外部依赖 |
| `internal/controller/post_controller.go` | 修改 | `UploadImage` → Vditor 原生格式,`ShowPage` 去 BodyHTML`html/template` import |
| `internal/common/response.go` | 新增函数 | `VditorUploadOk()` — Vditor 图片上传成功响应 |
| `templates/.../posts/new.html` | 重写 | Tiptap → Vditor去除 importmap/工具栏/语言选择器 |
| `templates/.../posts/show.html` | 重写 | Vditor 客户端 MD 渲染,去 highlight.js CDN去代码标签注入脚本 |
| `templates/.../posts/index.html` | 微调 | `Body` 截断 → `Excerpt`(带降级回退) |
| `templates/.../editor.js` | 重写 | Vditor 初始化 + CSRF + upload + draft + word count + submit |
| `templates/.../posts.css` | 删减 | 去 Tiptap 样式(~150 行)+ 工具栏样式(~40 行),加 Vditor 微调 |
| `static/vditor/dist/` | 新增 | 本地 Vditor 文件5.8MB |
| `go.mod / go.sum` | 清理 | 移除 bluemonday 依赖 |
**不涉及的路由/中间件/仓储:零改动。**
---
## 各层详细实现
### 1. Model `internal/model/post.go`
```go
type Post struct {
ID uint `gorm:"primarykey" json:"id"`
Title string `gorm:"type:varchar(200);not null" json:"title"`
Body string `gorm:"type:text" json:"body"` // Markdown content
Excerpt string `gorm:"type:varchar(500)" json:"excerpt"` // plain text summary
// ... 其余字段不变
}
```
### 2. Service `internal/service/post_service.go`
- 删除 `sanitizePolicy`bluemonday UGC`sanitizeHTML()`
- 新增 `generateExcerpt(md string) string`
- 按行逐条去掉 MD 语法(代码块、图片、标题 #、粗体/斜体、引用 >、列表标记等)
- 链接保留文字 `[text](url)``text`
- 按 rune 截断 300 字符,末尾加 `...`
- 纯 Go `regexp` 实现,零外部依赖
- `Create()` / `Update()` 生成 `Excerpt`MD 直存无消毒
### 3. Controller `internal/controller/post_controller.go`
**UploadImage — Vditor 原生响应格式:**
```json
{
"code": 0,
"msg": "",
"data": {
"errFiles": [],
"succMap": {
"微信截图_2026.png": "/uploads/posts/1_1717071234567.png"
}
}
}
```
**ShowPage — 去掉 `PostBodyHTML`**MD 由前端渲染。
### 4. Common `internal/common/response.go`
```go
func VditorUploadOk(c *gin.Context, succMap map[string]string) {
c.JSON(http.StatusOK, gin.H{
"code": 0,
"msg": "",
"data": gin.H{
"errFiles": []string{},
"succMap": succMap,
},
})
}
```
### 5. 编辑器 `posts/new.html` + `editor.js`
**Vditor 初始化关键配置:**
```javascript
new Vditor('vditor', {
mode: 'wysiwyg',
cdn: '/static/vditor', // 所有动态加载走本地
height: '100%',
lang: 'zh_CN',
toolbar: [
'headings', 'bold', 'italic', 'strike', '|',
'line', 'code', 'inline-code', 'link', 'quote', '|',
'list', 'ordered-list', 'check', 'outdent', 'indent', '|',
'upload', 'table', '|',
'undo', 'redo', '|',
'fullscreen', 'code-theme', '|',
'outline', 'preview', 'devtools',
],
upload: {
url: '/api/posts/upload-image',
fieldName: 'file',
max: 5 * 1024 * 1024,
accept: 'image/jpg,image/jpeg,image/png,image/gif,image/webp',
setHeaders() { // 每次请求重新读取 CSRF
const meta = document.querySelector('meta[name="csrf-token"]');
return { 'X-CSRF-Token': meta ? meta.getAttribute('content') : '' };
},
},
preview: {
theme: { current: 'light', path: '/static/vditor/dist/css/content-theme' },
hljs: { style: 'github-dark', enable: true },
},
})
```
**CSRF 处理**`upload.setHeaders` 为函数,每次上传前动态读取 `meta[name="csrf-token"]`,不会因 token 过期而失败。
**草稿机制**
- 每 2 秒自动保存 `title` + `md``localStorage`
- 键名:`draft_post_title` / `draft_post_body_md`(与旧 Tiptap HTML 草稿隔离)
- 新帖模式下自动恢复草稿
**表单提交**`vditor.getValue()` 获取 MD`POST /api/posts``PUT /api/posts/:id`
### 6. 详情页 `posts/show.html`
```html
<!-- MD 内容通过隐藏 textarea 安全传递给 JS -->
<textarea id="postMdContent" style="display:none">{{.Post.Body}}</textarea>
<!-- Vditor 仅需 method.min.js47KB不需要完整编辑器 -->
<script src="/static/vditor/dist/method.min.js"></script>
<script>
Vditor.preview(document.getElementById('postContent'), md, {
cdn: '/static/vditor',
theme: { current: 'light', path: '/static/vditor/dist/css/content-theme' },
hljs: { style: 'github-dark', enable: true },
});
</script>
```
### 7. 列表页 `posts/index.html`
```html
<!-- Excerpt 优先,降级回退 Body 截断(兼容旧数据) -->
<p class="post-card-summary">
{{if .Excerpt}}{{.Excerpt}}{{else}}{{printf "%.200s" .Body}}{{end}}
</p>
```
### 8. 样式 `posts.css`
- 移除:`.tiptap` 及所有子选择器(~100 行、highlight.js 硬编码主题色(~35 行)、手动工具栏样式(~40 行、JS 注入代码标签 CSS
- 保留:`.editor-layout` / `.editor-header` / `.editor-main` / `.editor-pane` / `.editor-statusbar`(布局)
- 新增:`#vditor` flex 适配、`.vditor-toolbar` / `.vditor-content` 主题微调
---
## Vditor 本地文件结构
```
templates/MetaLab-2026/static/vditor/dist/
├── index.css # 43KB 编辑器 + 预览全部样式
├── index.min.js # 292KB 编辑器核心wysiwyg/sv/ir + 工具栏 + 上传)
├── method.min.js # 47KB 独立方法Vditor.preview 等,详情页用)
├── css/content-theme/
│ ├── light.css # 内容区明亮主题
│ ├── dark.css # 内容区暗色主题
│ ├── ant-design.css # Ant Design 主题
│ └── wechat.css # 微信主题
├── images/
│ ├── img-loading.svg # 上传进度指示
│ ├── logo.png # Vditor logo
│ └── emoji/ # 内置表情包b3log/octocat/doge 等)
├── js/
│ ├── highlight.js/
│ │ ├── highlight.min.js # 1.4MB 代码高亮核心
│ │ ├── third-languages.js # 额外语言支持
│ │ └── styles/*.min.css # 60+ 代码主题
│ ├── lute/
│ │ └── lute.min.js # 3.9MB WASM MD 解析器Vditor 核心依赖)
│ ├── i18n/
│ │ └── zh_CN.js # 8KB 中文语言包
│ └── icons/
│ ├── material.js # Material Design 图标
│ └── ant.js # Ant Design 图标
```
**已剔除的插件**(节省 16MB+
| 插件 | 大小 | 功能 | 对技术论坛无用 |
|------|------|------|---------------|
| mathjax | 6.5MB | LaTeX 数学公式 | ❌ |
| mermaid | 2.6MB | 流程图/时序图 | ❌ |
| graphviz | 2.0MB | 图谱渲染 | ❌ |
| katex | 1.5MB | 数学公式引擎 | ❌ |
| echarts | 1.0MB | 图表渲染 | ❌ |
| markmap | 836KB | 思维导图 | ❌ |
| abcjs | 356KB | 五线谱 | ❌ |
| plantuml | 32KB | PlantUML | ❌ |
| flowchart.js / smiles-drawer | ~400KB | 流程图 / 化学分子式 | ❌ |
---
## Shortcode 扩展(`[zone:type:params]`
### 语法
在 Markdown 正文中嵌入特殊卡片,语法为 `[zone:类型:参数]`
```markdown
# 周末活动汇总
下面是我们本周的推荐活动:
[zone:event:summer2026]
更多内容请关注官方动态...
```
### 已注册类型
| 语法 | 渲染结果 | 状态 |
|------|---------|------|
| `[zone:event:活动ID]` | 活动卡片(🎪 图标 + ID + 跳转链接) | ✅ 已实现(占位) |
| `[zone:game:游戏slug]` | 游戏卡片(🎮 图标 + slug + 跳转链接) | ✅ 已实现(占位) |
| `[zone:poll:投票ID]` | 投票组件 | ⏳ 预留 |
| `[zone:resource:资源ID]` | 资源推荐卡片 | ⏳ 预留 |
### 架构
```
MD body "[zone:event:summer2026]"
ShortcodeService.Process() ← 后端:正则替换 [zone:...] → 占位 <div>
VDitor.preview() ← 前端MD → HTML占位 div 原样保留
renderShortcodes() ← 前端 shortcode.js扫描 .zone-card渲染 UI 卡片
```
### 文件清单
| 文件 | 职责 |
|------|------|
| `internal/model/shortcode.go` | 类型常量 + 正则 + DTO |
| `internal/service/shortcode_service.go` | 解析器 + 占位 HTML 生成器 |
| `internal/controller/post_controller.go` | ShowPage/ShowAPI 调用 Process() |
| `internal/router/deps_extra.go` | DI 注入 ShortcodeService |
| `templates/.../js/shortcode.js` | 前端卡片渲染器 |
| `templates/.../html/posts/show.html` | 引入 shortcode.js + 调用 renderShortcodes() |
| `templates/.../static/css/posts.css` | 卡片样式 |
| `templates/.../static/js/editor.js` | hint 自动补全提示 |
### 如何添加新类型
1. `internal/model/shortcode.go`:注册 `ShortcodeType` 常量
2. `internal/service/shortcode_service.go`:在 `renderPlaceholder()` 添加 case
3. `templates/.../js/shortcode.js`:在 `cardRenderers` 注册渲染函数
4. `templates/.../static/js/editor.js`:在 `hint.extend` 添加补全提示
### 注意事项
- 未注册或格式错误的 shortcode **不报错**,原文保留,避免破坏用户内容
- 占位 div 结构为 `<div class="zone-card" data-zone-type="..." data-zone-id="...">`
- 编辑器输入 `[zone:` 时自动弹出补全列表Vditor hint.extend
- 后端 API 就绪后,将 `shortcode.js` 中的静态卡片替换为 `fetch()` 动态数据即可
---
## 不涉及的部分
- `internal/router/api.go` — 路由不变(`POST /api/posts/upload-image` 端点不变)
- `internal/router/frontend.go` — SSR 页面路由不变
- `internal/repository/post_repo.go` — 仓储不变Model 字段变更由 GORM 自动映射)
- `internal/middleware/` — CSRF / Auth 中间件不变
- `internal/config/` — 配置不变
- `cmd/server/main.go``Static()` 映射不变(`/static``templates/.../static`
---
## 注意事项
1. **DB Schema**GORM AutoMigrate 自动添加 `excerpt` 列;旧的 `body_html` 列残留但不影响运行GORM 不会 DROP可手动清理
2. **已有帖子**:旧 HTML body 在 MD 预览中会显示为 HTML 源码,可手动清理或通过 SQL 迁移
3. **CSRF**`upload.setHeaders` 为函数,每次上传前动态读取最新 token不受 token 过期影响
4. **时序**Vditor 主文件先加载lute/highlight.js/i18n 等由 Vditor 内部按需动态加载,无需手动控制
5. **bluemonday**:已从 `go.mod` 中移除(`go mod tidy` 自动清理)

3
go.mod
View File

@ -6,7 +6,6 @@ require (
github.com/chai2010/webp v1.4.0 github.com/chai2010/webp v1.4.0
github.com/gin-gonic/gin v1.12.0 github.com/gin-gonic/gin v1.12.0
github.com/golang-jwt/jwt/v5 v5.3.1 github.com/golang-jwt/jwt/v5 v5.3.1
github.com/microcosm-cc/bluemonday v1.0.27
github.com/spf13/viper v1.21.0 github.com/spf13/viper v1.21.0
golang.org/x/crypto v0.52.0 golang.org/x/crypto v0.52.0
golang.org/x/image v0.41.0 golang.org/x/image v0.41.0
@ -15,7 +14,6 @@ require (
) )
require ( require (
github.com/aymerick/douceur v0.2.0 // indirect
github.com/bytedance/gopkg v0.1.3 // indirect github.com/bytedance/gopkg v0.1.3 // indirect
github.com/bytedance/sonic v1.15.0 // indirect github.com/bytedance/sonic v1.15.0 // indirect
github.com/bytedance/sonic/loader v0.5.0 // indirect github.com/bytedance/sonic/loader v0.5.0 // indirect
@ -29,7 +27,6 @@ require (
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
github.com/goccy/go-json v0.10.5 // indirect github.com/goccy/go-json v0.10.5 // indirect
github.com/goccy/go-yaml v1.19.2 // indirect github.com/goccy/go-yaml v1.19.2 // indirect
github.com/gorilla/css v1.0.1 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
github.com/jackc/pgx/v5 v5.6.0 // indirect github.com/jackc/pgx/v5 v5.6.0 // indirect

6
go.sum
View File

@ -1,5 +1,3 @@
github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
github.com/bytedance/gopkg v0.1.3 h1:TPBSwH8RsouGCBcMBktLt1AymVo2TVsBVCY4b6TnZ/M= github.com/bytedance/gopkg v0.1.3 h1:TPBSwH8RsouGCBcMBktLt1AymVo2TVsBVCY4b6TnZ/M=
github.com/bytedance/gopkg v0.1.3/go.mod h1:576VvJ+eJgyCzdjS+c4+77QF3p7ubbtiKARP3TxducM= github.com/bytedance/gopkg v0.1.3/go.mod h1:576VvJ+eJgyCzdjS+c4+77QF3p7ubbtiKARP3TxducM=
github.com/bytedance/sonic v1.15.0 h1:/PXeWFaR5ElNcVE84U0dOHjiMHQOwNIx3K4ymzh/uSE= github.com/bytedance/sonic v1.15.0 h1:/PXeWFaR5ElNcVE84U0dOHjiMHQOwNIx3K4ymzh/uSE=
@ -42,8 +40,6 @@ github.com/golang-jwt/jwt/v5 v5.3.1/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArs
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8=
github.com/gorilla/css v1.0.1/go.mod h1:BvnYkspnSzMmwRK+b8/xgNPLiIuNZr6vbZBTPQ2A3b0=
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
@ -68,8 +64,6 @@ github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/microcosm-cc/bluemonday v1.0.27 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwXFM08ygZfk=
github.com/microcosm-cc/bluemonday v1.0.27/go.mod h1:jFi9vgW+H7c3V0lb6nR74Ib/DIB5OBs92Dimizgw2cA=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=

View File

@ -22,3 +22,16 @@ func OkWithMessage(c *gin.Context, data interface{}, message string) {
func Error(c *gin.Context, code int, message string) { func Error(c *gin.Context, code int, message string) {
c.JSON(code, gin.H{"success": false, "message": message}) c.JSON(code, gin.H{"success": false, "message": message})
} }
// VditorUploadOk Vditor 图片上传成功响应
// succMap: key=原始文件名, value=文件 URL
func VditorUploadOk(c *gin.Context, succMap map[string]string) {
c.JSON(http.StatusOK, gin.H{
"code": 0,
"msg": "",
"data": gin.H{
"errFiles": []string{},
"succMap": succMap,
},
})
}

View File

@ -3,7 +3,6 @@ package controller
import ( import (
"errors" "errors"
"fmt" "fmt"
"html/template"
"mime/multipart" "mime/multipart"
"net/http" "net/http"
"os" "os"
@ -14,18 +13,20 @@ import (
"metazone.cc/metalab/internal/common" "metazone.cc/metalab/internal/common"
"metazone.cc/metalab/internal/model" "metazone.cc/metalab/internal/model"
"metazone.cc/metalab/internal/service"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
) )
// PostController 帖子控制器SSR 页面 + API // PostController 帖子控制器SSR 页面 + API
type PostController struct { type PostController struct {
postService postUseCase postService postUseCase
shortcodeSvc *service.ShortcodeService
} }
// NewPostController 构造函数 // NewPostController 构造函数
func NewPostController(ps postUseCase) *PostController { func NewPostController(ps postUseCase, scs *service.ShortcodeService) *PostController {
return &PostController{postService: ps} return &PostController{postService: ps, shortcodeSvc: scs}
} }
// ListPage 帖子列表页 // ListPage 帖子列表页
@ -105,13 +106,19 @@ func (ctrl *PostController) ShowPage(c *gin.Context) {
} }
} }
// 处理 shortcode[zone:type:params] → HTML 占位符
// 占位 div 会被 Vditor.preview() 保留,由前端 shortcode.js 渲染为卡片
if ctrl.shortcodeSvc != nil {
result := ctrl.shortcodeSvc.Process(post.Body)
post.Body = result.ProcessedBody
}
c.HTML(http.StatusOK, "posts/show.html", common.BuildPageData(c, gin.H{ c.HTML(http.StatusOK, "posts/show.html", common.BuildPageData(c, gin.H{
"Title": post.Title, "Title": post.Title,
"Post": post, "Post": post,
"PostBodyHTML": template.HTML(post.BodyHTML), "UID": uid,
"UID": uid, "StatusNames": model.PostStatusDisplayNames,
"StatusNames": model.PostStatusDisplayNames, "ExtraCSS": "/static/css/posts.css",
"ExtraCSS": "/static/css/posts.css",
})) }))
} }
@ -367,6 +374,12 @@ func (ctrl *PostController) ShowAPI(c *gin.Context) {
} }
} }
// 处理 shortcode[zone:type:params] → HTML 占位符
if ctrl.shortcodeSvc != nil {
result := ctrl.shortcodeSvc.Process(post.Body)
post.Body = result.ProcessedBody
}
common.Ok(c, post) common.Ok(c, post)
} }
@ -423,7 +436,7 @@ func (ctrl *PostController) UploadImage(c *gin.Context) {
} }
url := "/uploads/posts/" + filename url := "/uploads/posts/" + filename
common.Ok(c, gin.H{"url": url}) common.VditorUploadOk(c, map[string]string{header.Filename: url})
} }
// saveUploadedFile 将 multipart.File 写入目标路径 // saveUploadedFile 将 multipart.File 写入目标路径

View File

@ -8,17 +8,17 @@ import (
// Post 帖子/文章模型 // Post 帖子/文章模型
type Post struct { type Post struct {
ID uint `gorm:"primarykey" json:"id"` ID uint `gorm:"primarykey" json:"id"`
Title string `gorm:"type:varchar(200);not null" json:"title"` Title string `gorm:"type:varchar(200);not null" json:"title"`
Body string `gorm:"type:text" json:"body"` Body string `gorm:"type:text" json:"body"` // Markdown content
BodyHTML string `gorm:"type:text" json:"body_html"` Excerpt string `gorm:"type:varchar(500)" json:"excerpt"` // plain text summary for list
UserID uint `gorm:"index;not null" json:"user_id"` UserID uint `gorm:"index;not null" json:"user_id"`
Status string `gorm:"type:varchar(20);index;default:draft;not null" json:"status"` Status string `gorm:"type:varchar(20);index;default:draft;not null" json:"status"`
RejectReason string `gorm:"type:varchar(500);default:''" json:"reject_reason,omitempty"` RejectReason string `gorm:"type:varchar(500);default:''" json:"reject_reason,omitempty"`
AllowComment bool `gorm:"default:true" json:"allow_comment"` AllowComment bool `gorm:"default:true" json:"allow_comment"`
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"` DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
CreatedAt time.Time `json:"created_at"` CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"` UpdatedAt time.Time `json:"updated_at"`
// 非数据库字段(联表查询填充) // 非数据库字段(联表查询填充)
// gorm:"-:migration" 指定不创建/迁移该列,"<-:false" 禁止写入,"column:author_name" 允许 // gorm:"-:migration" 指定不创建/迁移该列,"<-:false" 禁止写入,"column:author_name" 允许

View File

@ -0,0 +1,92 @@
package model
// =============================================================================
// Shortcode — Markdown 扩展语法
//
// 用法:在 MD 正文中插入 [zone:类型:参数],渲染时替换为对应 UI 卡片。
//
// 支持的 shortcode
// [zone:event:活动ID] → 官方活动卡片(标题、时间、封面、链接)
// [zone:game:游戏slug] → 游戏信息卡片(名称、封面、类型)
// [zone:poll:投票ID] → 投票组件(预留)
// [zone:resource:资源ID] → 资源卡片(工具/素材推荐,预留)
//
// 扩展现有类型:在 ShortcodeRegistry 中注册新类型 + 实现 Renderer 即可。
// =============================================================================
import "regexp"
// =============================================================================
// 语法常量
// =============================================================================
// ShortcodePattern 匹配所有 [zone:type:params] 模式的 shortcode
// 捕获组:$1=类型, $2=参数
var ShortcodePattern = regexp.MustCompile(`\[zone:(\w+):([^\]]+)\]`)
// =============================================================================
// 已注册的 Shortcode 类型
// =============================================================================
// ShortcodeType 定义一种 shortcode 的类型标识
type ShortcodeType string
const (
ShortcodeEvent ShortcodeType = "event" // [zone:event:活动ID]
ShortcodeGame ShortcodeType = "game" // [zone:game:游戏slug]
ShortcodePoll ShortcodeType = "poll" // [zone:poll:投票ID] ※预留后端API未实现
ShortcodeResource ShortcodeType = "resource" // [zone:resource:资源ID] ※预留后端API未实现
)
// allTypes 所有已定义的 shortcode 类型,添加新类型时在这里追加
var allTypes = []ShortcodeType{
ShortcodeEvent,
ShortcodeGame,
ShortcodePoll,
ShortcodeResource,
}
// IsValidType 检查给定类型是否已注册
func IsValidType(t string) bool {
for _, st := range allTypes {
if string(st) == t {
return true
}
}
return false
}
// =============================================================================
// 解析结果
// =============================================================================
// Shortcode 一次解析的结果
type Shortcode struct {
Raw string // 原始文本,如 "[zone:event:abc123]"
Type ShortcodeType // 类型
Params string // 参数ID/slug 等)
}
// ShortcodeResult 整个 body 的解析结果
type ShortcodeResult struct {
// ProcessedBody 替换后的 bodyshortcode → HTML 占位符),可直接传给模板渲染
ProcessedBody string
// Shortcodes 本次解析到的所有 shortcode 列表
Shortcodes []Shortcode
}
// =============================================================================
// 卡片渲染数据 DTO
// =============================================================================
// ShortcodeCard 渲染一张卡片的通用数据
// 前端根据 Type 决定具体 UI 组件,字段按需填充
type ShortcodeCard struct {
Type ShortcodeType `json:"type"`
ID string `json:"id"` // 业务 ID活动ID / 游戏slug
Title string `json:"title,omitempty"`
Cover string `json:"cover,omitempty"`
Desc string `json:"desc,omitempty"`
URL string `json:"url,omitempty"`
Extra string `json:"extra,omitempty"` // 扩展字段时间、标签等JSON string
}

View File

@ -32,7 +32,8 @@ func buildDeps(db *gorm.DB, cfg *config.Config, siteSettings *config.SiteSetting
postRepo := repository.NewPostRepo(db) postRepo := repository.NewPostRepo(db)
postService := service.NewPostService(postRepo, siteSettings) postService := service.NewPostService(postRepo, siteSettings)
postCtrl := controller.NewPostController(postService) shortcodeSvc := service.NewShortcodeService()
postCtrl := controller.NewPostController(postService, shortcodeSvc)
adminPostCtrl := adminCtrl.NewAdminPostController(postService) adminPostCtrl := adminCtrl.NewAdminPostController(postService)
return &dependencies{ return &dependencies{

View File

@ -4,28 +4,74 @@ import (
"errors" "errors"
"fmt" "fmt"
"regexp" "regexp"
"strings"
"unicode/utf8"
"metazone.cc/metalab/internal/common" "metazone.cc/metalab/internal/common"
"metazone.cc/metalab/internal/config" "metazone.cc/metalab/internal/config"
"metazone.cc/metalab/internal/model" "metazone.cc/metalab/internal/model"
"github.com/microcosm-cc/bluemonday"
"gorm.io/gorm" "gorm.io/gorm"
) )
// sanitizePolicy Tiptap 输出 HTML 的消毒策略 // mdPatterns Markdown 语法正则(用于生成纯文本摘要)
// 前端 WYSIWYG 编辑器输出 HTML由 bluemonday UGC 策略消毒 var mdPatterns = []*regexp.Regexp{
// 放行 code/pre 的 class/data-language 属性以保留代码高亮和语言标签 // 代码块 ```...```
// 放行 span 元素及其 hljs-* class 以保留 highlight.js 语法高亮 regexp.MustCompile("(?s)```[^`]*```"),
// 放行 img 的 src/alt/title 属性以保留图片 // 行内代码 `...`
var sanitizePolicy = func() *bluemonday.Policy { regexp.MustCompile("`[^`]+`"),
p := bluemonday.UGCPolicy() // 图片 ![alt](url)
p.AllowElements("span") regexp.MustCompile(`!\[.*?\]\(.*?\)`),
p.AllowAttrs("class", "data-language").OnElements("code", "pre") // 链接 [text](url)
p.AllowAttrs("class").Matching(regexp.MustCompile("^hljs-")).OnElements("span") regexp.MustCompile(`\[([^\]]*)\]\(.*?\)`),
p.AllowAttrs("src", "alt", "title").OnElements("img") // 标题标记 #
return p regexp.MustCompile(`^#{1,6}\s+`),
}() // 粗体/斜体/删除线标记
regexp.MustCompile(`\*{1,3}|_{1,3}|~~`),
// 引用 >
regexp.MustCompile(`^>\s?`),
// 列表标记
regexp.MustCompile(`^[\s]*[-*+]\s+`),
regexp.MustCompile(`^[\s]*\d+\.\s+`),
// 分割线
regexp.MustCompile(`^[-*_]{3,}\s*$`),
}
// generateExcerpt 从 Markdown 生成纯文本摘要(最多 300 字符)
// MD 作为纯文本存储无 XSS 风险,前端由 Vditor 渲染
func generateExcerpt(md string) string {
text := md
// 按行处理,保留行结构
lines := strings.Split(text, "\n")
var cleaned []string
for _, line := range lines {
cleanedLine := line
for _, re := range mdPatterns {
if re == mdPatterns[2] {
// 链接保留文字: [text](url) → text
cleanedLine = re.ReplaceAllString(cleanedLine, "$1")
} else {
cleanedLine = re.ReplaceAllString(cleanedLine, "")
}
}
cleanedLine = strings.TrimSpace(cleanedLine)
if cleanedLine != "" {
cleaned = append(cleaned, cleanedLine)
}
}
result := strings.Join(cleaned, " ")
// 按 rune 截断,不切中文字符
const maxChars = 300
if utf8.RuneCountInString(result) > maxChars {
runes := []rune(result)
result = string(runes[:maxChars]) + "..."
}
return result
}
// PostService 帖子业务逻辑 // PostService 帖子业务逻辑
type PostService struct { type PostService struct {
@ -70,16 +116,10 @@ func (s *PostService) findPostWithAuthor(id uint) (*model.Post, error) {
return post, nil return post, nil
} }
// sanitizeHTML 对 Tiptap 输出的 HTML 进行消毒
// 前端 WYSIWYG 编辑器直接输出 HTML后端仅做安全消毒
func (s *PostService) sanitizeHTML(body string) string {
return sanitizePolicy.Sanitize(body)
}
// Create 创建帖子 // Create 创建帖子
// body 为 Vditor 输出的 Markdown后端直接存储不做 HTML 消毒
// MD 是纯文本,无 XSS 注入风险;前端渲染时由 Vditor 转 HTML
func (s *PostService) Create(userID uint, title, body string) (*model.Post, error) { func (s *PostService) Create(userID uint, title, body string) (*model.Post, error) {
bodyHTML := s.sanitizeHTML(body)
status := model.PostStatusApproved status := model.PostStatusApproved
if s.auditEnabled() { if s.auditEnabled() {
status = model.PostStatusDraft status = model.PostStatusDraft
@ -88,7 +128,7 @@ func (s *PostService) Create(userID uint, title, body string) (*model.Post, erro
post := &model.Post{ post := &model.Post{
Title: title, Title: title,
Body: body, Body: body,
BodyHTML: bodyHTML, Excerpt: generateExcerpt(body),
UserID: userID, UserID: userID,
Status: status, Status: status,
AllowComment: true, AllowComment: true,
@ -131,7 +171,7 @@ func (s *PostService) Update(postID uint, title, body string) error {
post.Title = title post.Title = title
post.Body = body post.Body = body
post.BodyHTML = s.sanitizeHTML(body) post.Excerpt = generateExcerpt(body)
// rejected 状态编辑后自动重置为 draft // rejected 状态编辑后自动重置为 draft
if post.Status == model.PostStatusRejected { if post.Status == model.PostStatusRejected {

View File

@ -0,0 +1,143 @@
package service
import (
"fmt"
"html"
"strings"
"metazone.cc/metalab/internal/model"
)
// =============================================================================
// ShortcodeService — Markdown shortcode 解析与渲染
//
// 职责:
// 1. 从 Markdown body 中提取所有 [zone:type:params] 语法
// 2. 将合法的 shortcode 替换为前端可识别的占位 HTML<div class="zone-card">
// 3. 未注册的 shortcode 保持不变(不报错,避免破坏用户输入)
//
// 扩展新类型:
// 1. 在 model/shortcode.go 中注册新 ShortcodeType 常量
// 2. 在本文件的 renderPlaceholder() 中添加 case 分支
// 3. 在 templates/.../shortcode.js 中添加前端卡片渲染逻辑
// =============================================================================
// ShortcodeService 无状态,纯函数集合
type ShortcodeService struct{}
// NewShortcodeService 构造函数
func NewShortcodeService() *ShortcodeService {
return &ShortcodeService{}
}
// =============================================================================
// 主入口:解析 + 替换
// =============================================================================
// Process 从 MD body 中提取 shortcode 并替换为占位 HTML
// 返回的 ProcessedBody 可直接传给模板渲染Go template 会自动对 HTML 实体转义,
// 因此占位 div 需要用 template.HTML 类型标记为安全)
func (s *ShortcodeService) Process(body string) model.ShortcodeResult {
result := model.ShortcodeResult{
ProcessedBody: body,
}
// 一次遍历替换所有合法的 shortcode
result.ProcessedBody = model.ShortcodePattern.ReplaceAllStringFunc(body, func(match string) string {
sc, ok := parseShortcode(match)
if !ok {
return match // 格式不合法,保留原文
}
if !model.IsValidType(string(sc.Type)) {
return match // 类型未注册,保留原文
}
// 生成占位 HTML
placeholder := renderPlaceholder(sc)
if placeholder == "" {
return match
}
result.Shortcodes = append(result.Shortcodes, sc)
return placeholder
})
return result
}
// =============================================================================
// 解析
// =============================================================================
// parseShortcode 从 "[zone:type:params]" 字符串中解析出 Shortcode
// 返回 false 表示格式不合法
func parseShortcode(raw string) (model.Shortcode, bool) {
matches := model.ShortcodePattern.FindStringSubmatch(raw)
if len(matches) != 3 {
return model.Shortcode{}, false
}
sc := model.Shortcode{
Raw: raw,
Type: model.ShortcodeType(matches[1]),
Params: strings.TrimSpace(matches[2]),
}
if sc.Params == "" {
return model.Shortcode{}, false
}
return sc, true
}
// =============================================================================
// 占位 HTML 渲染器(每个类型一个 case
// =============================================================================
// renderPlaceholder 根据 shortcode 类型生成前端占位 HTML
//
// 占位 div 结构约定:
// <div class="zone-card" data-zone-type="类型" data-zone-id="参数"
// data-zone-extra="附加信息">
// <span class="zone-card-loading">卡片加载中...</span>
// </div>
//
// 前端 shortcode.js 根据 data-zone-* 属性决定如何渲染。
// "加载中..." 文本会被 shortcode.js 在渲染时替换,仅作降级显示。
func renderPlaceholder(sc model.Shortcode) string {
switch sc.Type {
case model.ShortcodeEvent:
// [zone:event:活动ID]
// 前端根据活动ID获取标题、时间、封面等信息
return fmt.Sprintf(
`<div class="zone-card" data-zone-type="event" data-zone-id="%s"><span class="zone-card-loading">活动卡片加载中...</span></div>`,
html.EscapeString(sc.Params),
)
case model.ShortcodeGame:
// [zone:game:游戏slug]
// 前端根据游戏slug获取名称、封面、标签等信息
return fmt.Sprintf(
`<div class="zone-card" data-zone-type="game" data-zone-id="%s"><span class="zone-card-loading">游戏卡片加载中...</span></div>`,
html.EscapeString(sc.Params),
)
case model.ShortcodePoll:
// [zone:poll:投票ID] ※预留
return fmt.Sprintf(
`<div class="zone-card" data-zone-type="poll" data-zone-id="%s"><span class="zone-card-loading">投票组件加载中...</span></div>`,
html.EscapeString(sc.Params),
)
case model.ShortcodeResource:
// [zone:resource:资源ID] ※预留
return fmt.Sprintf(
`<div class="zone-card" data-zone-type="resource" data-zone-id="%s"><span class="zone-card-loading">资源卡片加载中...</span></div>`,
html.EscapeString(sc.Params),
)
default:
return ""
}
}

View File

@ -32,7 +32,7 @@
<span class="post-author">{{if .AuthorName}}{{.AuthorName}}({{.UserID}}){{else}}UID{{.UserID}}{{end}}</span> <span class="post-author">{{if .AuthorName}}{{.AuthorName}}({{.UserID}}){{else}}UID{{.UserID}}{{end}}</span>
<span class="post-time">{{.CreatedAt.Format "2006-01-02 15:04"}}</span> <span class="post-time">{{.CreatedAt.Format "2006-01-02 15:04"}}</span>
</div> </div>
<p class="post-card-summary">{{printf "%.200s" .Body}}</p> <p class="post-card-summary">{{if .Excerpt}}{{.Excerpt}}{{else}}{{printf "%.200s" .Body}}{{end}}</p>
</article> </article>
{{end}} {{end}}
{{end}} {{end}}

View File

@ -6,6 +6,10 @@
<div class="editor-layout"> <div class="editor-layout">
<form id="postForm" class="editor-form"> <form id="postForm" class="editor-form">
<input type="hidden" id="postId" value="{{if .Post}}{{.Post.ID}}{{end}}"> <input type="hidden" id="postId" value="{{if .Post}}{{.Post.ID}}{{end}}">
{{/* 编辑模式下传入已有的 MD 内容 */}}
{{if .Post}}
<textarea id="editBody" style="display:none">{{.Post.Body}}</textarea>
{{end}}
{{/* 标题区域 */}} {{/* 标题区域 */}}
<div class="editor-header"> <div class="editor-header">
@ -15,62 +19,10 @@
maxlength="200" required autofocus> maxlength="200" required autofocus>
</div> </div>
{{/* 工具栏 */}} {{/* Vditor 编辑器容器(工具栏+编辑区由 Vditor 自动生成) */}}
<div class="editor-toolbar" id="toolbar">
<button type="button" data-action="bold" title="加粗 (Ctrl+B)"><strong>B</strong></button>
<button type="button" data-action="italic" title="斜体 (Ctrl+I)"><em>I</em></button>
<button type="button" data-action="strike" title="删除线"><s>S</s></button>
<button type="button" data-action="code" title="行内代码">&lt;/&gt;</button>
<span class="toolbar-divider"></span>
<button type="button" data-action="h1" title="标题 1">H1</button>
<button type="button" data-action="h2" title="标题 2">H2</button>
<button type="button" data-action="h3" title="标题 3">H3</button>
<span class="toolbar-divider"></span>
<button type="button" data-action="bulletList" title="无序列表"></button>
<button type="button" data-action="orderedList" title="有序列表">1.</button>
<button type="button" data-action="blockquote" title="引用">"</button>
<span class="toolbar-divider"></span>
<button type="button" data-action="codeBlock" title="代码块">{ }</button>
<select id="codeLangSelect" class="code-lang-select" style="display:none" title="选择语言">
<option value="text">text</option>
<option value="javascript">JavaScript</option>
<option value="typescript">TypeScript</option>
<option value="python">Python</option>
<option value="go">Go</option>
<option value="rust">Rust</option>
<option value="java">Java</option>
<option value="cpp">C++</option>
<option value="c">C</option>
<option value="csharp">C#</option>
<option value="ruby">Ruby</option>
<option value="php">PHP</option>
<option value="swift">Swift</option>
<option value="kotlin">Kotlin</option>
<option value="sql">SQL</option>
<option value="bash">Bash</option>
<option value="json">JSON</option>
<option value="yaml">YAML</option>
<option value="xml">XML</option>
<option value="css">CSS</option>
<option value="scss">SCSS</option>
<option value="html">HTML</option>
<option value="markdown">Markdown</option>
<option value="shell">Shell</option>
<option value="dockerfile">Dockerfile</option>
<option value="nginx">Nginx</option>
<option value="diff">Diff</option>
</select>
<button type="button" data-action="horizontalRule" title="分隔线"></button>
<span class="toolbar-divider"></span>
<button type="button" data-action="undo" title="撤销"></button>
<button type="button" data-action="redo" title="重做"></button>
</div>
{{/* 编辑器主体 */}}
<div class="editor-main"> <div class="editor-main">
<div class="editor-pane" id="editorPane"> <div class="editor-pane">
<div id="editor-container" <div id="vditor"></div>
data-content="{{if .Post}}{{.Post.BodyHTML}}{{end}}"></div>
</div> </div>
</div> </div>
@ -79,7 +31,6 @@
<span class="status-item" id="wordCount">0 字</span> <span class="status-item" id="wordCount">0 字</span>
<span class="status-item status-draft" id="draftStatus" style="display:none">草稿已保存</span> <span class="status-item status-draft" id="draftStatus" style="display:none">草稿已保存</span>
<span class="status-spacer"></span> <span class="status-spacer"></span>
<span class="status-item" id="btnFullscreen" style="cursor:pointer" title="全屏编辑 (F11)">全屏</span>
<button type="submit" class="btn btn-primary btn-submit">{{if .Post}}保存修改{{else}}发布帖子{{end}}</button> <button type="submit" class="btn btn-primary btn-submit">{{if .Post}}保存修改{{else}}发布帖子{{end}}</button>
</div> </div>
</form> </form>
@ -87,18 +38,9 @@
{{template "layout/footer.html" .}} {{template "layout/footer.html" .}}
<script type="importmap"> <link rel="stylesheet" href="/static/vditor/dist/index.css">
{ <script src="/static/vditor/dist/index.min.js"></script>
"imports": {
"@tiptap/core": "https://esm.sh/@tiptap/core@3.13.0",
"@tiptap/starter-kit": "https://esm.sh/@tiptap/starter-kit@3.13.0",
"@tiptap/extension-placeholder": "https://esm.sh/@tiptap/extension-placeholder@3.13.0",
"@tiptap/extension-image": "https://esm.sh/@tiptap/extension-image@3.13.0",
"@tiptap/extension-code-block-lowlight": "https://esm.sh/@tiptap/extension-code-block-lowlight@3.13.0",
"lowlight": "https://esm.sh/lowlight@3.3.0",
"highlight.js/lib/languages/": "https://esm.sh/highlight.js@11.11.0/es/languages/"
}
}
</script>
<script src="/static/js/common.js"></script> <script src="/static/js/common.js"></script>
<script type="module" src="/static/js/editor.js"></script> <script type="module" src="/static/js/editor.js"></script>
</body>
</html>

View File

@ -1,5 +1,4 @@
{{template "layout/header.html" .}} {{template "layout/header.html" .}}
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.0/styles/github-dark.min.css">
<body> <body>
{{template "layout/nav.html" .}} {{template "layout/nav.html" .}}
@ -9,7 +8,7 @@
<header class="post-detail-header"> <header class="post-detail-header">
<h1 class="post-detail-title">{{.Post.Title}}</h1> <h1 class="post-detail-title">{{.Post.Title}}</h1>
<div class="post-detail-meta"> <div class="post-detail-meta">
<span class="post-author">{{if .Post.AuthorName}}{{.Post.AuthorName}}{{else}}UID{{.Post.UserID}}{{end}}</span> <span class="post-author">{{if .Post.AuthorName}}{{.Post.AuthorName}}({{.Post.UserID}}){{else}}UID{{.Post.UserID}}{{end}}</span>
<span class="post-time">{{.Post.CreatedAt.Format "2006-01-02 15:04"}}</span> <span class="post-time">{{.Post.CreatedAt.Format "2006-01-02 15:04"}}</span>
{{if ne .Post.Status "approved"}} {{if ne .Post.Status "approved"}}
<span class="post-status post-status-{{.Post.Status}}">{{index $.StatusNames .Post.Status}}</span> <span class="post-status post-status-{{.Post.Status}}">{{index $.StatusNames .Post.Status}}</span>
@ -17,13 +16,7 @@
</div> </div>
</header> </header>
<div class="post-detail-body"> <div class="post-detail-body" id="postContent"></div>
{{.PostBodyHTML}}
{{/* 如果 BodyHTML 为空,直接显示原文 */}}
</div>
{{if not .PostBodyHTML}}
<div class="post-detail-body-raw">{{.Post.Body}}</div>
{{end}}
{{if .Post.RejectReason}} {{if .Post.RejectReason}}
<div class="post-reject-reason"> <div class="post-reject-reason">
@ -46,38 +39,34 @@
{{template "layout/footer.html" .}} {{template "layout/footer.html" .}}
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.0/highlight.min.js"></script> {{/* MD 内容安全传递给 JS */}}
<textarea id="postMdContent" style="display:none">{{.Post.Body}}</textarea>
<link rel="stylesheet" href="/static/vditor/dist/index.css">
<script src="/static/vditor/dist/method.min.js"></script>
<script src="/static/js/common.js"></script> <script src="/static/js/common.js"></script>
<script src="/static/js/shortcode.js"></script>
<script> <script>
// ---- 代码块语言标签注入 + 语法高亮 ----
(function() { (function() {
document.querySelectorAll('.post-detail-body pre').forEach(function(pre) { var el = document.getElementById('postContent');
// 从 pre 的 data-language 或 code 的 class 中获取语言 var mdEl = document.getElementById('postMdContent');
var lang = pre.getAttribute('data-language'); if (!el || !mdEl) return;
if (!lang) {
var code = pre.querySelector('code');
if (code) {
var match = code.className.match(/language-(\w+)/);
if (match) lang = match[1];
}
}
lang = lang || 'text';
var label = document.createElement('div'); var md = mdEl.value;
label.className = 'code-lang-label'; Vditor.preview(el, md, {
label.textContent = lang; cdn: '/static/vditor',
pre.insertBefore(label, pre.firstChild); theme: { current: 'light', path: '/static/vditor/dist/css/content-theme' },
pre.style.paddingTop = '32px'; hljs: { style: 'github-dark', enable: true },
// 语法高亮
var code = pre.querySelector('code');
if (code && lang !== 'text') {
code.classList.add('language-' + lang);
hljs.highlightElement(code);
}
}); });
})();
// Vditor.preview() 渲染 MDshortcode 占位 div 原样保留
// 渲染完成后,将占位 div 替换为对应卡片 UI
renderShortcodes(el);
})();
</script>
<script>
// 提交审核按钮
(function() { (function() {
var btn = document.getElementById('submitAuditBtn'); var btn = document.getElementById('submitAuditBtn');
if (!btn) return; if (!btn) return;

View File

@ -125,7 +125,7 @@
color: #2980b9; color: #2980b9;
} }
/* 代码块基础样式(背景色与 github-dark 主题保持一致) */ /* 代码块基础样式 */
.post-detail-body pre { .post-detail-body pre {
position: relative; position: relative;
background: #0d1117; background: #0d1117;
@ -138,23 +138,6 @@
font-family: 'Menlo', 'Consolas', 'Monaco', 'Liberation Mono', monospace; font-family: 'Menlo', 'Consolas', 'Monaco', 'Liberation Mono', monospace;
} }
/* 代码块语言标签JS 动态注入) */
.post-detail-body pre .code-lang-label {
position: absolute;
top: 0;
left: 0;
right: 0;
padding: 4px 16px;
font-size: 11px;
font-weight: 600;
color: #8b949e;
background: #161b22;
border-radius: 8px 8px 0 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Helvetica Neue', Arial, sans-serif;
pointer-events: none;
z-index: 1;
}
.post-detail-body pre code { .post-detail-body pre code {
background: transparent !important; background: transparent !important;
border: none !important; border: none !important;
@ -171,8 +154,6 @@
font-size: 0.9em; font-size: 0.9em;
} }
/* 代码块语法高亮由 highlight.js CDN 主题提供,此处仅保留基础布局 */
.post-detail-body img { .post-detail-body img {
max-width: 100%; max-width: 100%;
height: auto; height: auto;
@ -385,166 +366,148 @@
background: #fff; background: #fff;
} }
/* ========== Tiptap 编辑器容器 ========== */ /* ========== Vditor 编辑器容器 ========== */
#editor-container { /* Vditor 自动生成工具栏、编辑区、状态栏,仅做外层布局微调 */
#vditor {
flex: 1; flex: 1;
overflow: hidden; overflow: hidden;
min-height: 0; min-height: 0;
background: #fff; display: flex;
flex-direction: column;
} }
.tiptap { /* Vditor 工具栏微调 — 匹配项目主题色 */
padding: 16px 24px; .vditor-toolbar {
outline: none; background: #fafafa !important;
min-height: 100%; border-bottom: 1px solid #e5e7eb !important;
}
/* Vditor 内容区适配 */
.vditor-content {
font-size: 15px; font-size: 15px;
line-height: 1.7; line-height: 1.7;
color: #24292f; color: #24292f;
} }
.tiptap p { margin: 0.5em 0; } /* Vditor 预览/渲染区域 — 详情页 */
.tiptap h1 { margin: 1em 0 0.5em; font-size: 2em; font-weight: 700; } .post-detail-body .vditor-reset {
.tiptap h2 { margin: 0.8em 0 0.4em; font-size: 1.5em; font-weight: 700; } font-size: 16px;
.tiptap h3 { margin: 0.6em 0 0.3em; font-size: 1.17em; font-weight: 700; } line-height: 1.8;
color: #1f2937;
.tiptap ul, .tiptap ol { padding-left: 1.5em; margin: 0.5em 0; }
.tiptap li { margin: 0.2em 0; }
.tiptap ul[data-type="taskList"] { list-style: none; padding-left: 0; }
.tiptap ul[data-type="taskList"] li { display: flex; align-items: flex-start; gap: 0.4em; }
.tiptap ul[data-type="taskList"] li > label { flex-shrink: 0; margin-top: 0.15em; }
.tiptap blockquote {
border-left: 3px solid #3498db;
padding-left: 1em;
margin: 0.5em 0;
color: #6b7280;
} }
.tiptap pre { /* Vditor 图片上传进度条 */
background: #1a2332; .vditor-upload {
color: #e2e8f0; position: fixed;
padding: 12px 16px; top: 0;
border-radius: 6px; left: 0;
z-index: 10000;
}
/* ========== Shortcode 卡片通用 ========== */
/* 所有 shortcode 卡片共享的基础外观 */
.zone-card {
display: block;
margin: 16px 0;
}
.zone-card-loading {
display: block;
padding: 16px;
text-align: center;
color: #9ca3af;
font-size: 13px; font-size: 13px;
line-height: 1.5; background: #f9fafb;
overflow-x: auto; border: 1px dashed #d1d5db;
margin: 0.5em 0; border-radius: 8px;
position: relative;
}
.tiptap pre code { background: transparent; padding: 0; color: inherit; font-size: inherit; white-space: pre; display: block; }
.tiptap code {
background: #f3f4f6;
padding: 2px 5px;
border-radius: 4px;
font-size: 0.9em;
font-family: 'SF Mono', 'Menlo', 'Monaco', monospace;
} }
.tiptap pre .code-lang-label { /* 通用卡片布局 */
position: absolute; .zone-card-event,
top: 0; left: 0; right: 0; .zone-card-game,
padding: 4px 16px; .zone-card-poll,
font-size: 11px; .zone-card-resource {
font-weight: 600;
color: #94a3b8;
background: #2c3e50;
border-radius: 6px 6px 0 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Helvetica Neue', Arial, sans-serif;
pointer-events: none;
z-index: 1;
}
/* highlight.js One Dark 语法高亮 — 编辑器内 */
.tiptap pre code .hljs-keyword,
.tiptap pre code .hljs-selector-tag,
.tiptap pre code .hljs-literal,
.tiptap pre code .hljs-section,
.tiptap pre code .hljs-link { color: #c792ea; }
.tiptap pre code .hljs-string,
.tiptap pre code .hljs-title,
.tiptap pre code .hljs-name,
.tiptap pre code .hljs-type,
.tiptap pre code .hljs-attribute,
.tiptap pre code .hljs-symbol,
.tiptap pre code .hljs-bullet,
.tiptap pre code .hljs-addition,
.tiptap pre code .hljs-variable,
.tiptap pre code .hljs-template-tag,
.tiptap pre code .hljs-template-variable { color: #c3e88d; }
.tiptap pre code .hljs-comment,
.tiptap pre code .hljs-quote,
.tiptap pre code .hljs-deletion,
.tiptap pre code .hljs-meta { color: #676e95; }
.tiptap pre code .hljs-number,
.tiptap pre code .hljs-regexp,
.tiptap pre code .hljs-built_in { color: #f78c6c; }
.tiptap pre code .hljs-function .hljs-title,
.tiptap pre code .hljs-doctag,
.tiptap pre code .hljs-formula { color: #82aaff; }
.tiptap pre code .hljs-tag,
.tiptap pre code .hljs-subst { color: #f07178; }
.tiptap pre code .hljs-emphasis { font-style: italic; }
.tiptap pre code .hljs-strong { font-weight: bold; }
.tiptap pre code .hljs-attr,
.tiptap pre code .hljs-selector-attr,
.tiptap pre code .hljs-selector-class,
.tiptap pre code .hljs-selector-id,
.tiptap pre code .hljs-selector-pseudo { color: #ffcb6b; }
.tiptap hr { border: none; border-top: 1px solid #e5e7eb; margin: 1em 0; }
.tiptap img { max-width: 100%; height: auto; border-radius: 6px; }
.tiptap a { color: #3498db; text-decoration: underline; }
.tiptap p.is-editor-empty:first-child::before {
content: attr(data-placeholder);
float: left;
color: #c4c4c4;
pointer-events: none;
height: 0;
}
/* ========== 工具栏 ========== */
.editor-toolbar {
display: flex; display: flex;
gap: 2px; align-items: center;
padding: 6px 12px; gap: 14px;
padding: 16px 18px;
margin: 14px 0;
border-radius: 10px;
border: 1px solid #e5e7eb;
background: #fafafa; background: #fafafa;
border-bottom: 1px solid #e5e7eb; transition: box-shadow 0.2s;
flex-shrink: 0;
flex-wrap: wrap;
} }
.editor-toolbar button { .zone-card-event:hover,
display: inline-flex; .zone-card-game:hover,
.zone-card-poll:hover,
.zone-card-resource:hover {
box-shadow: 0 2px 8px rgba(0,0,0,0.06);
}
.zone-card-event-icon,
.zone-card-game-icon,
.zone-card-poll-icon,
.zone-card-resource-icon {
font-size: 28px;
flex-shrink: 0;
width: 40px;
height: 40px;
display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
width: 32px; border-radius: 8px;
height: 30px; background: #f3f4f6;
border: 1px solid transparent;
border-radius: 4px;
background: transparent;
color: #374151;
font-size: 13px;
cursor: pointer;
transition: background 0.15s;
} }
.editor-toolbar button:hover { background: #e5e7eb; }
.editor-toolbar button.is-active { background: #1a2332; color: #fff; } .zone-card-event-body,
.code-lang-select { .zone-card-game-body,
height: 30px; .zone-card-poll-body,
padding: 0 6px; .zone-card-resource-body {
border: 1px solid transparent; flex: 1;
border-radius: 4px; min-width: 0;
background: transparent; }
color: #374151;
.zone-card-event-title,
.zone-card-game-title,
.zone-card-poll-title,
.zone-card-resource-title {
font-size: 14px;
font-weight: 600;
color: #111827;
}
.zone-card-event-id,
.zone-card-game-slug,
.zone-card-poll-id,
.zone-card-resource-id {
font-size: 12px; font-size: 12px;
cursor: pointer; color: #9ca3af;
font-family: inherit; margin-top: 2px;
outline: none; font-family: 'Menlo', 'Consolas', monospace;
} }
.code-lang-select:hover { border-color: #e5e7eb; }
.code-lang-select:focus { border-color: #3498db; } .zone-card-event-link,
.editor-toolbar .toolbar-divider { .zone-card-game-link,
width: 1px; .zone-card-resource-link {
background: #e5e7eb; font-size: 13px;
margin: 3px 4px; color: #6366f1;
text-decoration: none;
font-weight: 500;
white-space: nowrap;
}
.zone-card-event-link:hover,
.zone-card-game-link:hover,
.zone-card-resource-link:hover {
color: #4f46e5;
}
/* 投票 — 预留状态 */
.zone-card-poll-pending {
font-size: 12px;
color: #d97706;
background: #fef3c7;
padding: 4px 10px;
border-radius: 12px;
white-space: nowrap;
} }

View File

@ -1,319 +1,209 @@
/** /**
* MetaLab Editor — Tiptap 所见即所得编辑器 * MetaLab Editor — Vditor Markdown 所见即所得编辑器
* *
* Tiptap 基于 ProseMirror输出 HTML无需 Markdown 知识即可使用 * Vditor 原生支持 MD + WYSIWYG后端存储 Markdown。
* 后端存储 HTML通过 bluemonday 消毒确保安全 * 图片上传复用现有 /api/posts/upload-image后端已返回 Vditor 原生响应格式
* 安全模型:前端输出 MD纯文本→ 后端直接存储 → 详情页客户端 Vditor 渲染为 HTML。
* *
* 安全模型Tiptap → HTML富文本→ 后端 bluemonday 消毒 → 存储 * Shortcode 扩展语法([zone:type:params]
* 输入 [zone: 触发自动补全,选择类型后自动生成对应语法。
* 支持的 shortcode 详情见 /docs/vditor-migration-plan.md
*
* Vditor 全局对象由 /static/vditor/dist/index.min.js 提供。
*/ */
import { Editor } from '@tiptap/core'
import StarterKit from '@tiptap/starter-kit'
import Placeholder from '@tiptap/extension-placeholder'
import Image from '@tiptap/extension-image'
import CodeBlockLowlight from '@tiptap/extension-code-block-lowlight'
import { common, createLowlight } from 'lowlight'
// ---- 状态 ---- let vditor = null
let editor = null
let currentHTML = ''
let draftTimer = null let draftTimer = null
let submitting = false let submitting = false
// ---- CSRF Token ----
function getCsrfToken() {
const meta = document.querySelector('meta[name="csrf-token"]')
return meta ? meta.getAttribute('content') : ''
}
// ---- 初始化 ---- // ---- 初始化 ----
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
const container = document.getElementById('editor-container') const container = document.getElementById('vditor')
if (!container) return if (!container) return
const postId = document.getElementById('postId')?.value const postId = document.getElementById('postId')?.value
const isEdit = !!postId const isEdit = !!postId
// 从容器 data-content 属性获取初始 HTML 内容(由后端 SSR 渲染) // 获取初始内容:编辑模式下从后端传入,新帖模式尝试恢复草稿
const initialContent = container.dataset.content || '' let initialMD = ''
currentHTML = initialContent if (isEdit) {
// 编辑模式:从后端 JSON 嵌入的 Post.BodyMD中获取
editor = new Editor({ // 由 Go 模板 {{.Post.Body}} 嵌入到 hidden 字段或 script 标签
element: container, initialMD = ''
extensions: [
StarterKit.configure({
heading: { levels: [1, 2, 3] },
link: {
openOnClick: false,
HTMLAttributes: { rel: 'noopener noreferrer', target: '_blank' },
},
codeBlock: false,
}),
CodeBlockLowlight.configure({
lowlight: createLowlight(common),
defaultLanguage: null,
}),
Placeholder.configure({
placeholder: '在此输入正文...',
}),
Image.configure({
inline: true,
}),
],
content: initialContent,
onUpdate: ({ editor }) => {
const html = editor.getHTML()
// 忽略空内容(<p></p>
currentHTML = html === '<p></p>' ? '' : html
updateWordCount(editor.getText())
scheduleDraft(currentHTML)
},
})
// 初始字数统计
updateWordCount(editor.getText())
// 恢复草稿(延迟确保编辑器完全初始化)
if (!isEdit) {
setTimeout(() => restoreDraft(), 100)
} }
// 工具栏按钮绑定 // 从隐藏字段获取编辑内容Go 模板传入)
setupToolbar() const editBodyEl = document.getElementById('editBody')
if (editBodyEl) {
initialMD = editBodyEl.value
}
// 全屏切换 vditor = new Vditor('vditor', {
document.getElementById('btnFullscreen')?.addEventListener('click', toggleFullscreen) // 核心配置
mode: 'wysiwyg',
cdn: '/static/vditor',
height: '100%',
lang: 'zh_CN',
placeholder: '在此输入正文...',
// 工具栏(精简,适合技术社区)
toolbar: [
'headings', 'bold', 'italic', 'strike', '|',
'line', 'code', 'inline-code', 'link', 'quote', '|',
'list', 'ordered-list', 'check', 'outdent', 'indent', '|',
'upload', 'table', '|',
'undo', 'redo', '|',
'fullscreen', 'code-theme', '|',
'outline', 'preview', 'devtools',
],
// 计数器
counter: {
enable: true,
type: 'text',
},
// Shortcode 自动补全提示
// 输入 [zone: 后触发,列出所有已注册的 shortcode 类型
hint: {
extend: [
{
key: '[zone:event:',
value: '[zone:event:活动ID]',
},
{
key: '[zone:game:',
value: '[zone:game:游戏Slug]',
},
{
key: '[zone:poll:',
value: '[zone:poll:投票ID]',
},
{
key: '[zone:resource:',
value: '[zone:resource:资源ID]',
},
],
},
// 大纲
outline: {
enable: true,
position: 'right',
},
// 预览配置
preview: {
theme: { current: 'light', path: '/static/vditor/dist/css/content-theme' },
hljs: { style: 'github-dark', enable: true },
markdown: { codeBlockPreview: true },
},
// 图片上传
upload: {
url: '/api/posts/upload-image',
fieldName: 'file',
max: 5 * 1024 * 1024,
accept: 'image/jpg,image/jpeg,image/png,image/gif,image/webp',
// setHeaders 为函数,每次上传前重新读取 CSRF token
setHeaders() {
return { 'X-CSRF-Token': getCsrfToken() }
},
},
// 初始内容
value: initialMD,
// 初始化完成
after() {
// 新帖模式下恢复草稿
if (!isEdit) {
setTimeout(() => restoreDraft(), 100)
}
// 更新字数
updateWordCount()
},
// 内容变化
input(value) {
scheduleDraft(value)
updateWordCount()
},
})
// 表单提交 // 表单提交
document.getElementById('postForm')?.addEventListener('submit', handleSubmit) document.getElementById('postForm')?.addEventListener('submit', handleSubmit)
// Ctrl+S / F11 快捷键 // 快捷键 Ctrl+S
document.addEventListener('keydown', handleKeyboard) document.addEventListener('keydown', (e) => {
}) if ((e.ctrlKey || e.metaKey) && e.key === 's') {
e.preventDefault()
// ---- 工具栏 ---- document.getElementById('postForm')?.dispatchEvent(new Event('submit'))
function setupToolbar() {
const toolbar = document.getElementById('toolbar')
if (!toolbar) return
const actionMap = {
bold: () => editor.chain().focus().toggleBold().run(),
italic: () => editor.chain().focus().toggleItalic().run(),
strike: () => editor.chain().focus().toggleStrike().run(),
code: () => editor.chain().focus().toggleCode().run(),
h1: () => editor.chain().focus().toggleHeading({ level: 1 }).run(),
h2: () => editor.chain().focus().toggleHeading({ level: 2 }).run(),
h3: () => editor.chain().focus().toggleHeading({ level: 3 }).run(),
bulletList: () => editor.chain().focus().toggleBulletList().run(),
orderedList: () => editor.chain().focus().toggleOrderedList().run(),
blockquote: () => editor.chain().focus().toggleBlockquote().run(),
codeBlock: () => editor.chain().focus().toggleCodeBlock().run(),
horizontalRule: () => editor.chain().focus().setHorizontalRule().run(),
undo: () => editor.chain().focus().undo().run(),
redo: () => editor.chain().focus().redo().run(),
}
const activeCheck = {
bold: () => editor.isActive('bold'),
italic: () => editor.isActive('italic'),
strike: () => editor.isActive('strike'),
code: () => editor.isActive('code'),
h1: () => editor.isActive('heading', { level: 1 }),
h2: () => editor.isActive('heading', { level: 2 }),
h3: () => editor.isActive('heading', { level: 3 }),
bulletList: () => editor.isActive('bulletList'),
orderedList: () => editor.isActive('orderedList'),
blockquote: () => editor.isActive('blockquote'),
codeBlock: () => editor.isActive('codeBlock'),
}
function updateActive() {
toolbar.querySelectorAll('[data-action]').forEach(btn => {
const action = btn.dataset.action
const check = activeCheck[action]
btn.classList.toggle('is-active', check ? check() : false)
})
}
toolbar.addEventListener('click', (e) => {
const btn = e.target.closest('[data-action]')
if (!btn) return
const action = btn.dataset.action
if (actionMap[action]) {
actionMap[action]()
updateActive()
} }
}) })
// ---- 代码块语言选择器 ---- // 全屏快捷键 F11
const langSelect = document.getElementById('codeLangSelect') document.addEventListener('keydown', (e) => {
if (langSelect) { if (e.key === 'F11') {
// 阻止 select 获取焦点导致编辑器失焦
langSelect.addEventListener('mousedown', (e) => {
e.preventDefault() e.preventDefault()
// 手动切换选项 const layout = document.querySelector('.editor-layout')
const opts = langSelect.options if (layout) layout.classList.toggle('fullscreen')
let nextIdx = langSelect.selectedIndex + 1 }
if (nextIdx >= opts.length) nextIdx = 0 })
langSelect.selectedIndex = nextIdx
const lang = opts[nextIdx].value
// 在编辑器内更新代码块语言属性
editor.chain().focus().updateAttributes('codeBlock', { language: lang === 'text' ? null : lang }).run()
})
// 当光标在代码块内时显示语言选择器并同步当前语言,移出时隐藏
editor.on('selectionUpdate', () => {
if (editor.isActive('codeBlock')) {
langSelect.style.display = ''
const attrs = editor.getAttributes('codeBlock')
langSelect.value = attrs.language || 'text'
} else {
langSelect.style.display = 'none'
}
})
}
// ---- 代码块语言标签注入 ----
// 为每个 <pre> 注入语言标签 DOM 元素(因为 CSS ::before 在 overflow:auto 容器中不可靠)
// Tiptap CodeBlockLowlight 在 pre 上输出 data-language 属性,同时 code 上有 language-xxx class
function injectCodeLangLabels() {
const editorEl = document.querySelector('#editor-container .tiptap')
if (!editorEl) return
editorEl.querySelectorAll('pre').forEach(pre => {
// 移除旧标签
const old = pre.querySelector('.code-lang-label')
if (old) old.remove()
// 从 pre 的 data-language 或 code 的 class 中获取语言
let lang = pre.getAttribute('data-language')
if (!lang) {
const code = pre.querySelector('code')
if (code) {
const match = code.className.match(/language-(\w+)/)
if (match) lang = match[1]
}
}
lang = lang || 'text'
const label = document.createElement('div')
label.className = 'code-lang-label'
label.textContent = lang
pre.insertBefore(label, pre.firstChild)
pre.style.paddingTop = '32px'
})
}
editor.on('update', injectCodeLangLabels)
// 初始注入
setTimeout(injectCodeLangLabels, 50)
editor.on('selectionUpdate', updateActive)
editor.on('update', updateActive)
}
// ---- 图片上传(拖拽/粘贴) ----
// Tiptap 默认支持图片拖入,但我们需要上传到服务器并替换 src
// 通过监听 editor 的 drop/paste 事件处理图片
document.addEventListener('DOMContentLoaded', () => {
// 延迟绑定,等 editor 初始化
setTimeout(() => {
const editorEl = document.querySelector('#editor-container .tiptap')
if (!editorEl) return
// 粘贴图片
editorEl.addEventListener('paste', async (e) => {
const items = e.clipboardData?.items
if (!items) return
for (const item of items) {
if (item.type.startsWith('image/')) {
e.preventDefault()
const file = item.getAsFile()
const url = await uploadSingleImage(file)
if (url) {
editor.chain().focus().setImage({ src: url }).run()
}
break
}
}
})
// 拖入图片
editorEl.addEventListener('drop', async (e) => {
const files = e.dataTransfer?.files
if (!files || files.length === 0) return
const imageFiles = Array.from(files).filter(f => f.type.startsWith('image/'))
if (imageFiles.length === 0) return
e.preventDefault()
for (const file of imageFiles) {
const url = await uploadSingleImage(file)
if (url) {
editor.chain().focus().setImage({ src: url }).run()
}
}
})
}, 200)
}) })
async function uploadSingleImage(file) {
const csrfMeta = document.querySelector('meta[name="csrf-token"]')
const csrf = csrfMeta ? csrfMeta.getAttribute('content') : ''
const formData = new FormData()
formData.append('file', file)
try {
const resp = await fetch('/api/posts/upload-image', {
method: 'POST',
headers: { 'X-CSRF-Token': csrf },
body: formData,
})
const data = await resp.json()
if (data.success || data.code === 200) {
return data.data?.url || data.url || ''
}
} catch (e) {
// 上传失败,静默忽略
}
return ''
}
// ---- 字数统计 ---- // ---- 字数统计 ----
function updateWordCount(text) { function updateWordCount() {
const el = document.getElementById('wordCount') const el = document.getElementById('wordCount')
if (!el) return if (!el || !vditor) return
const len = (text || '').replace(/\s/g, '').length // Vditor 自带计数器已渲染到 .vditor-counter我们同步到自定义的状态栏
el.textContent = len + ' 字' const counterEl = document.querySelector('.vditor-counter')
if (counterEl) {
el.textContent = counterEl.textContent
}
} }
// ---- 草稿保存/恢复 ---- // ---- 草稿保存/恢复 ----
function scheduleDraft(html) { function scheduleDraft(md) {
clearTimeout(draftTimer) clearTimeout(draftTimer)
draftTimer = setTimeout(() => saveDraft(html), 2000) draftTimer = setTimeout(() => saveDraft(md), 2000)
} }
function saveDraft(html) { function saveDraft(md) {
const title = document.getElementById('postTitle')?.value || '' const title = document.getElementById('postTitle')?.value || ''
if (!html.trim() && !title.trim()) return if (!md.trim() && !title.trim()) return
try { try {
localStorage.setItem('draft_post_title', title) localStorage.setItem('draft_post_title', title)
localStorage.setItem('draft_post_body_html', html) localStorage.setItem('draft_post_body_md', md)
const statusEl = document.getElementById('draftStatus') const statusEl = document.getElementById('draftStatus')
if (statusEl) { if (statusEl) {
statusEl.style.display = '' statusEl.style.display = ''
setTimeout(() => { statusEl.style.display = 'none' }, 2000) setTimeout(() => { statusEl.style.display = 'none' }, 2000)
} }
} catch (e) { } catch (e) {
// localStorage 不可用,静默忽略 // localStorage 不可用
} }
} }
function restoreDraft() { function restoreDraft() {
try { try {
const title = localStorage.getItem('draft_post_title') const title = localStorage.getItem('draft_post_title')
const html = localStorage.getItem('draft_post_body_html') const md = localStorage.getItem('draft_post_body_md')
if (title) { if (title) {
document.getElementById('postTitle').value = title document.getElementById('postTitle').value = title
} }
if (html && editor) { if (md && vditor) {
editor.commands.setContent(html) vditor.setValue(md)
currentHTML = html updateWordCount()
updateWordCount(editor.getText())
} }
} catch (e) { } catch (e) {
// 静默忽略 // 静默忽略
@ -323,30 +213,10 @@ function restoreDraft() {
function clearDraft() { function clearDraft() {
try { try {
localStorage.removeItem('draft_post_title') localStorage.removeItem('draft_post_title')
localStorage.removeItem('draft_post_body_html') localStorage.removeItem('draft_post_body_md')
} catch (e) { /* ignore */ } } catch (e) { /* ignore */ }
} }
// ---- 全屏 ----
function toggleFullscreen() {
const layout = document.querySelector('.editor-layout')
if (!layout) return
layout.classList.toggle('fullscreen')
}
// ---- 键盘快捷键 ----
function handleKeyboard(e) {
if (e.key === 'F11') {
e.preventDefault()
toggleFullscreen()
}
if ((e.ctrlKey || e.metaKey) && e.key === 's') {
e.preventDefault()
document.getElementById('postForm')?.dispatchEvent(new Event('submit'))
}
}
// ---- 表单提交 ---- // ---- 表单提交 ----
async function handleSubmit(e) { async function handleSubmit(e) {
e.preventDefault() e.preventDefault()
@ -366,26 +236,21 @@ async function handleSubmit(e) {
if (!title) { alert('请输入标题'); return } if (!title) { alert('请输入标题'); return }
// 获取编辑器 HTML 内容 // 获取 Vditor Markdown 内容
const body = editor ? editor.getHTML() : '' const body = vditor ? vditor.getValue() : ''
// 忽略空段落 if (!body.trim()) { alert('请输入正文'); return }
const cleanBody = body === '<p></p>' ? '' : body
if (!cleanBody.trim()) { alert('请输入正文'); return }
const isEdit = !!postId const isEdit = !!postId
const url = isEdit ? '/api/posts/' + postId : '/api/posts' const url = isEdit ? '/api/posts/' + postId : '/api/posts'
const method = isEdit ? 'PUT' : 'POST' const method = isEdit ? 'PUT' : 'POST'
const csrfMeta = document.querySelector('meta[name="csrf-token"]')
const csrf = csrfMeta ? csrfMeta.getAttribute('content') : ''
const resp = await fetch(url, { const resp = await fetch(url, {
method, method,
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
'X-CSRF-Token': csrf, 'X-CSRF-Token': getCsrfToken(),
}, },
body: JSON.stringify({ title, body: cleanBody }), body: JSON.stringify({ title, body }),
}) })
const data = await resp.json() const data = await resp.json()
@ -407,8 +272,8 @@ async function handleSubmit(e) {
submitting = false submitting = false
if (submitBtn) { if (submitBtn) {
submitBtn.disabled = false submitBtn.disabled = false
submitBtn.textContent = submitBtn.textContent === '提交中...' ? const isEdit = !!document.getElementById('postId')?.value
(document.getElementById('postId')?.value ? '保存修改' : '发布帖子') : submitBtn.textContent submitBtn.textContent = isEdit ? '保存修改' : '发布帖子'
} }
} }
} }

View File

@ -0,0 +1,135 @@
/**
* Shortcode 卡片渲染器 — 配合 [zone:type:params] Markdown 扩展语法
*
* 工作流程:
* 1. 后端 ShortcodeService 将 MD 中的 [zone:type:params] 替换为占位 <div class="zone-card">
* 2. Vditor.preview() 渲染 MD占位 div 原样保留
* 3. 本脚本扫描所有 .zone-card根据 data-zone-* 属性渲染对应卡片 UI
*
* 添加新卡片类型:
* 1. 在 cardRenderers 中注册类型标识和渲染函数
* 2. 渲染函数接收 id 参数,返回 HTML 字符串
* 3. 如果后端 API 已可用,在渲染函数中 fetch 数据;否则展示静态占位卡片
*
* 后端 shortcode 语法参考: /docs/vditor-migration-plan.md
*/
(function () {
'use strict';
// =========================================================================
// 卡片渲染函数注册表
//
// 每个渲染函数签名为: function(id: string) → html: string
// id 来自 [zone:type:id] 中的 id 部分
// =========================================================================
var cardRenderers = {
/**
* 活动卡片 — [zone:event:活动ID]
* TODO: 接入活动 API 后改为 fetch 动态数据
*/
event: function (id) {
return '' +
'<div class="zone-card-event">' +
' <div class="zone-card-event-icon">🎪</div>' +
' <div class="zone-card-event-body">' +
' <div class="zone-card-event-title">官方活动</div>' +
' <div class="zone-card-event-id">活动 ID: ' + escapeHtml(id) + '</div>' +
' </div>' +
' <a class="zone-card-event-link" href="/events/' + escapeHtml(id) + '" target="_blank">查看详情 →</a>' +
'</div>';
},
/**
* 游戏卡片 — [zone:game:游戏slug]
* TODO: 接入游戏数据库 API 后改为 fetch 动态数据
*/
game: function (id) {
return '' +
'<div class="zone-card-game">' +
' <div class="zone-card-game-icon">🎮</div>' +
' <div class="zone-card-game-body">' +
' <div class="zone-card-game-title">相关游戏</div>' +
' <div class="zone-card-game-slug">' + escapeHtml(id) + '</div>' +
' </div>' +
' <a class="zone-card-game-link" href="/games/' + escapeHtml(id) + '" target="_blank">查看 →</a>' +
'</div>';
},
/**
* 投票组件 — [zone:poll:投票ID]
* ※预留:后端投票 API 未实现,当前展示占位卡片
*/
poll: function (id) {
return '' +
'<div class="zone-card-poll">' +
' <div class="zone-card-poll-icon">📊</div>' +
' <div class="zone-card-poll-body">' +
' <div class="zone-card-poll-title">社区投票</div>' +
' <div class="zone-card-poll-id">投票 ID: ' + escapeHtml(id) + '</div>' +
' </div>' +
' <span class="zone-card-poll-pending">即将开放</span>' +
'</div>';
},
/**
* 资源卡片 — [zone:resource:资源ID]
* ※预留:后端资源 API 未实现,当前展示占位卡片
*/
resource: function (id) {
return '' +
'<div class="zone-card-resource">' +
' <div class="zone-card-resource-icon">📦</div>' +
' <div class="zone-card-resource-body">' +
' <div class="zone-card-resource-title">推荐资源</div>' +
' <div class="zone-card-resource-id">资源 ID: ' + escapeHtml(id) + '</div>' +
' </div>' +
' <a class="zone-card-resource-link" href="/resources/' + escapeHtml(id) + '" target="_blank">查看 →</a>' +
'</div>';
}
};
// =========================================================================
// 工具函数
// =========================================================================
function escapeHtml(str) {
var div = document.createElement('div');
div.appendChild(document.createTextNode(str));
return div.innerHTML;
}
// =========================================================================
// 主渲染逻辑
// =========================================================================
/**
* renderShortcodes — 扫描容器内所有 .zone-card 占位并渲染
* @param {HTMLElement} container — 包含 shortcode 占位的 DOM 容器
*/
window.renderShortcodes = function (container) {
if (!container) return;
var placeholders = container.querySelectorAll('.zone-card');
for (var i = 0; i < placeholders.length; i++) {
var el = placeholders[i];
var type = el.getAttribute('data-zone-type');
var id = el.getAttribute('data-zone-id');
if (!type || !id) continue;
var renderer = cardRenderers[type];
if (!renderer) continue;
// 渲染卡片 HTML 并替换占位 div
try {
el.outerHTML = renderer(id);
} catch (e) {
// 渲染失败时保留占位文本
console.warn('[Shortcode] render failed for type=' + type + ' id=' + id, e);
}
}
};
})();

View File

@ -0,0 +1,210 @@
/*!
* Vditor - A markdown editor written in TypeScript.
*
* MIT License
*
* Copyright (c) 2018-present B3log 开源, b3log.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*/
.vditor-reset {
color: rgba(0, 0, 0, .85);
font-size: 14px;
line-height: 2;
}
.vditor-reset img {
max-width: calc(100% - 32px);
}
.vditor-reset p > img {
margin: 34px 0;
box-shadow: 0 8px 20px rgba(143, 168, 191, 0.35);
}
.vditor-reset h1 {
margin-bottom: 20px;
color: rgba(0, 0, 0, .85);
font-weight: 500;
font-size: 30px;
font-family: Avenir, -apple-system, BlinkMacSystemFont, segoe ui, Roboto, helvetica neue, Arial, noto sans, sans-serif, apple color emoji, segoe ui emoji, segoe ui symbol, noto color emoji, sans-serif;
line-height: 38px
}
.vditor-reset h2 {
font-size: 24px;
line-height: 32px;
}
.vditor-reset h2,
.vditor-reset h3,
.vditor-reset h4,
.vditor-reset h5,
.vditor-reset h6 {
clear: both;
margin: 1.6em 0 .6em;
color: rgba(0, 0, 0, .85);
font-weight: 500;
font-family: Avenir, -apple-system, BlinkMacSystemFont, segoe ui, Roboto, helvetica neue, Arial, noto sans, sans-serif, apple color emoji, segoe ui emoji, segoe ui symbol, noto color emoji, sans-serif
}
.vditor-reset h3 {
font-size: 18px;
}
.vditor-reset h4 {
font-size: 16px;
}
.vditor-reset h5 {
font-size: 14px;
}
.vditor-reset h6 {
font-size: 12px;
}
.vditor-reset hr {
clear: both;
height: 1px;
margin: 56px 0;
background: #f0f0f0;
border: 0;
}
.vditor-reset p,
.vditor-reset pre {
margin: 1em 0;
}
.vditor-reset ul > li {
margin-left: 20px;
padding-left: 4px;
list-style-type: circle;
}
.vditor-reset ol > li {
margin-left: 20px;
padding-left: 4px;
list-style-type: decimal;
}
.vditor-reset ul > li > p,
.vditor-reset ol > li > p {
margin: 0.2em 0;
}
.vditor-reset code {
margin: 0 1px;
padding: .2em .4em;
font-size: .9em;
border-radius: 3px;
border: 1px solid #f0f0f0;
font-family: sfmono-regular, Consolas, liberation mono, Menlo, Courier, monospace;
}
.vditor-reset code:not(.hljs):not(.highlight-chroma) {
background: #f2f4f5;
color: rgba(0, 0, 0, .65);
}
.vditor-reset pre {
font-family: sfmono-regular, Consolas, liberation mono, Menlo, Courier, monospace;
border-radius: 2px;
}
.vditor-reset .language-abc svg,
.vditor-reset .language-abc path {
fill: currentColor;
color: rgba(0, 0, 0, .85);
}
.vditor-reset .language-graphviz polygon {
fill: transparent;
}
.vditor-reset strong,
.vditor-reset b {
font-weight: 500;
}
.vditor-reset > table {
width: 100%;
margin: 8px 0 16px;
direction: ltr;
empty-cells: show;
border: 1px solid #f0f0f0;
border-collapse: collapse;
border-spacing: 0
}
.vditor-reset > table th {
color: #5c6b77;
font-weight: 500;
white-space: nowrap;
background: rgba(0, 0, 0, .02)
}
.vditor-reset > table th, .vditor-reset > table td {
padding: 16px 24px;
text-align: left;
border: 1px solid #f0f0f0
}
.vditor-reset blockquote {
margin: 1em 0;
padding-left: .8em;
color: rgba(0, 0, 0, .45);
font-size: 90%;
border-left: 4px solid #f0f0f0
}
.vditor-reset blockquote p {
margin: 0;
}
.vditor-reset .vditor-anchor {
margin-left: 8px;
opacity: 0;
transition: opacity 0.3s;
}
.vditor-reset h1:hover .vditor-anchor,
.vditor-reset h2:hover .vditor-anchor,
.vditor-reset h3:hover .vditor-anchor,
.vditor-reset h4:hover .vditor-anchor,
.vditor-reset h5:hover .vditor-anchor,
.vditor-reset h6:hover .vditor-anchor {
display: inline-block;
opacity: 1;
}
.vditor-reset > br,
.vditor-reset > p > br {
clear: both;
}
.vditor-reset a, .vditor-ir__link {
color: #1890ff;
text-decoration: none;
outline: none;
cursor: pointer;
}

View File

@ -0,0 +1,123 @@
/*!
* Vditor - A markdown editor written in TypeScript.
*
* MIT License
*
* Copyright (c) 2018-present B3log 开源, b3log.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*/
.vditor-reset {
color: #d1d5da;
}
.vditor-reset a, .vditor-ir__link {
color: #4285f4;
}
.vditor-reset h1, .vditor-reset h2 {
padding-bottom: 0.3em;
border-bottom: 1px solid #d1d5da
}
.vditor-reset hr {
background-color: #d1d5da
}
.vditor-reset blockquote {
padding: 0 1em;
color: #b9b9b9;
border-left: .25em solid #d1d5da
}
.vditor-reset iframe {
border: 1px solid #141414
}
.vditor-reset table tr {
background-color: #2f363d
}
.vditor-reset table td, .vditor-reset table th {
border: 1px solid #dfe2e5
}
.vditor-reset table tbody tr:nth-child(2n) {
background-color: #24292e
}
.vditor-reset code:not(.hljs):not(.highlight-chroma) {
background-color: rgba(66, 133, 244, .36);
}
.vditor-reset .language-abc svg,
.vditor-reset .language-abc path {
fill: currentColor;
color: #d1d5da;
}
.language-graphviz polygon {
fill: rgba(66, 133, 244, .36);
}
.vditor-reset kbd {
color: #d1d5da;
background-color: #2f363d;
border: 1px solid #141414;
box-shadow: inset 0 -1px 0 #141414
}
.vditor-copy svg {
color: #b9b9b9
}
.vditor-speech {
background-color: #1d2125;
border: 1px solid #141414;
color: #b9b9b9
}
.vditor-speech--current, .vditor-speech:hover {
color: #fff
}
.vditor-linkcard a {
background-color: #1d2125;
}
.vditor-linkcard a:visited .vditor-linkcard__abstract {
color: hsla(0, 0%, 72.5%, .36)
}
.vditor-linkcard__title {
color: #d1d5da
}
.vditor-linkcard__abstract {
color: #b9b9b9
}
.vditor-linkcard__site {
color: #fff
}
.vditor-linkcard__image {
background-color: hsla(0, 0%, 72.5%, .36)
}

View File

@ -0,0 +1,101 @@
/*!
* Vditor - A markdown editor written in TypeScript.
*
* MIT License
*
* Copyright (c) 2018-present B3log 开源, b3log.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*/
.vditor-reset h1, .vditor-reset h2 {
padding-bottom: 0.3em;
border-bottom: 1px solid #eaecef;
}
.vditor-reset hr {
background-color: #eaecef;
}
.vditor-reset blockquote {
color: #6a737d;
border-left: .25em solid #eaecef
}
.vditor-reset iframe {
border: 1px solid #d1d5da
}
.vditor-reset table tr {
border-top: 1px solid #c6cbd1;
background-color: #fafbfc
}
.vditor-reset table td, .vditor-reset table th {
border: 1px solid #dfe2e5
}
.vditor-reset table tbody tr:nth-child(2n) {
background-color: #fff
}
.vditor-reset code:not(.hljs):not(.highlight-chroma) {
background-color: rgba(27, 31, 35, .05);
}
.vditor-reset kbd {
color: #24292e;
background-color: #fafbfc;
border: solid 1px #d1d5da;
box-shadow: inset 0 -1px 0 #d1d5da;
}
.vditor-speech {
background-color: #f6f8fa;
border: 1px solid #d1d5da;
color: #586069;
}
.vditor-speech--current, .vditor-speech:hover {
color: #4285f4;
}
.vditor-linkcard a {
background-color: #f6f8fa;
}
.vditor-linkcard a:visited .vditor-linkcard__abstract {
color: rgba(88, 96, 105, 0.36);
}
.vditor-linkcard__title {
color: #24292e;
}
.vditor-linkcard__abstract {
color: #586069;
}
.vditor-linkcard__site {
color: #4285f4;
}
.vditor-linkcard__image {
background-color: rgba(88, 96, 105, 0.36);
}

View File

@ -0,0 +1,94 @@
/*!
* Vditor - A markdown editor written in TypeScript.
*
* MIT License
*
* Copyright (c) 2018-present B3log 开源, b3log.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*/
.vditor-reset {
font-family: "mp-quote", -apple-system-font, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Noto Color Emoji", "Segoe UI Symbol", "Android Emoji", "EmojiSymbols";
color: rgb(62, 62, 62);
}
.vditor-reset a, .vditor-ir__link {
color: #576b95;
}
.vditor-reset h1 {
font-weight: 400;
text-align: center;
color: rgb(26, 173, 25);
font-size: 24px;
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAACCAYAAABYBvyLAAAAGElEQVQImWNkXs/wn4GKgImahjEwMDAAAA6aAbVUOzXRAAAAAElFTkSuQmCC);
background-repeat: no-repeat;
background-position: center bottom;
}
.vditor-reset h2 {
font-weight: 400;
text-align: center;
font-size: 20px;
}
.vditor-reset h3,
.vditor-reset h4,
.vditor-reset h5,
.vditor-reset h6 {
font-weight: 400;
}
.vditor-reset hr {
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
transform-origin: 0 0;
transform: scale(1, 0.5);
height: 0;
}
.vditor-reset blockquote {
padding: 4px 0 0 10px;
border-left: 3px solid #dbdbdb;
color: #9a9a9a;
line-height: 1.6;
font-size: 15px;
margin: 1em 0;
}
.vditor-reset code {
font-size: 14px;
border: 1px solid #f0f0f0;
border-radius: 2px;
}
.vditor-reset code:not(.hljs):not(.highlight-chroma) {
background-color: rgba(0, 0, 0, 0.03);
color: #333;
}
.vditor-reset .language-abc svg,
.vditor-reset .language-abc path {
fill: currentColor;
color: rgb(62, 62, 62);
}
.vditor-reset .language-graphviz polygon {
fill: transparent;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

View File

@ -0,0 +1 @@
<svg width="160px" height="160px" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" preserveAspectRatio="xMidYMid" class="lds-double-ring" style="background: none;"><circle cx="50" cy="50" ng-attr-r="{{config.radius}}" ng-attr-stroke-width="{{config.width}}" ng-attr-stroke="{{config.c1}}" ng-attr-stroke-dasharray="{{config.dasharray}}" fill="none" stroke-linecap="round" r="20" stroke-width="4" stroke="#3b3e43" stroke-dasharray="31.41592653589793 31.41592653589793" transform="rotate(111.924 50 50)"><animateTransform attributeName="transform" type="rotate" calcMode="linear" values="0 50 50;360 50 50" keyTimes="0;1" dur="1.5s" begin="0s" repeatCount="indefinite"></animateTransform></circle><circle cx="50" cy="50" ng-attr-r="{{config.radius2}}" ng-attr-stroke-width="{{config.width}}" ng-attr-stroke="{{config.c2}}" ng-attr-stroke-dasharray="{{config.dasharray2}}" ng-attr-stroke-dashoffset="{{config.dashoffset2}}" fill="none" stroke-linecap="round" r="15" stroke-width="4" stroke="#d23f31" stroke-dasharray="23.561944901923447 23.561944901923447" stroke-dashoffset="23.561944901923447" transform="rotate(-111.924 50 50)"><animateTransform attributeName="transform" type="rotate" calcMode="linear" values="0 50 50;-360 50 50" keyTimes="0;1" dur="1.5s" begin="0s" repeatCount="indefinite"></animateTransform></circle></svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,7 @@
pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}/*!
Theme: a11y-dark
Author: @ericwbailey
Maintainer: @ericwbailey
Based on the Tomorrow Night Eighties theme: https://github.com/isagalaev/highlight.js/blob/master/src/styles/tomorrow-night-eighties.css
*/.hljs{background:#2b2b2b;color:#f8f8f2}.hljs-comment,.hljs-quote{color:#d4d0ab}.hljs-deletion,.hljs-name,.hljs-regexp,.hljs-selector-class,.hljs-selector-id,.hljs-tag,.hljs-template-variable,.hljs-variable{color:#ffa07a}.hljs-built_in,.hljs-link,.hljs-literal,.hljs-meta,.hljs-number,.hljs-params,.hljs-type{color:#f5ab35}.hljs-attribute{color:gold}.hljs-addition,.hljs-bullet,.hljs-string,.hljs-symbol{color:#abe338}.hljs-section,.hljs-title{color:#00e0e0}.hljs-keyword,.hljs-selector-tag{color:#dcc6e0}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700}@media screen and (-ms-high-contrast:active){.hljs-addition,.hljs-attribute,.hljs-built_in,.hljs-bullet,.hljs-comment,.hljs-link,.hljs-literal,.hljs-meta,.hljs-number,.hljs-params,.hljs-quote,.hljs-string,.hljs-symbol,.hljs-type{color:highlight}.hljs-keyword,.hljs-selector-tag{font-weight:700}}

View File

@ -0,0 +1,7 @@
pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}/*!
Theme: a11y-light
Author: @ericwbailey
Maintainer: @ericwbailey
Based on the Tomorrow Night Eighties theme: https://github.com/isagalaev/highlight.js/blob/master/src/styles/tomorrow-night-eighties.css
*/.hljs{background:#fefefe;color:#545454}.hljs-comment,.hljs-quote{color:#696969}.hljs-deletion,.hljs-name,.hljs-regexp,.hljs-selector-class,.hljs-selector-id,.hljs-tag,.hljs-template-variable,.hljs-variable{color:#d91e18}.hljs-attribute,.hljs-built_in,.hljs-link,.hljs-literal,.hljs-meta,.hljs-number,.hljs-params,.hljs-type{color:#aa5d00}.hljs-addition,.hljs-bullet,.hljs-string,.hljs-symbol{color:green}.hljs-section,.hljs-title{color:#007faa}.hljs-keyword,.hljs-selector-tag{color:#7928a1}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700}@media screen and (-ms-high-contrast:active){.hljs-addition,.hljs-attribute,.hljs-built_in,.hljs-bullet,.hljs-comment,.hljs-link,.hljs-literal,.hljs-meta,.hljs-number,.hljs-params,.hljs-quote,.hljs-string,.hljs-symbol,.hljs-type{color:highlight}.hljs-keyword,.hljs-selector-tag{font-weight:700}}

View File

@ -0,0 +1,20 @@
pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}/*!
Theme: Agate
Author: (c) Taufik Nurrohman <hi@taufik-nurrohman.com>
Maintainer: @taufik-nurrohman
Updated: 2021-04-24
#333
#62c8f3
#7bd694
#888
#a2fca2
#ade5fc
#b8d8a2
#c6b4f0
#d36363
#fc9b9b
#fcc28c
#ffa
#fff
*/.hljs{background:#333;color:#fff}.hljs-doctag,.hljs-meta-keyword,.hljs-name,.hljs-strong{font-weight:700}.hljs-code,.hljs-emphasis{font-style:italic}.hljs-section,.hljs-tag{color:#62c8f3}.hljs-selector-class,.hljs-selector-id,.hljs-template-variable,.hljs-variable{color:#ade5fc}.hljs-meta-string,.hljs-string{color:#a2fca2}.hljs-attr,.hljs-quote,.hljs-selector-attr{color:#7bd694}.hljs-tag .hljs-attr{color:inherit}.hljs-attribute,.hljs-title,.hljs-type{color:#ffa}.hljs-number,.hljs-symbol{color:#d36363}.hljs-bullet,.hljs-template-tag{color:#b8d8a2}.hljs-built_in,.hljs-keyword,.hljs-literal,.hljs-selector-tag{color:#fcc28c}.hljs-code,.hljs-comment,.hljs-formula{color:#888}.hljs-link,.hljs-regexp,.hljs-selector-pseudo{color:#c6b4f0}.hljs-meta{color:#fc9b9b}.hljs-deletion{background:#fc9b9b;color:#333}.hljs-addition{background:#a2fca2;color:#333}.hljs-subst{color:#fff}.hljs a{color:inherit}.hljs a:focus,.hljs a:hover{color:inherit;text-decoration:underline}.hljs mark{background:#555;color:inherit}

View File

@ -0,0 +1,9 @@
pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}/*!
Theme: An Old Hope Star Wars Syntax
Author: (c) Gustavo Costa <gusbemacbe@gmail.com>
Maintainer: @gusbemacbe
Original theme - Ocean Dark Theme by https://github.com/gavsiu
Based on Jesse Leite's Atom syntax theme 'An Old Hope'
https://github.com/JesseLeite/an-old-hope-syntax-atom
*/.hljs{background:#1c1d21;color:#c0c5ce}.hljs-comment,.hljs-quote{color:#b6b18b}.hljs-deletion,.hljs-name,.hljs-regexp,.hljs-selector-class,.hljs-selector-id,.hljs-tag,.hljs-template-variable,.hljs-variable{color:#eb3c54}.hljs-built_in,.hljs-link,.hljs-literal,.hljs-meta,.hljs-number,.hljs-params,.hljs-type{color:#e7ce56}.hljs-attribute{color:#ee7c2b}.hljs-addition,.hljs-bullet,.hljs-string,.hljs-symbol{color:#4fb4d7}.hljs-section,.hljs-title{color:#78bb65}.hljs-keyword,.hljs-selector-tag{color:#b45ea4}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700}

View File

@ -0,0 +1 @@
pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}.hljs{color:#a9b7c6;background:#282b2e}.hljs-bullet,.hljs-literal,.hljs-number,.hljs-symbol{color:#6897bb}.hljs-deletion,.hljs-keyword,.hljs-selector-tag{color:#cc7832}.hljs-link,.hljs-template-variable,.hljs-variable{color:#629755}.hljs-comment,.hljs-quote{color:grey}.hljs-meta{color:#bbb529}.hljs-addition,.hljs-attribute,.hljs-string{color:#6a8759}.hljs-section,.hljs-title,.hljs-type{color:#ffc66d}.hljs-name,.hljs-selector-class,.hljs-selector-id{color:#e8bf6a}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700}

View File

@ -0,0 +1,84 @@
.hljs {
display: block;
padding: 1em;
overflow-x: auto;
color: rgba(0, 0, 0, .85);
border-radius: 2px;
}
.hljs::selection {
text-shadow: none;
background: #b3d4fc;
}
.hljs-comment,
.hljs-quote {
color: #708090;
font-style: italic;
}
.hljs-doctag,
.hljs-keyword,
.hljs-formula {
color: #008dff;
}
.hljs-section,
.hljs-name,
.hljs-selector-tag,
.hljs-deletion,
.hljs-subst {
color: #999;
}
.hljs-literal {
color: #56b6c2;
}
.hljs-regexp {
color: #e90;
}
.hljs-string,
.hljs-addition,
.hljs-attribute,
.hljs-meta-string {
color: #0b8235;
}
.hljs-built_in,
.hljs-class .hljs-title {
color: #e6c07b;
}
.hljs-attr,
.hljs-variable,
.hljs-template-variable,
.hljs-type,
.hljs-selector-class,
.hljs-selector-attr,
.hljs-selector-pseudo,
.hljs-number {
color: #f81d22;
}
.hljs-symbol,
.hljs-bullet,
.hljs-link,
.hljs-meta,
.hljs-selector-id,
.hljs-title {
color: #f81d22;
}
.hljs-emphasis {
font-style: italic;
}
.hljs-strong {
font-weight: bold;
}
.hljs-link {
text-decoration: underline;
}

View File

@ -0,0 +1 @@
pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}.hljs{background:#fff;color:#434f54}.hljs-subst{color:#434f54}.hljs-attribute,.hljs-doctag,.hljs-keyword,.hljs-name,.hljs-selector-tag{color:#00979d}.hljs-addition,.hljs-built_in,.hljs-bullet,.hljs-code,.hljs-literal{color:#d35400}.hljs-link,.hljs-regexp,.hljs-selector-attr,.hljs-selector-pseudo,.hljs-symbol,.hljs-template-variable,.hljs-variable{color:#00979d}.hljs-deletion,.hljs-quote,.hljs-selector-class,.hljs-selector-id,.hljs-string,.hljs-template-tag,.hljs-type{color:#005c5f}.hljs-comment{color:rgba(149,165,166,.8)}.hljs-meta .hljs-keyword{color:#728e00}.hljs-meta{color:#434f54}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700}.hljs-function{color:#728e00}.hljs-section,.hljs-title{color:#800;font-weight:700}.hljs-number{color:#8a7b52}

View File

@ -0,0 +1 @@
pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}.hljs{background:#222;color:#aaa}.hljs-subst{color:#aaa}.hljs-section{color:#fff}.hljs-comment,.hljs-meta,.hljs-quote{color:#444}.hljs-bullet,.hljs-regexp,.hljs-string,.hljs-symbol{color:#fc3}.hljs-addition,.hljs-number{color:#0c6}.hljs-attribute,.hljs-built_in,.hljs-link,.hljs-literal,.hljs-template-variable,.hljs-type{color:#32aaee}.hljs-keyword,.hljs-name,.hljs-selector-class,.hljs-selector-id,.hljs-selector-tag{color:#64a}.hljs-deletion,.hljs-template-tag,.hljs-title,.hljs-variable{color:#b16}.hljs-doctag,.hljs-section,.hljs-strong{font-weight:700}.hljs-emphasis{font-style:italic}

View File

@ -0,0 +1 @@
pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}.hljs{background:#fff;color:#000}.hljs-addition,.hljs-attribute,.hljs-bullet,.hljs-link,.hljs-section,.hljs-string,.hljs-symbol,.hljs-template-variable,.hljs-variable{color:#888}.hljs-comment,.hljs-deletion,.hljs-meta,.hljs-quote{color:#ccc}.hljs-keyword,.hljs-name,.hljs-section,.hljs-selector-tag,.hljs-strong,.hljs-type{font-weight:700}.hljs-emphasis{font-style:italic}

View File

@ -0,0 +1 @@
pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}.hljs{color:#abb2bf;background:#282c34}.hljs-keyword,.hljs-operator,.hljs-pattern-match{color:#f92672}.hljs-function,.hljs-pattern-match .hljs-constructor{color:#61aeee}.hljs-function .hljs-params{color:#a6e22e}.hljs-function .hljs-params .hljs-typing{color:#fd971f}.hljs-module-access .hljs-module{color:#7e57c2}.hljs-constructor{color:#e2b93d}.hljs-constructor .hljs-string{color:#9ccc65}.hljs-comment,.hljs-quote{color:#b18eb1;font-style:italic}.hljs-doctag,.hljs-formula{color:#c678dd}.hljs-deletion,.hljs-name,.hljs-section,.hljs-selector-tag,.hljs-subst{color:#e06c75}.hljs-literal{color:#56b6c2}.hljs-addition,.hljs-attribute,.hljs-meta .hljs-string,.hljs-regexp,.hljs-string{color:#98c379}.hljs-built_in,.hljs-class .hljs-title,.hljs-title.class_{color:#e6c07b}.hljs-attr,.hljs-number,.hljs-selector-attr,.hljs-selector-class,.hljs-selector-pseudo,.hljs-template-variable,.hljs-type,.hljs-variable{color:#d19a66}.hljs-bullet,.hljs-link,.hljs-meta,.hljs-selector-id,.hljs-symbol,.hljs-title{color:#61aeee}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700}.hljs-link{text-decoration:underline}

View File

@ -0,0 +1 @@
pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}.hljs{color:#abb2bf;background:#282c34}.hljs-comment,.hljs-quote{color:#5c6370;font-style:italic}.hljs-doctag,.hljs-formula,.hljs-keyword{color:#c678dd}.hljs-deletion,.hljs-name,.hljs-section,.hljs-selector-tag,.hljs-subst{color:#e06c75}.hljs-literal{color:#56b6c2}.hljs-addition,.hljs-attribute,.hljs-meta .hljs-string,.hljs-regexp,.hljs-string{color:#98c379}.hljs-attr,.hljs-number,.hljs-selector-attr,.hljs-selector-class,.hljs-selector-pseudo,.hljs-template-variable,.hljs-type,.hljs-variable{color:#d19a66}.hljs-bullet,.hljs-link,.hljs-meta,.hljs-selector-id,.hljs-symbol,.hljs-title{color:#61aeee}.hljs-built_in,.hljs-class .hljs-title,.hljs-title.class_{color:#e6c07b}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700}.hljs-link{text-decoration:underline}

View File

@ -0,0 +1 @@
pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}.hljs{color:#383a42;background:#fafafa}.hljs-comment,.hljs-quote{color:#a0a1a7;font-style:italic}.hljs-doctag,.hljs-formula,.hljs-keyword{color:#a626a4}.hljs-deletion,.hljs-name,.hljs-section,.hljs-selector-tag,.hljs-subst{color:#e45649}.hljs-literal{color:#0184bb}.hljs-addition,.hljs-attribute,.hljs-meta .hljs-string,.hljs-regexp,.hljs-string{color:#50a14f}.hljs-attr,.hljs-number,.hljs-selector-attr,.hljs-selector-class,.hljs-selector-pseudo,.hljs-template-variable,.hljs-type,.hljs-variable{color:#986801}.hljs-bullet,.hljs-link,.hljs-meta,.hljs-selector-id,.hljs-symbol,.hljs-title{color:#4078f2}.hljs-built_in,.hljs-class .hljs-title,.hljs-title.class_{color:#c18401}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700}.hljs-link{text-decoration:underline}

View File

@ -0,0 +1 @@
pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}.hljs{color:#363c69;background:url(./brown-papersq.png) #b7a68e}.hljs-keyword,.hljs-literal,.hljs-selector-tag{color:#059}.hljs-addition,.hljs-attribute,.hljs-built_in,.hljs-bullet,.hljs-link,.hljs-name,.hljs-section,.hljs-string,.hljs-symbol,.hljs-template-tag,.hljs-template-variable,.hljs-title,.hljs-type,.hljs-variable{color:#2c009f}.hljs-comment,.hljs-deletion,.hljs-meta,.hljs-quote{color:#802022}.hljs-doctag,.hljs-keyword,.hljs-literal,.hljs-name,.hljs-section,.hljs-selector-tag,.hljs-strong,.hljs-title,.hljs-type{font-weight:700}.hljs-emphasis{font-style:italic}

View File

@ -0,0 +1 @@
pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}.hljs{background:#222;color:#fff}.hljs-comment,.hljs-quote{color:#777}.hljs-built_in,.hljs-bullet,.hljs-deletion,.hljs-link,.hljs-literal,.hljs-meta,.hljs-number,.hljs-params,.hljs-regexp,.hljs-symbol,.hljs-tag,.hljs-template-variable,.hljs-variable{color:#ab875d}.hljs-attribute,.hljs-name,.hljs-section,.hljs-selector-class,.hljs-selector-id,.hljs-title,.hljs-type{color:#9b869b}.hljs-addition,.hljs-keyword,.hljs-selector-tag,.hljs-string{color:#8f9c6c}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700}

View File

@ -0,0 +1 @@
pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}.hljs{color:#000;background:#fff}.hljs-addition,.hljs-meta,.hljs-string,.hljs-symbol,.hljs-template-tag,.hljs-template-variable{color:#756bb1}.hljs-comment,.hljs-quote{color:#636363}.hljs-bullet,.hljs-link,.hljs-literal,.hljs-number,.hljs-regexp{color:#31a354}.hljs-deletion,.hljs-variable{color:#88f}.hljs-built_in,.hljs-doctag,.hljs-keyword,.hljs-name,.hljs-section,.hljs-selector-class,.hljs-selector-id,.hljs-selector-tag,.hljs-strong,.hljs-tag,.hljs-title,.hljs-type{color:#3182bd}.hljs-emphasis{font-style:italic}.hljs-attribute{color:#e6550d}

View File

@ -0,0 +1 @@
pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}.hljs{color:#ddd;background:#303030}.hljs-keyword,.hljs-link,.hljs-literal,.hljs-section,.hljs-selector-tag{color:#fff}.hljs-addition,.hljs-attribute,.hljs-built_in,.hljs-bullet,.hljs-name,.hljs-string,.hljs-symbol,.hljs-template-tag,.hljs-template-variable,.hljs-title,.hljs-type,.hljs-variable{color:#d88}.hljs-comment,.hljs-deletion,.hljs-meta,.hljs-quote{color:#979797}.hljs-doctag,.hljs-keyword,.hljs-literal,.hljs-name,.hljs-section,.hljs-selector-tag,.hljs-strong,.hljs-title,.hljs-type{font-weight:700}.hljs-emphasis{font-style:italic}

View File

@ -0,0 +1,9 @@
/*!
Theme: Default
Description: Original highlight.js style
Author: (c) Ivan Sagalaev <maniac@softwaremaniacs.org>
Maintainer: @highlightjs/core-team
Website: https://highlightjs.org/
License: see project LICENSE
Touched: 2021
*/pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}.hljs{background:#f3f3f3;color:#444}.hljs-comment{color:#697070}.hljs-punctuation,.hljs-tag{color:#444a}.hljs-tag .hljs-attr,.hljs-tag .hljs-name{color:#444}.hljs-attribute,.hljs-doctag,.hljs-keyword,.hljs-meta .hljs-keyword,.hljs-name,.hljs-selector-tag{font-weight:700}.hljs-deletion,.hljs-number,.hljs-quote,.hljs-selector-class,.hljs-selector-id,.hljs-string,.hljs-template-tag,.hljs-type{color:#800}.hljs-section,.hljs-title{color:#800;font-weight:700}.hljs-link,.hljs-operator,.hljs-regexp,.hljs-selector-attr,.hljs-selector-pseudo,.hljs-symbol,.hljs-template-variable,.hljs-variable{color:#ab5656}.hljs-literal{color:#695}.hljs-addition,.hljs-built_in,.hljs-bullet,.hljs-code{color:#397300}.hljs-meta{color:#1f7199}.hljs-meta .hljs-string{color:#38a}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700}

View File

@ -0,0 +1,7 @@
pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}/*!
Theme: devibeans (dark)
Author: @terminaldweller
Maintainer: @terminaldweller
Inspired by vim's jellybeans theme (https://github.com/nanotech/jellybeans.vim)
*/.hljs{background:#000;color:#a39e9b}.hljs-attr,.hljs-template-tag{color:#8787d7}.hljs-comment,.hljs-doctag,.hljs-quote{color:#396}.hljs-params{color:#a39e9b}.hljs-regexp{color:#d700ff}.hljs-literal,.hljs-number,.hljs-selector-id,.hljs-tag{color:#ef5350}.hljs-meta,.hljs-meta .hljs-keyword{color:#0087ff}.hljs-code,.hljs-formula,.hljs-keyword,.hljs-link,.hljs-selector-attr,.hljs-selector-class,.hljs-selector-pseudo,.hljs-template-variable,.hljs-variable{color:#64b5f6}.hljs-built_in,.hljs-deletion,.hljs-title{color:#ff8700}.hljs-attribute,.hljs-function,.hljs-name,.hljs-property,.hljs-section,.hljs-type{color:#ffd75f}.hljs-addition,.hljs-bullet,.hljs-meta .hljs-string,.hljs-string,.hljs-subst,.hljs-symbol{color:#558b2f}.hljs-selector-tag{color:#96f}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700}

View File

@ -0,0 +1 @@
pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}.hljs{color:#000;background:#f8f8ff}.hljs-comment,.hljs-quote{color:#408080;font-style:italic}.hljs-keyword,.hljs-literal,.hljs-selector-tag,.hljs-subst{color:#954121}.hljs-number{color:#40a070}.hljs-doctag,.hljs-string{color:#219161}.hljs-section,.hljs-selector-class,.hljs-selector-id,.hljs-type{color:#19469d}.hljs-params{color:#00f}.hljs-title{color:#458;font-weight:700}.hljs-attribute,.hljs-name,.hljs-tag{color:navy;font-weight:400}.hljs-template-variable,.hljs-variable{color:teal}.hljs-link,.hljs-regexp{color:#b68}.hljs-bullet,.hljs-symbol{color:#990073}.hljs-built_in{color:#0086b3}.hljs-meta{color:#999;font-weight:700}.hljs-deletion{background:#fdd}.hljs-addition{background:#dfd}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700}

View File

@ -0,0 +1 @@
pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}.hljs{color:#0ff;background:navy}.hljs-addition,.hljs-attribute,.hljs-built_in,.hljs-bullet,.hljs-string,.hljs-symbol,.hljs-template-tag,.hljs-template-variable{color:#ff0}.hljs-keyword,.hljs-name,.hljs-section,.hljs-selector-class,.hljs-selector-id,.hljs-selector-tag,.hljs-type,.hljs-variable{color:#fff}.hljs-comment,.hljs-deletion,.hljs-doctag,.hljs-quote{color:#888}.hljs-link,.hljs-literal,.hljs-number,.hljs-regexp{color:#0f0}.hljs-meta{color:teal}.hljs-keyword,.hljs-name,.hljs-section,.hljs-selector-tag,.hljs-strong,.hljs-title{font-weight:700}.hljs-emphasis{font-style:italic}

View File

@ -0,0 +1,7 @@
pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}/*!
* Theme: FelipeC
* Author: (c) 2021 Felipe Contreras <felipe.contreras@gmail.com>
* Website: https://github.com/felipec/vim-felipec
*
* Autogenerated with vim-felipec's generator.
*/.hljs{color:#dddde1;background:#1e1e22}.hljs ::selection,.hljs::selection{color:#1e1e22;background:#bf8fef}.hljs-code,.hljs-comment,.hljs-quote{color:#888896}.hljs-deletion,.hljs-literal,.hljs-number{color:#ef8f8f}.hljs-doctag,.hljs-meta,.hljs-operator,.hljs-punctuation,.hljs-selector-attr,.hljs-subst,.hljs-template-variable{color:#efbf8f}.hljs-type{color:#efef8f}.hljs-selector-class,.hljs-selector-id,.hljs-tag,.hljs-title{color:#bfef8f}.hljs-addition,.hljs-regexp,.hljs-string{color:#8fef8f}.hljs-class,.hljs-property{color:#8fefbf}.hljs-name,.hljs-selector-tag{color:#8fefef}.hljs-built_in,.hljs-keyword{color:#8fbfef}.hljs-bullet,.hljs-section{color:#8f8fef}.hljs-selector-pseudo{color:#bf8fef}.hljs-attr,.hljs-attribute,.hljs-params,.hljs-variable{color:#ef8fef}.hljs-link,.hljs-symbol{color:#ef8fbf}.hljs-literal,.hljs-strong,.hljs-title{font-weight:700}.hljs-emphasis{font-style:italic}

View File

@ -0,0 +1 @@
pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}.hljs{background:#eee;color:#000}.hljs-addition,.hljs-attribute,.hljs-emphasis,.hljs-link{color:#070}.hljs-emphasis{font-style:italic}.hljs-deletion,.hljs-string,.hljs-strong{color:#d14}.hljs-strong{font-weight:700}.hljs-comment,.hljs-quote{color:#998;font-style:italic}.hljs-section,.hljs-title{color:#900}.hljs-class .hljs-title,.hljs-title.class_,.hljs-type{color:#458}.hljs-template-variable,.hljs-variable{color:#369}.hljs-bullet{color:#970}.hljs-meta{color:#34b}.hljs-code,.hljs-keyword,.hljs-literal,.hljs-number,.hljs-selector-tag{color:#099}.hljs-regexp{background-color:#fff0ff;color:#808}.hljs-symbol{color:#990073}.hljs-name,.hljs-selector-class,.hljs-selector-id,.hljs-tag{color:#070}

View File

@ -0,0 +1,9 @@
pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}/*!
Theme: GitHub Dark Dimmed
Description: Dark dimmed theme as seen on github.com
Author: github.com
Maintainer: @Hirse
Updated: 2021-05-15
Colors taken from GitHub's CSS
*/.hljs{color:#adbac7;background:#22272e}.hljs-doctag,.hljs-keyword,.hljs-meta .hljs-keyword,.hljs-template-tag,.hljs-template-variable,.hljs-type,.hljs-variable.language_{color:#f47067}.hljs-title,.hljs-title.class_,.hljs-title.class_.inherited__,.hljs-title.function_{color:#dcbdfb}.hljs-attr,.hljs-attribute,.hljs-literal,.hljs-meta,.hljs-number,.hljs-operator,.hljs-selector-attr,.hljs-selector-class,.hljs-selector-id,.hljs-variable{color:#6cb6ff}.hljs-meta .hljs-string,.hljs-regexp,.hljs-string{color:#96d0ff}.hljs-built_in,.hljs-symbol{color:#f69d50}.hljs-code,.hljs-comment,.hljs-formula{color:#768390}.hljs-name,.hljs-quote,.hljs-selector-pseudo,.hljs-selector-tag{color:#8ddb8c}.hljs-subst{color:#adbac7}.hljs-section{color:#316dca;font-weight:700}.hljs-bullet{color:#eac55f}.hljs-emphasis{color:#adbac7;font-style:italic}.hljs-strong{color:#adbac7;font-weight:700}.hljs-addition{color:#b4f1b4;background-color:#1b4721}.hljs-deletion{color:#ffd8d3;background-color:#78191b}

View File

@ -0,0 +1,10 @@
pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}/*!
Theme: GitHub Dark
Description: Dark theme as seen on github.com
Author: github.com
Maintainer: @Hirse
Updated: 2021-05-15
Outdated base version: https://github.com/primer/github-syntax-dark
Current colors taken from GitHub's CSS
*/.hljs{color:#c9d1d9;background:#0d1117}.hljs-doctag,.hljs-keyword,.hljs-meta .hljs-keyword,.hljs-template-tag,.hljs-template-variable,.hljs-type,.hljs-variable.language_{color:#ff7b72}.hljs-title,.hljs-title.class_,.hljs-title.class_.inherited__,.hljs-title.function_{color:#d2a8ff}.hljs-attr,.hljs-attribute,.hljs-literal,.hljs-meta,.hljs-number,.hljs-operator,.hljs-selector-attr,.hljs-selector-class,.hljs-selector-id,.hljs-variable{color:#79c0ff}.hljs-meta .hljs-string,.hljs-regexp,.hljs-string{color:#a5d6ff}.hljs-built_in,.hljs-symbol{color:#ffa657}.hljs-code,.hljs-comment,.hljs-formula{color:#8b949e}.hljs-name,.hljs-quote,.hljs-selector-pseudo,.hljs-selector-tag{color:#7ee787}.hljs-subst{color:#c9d1d9}.hljs-section{color:#1f6feb;font-weight:700}.hljs-bullet{color:#f2cc60}.hljs-emphasis{color:#c9d1d9;font-style:italic}.hljs-strong{color:#c9d1d9;font-weight:700}.hljs-addition{color:#aff5b4;background-color:#033a16}.hljs-deletion{color:#ffdcd7;background-color:#67060c}

View File

@ -0,0 +1,10 @@
pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}/*!
Theme: GitHub
Description: Light theme as seen on github.com
Author: github.com
Maintainer: @Hirse
Updated: 2021-05-15
Outdated base version: https://github.com/primer/github-syntax-light
Current colors taken from GitHub's CSS
*/.hljs{color:#24292e;background:#fff}.hljs-doctag,.hljs-keyword,.hljs-meta .hljs-keyword,.hljs-template-tag,.hljs-template-variable,.hljs-type,.hljs-variable.language_{color:#d73a49}.hljs-title,.hljs-title.class_,.hljs-title.class_.inherited__,.hljs-title.function_{color:#6f42c1}.hljs-attr,.hljs-attribute,.hljs-literal,.hljs-meta,.hljs-number,.hljs-operator,.hljs-selector-attr,.hljs-selector-class,.hljs-selector-id,.hljs-variable{color:#005cc5}.hljs-meta .hljs-string,.hljs-regexp,.hljs-string{color:#032f62}.hljs-built_in,.hljs-symbol{color:#e36209}.hljs-code,.hljs-comment,.hljs-formula{color:#6a737d}.hljs-name,.hljs-quote,.hljs-selector-pseudo,.hljs-selector-tag{color:#22863a}.hljs-subst{color:#24292e}.hljs-section{color:#005cc5;font-weight:700}.hljs-bullet{color:#735c0f}.hljs-emphasis{color:#24292e;font-style:italic}.hljs-strong{color:#24292e;font-weight:700}.hljs-addition{color:#22863a;background-color:#f0fff4}.hljs-deletion{color:#b31d28;background-color:#ffeef0}

View File

@ -0,0 +1 @@
pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}.hljs{background:#222;color:silver}.hljs-keyword{color:#ffb871;font-weight:700}.hljs-built_in{color:#ffb871}.hljs-literal{color:#ff8080}.hljs-symbol{color:#58e55a}.hljs-comment{color:#5b995b}.hljs-string{color:#ff0}.hljs-number{color:#ff8080}.hljs-addition,.hljs-attribute,.hljs-bullet,.hljs-code,.hljs-deletion,.hljs-doctag,.hljs-function,.hljs-link,.hljs-meta,.hljs-meta .hljs-keyword,.hljs-name,.hljs-quote,.hljs-regexp,.hljs-section,.hljs-selector-attr,.hljs-selector-class,.hljs-selector-id,.hljs-selector-pseudo,.hljs-selector-tag,.hljs-subst,.hljs-template-tag,.hljs-template-variable,.hljs-title,.hljs-type,.hljs-variable{color:silver}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700}

View File

@ -0,0 +1 @@
pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}.hljs{background:#fff;color:#000}.hljs-comment,.hljs-quote{color:#800}.hljs-keyword,.hljs-name,.hljs-section,.hljs-selector-tag,.hljs-title{color:#008}.hljs-template-variable,.hljs-variable{color:#660}.hljs-regexp,.hljs-selector-attr,.hljs-selector-pseudo,.hljs-string{color:#080}.hljs-bullet,.hljs-link,.hljs-literal,.hljs-meta,.hljs-number,.hljs-symbol{color:#066}.hljs-attr,.hljs-built_in,.hljs-doctag,.hljs-params,.hljs-title,.hljs-type{color:#606}.hljs-attribute,.hljs-subst{color:#000}.hljs-formula{background-color:#eee;font-style:italic}.hljs-selector-class,.hljs-selector-id{color:#9b703f}.hljs-addition{background-color:#baeeba}.hljs-deletion{background-color:#ffc8bd}.hljs-doctag,.hljs-strong{font-weight:700}.hljs-emphasis{font-style:italic}

View File

@ -0,0 +1 @@
pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}.hljs{background-color:#652487;background-image:linear-gradient(160deg,#652487 0,#443ac3 35%,#0174b7 68%,#04988e 100%);color:#e7e4eb}.hljs-subtr{color:#e7e4eb}.hljs-comment,.hljs-doctag,.hljs-meta,.hljs-quote{color:#af8dd9}.hljs-attr,.hljs-regexp,.hljs-selector-id,.hljs-selector-tag,.hljs-tag,.hljs-template-tag{color:#aefbff}.hljs-bullet,.hljs-params,.hljs-selector-class{color:#f19fff}.hljs-keyword,.hljs-meta .hljs-keyword,.hljs-section,.hljs-symbol,.hljs-type{color:#17fc95}.hljs-addition,.hljs-link,.hljs-number{color:#c5fe00}.hljs-string{color:#38c0ff}.hljs-addition,.hljs-attribute{color:#e7ff9f}.hljs-template-variable,.hljs-variable{color:#e447ff}.hljs-built_in,.hljs-class,.hljs-formula,.hljs-function,.hljs-name,.hljs-title{color:#ffc800}.hljs-deletion,.hljs-literal,.hljs-selector-pseudo{color:#ff9e44}.hljs-emphasis,.hljs-quote{font-style:italic}.hljs-keyword,.hljs-params,.hljs-section,.hljs-selector-class,.hljs-selector-id,.hljs-selector-tag,.hljs-strong,.hljs-template-tag{font-weight:700}

View File

@ -0,0 +1 @@
pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}.hljs{background-color:#f9ccff;background-image:linear-gradient(295deg,#f9ccff 0,#e6bbf9 11%,#9ec6f9 32%,#55e6ee 60%,#91f5d1 74%,#f9ffbf 98%);color:#250482}.hljs-subtr{color:#01958b}.hljs-comment,.hljs-doctag,.hljs-meta,.hljs-quote{color:#cb7200}.hljs-attr,.hljs-regexp,.hljs-selector-id,.hljs-selector-tag,.hljs-tag,.hljs-template-tag{color:#07bd5f}.hljs-bullet,.hljs-params,.hljs-selector-class{color:#43449f}.hljs-keyword,.hljs-meta .hljs-keyword,.hljs-section,.hljs-symbol,.hljs-type{color:#7d2801}.hljs-addition,.hljs-link,.hljs-number{color:#7f0096}.hljs-string{color:#2681ab}.hljs-addition,.hljs-attribute{color:#296562}.hljs-template-variable,.hljs-variable{color:#025c8f}.hljs-built_in,.hljs-class,.hljs-formula,.hljs-function,.hljs-name,.hljs-title{color:#529117}.hljs-deletion,.hljs-literal,.hljs-selector-pseudo{color:#ad13ff}.hljs-emphasis,.hljs-quote{font-style:italic}.hljs-keyword,.hljs-params,.hljs-section,.hljs-selector-class,.hljs-selector-id,.hljs-selector-tag,.hljs-strong,.hljs-template-tag{font-weight:700}

View File

@ -0,0 +1 @@
pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}.hljs{color:#333;background:#fff}.hljs-comment,.hljs-quote{color:#777;font-style:italic}.hljs-keyword,.hljs-selector-tag,.hljs-subst{color:#333;font-weight:700}.hljs-literal,.hljs-number{color:#777}.hljs-doctag,.hljs-formula,.hljs-string{color:#333;background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAAECAYAAACp8Z5+AAAAJ0lEQVQIW2O8e/fufwYGBgZBQUEQxcCIIfDu3Tuwivfv30NUoAsAALHpFMMLqZlPAAAAAElFTkSuQmCC)}.hljs-section,.hljs-selector-id,.hljs-title{color:#000;font-weight:700}.hljs-subst{font-weight:400}.hljs-class .hljs-title,.hljs-name,.hljs-title.class_,.hljs-type{color:#333;font-weight:700}.hljs-tag{color:#333}.hljs-regexp{color:#333;background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAICAYAAADA+m62AAAAPUlEQVQYV2NkQAN37979r6yszIgujiIAU4RNMVwhuiQ6H6wQl3XI4oy4FMHcCJPHcDS6J2A2EqUQpJhohQDexSef15DBCwAAAABJRU5ErkJggg==)}.hljs-bullet,.hljs-link,.hljs-symbol{color:#000;background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAKElEQVQIW2NkQAO7d+/+z4gsBhJwdXVlhAvCBECKwIIwAbhKZBUwBQA6hBpm5efZsgAAAABJRU5ErkJggg==)}.hljs-built_in{color:#000;text-decoration:underline}.hljs-meta{color:#999;font-weight:700}.hljs-deletion{color:#fff;background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAADCAYAAABS3WWCAAAAE0lEQVQIW2MMDQ39zzhz5kwIAQAyxweWgUHd1AAAAABJRU5ErkJggg==)}.hljs-addition{color:#000;background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAkAAAAJCAYAAADgkQYQAAAALUlEQVQYV2N89+7dfwYk8P79ewZBQUFkIQZGOiu6e/cuiptQHAPl0NtNxAQBAM97Oejj3Dg7AAAAAElFTkSuQmCC)}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700}

View File

@ -0,0 +1 @@
pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}.hljs{background:#1d1f21;color:#c5c8c6}.hljs span::selection,.hljs::selection{background:#373b41}.hljs span::-moz-selection,.hljs::-moz-selection{background:#373b41}.hljs-name,.hljs-title{color:#f0c674}.hljs-comment,.hljs-meta,.hljs-meta .hljs-keyword{color:#707880}.hljs-deletion,.hljs-link,.hljs-literal,.hljs-number,.hljs-symbol{color:#c66}.hljs-addition,.hljs-doctag,.hljs-regexp,.hljs-selector-attr,.hljs-selector-pseudo,.hljs-string{color:#b5bd68}.hljs-attribute,.hljs-code,.hljs-selector-id{color:#b294bb}.hljs-bullet,.hljs-keyword,.hljs-selector-tag,.hljs-tag{color:#81a2be}.hljs-subst,.hljs-template-tag,.hljs-template-variable,.hljs-variable{color:#8abeb7}.hljs-built_in,.hljs-quote,.hljs-section,.hljs-selector-class,.hljs-type{color:#de935f}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700}

View File

@ -0,0 +1 @@
pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}.hljs{color:#000;background:#fff}.hljs-subst,.hljs-title{font-weight:400;color:#000}.hljs-comment,.hljs-quote{color:grey;font-style:italic}.hljs-meta{color:olive}.hljs-tag{background:#efefef}.hljs-keyword,.hljs-literal,.hljs-name,.hljs-section,.hljs-selector-class,.hljs-selector-id,.hljs-selector-tag,.hljs-type{font-weight:700;color:navy}.hljs-attribute,.hljs-link,.hljs-number,.hljs-regexp{font-weight:700;color:#00f}.hljs-link,.hljs-number,.hljs-regexp{font-weight:400}.hljs-string{color:green;font-weight:700}.hljs-bullet,.hljs-formula,.hljs-symbol{color:#000;background:#d0eded;font-style:italic}.hljs-doctag{text-decoration:underline}.hljs-template-variable,.hljs-variable{color:#660e7a}.hljs-addition{background:#baeeba}.hljs-deletion{background:#ffc8bd}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700}

View File

@ -0,0 +1 @@
pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}.hljs{color:#000;background:#fff}.hljs-subst,.hljs-title{font-weight:400;color:#000}.hljs-title.function_{color:#7a7a43}.hljs-code,.hljs-comment,.hljs-quote{color:#8c8c8c;font-style:italic}.hljs-meta{color:#9e880d}.hljs-section{color:#871094}.hljs-built_in,.hljs-keyword,.hljs-literal,.hljs-meta .hljs-keyword,.hljs-name,.hljs-selector-attr,.hljs-selector-class,.hljs-selector-id,.hljs-selector-pseudo,.hljs-selector-tag,.hljs-symbol,.hljs-template-tag,.hljs-type,.hljs-variable.language_{color:#0033b3}.hljs-attr,.hljs-property{color:#871094}.hljs-attribute{color:#174ad4}.hljs-number{color:#1750eb}.hljs-regexp{color:#264eff}.hljs-link{text-decoration:underline;color:#006dcc}.hljs-meta .hljs-string,.hljs-string{color:#067d17}.hljs-char.escape_{color:#0037a6}.hljs-doctag{text-decoration:underline}.hljs-template-variable{color:#248f8f}.hljs-addition{background:#bee6be}.hljs-deletion{background:#d6d6d6}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700}

View File

@ -0,0 +1 @@
pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}.hljs{background:#000;color:#f8f8f8}.hljs-comment,.hljs-meta,.hljs-quote{color:#7c7c7c}.hljs-keyword,.hljs-name,.hljs-selector-tag,.hljs-tag{color:#96cbfe}.hljs-attribute,.hljs-selector-id{color:#ffffb6}.hljs-addition,.hljs-selector-attr,.hljs-selector-pseudo,.hljs-string{color:#a8ff60}.hljs-subst{color:#daefa3}.hljs-link,.hljs-regexp{color:#e9c062}.hljs-doctag,.hljs-section,.hljs-title,.hljs-type{color:#ffffb6}.hljs-bullet,.hljs-literal,.hljs-symbol,.hljs-template-variable,.hljs-variable{color:#c6c5fe}.hljs-deletion,.hljs-number{color:#ff73fd}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700}

View File

@ -0,0 +1 @@
pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}.hljs{background:#404040}.hljs,.hljs-subst{color:#f0f0f0}.hljs-comment{color:#b5b5b5;font-style:italic}.hljs-attribute,.hljs-doctag,.hljs-keyword,.hljs-meta .hljs-keyword,.hljs-name,.hljs-selector-tag{color:#f0f0f0;font-weight:700}.hljs-string{color:#97bf0d}.hljs-deletion,.hljs-number,.hljs-quote,.hljs-selector-class,.hljs-selector-id,.hljs-template-tag,.hljs-type{color:#f0f0f0}.hljs-link,.hljs-regexp,.hljs-selector-attr,.hljs-selector-pseudo,.hljs-symbol,.hljs-template-variable,.hljs-variable{color:#e2c696}.hljs-built_in,.hljs-literal{color:#97bf0d;font-weight:700}.hljs-addition,.hljs-bullet,.hljs-code{color:#397300}.hljs-class{color:#ce9d4d;font-weight:700}.hljs-section,.hljs-title{color:#df471e}.hljs-title>.hljs-built_in{color:#81bce9;font-weight:400}.hljs-meta{color:#1f7199}.hljs-meta .hljs-string{color:#4d99bf}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700}

View File

@ -0,0 +1 @@
pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}.hljs{background:#fff;color:#000}.hljs-subst{color:#000}.hljs-comment{color:#555;font-style:italic}.hljs-attribute,.hljs-doctag,.hljs-keyword,.hljs-meta .hljs-keyword,.hljs-name,.hljs-selector-tag{color:#000;font-weight:700}.hljs-string{color:navy}.hljs-deletion,.hljs-number,.hljs-quote,.hljs-selector-class,.hljs-selector-id,.hljs-template-tag,.hljs-type{color:#000}.hljs-link,.hljs-regexp,.hljs-selector-attr,.hljs-selector-pseudo,.hljs-symbol,.hljs-template-variable,.hljs-variable{color:#5e1700}.hljs-built_in,.hljs-literal{color:navy;font-weight:700}.hljs-addition,.hljs-bullet,.hljs-code{color:#397300}.hljs-class{color:#6f1c00;font-weight:700}.hljs-section,.hljs-title{color:#fb2c00}.hljs-title>.hljs-built_in{color:teal;font-weight:400}.hljs-meta{color:#1f7199}.hljs-meta .hljs-string{color:#4d99bf}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700}

View File

@ -0,0 +1 @@
pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}.hljs{background:#221a0f;color:#d3af86}.hljs-comment,.hljs-quote{color:#d6baad}.hljs-meta,.hljs-name,.hljs-regexp,.hljs-selector-class,.hljs-selector-id,.hljs-tag,.hljs-template-variable,.hljs-variable{color:#dc3958}.hljs-built_in,.hljs-deletion,.hljs-link,.hljs-literal,.hljs-number,.hljs-params,.hljs-type{color:#f79a32}.hljs-addition,.hljs-bullet,.hljs-string,.hljs-symbol{color:#889b4a}.hljs-function,.hljs-keyword,.hljs-selector-tag{color:#98676a}.hljs-attribute,.hljs-section,.hljs-title{color:#f06431}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700}

View File

@ -0,0 +1 @@
pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}.hljs{background:#fbebd4;color:#84613d}.hljs-comment,.hljs-quote{color:#a57a4c}.hljs-meta,.hljs-name,.hljs-regexp,.hljs-selector-class,.hljs-selector-id,.hljs-tag,.hljs-template-variable,.hljs-variable{color:#dc3958}.hljs-built_in,.hljs-deletion,.hljs-link,.hljs-literal,.hljs-number,.hljs-params,.hljs-type{color:#f79a32}.hljs-addition,.hljs-bullet,.hljs-string,.hljs-symbol{color:#889b4a}.hljs-function,.hljs-keyword,.hljs-selector-tag{color:#98676a}.hljs-attribute,.hljs-section,.hljs-title{color:#f06431}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700}

View File

@ -0,0 +1 @@
pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}.hljs{color:#444;background:#fff}.hljs-name{color:#01a3a3}.hljs-meta,.hljs-tag{color:#789}.hljs-comment{color:#888}.hljs-attribute,.hljs-doctag,.hljs-keyword,.hljs-meta .hljs-keyword,.hljs-name,.hljs-selector-tag{font-weight:700}.hljs-deletion,.hljs-number,.hljs-quote,.hljs-selector-class,.hljs-selector-id,.hljs-string,.hljs-template-tag,.hljs-type{color:#4286f4}.hljs-section,.hljs-title{color:#4286f4;font-weight:700}.hljs-link,.hljs-regexp,.hljs-selector-attr,.hljs-selector-pseudo,.hljs-symbol,.hljs-template-variable,.hljs-variable{color:#bc6060}.hljs-literal{color:#62bcbc}.hljs-addition,.hljs-built_in,.hljs-bullet,.hljs-code{color:#25c6c6}.hljs-meta .hljs-string{color:#4d99bf}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700}

View File

@ -0,0 +1 @@
pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}.hljs{background:#303030;color:#c5c8c6}.hljs-comment{color:#8d8d8d}.hljs-quote{color:#b3c7d8}.hljs-deletion,.hljs-name,.hljs-regexp,.hljs-selector-class,.hljs-selector-id,.hljs-tag,.hljs-template-variable,.hljs-variable{color:#c66}.hljs-built_in,.hljs-literal,.hljs-number,.hljs-subst .hljs-link,.hljs-type{color:#de935f}.hljs-attribute{color:#f0c674}.hljs-addition,.hljs-bullet,.hljs-params,.hljs-string{color:#b5bd68}.hljs-class,.hljs-function,.hljs-keyword,.hljs-selector-tag{color:#be94bb}.hljs-meta,.hljs-section,.hljs-title{color:#81a2be}.hljs-symbol{color:#dbc4d9}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700}

View File

@ -0,0 +1 @@
pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}.hljs{background-color:#f4f4f4;color:#000}.hljs-subst{color:#000}.hljs-addition,.hljs-attribute,.hljs-bullet,.hljs-string,.hljs-symbol,.hljs-template-tag,.hljs-template-variable,.hljs-title,.hljs-variable{color:#050}.hljs-comment,.hljs-quote{color:#777}.hljs-link,.hljs-literal,.hljs-number,.hljs-regexp,.hljs-type{color:#800}.hljs-deletion,.hljs-meta{color:#00e}.hljs-built_in,.hljs-doctag,.hljs-keyword,.hljs-name,.hljs-section,.hljs-selector-tag,.hljs-tag,.hljs-title{font-weight:700;color:navy}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700}

View File

@ -0,0 +1 @@
pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}.hljs{background:#eaeef3;color:#00193a}.hljs-doctag,.hljs-keyword,.hljs-name,.hljs-section,.hljs-selector-tag,.hljs-strong,.hljs-title{font-weight:700}.hljs-comment{color:#738191}.hljs-addition,.hljs-built_in,.hljs-literal,.hljs-name,.hljs-quote,.hljs-section,.hljs-selector-class,.hljs-selector-id,.hljs-string,.hljs-tag,.hljs-title,.hljs-type{color:#0048ab}.hljs-attribute,.hljs-bullet,.hljs-deletion,.hljs-link,.hljs-meta,.hljs-regexp,.hljs-subst,.hljs-symbol,.hljs-template-variable,.hljs-variable{color:#4c81c9}.hljs-emphasis{font-style:italic}

View File

@ -0,0 +1 @@
pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}.hljs{background:#23241f;color:#f8f8f2}.hljs-subst,.hljs-tag{color:#f8f8f2}.hljs-emphasis,.hljs-strong{color:#a8a8a2}.hljs-bullet,.hljs-link,.hljs-literal,.hljs-number,.hljs-quote,.hljs-regexp{color:#ae81ff}.hljs-code,.hljs-section,.hljs-selector-class,.hljs-title{color:#a6e22e}.hljs-strong{font-weight:700}.hljs-emphasis{font-style:italic}.hljs-attr,.hljs-keyword,.hljs-name,.hljs-selector-tag{color:#f92672}.hljs-attribute,.hljs-symbol{color:#66d9ef}.hljs-class .hljs-title,.hljs-params,.hljs-title.class_{color:#f8f8f2}.hljs-addition,.hljs-built_in,.hljs-selector-attr,.hljs-selector-id,.hljs-selector-pseudo,.hljs-string,.hljs-template-variable,.hljs-type,.hljs-variable{color:#e6db74}.hljs-comment,.hljs-deletion,.hljs-meta{color:#75715e}

View File

@ -0,0 +1 @@
pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}.hljs{background:#272822;color:#ddd}.hljs-keyword,.hljs-literal,.hljs-name,.hljs-selector-tag,.hljs-strong,.hljs-tag{color:#f92672}.hljs-code{color:#66d9ef}.hljs-attribute,.hljs-link,.hljs-regexp,.hljs-symbol{color:#bf79db}.hljs-addition,.hljs-built_in,.hljs-bullet,.hljs-emphasis,.hljs-section,.hljs-selector-attr,.hljs-selector-pseudo,.hljs-string,.hljs-subst,.hljs-template-tag,.hljs-template-variable,.hljs-title,.hljs-type,.hljs-variable{color:#a6e22e}.hljs-class .hljs-title,.hljs-title.class_{color:#fff}.hljs-comment,.hljs-deletion,.hljs-meta,.hljs-quote{color:#75715e}.hljs-doctag,.hljs-keyword,.hljs-literal,.hljs-section,.hljs-selector-id,.hljs-selector-tag,.hljs-title,.hljs-type{font-weight:700}

View File

@ -0,0 +1 @@
pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}.hljs{background:#011627;color:#d6deeb}.hljs-keyword{color:#c792ea;font-style:italic}.hljs-built_in{color:#addb67;font-style:italic}.hljs-type{color:#82aaff}.hljs-literal{color:#ff5874}.hljs-number{color:#f78c6c}.hljs-regexp{color:#5ca7e4}.hljs-string{color:#ecc48d}.hljs-subst{color:#d3423e}.hljs-symbol{color:#82aaff}.hljs-class{color:#ffcb8b}.hljs-function{color:#82aaff}.hljs-title{color:#dcdcaa;font-style:italic}.hljs-params{color:#7fdbca}.hljs-comment{color:#637777;font-style:italic}.hljs-doctag{color:#7fdbca}.hljs-meta,.hljs-meta .hljs-keyword{color:#82aaff}.hljs-meta .hljs-string{color:#ecc48d}.hljs-section{color:#82b1ff}.hljs-attr,.hljs-name,.hljs-tag{color:#7fdbca}.hljs-attribute{color:#80cbc4}.hljs-variable{color:#addb67}.hljs-bullet{color:#d9f5dd}.hljs-code{color:#80cbc4}.hljs-emphasis{color:#c792ea;font-style:italic}.hljs-strong{color:#addb67;font-weight:700}.hljs-formula{color:#c792ea}.hljs-link{color:#ff869a}.hljs-quote{color:#697098;font-style:italic}.hljs-selector-tag{color:#ff6363}.hljs-selector-id{color:#fad430}.hljs-selector-class{color:#addb67;font-style:italic}.hljs-selector-attr,.hljs-selector-pseudo{color:#c792ea;font-style:italic}.hljs-template-tag{color:#c792ea}.hljs-template-variable{color:#addb67}.hljs-addition{color:#addb67ff;font-style:italic}.hljs-deletion{color:#ef535090;font-style:italic}

View File

@ -0,0 +1,10 @@
pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}/*!
Theme: nnfx dark
Description: a theme inspired by Netscape Navigator/Firefox
Author: (c) 2020-2021 Jim Mason <jmason@ibinx.com>
Maintainer: @RocketMan
License: https://creativecommons.org/licenses/by-sa/4.0 CC BY-SA 4.0
Updated: 2021-05-17
@version 1.1.0
*/.hljs{background:#333;color:#fff}.language-xml .hljs-meta,.language-xml .hljs-meta-string{font-weight:700;font-style:italic;color:#69f}.hljs-comment,.hljs-quote{font-style:italic;color:#9c6}.hljs-built_in,.hljs-keyword,.hljs-name{color:#a7a}.hljs-attr,.hljs-name{font-weight:700}.hljs-string{font-weight:400}.hljs-code,.hljs-link,.hljs-meta .hljs-string,.hljs-number,.hljs-regexp,.hljs-string{color:#bce}.hljs-bullet,.hljs-symbol,.hljs-template-variable,.hljs-title,.hljs-variable{color:#d40}.hljs-class .hljs-title,.hljs-title.class_,.hljs-type{font-weight:700;color:#96c}.hljs-attr,.hljs-function .hljs-title,.hljs-subst,.hljs-tag,.hljs-title.function_{color:#fff}.hljs-formula{background-color:#eee;font-style:italic}.hljs-addition{background-color:#797}.hljs-deletion{background-color:#c99}.hljs-meta{color:#69f}.hljs-section,.hljs-selector-class,.hljs-selector-id,.hljs-selector-pseudo,.hljs-selector-tag{font-weight:700;color:#69f}.hljs-selector-pseudo{font-style:italic}.hljs-doctag,.hljs-strong{font-weight:700}.hljs-emphasis{font-style:italic}

View File

@ -0,0 +1,10 @@
pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}/*!
Theme: nnfx light
Description: a theme inspired by Netscape Navigator/Firefox
Author: (c) 2020-2021 Jim Mason <jmason@ibinx.com>
Maintainer: @RocketMan
License: https://creativecommons.org/licenses/by-sa/4.0 CC BY-SA 4.0
Updated: 2021-05-17
@version 1.1.0
*/.hljs{background:#fff;color:#000}.language-xml .hljs-meta,.language-xml .hljs-meta-string{font-weight:700;font-style:italic;color:#48b}.hljs-comment,.hljs-quote{font-style:italic;color:#070}.hljs-built_in,.hljs-keyword,.hljs-name{color:#808}.hljs-attr,.hljs-name{font-weight:700}.hljs-string{font-weight:400}.hljs-code,.hljs-link,.hljs-meta .hljs-string,.hljs-number,.hljs-regexp,.hljs-string{color:#00f}.hljs-bullet,.hljs-symbol,.hljs-template-variable,.hljs-title,.hljs-variable{color:#f40}.hljs-class .hljs-title,.hljs-title.class_,.hljs-type{font-weight:700;color:#639}.hljs-attr,.hljs-function .hljs-title,.hljs-subst,.hljs-tag,.hljs-title.function_{color:#000}.hljs-formula{background-color:#eee;font-style:italic}.hljs-addition{background-color:#beb}.hljs-deletion{background-color:#fbb}.hljs-meta{color:#269}.hljs-section,.hljs-selector-class,.hljs-selector-id,.hljs-selector-pseudo,.hljs-selector-tag{font-weight:700;color:#48b}.hljs-selector-pseudo{font-style:italic}.hljs-doctag,.hljs-strong{font-weight:700}.hljs-emphasis{font-style:italic}

View File

@ -0,0 +1 @@
pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}.hljs{background:#2e3440}.hljs,.hljs-subst{color:#d8dee9}.hljs-selector-tag{color:#81a1c1}.hljs-selector-id{color:#8fbcbb;font-weight:700}.hljs-selector-attr,.hljs-selector-class{color:#8fbcbb}.hljs-property,.hljs-selector-pseudo{color:#88c0d0}.hljs-addition{background-color:rgba(163,190,140,.5)}.hljs-deletion{background-color:rgba(191,97,106,.5)}.hljs-built_in,.hljs-class,.hljs-type{color:#8fbcbb}.hljs-function,.hljs-function>.hljs-title,.hljs-title.hljs-function{color:#88c0d0}.hljs-keyword,.hljs-literal,.hljs-symbol{color:#81a1c1}.hljs-number{color:#b48ead}.hljs-regexp{color:#ebcb8b}.hljs-string{color:#a3be8c}.hljs-title{color:#8fbcbb}.hljs-params{color:#d8dee9}.hljs-bullet{color:#81a1c1}.hljs-code{color:#8fbcbb}.hljs-emphasis{font-style:italic}.hljs-formula{color:#8fbcbb}.hljs-strong{font-weight:700}.hljs-link:hover{text-decoration:underline}.hljs-comment,.hljs-quote{color:#4c566a}.hljs-doctag{color:#8fbcbb}.hljs-meta,.hljs-meta .hljs-keyword{color:#5e81ac}.hljs-meta .hljs-string{color:#a3be8c}.hljs-attr{color:#8fbcbb}.hljs-attribute{color:#d8dee9}.hljs-name{color:#81a1c1}.hljs-section{color:#88c0d0}.hljs-tag{color:#81a1c1}.hljs-template-variable,.hljs-variable{color:#d8dee9}.hljs-template-tag{color:#5e81ac}.language-abnf .hljs-attribute{color:#88c0d0}.language-abnf .hljs-symbol{color:#ebcb8b}.language-apache .hljs-attribute{color:#88c0d0}.language-apache .hljs-section{color:#81a1c1}.language-arduino .hljs-built_in{color:#88c0d0}.language-aspectj .hljs-meta{color:#d08770}.language-aspectj>.hljs-title{color:#88c0d0}.language-bnf .hljs-attribute{color:#8fbcbb}.language-clojure .hljs-name{color:#88c0d0}.language-clojure .hljs-symbol{color:#ebcb8b}.language-coq .hljs-built_in{color:#88c0d0}.language-cpp .hljs-meta .hljs-string{color:#8fbcbb}.language-css .hljs-built_in{color:#88c0d0}.language-css .hljs-keyword{color:#d08770}.language-diff .hljs-meta,.language-ebnf .hljs-attribute{color:#8fbcbb}.language-glsl .hljs-built_in{color:#88c0d0}.language-groovy .hljs-meta:not(:first-child),.language-haxe .hljs-meta,.language-java .hljs-meta{color:#d08770}.language-ldif .hljs-attribute{color:#8fbcbb}.language-lisp .hljs-name,.language-lua .hljs-built_in,.language-moonscript .hljs-built_in,.language-nginx .hljs-attribute{color:#88c0d0}.language-nginx .hljs-section{color:#5e81ac}.language-pf .hljs-built_in,.language-processing .hljs-built_in{color:#88c0d0}.language-scss .hljs-keyword,.language-stylus .hljs-keyword{color:#81a1c1}.language-swift .hljs-meta{color:#d08770}.language-vim .hljs-built_in{color:#88c0d0;font-style:italic}.language-yaml .hljs-meta{color:#d08770}

View File

@ -0,0 +1 @@
pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}.hljs{color:#e0e2e4;background:#282b2e}.hljs-keyword,.hljs-literal,.hljs-selector-id,.hljs-selector-tag{color:#93c763}.hljs-number{color:#ffcd22}.hljs-attribute{color:#668bb0}.hljs-link,.hljs-regexp{color:#d39745}.hljs-meta{color:#557182}.hljs-addition,.hljs-built_in,.hljs-bullet,.hljs-emphasis,.hljs-name,.hljs-selector-attr,.hljs-selector-pseudo,.hljs-subst,.hljs-tag,.hljs-template-tag,.hljs-template-variable,.hljs-type,.hljs-variable{color:#8cbbad}.hljs-string,.hljs-symbol{color:#ec7600}.hljs-comment,.hljs-deletion,.hljs-quote{color:#818e96}.hljs-selector-class{color:#a082bd}.hljs-doctag,.hljs-keyword,.hljs-literal,.hljs-name,.hljs-section,.hljs-selector-tag,.hljs-strong,.hljs-title,.hljs-type{font-weight:700}.hljs-class .hljs-title,.hljs-code,.hljs-section,.hljs-title.class_{color:#fff}

View File

@ -0,0 +1 @@
pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}.hljs{color:#e6e6e6;background:#2a2c2d}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700}.hljs-link{text-decoration:underline}.hljs-comment,.hljs-quote{color:#bbb;font-style:italic}.hljs-params{color:#bbb}.hljs-attr,.hljs-punctuation{color:#e6e6e6}.hljs-meta,.hljs-name,.hljs-selector-tag{color:#ff4b82}.hljs-char.escape_,.hljs-operator{color:#b084eb}.hljs-deletion,.hljs-keyword{color:#ff75b5}.hljs-regexp,.hljs-selector-attr,.hljs-selector-pseudo,.hljs-variable.language_{color:#ff9ac1}.hljs-code,.hljs-formula,.hljs-property,.hljs-section,.hljs-subst,.hljs-title.function_{color:#45a9f9}.hljs-addition,.hljs-bullet,.hljs-meta .hljs-string,.hljs-selector-class,.hljs-string,.hljs-symbol,.hljs-title.class_,.hljs-title.class_.inherited__{color:#19f9d8}.hljs-attribute,.hljs-built_in,.hljs-doctag,.hljs-link,.hljs-literal,.hljs-meta .hljs-keyword,.hljs-number,.hljs-punctuation,.hljs-selector-id,.hljs-tag,.hljs-template-tag,.hljs-template-variable,.hljs-title,.hljs-type,.hljs-variable{color:#ffb86c}

View File

@ -0,0 +1 @@
pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}.hljs{color:#2a2c2d;background:#e6e6e6}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700}.hljs-link{text-decoration:underline}.hljs-comment,.hljs-quote{color:#676b79;font-style:italic}.hljs-params{color:#676b79}.hljs-attr,.hljs-punctuation{color:#2a2c2d}.hljs-char.escape_,.hljs-meta,.hljs-name,.hljs-operator,.hljs-selector-tag{color:#c56200}.hljs-deletion,.hljs-keyword{color:#d92792}.hljs-regexp,.hljs-selector-attr,.hljs-selector-pseudo,.hljs-variable.language_{color:#cc5e91}.hljs-code,.hljs-formula,.hljs-property,.hljs-section,.hljs-subst,.hljs-title.function_{color:#3787c7}.hljs-addition,.hljs-bullet,.hljs-meta .hljs-string,.hljs-selector-class,.hljs-string,.hljs-symbol,.hljs-title.class_,.hljs-title.class_.inherited__{color:#0d7d6c}.hljs-attribute,.hljs-built_in,.hljs-doctag,.hljs-link,.hljs-literal,.hljs-meta .hljs-keyword,.hljs-number,.hljs-selector-id,.hljs-tag,.hljs-template-tag,.hljs-template-variable,.hljs-title,.hljs-type,.hljs-variable{color:#7641bb}

View File

@ -0,0 +1 @@
pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}.hljs{background:#2f1e2e;color:#a39e9b}.hljs-comment,.hljs-quote{color:#8d8687}.hljs-link,.hljs-meta,.hljs-name,.hljs-regexp,.hljs-selector-class,.hljs-selector-id,.hljs-tag,.hljs-template-variable,.hljs-variable{color:#ef6155}.hljs-built_in,.hljs-deletion,.hljs-literal,.hljs-number,.hljs-params,.hljs-type{color:#f99b15}.hljs-attribute,.hljs-section,.hljs-title{color:#fec418}.hljs-addition,.hljs-bullet,.hljs-string,.hljs-symbol{color:#48b685}.hljs-keyword,.hljs-selector-tag{color:#815ba4}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700}

View File

@ -0,0 +1 @@
pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}.hljs{background:#e7e9db;color:#4f424c}.hljs-comment,.hljs-quote{color:#776e71}.hljs-link,.hljs-meta,.hljs-name,.hljs-regexp,.hljs-selector-class,.hljs-selector-id,.hljs-tag,.hljs-template-variable,.hljs-variable{color:#ef6155}.hljs-built_in,.hljs-deletion,.hljs-literal,.hljs-number,.hljs-params,.hljs-type{color:#f99b15}.hljs-attribute,.hljs-section,.hljs-title{color:#fec418}.hljs-addition,.hljs-bullet,.hljs-string,.hljs-symbol{color:#48b685}.hljs-keyword,.hljs-selector-tag{color:#815ba4}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700}

View File

@ -0,0 +1 @@
pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}.hljs{color:#dccf8f;background:url(./pojoaque.jpg) left top #181914}.hljs-comment,.hljs-quote{color:#586e75;font-style:italic}.hljs-addition,.hljs-keyword,.hljs-literal,.hljs-selector-tag{color:#b64926}.hljs-doctag,.hljs-number,.hljs-regexp,.hljs-string{color:#468966}.hljs-built_in,.hljs-name,.hljs-section,.hljs-title{color:#ffb03b}.hljs-class .hljs-title,.hljs-tag,.hljs-template-variable,.hljs-title.class_,.hljs-type,.hljs-variable{color:#b58900}.hljs-attribute{color:#b89859}.hljs-bullet,.hljs-link,.hljs-meta,.hljs-subst,.hljs-symbol{color:#cb4b16}.hljs-deletion{color:#dc322f}.hljs-selector-class,.hljs-selector-id{color:#d3a60c}.hljs-formula{background:#073642}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700}

View File

@ -0,0 +1 @@
pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}.hljs{background:#ffffdf}.hljs,.hljs-attr,.hljs-function,.hljs-name,.hljs-number,.hljs-params,.hljs-subst,.hljs-type{color:#000}.hljs-addition,.hljs-comment,.hljs-regexp,.hljs-section,.hljs-selector-pseudo{color:#0aa}.hljs-built_in,.hljs-class,.hljs-keyword,.hljs-meta .hljs-keyword,.hljs-selector-class{color:#066;font-weight:700}.hljs-code,.hljs-tag,.hljs-title,.hljs-variable{color:#066}.hljs-selector-attr,.hljs-string{color:#0080ff}.hljs-attribute,.hljs-deletion,.hljs-link,.hljs-symbol{color:#924b72}.hljs-literal,.hljs-meta,.hljs-selector-id{color:#924b72;font-weight:700}.hljs-name,.hljs-strong{font-weight:700}.hljs-emphasis{font-style:italic}

View File

@ -0,0 +1 @@
pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}.hljs{color:#aaa;background:#000}.hljs-emphasis,.hljs-strong{color:#a8a8a2}.hljs-bullet,.hljs-literal,.hljs-number,.hljs-quote,.hljs-regexp{color:#f5f}.hljs-code .hljs-selector-class{color:#aaf}.hljs-emphasis,.hljs-stronge,.hljs-type{font-style:italic}.hljs-function,.hljs-keyword,.hljs-name,.hljs-section,.hljs-selector-tag,.hljs-symbol{color:#ff5}.hljs-subst,.hljs-tag,.hljs-title{color:#aaa}.hljs-attribute{color:#f55}.hljs-class .hljs-title,.hljs-params,.hljs-title.class_,.hljs-variable{color:#88f}.hljs-addition,.hljs-built_in,.hljs-link,.hljs-selector-attr,.hljs-selector-id,.hljs-selector-pseudo,.hljs-string,.hljs-template-tag,.hljs-template-variable,.hljs-type{color:#f5f}.hljs-comment,.hljs-deletion,.hljs-meta{color:#5ff}

View File

@ -0,0 +1 @@
pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}.hljs{color:#000;background:#fff}.hljs-emphasis,.hljs-strong{color:#000}.hljs-bullet,.hljs-literal,.hljs-number,.hljs-quote,.hljs-regexp{color:navy}.hljs-code .hljs-selector-class{color:purple}.hljs-emphasis,.hljs-stronge,.hljs-type{font-style:italic}.hljs-function,.hljs-keyword,.hljs-name,.hljs-section,.hljs-selector-tag,.hljs-symbol{color:olive}.hljs-subst,.hljs-tag,.hljs-title{color:#000}.hljs-attribute{color:maroon}.hljs-class .hljs-title,.hljs-params,.hljs-title.class_,.hljs-variable{color:#0055af}.hljs-addition,.hljs-built_in,.hljs-comment,.hljs-deletion,.hljs-link,.hljs-meta,.hljs-selector-attr,.hljs-selector-id,.hljs-selector-pseudo,.hljs-string,.hljs-template-tag,.hljs-template-variable,.hljs-type{color:green}

View File

@ -0,0 +1 @@
pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}.hljs{background:#474949;color:#d1d9e1}.hljs-comment,.hljs-quote{color:#969896;font-style:italic}.hljs-addition,.hljs-keyword,.hljs-literal,.hljs-selector-tag,.hljs-type{color:#c9c}.hljs-number,.hljs-selector-attr,.hljs-selector-pseudo{color:#f99157}.hljs-doctag,.hljs-regexp,.hljs-string{color:#8abeb7}.hljs-built_in,.hljs-name,.hljs-section,.hljs-title{color:#b5bd68}.hljs-class .hljs-title,.hljs-selector-id,.hljs-template-variable,.hljs-title.class_,.hljs-variable{color:#fc6}.hljs-name,.hljs-section,.hljs-strong{font-weight:700}.hljs-bullet,.hljs-link,.hljs-meta,.hljs-subst,.hljs-symbol{color:#f99157}.hljs-deletion{color:#dc322f}.hljs-formula{background:#eee8d5}.hljs-attr,.hljs-attribute{color:#81a2be}.hljs-emphasis{font-style:italic}

Some files were not shown because too many files have changed in this diff Show More