md-preview-cli-plus 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.previewrc +6 -0
- package/LICENSE +21 -0
- package/README.md +70 -0
- package/bin/cli.js +59 -0
- package/lib/loadConfig.js +21 -0
- package/lib/loader.js +30 -0
- package/lib/pluginManager.js +58 -0
- package/lib/preview.js +46 -0
- package/lib/renderMarkdown.js +49 -0
- package/package.json +39 -0
- package/plugins/copyright-plugin.js +14 -0
- package/public/useSocket.js +24 -0
- package/styles/dark.css +41 -0
- package/styles/default.css +20 -0
package/.previewrc
ADDED
package/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2025 70v
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
# Md-preview-CLI
|
2
|
+
Markdown real-time preview + export
|
3
|
+
|
4
|
+
Markdown 实时预览 + 导出工具
|
5
|
+
|
6
|
+
CLI + Web + Markdown 三位一体
|
7
|
+
|
8
|
+
由于CLI对兼容性要求高,选择 CommonJS 模块化方式
|
9
|
+
# Get Start
|
10
|
+
```bash
|
11
|
+
npx md-preview --help #help
|
12
|
+
npx md-preview README.md --port 4000 --theme dark --export ./666.html #无需下载 用完删除 不占空间
|
13
|
+
```
|
14
|
+
# Features
|
15
|
+
1. **Build CLI Tool:** Use `commander` for argument parsing, help messages (`--help`), versioning (`-V` / `--version`), and error handling.
|
16
|
+
2. **Watch File Changes:** Leverage `chokidar` for efficient, cross-platform file watching.
|
17
|
+
3. **Open Files:** Use `open` to launch files or apps across platforms.
|
18
|
+
4. **Colorful Terminal:** Integrate `chalk` for vibrant console output. 🌈
|
19
|
+
5. **Hot Reloading:** Wrap `socket.io` in a `useSocket()` function to watch file changes and trigger browser refresh (similar to Vite).
|
20
|
+
6. **HTML Exporting:** Add `--export <path>` to save rendered HTML output to a file.
|
21
|
+
7. **Theme Support:** Provide multi-theme styling via `--theme=dark` or similar options.
|
22
|
+
8. **Plugin Architecture:** Support custom parser plugins.
|
23
|
+
9. **Packaging & Publishing:** Publish as an NPM package; allow usage via `npx md-preview`.
|
24
|
+
10. **Syntax Highlighting:** Use `highlight.js` to render beautifully highlighted code blocks.
|
25
|
+
# 🔌 Plugin System
|
26
|
+
1. **Multiple Integration Options:**
|
27
|
+
- Pass plugin path via CLI arguments.
|
28
|
+
- Place plugins inside a directory (e.g. `plugins/`) and use a configuration file `.previewrc`.
|
29
|
+
- In case of conflicts, CLI arguments have higher priority than `.previewrc`.
|
30
|
+
2. **Custom Plugin Lifecycle Hooks Supported:**
|
31
|
+
- `init`
|
32
|
+
- `beforeRender`
|
33
|
+
- `afterRender`
|
34
|
+
# 功能细化
|
35
|
+
1. 构建CLI 采用commander 解析参数、增加帮助信息--help、版本控制-V --version、错误处理
|
36
|
+
2. 文件监听变化 chokidar 跨平台兼容性好 低耗能
|
37
|
+
3. 打开open 打开各种文件程序 跨平台兼容性好
|
38
|
+
4. 打造彩色终端 🌈chalk
|
39
|
+
5. 热刷新页面:封装socket.io为useSocket()监听变化并通知刷新页面(类似 Vite)
|
40
|
+
6. 导出 HTML 功能:增加 --export 参数保存 HTML 文件
|
41
|
+
7. 多主题支持:通过 --theme=dark 选择不同样式
|
42
|
+
8. 插件机制:支持定制解析器
|
43
|
+
9. 打包发布:发布为 NPM 包,支持 npx md-preview
|
44
|
+
10. 引入代码高亮 highlight.js
|
45
|
+
# 插件机制
|
46
|
+
1. 任选其一:可通过命令行传插件地址;也可以将插件放入某个目录下(例如plugins/)并设置插件配置文件`.previewrc`;如有冲突,则`.previewrc`配置优先级小于命令行参数
|
47
|
+
2. 支持自定义插件,插件系统生命周期钩子 init、beforeRender/afterRender
|
48
|
+
# 项目结构
|
49
|
+
```bash
|
50
|
+
md-preview/
|
51
|
+
├── bin/
|
52
|
+
│ └── cli.js # CLI 命令入口
|
53
|
+
├── lib/
|
54
|
+
│ ├── plugins.js # 插件加载器
|
55
|
+
│ ├── preview.js # 启动本地服务
|
56
|
+
│ └── renderMarkdown.js # Markdown 渲染函数
|
57
|
+
├── public/
|
58
|
+
│ └── template.html # HTML 模板页面
|
59
|
+
├── styles/
|
60
|
+
│ └── style.css # 样式(可替换主题)
|
61
|
+
├── README.md
|
62
|
+
├── package.json
|
63
|
+
└── .gitignore
|
64
|
+
plugins/
|
65
|
+
├── copyright-plugin.js # 示例插件
|
66
|
+
```
|
67
|
+
```js
|
68
|
+
console.log("hello world")
|
69
|
+
```
|
70
|
+
|
package/bin/cli.js
ADDED
@@ -0,0 +1,59 @@
|
|
1
|
+
#!/usr/bin/env node
|
2
|
+
//const inquirer=require('inquirer')
|
3
|
+
const { program } = require('commander')
|
4
|
+
const path = require('path')
|
5
|
+
const startPreview = require('../lib/preview')
|
6
|
+
const pluginManager = require('../lib/pluginManager')
|
7
|
+
const loadConfig=require('../lib/loadConfig')
|
8
|
+
const loadPlugins=require('../lib/loader')
|
9
|
+
|
10
|
+
//帮助信息自动生成,不需要手动写
|
11
|
+
//默认命令触发条件就是和基本信息分开
|
12
|
+
let file_d='path to the Markdown file'
|
13
|
+
let port_d='specify preview port'
|
14
|
+
let theme_d='choose theme style: default/dark'
|
15
|
+
let export_d='export HTML to a given path (optional)'
|
16
|
+
let plugin_d='load your plugins'
|
17
|
+
|
18
|
+
program
|
19
|
+
.name('md-preview')
|
20
|
+
.description('Markdown real-time preview CLI')
|
21
|
+
.version('1.0.0')
|
22
|
+
|
23
|
+
program
|
24
|
+
.argument('<file>',`${file_d}` )
|
25
|
+
.option('--port <port>',`${port_d}` , 3000)
|
26
|
+
.option('--theme <theme>',`${theme_d}` , 'default')
|
27
|
+
.option('--export <path>',`${export_d}` , null)
|
28
|
+
//修改CLI支持--plugin参数 可多个
|
29
|
+
.option('--plugin <path...>', `${plugin_d}`)//...是commander里的rest参数
|
30
|
+
.action((file, options) => {
|
31
|
+
//console.log(options)
|
32
|
+
|
33
|
+
//调用加载配置并合并参数
|
34
|
+
// 加载 .previewrc
|
35
|
+
const rc = loadConfig()
|
36
|
+
|
37
|
+
// 合并 theme 设置(命令行优先生效)
|
38
|
+
options.theme = options.theme || rc.theme || 'default'
|
39
|
+
|
40
|
+
// 加载插件(来自 CLI 参数 + .previewrc + plugins/ 目录)
|
41
|
+
options.plugin = Array.from(new Set([
|
42
|
+
...(loadPlugins(rc.pluginsFolder) || []),
|
43
|
+
...(options.plugin || [])
|
44
|
+
]));
|
45
|
+
//console.log(options.plugin)
|
46
|
+
// 注册所有传入的插件
|
47
|
+
if (options.plugin && options.plugin.length) {
|
48
|
+
for (const pluginPath of options.plugin) {
|
49
|
+
const abs = path.resolve(process.cwd(), pluginPath)
|
50
|
+
const plugin = require(abs)
|
51
|
+
pluginManager.use(plugin)
|
52
|
+
}
|
53
|
+
}
|
54
|
+
|
55
|
+
const absolutePath = path.resolve(process.cwd(), file);
|
56
|
+
startPreview(absolutePath, options,pluginManager);
|
57
|
+
});
|
58
|
+
|
59
|
+
program.parse() // 默认使用 process.argv
|
@@ -0,0 +1,21 @@
|
|
1
|
+
//配置读取模块 lib/loadConfig.js
|
2
|
+
|
3
|
+
const fs = require('fs')
|
4
|
+
const path = require('path')
|
5
|
+
|
6
|
+
function loadConfig() {
|
7
|
+
const configPath = path.resolve(process.cwd(), '.previewrc')
|
8
|
+
if (!fs.existsSync(configPath)) return {}
|
9
|
+
|
10
|
+
try {
|
11
|
+
const raw = fs.readFileSync(configPath, 'utf-8')
|
12
|
+
const config = JSON.parse(raw)
|
13
|
+
return config
|
14
|
+
} catch (e) {
|
15
|
+
console.warn('⚠️ Failed to load .previewrc :', e.message)
|
16
|
+
return {}
|
17
|
+
}
|
18
|
+
}
|
19
|
+
|
20
|
+
module.exports = loadConfig
|
21
|
+
|
package/lib/loader.js
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
//loader.js
|
2
|
+
//把配置的插件都加载放入并返回
|
3
|
+
const path = require('path')
|
4
|
+
const fs = require('fs')
|
5
|
+
|
6
|
+
function loadPlugins(folder_path) {
|
7
|
+
let plugins = []
|
8
|
+
//把folder_path中每个目录里的插件注册完
|
9
|
+
for(const folder of folder_path){
|
10
|
+
const pluginsDir = path.resolve(process.cwd(), folder);
|
11
|
+
|
12
|
+
fs.readdirSync(pluginsDir).forEach(file => {
|
13
|
+
const fullPath = path.join(pluginsDir, file);
|
14
|
+
|
15
|
+
// 只加载 .js 文件
|
16
|
+
if (fs.statSync(fullPath).isFile() && file.endsWith('.js')) {
|
17
|
+
//const plugin = require(fullPath);
|
18
|
+
plugins.push(fullPath)
|
19
|
+
|
20
|
+
}else console.warn(`⚠️ Plugin not found at ${fullPath}`)
|
21
|
+
});
|
22
|
+
}
|
23
|
+
|
24
|
+
return plugins
|
25
|
+
}
|
26
|
+
|
27
|
+
module.exports = loadPlugins
|
28
|
+
|
29
|
+
|
30
|
+
|
@@ -0,0 +1,58 @@
|
|
1
|
+
//lib/pluginManager.js
|
2
|
+
const fs = require('fs')
|
3
|
+
const path = require('path')
|
4
|
+
|
5
|
+
class PluginManager {
|
6
|
+
constructor() {
|
7
|
+
this.plugins = []
|
8
|
+
}
|
9
|
+
// 注册插件
|
10
|
+
use(plugin) {
|
11
|
+
if (typeof plugin === 'object') {
|
12
|
+
// 调用 init 钩子
|
13
|
+
if (typeof plugin.init === 'function') {
|
14
|
+
try {
|
15
|
+
plugin.init()
|
16
|
+
} catch (e) {
|
17
|
+
console.warn(`Failed to init plugin: ${plugin.name} `, e)
|
18
|
+
}
|
19
|
+
console.log(`✅ Loaded plugin: ${plugin.name}`);
|
20
|
+
}
|
21
|
+
this.plugins.push(plugin)
|
22
|
+
} else {
|
23
|
+
console.warn(`Invalid plugin:${plugin}`)
|
24
|
+
}
|
25
|
+
return this // 支持链式调用
|
26
|
+
}
|
27
|
+
|
28
|
+
allBeforeRender(content,theme='default'){
|
29
|
+
try{
|
30
|
+
// 插件处理原始内容
|
31
|
+
for (const plugin of this.plugins) {
|
32
|
+
if (plugin.beforeRender) {
|
33
|
+
content = plugin.beforeRender(content, { theme })
|
34
|
+
}
|
35
|
+
}
|
36
|
+
return content;
|
37
|
+
}catch(err){
|
38
|
+
console.error(`❌ Failed to use the plugin: ${err.message}`)
|
39
|
+
}
|
40
|
+
}
|
41
|
+
|
42
|
+
allAfterRender(html,theme='default'){
|
43
|
+
// 插件处理 HTML 内容
|
44
|
+
try{
|
45
|
+
for (const plugin of this.plugins) {
|
46
|
+
if (plugin.afterRender) {
|
47
|
+
html = plugin.afterRender(html, { theme })
|
48
|
+
}
|
49
|
+
}
|
50
|
+
return html;
|
51
|
+
}catch(err){
|
52
|
+
console.error(`❌ Failed to use the plugin:${err.message}`)
|
53
|
+
}
|
54
|
+
}
|
55
|
+
// 渲染流程,交给插件机制外部
|
56
|
+
}
|
57
|
+
|
58
|
+
module.exports = new PluginManager()
|
package/lib/preview.js
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
const express = require('express')
|
2
|
+
const fs = require('fs')
|
3
|
+
const path = require('path')
|
4
|
+
const {Server} =require('socket.io')
|
5
|
+
const chokidar = require('chokidar')
|
6
|
+
const markdownToHtml = require('./renderMarkdown')
|
7
|
+
const open = require('open')
|
8
|
+
const chalk = require('chalk')
|
9
|
+
|
10
|
+
function startPreview(filePath, options,pluginManager) {
|
11
|
+
const app = express()
|
12
|
+
const port = options.port || 3000
|
13
|
+
|
14
|
+
app.get('/', (req, res) => {
|
15
|
+
let html = markdownToHtml(filePath, options.theme,options.port,pluginManager)
|
16
|
+
html=pluginManager.allAfterRender(html,options.theme)
|
17
|
+
if(options.export){
|
18
|
+
const exportPath = path.resolve(process.cwd(), options.export);
|
19
|
+
fs.writeFileSync(exportPath, html);
|
20
|
+
console.log(chalk.green(`✅ HTML exported to: ${exportPath}`));
|
21
|
+
}
|
22
|
+
res.send(html)
|
23
|
+
})
|
24
|
+
|
25
|
+
const watcher = chokidar.watch(filePath)
|
26
|
+
|
27
|
+
app.use(express.static(path.join(__dirname, '../styles')))
|
28
|
+
app.use(express.static(path.join(__dirname, '../public')))
|
29
|
+
|
30
|
+
const server=app.listen(port, () => {
|
31
|
+
console.log(chalk.cyan(`🚀 Local preview started at http://localhost:${port}`))
|
32
|
+
open(`http://localhost:${port}`)
|
33
|
+
})
|
34
|
+
const io = new Server(server, {
|
35
|
+
cors: { origin: '*' }
|
36
|
+
})
|
37
|
+
io.on('connection', (socket) => {
|
38
|
+
//console.log('connecting...')
|
39
|
+
})
|
40
|
+
watcher.on('change', () => {
|
41
|
+
console.log(chalk.green('📄 Markdown file updated, refreshing page automatically.'))
|
42
|
+
io.emit('update')
|
43
|
+
})
|
44
|
+
}
|
45
|
+
|
46
|
+
module.exports = startPreview
|
@@ -0,0 +1,49 @@
|
|
1
|
+
const fs = require('fs')
|
2
|
+
const markdownIt = require('markdown-it')
|
3
|
+
|
4
|
+
function markdownToHtml(filePath, theme = 'default',port,pluginManager) {
|
5
|
+
const md = new markdownIt({
|
6
|
+
html: true,
|
7
|
+
linkify: true,//自动识别链接文本
|
8
|
+
typographer: true//排版符号美化
|
9
|
+
})
|
10
|
+
|
11
|
+
let markdownContent = fs.readFileSync(filePath, 'utf-8')
|
12
|
+
markdownContent=pluginManager.allBeforeRender(markdownContent,theme)
|
13
|
+
|
14
|
+
let htmlContent = md.render(markdownContent)
|
15
|
+
|
16
|
+
return `
|
17
|
+
<!DOCTYPE html>
|
18
|
+
<html lang="en">
|
19
|
+
<head>
|
20
|
+
<meta charset="UTF-8" />
|
21
|
+
<title>Markdown Preview</title>
|
22
|
+
<link rel="stylesheet" href="/${theme}.css" />
|
23
|
+
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.6.0/styles/github-dark.min.css">
|
24
|
+
</head>
|
25
|
+
<body>
|
26
|
+
<div class="content">
|
27
|
+
${htmlContent}
|
28
|
+
</div>
|
29
|
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.6.0/highlight.min.js"></script>
|
30
|
+
<script>
|
31
|
+
hljs.highlightAll(); // 自动高亮所有 <pre><code> 块
|
32
|
+
</script>
|
33
|
+
<script type="module">
|
34
|
+
import { useSocket } from '/useSocket.js'
|
35
|
+
|
36
|
+
useSocket({
|
37
|
+
serverUrl: 'http://localhost:${port}',
|
38
|
+
onUpdate: (html) => {
|
39
|
+
//document.querySelector('.content').innerHTML = html
|
40
|
+
location.reload()
|
41
|
+
}
|
42
|
+
})
|
43
|
+
</script>
|
44
|
+
</body>
|
45
|
+
</html>`;
|
46
|
+
|
47
|
+
}
|
48
|
+
|
49
|
+
module.exports = markdownToHtml
|
package/package.json
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
{
|
2
|
+
"name": "md-preview-cli-plus",
|
3
|
+
"version": "1.0.0",
|
4
|
+
"description": "md real-time preview + export",
|
5
|
+
"keywords": [
|
6
|
+
"markdown",
|
7
|
+
"cli",
|
8
|
+
"preview",
|
9
|
+
"node"
|
10
|
+
],
|
11
|
+
"homepage": "https://github.com/70v-Yoyo/Md-preview-CLI#readme",
|
12
|
+
"bugs": {
|
13
|
+
"url": "https://github.com/70v-Yoyo/Md-preview-CLI/issues"
|
14
|
+
},
|
15
|
+
"repository": {
|
16
|
+
"type": "git",
|
17
|
+
"url": "git+https://github.com/70v-Yoyo/Md-preview-CLI.git"
|
18
|
+
},
|
19
|
+
"license": "MIT",
|
20
|
+
"author": "WYY",
|
21
|
+
"type": "commonjs",
|
22
|
+
"main": "bin/cli.js",
|
23
|
+
"bin": {
|
24
|
+
"md-preview": "./bin/cli.js"
|
25
|
+
},
|
26
|
+
"scripts": {
|
27
|
+
"start": "node bin/cli.js"
|
28
|
+
},
|
29
|
+
"dependencies": {
|
30
|
+
"chalk": "^4.1.2",
|
31
|
+
"chokidar": "^4.0.3",
|
32
|
+
"commander": "^14.0.0",
|
33
|
+
"express": "^5.1.0",
|
34
|
+
"markdown-it": "^14.1.0",
|
35
|
+
"mongoose": "^8.16.3",
|
36
|
+
"open": "^8.4.0",
|
37
|
+
"socket.io": "^4.8.1"
|
38
|
+
}
|
39
|
+
}
|
@@ -0,0 +1,14 @@
|
|
1
|
+
//This is also a sample plugin.
|
2
|
+
module.exports = {
|
3
|
+
name: 'copyright-plugin',
|
4
|
+
|
5
|
+
// 在渲染前调用,可修改原始内容
|
6
|
+
beforeRender(content, options) {
|
7
|
+
return content
|
8
|
+
},
|
9
|
+
|
10
|
+
// 渲染为 HTML 后调用,可修改 HTML 字符串
|
11
|
+
afterRender(html, options) {
|
12
|
+
return html + `<footer><p style="text-align:center;color:#aaa;">© 70v-Yoyo </p></footer>`
|
13
|
+
}
|
14
|
+
}
|
@@ -0,0 +1,24 @@
|
|
1
|
+
// useSocket.js(浏览器端模块,支持原生或 Vite 项目)
|
2
|
+
import { io } from 'https://cdn.socket.io/4.7.5/socket.io.esm.min.js'
|
3
|
+
|
4
|
+
export function useSocket({ serverUrl, onUpdate }) {
|
5
|
+
const socket = io(serverUrl)
|
6
|
+
|
7
|
+
socket.on('connect', () => {
|
8
|
+
console.log('✅ 已连接 Socket.IO 服务')
|
9
|
+
})
|
10
|
+
|
11
|
+
socket.on('disconnect', () => {
|
12
|
+
console.warn('⚠️ Socket 断开连接')
|
13
|
+
})
|
14
|
+
|
15
|
+
// 接收后端推送的 HTML 内容
|
16
|
+
socket.on('update', (htmlContent) => {
|
17
|
+
console.log('📥 收到 update 事件')
|
18
|
+
if (typeof onUpdate === 'function') {
|
19
|
+
onUpdate(htmlContent)
|
20
|
+
}
|
21
|
+
})
|
22
|
+
|
23
|
+
return socket
|
24
|
+
}
|
package/styles/dark.css
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
body {
|
2
|
+
background-color: #1e1e1e;
|
3
|
+
color: #d4d4d4;
|
4
|
+
font-family: 'Segoe UI', sans-serif;
|
5
|
+
line-height: 1.6;
|
6
|
+
padding: 2rem;
|
7
|
+
}
|
8
|
+
|
9
|
+
h1, h2, h3, h4, h5, h6 {
|
10
|
+
color: #ffffff;
|
11
|
+
border-bottom: 1px solid #333;
|
12
|
+
}
|
13
|
+
|
14
|
+
a {
|
15
|
+
color: #4fc3f7;
|
16
|
+
text-decoration: none;
|
17
|
+
}
|
18
|
+
|
19
|
+
code, pre {
|
20
|
+
background-color: #2d2d2d;
|
21
|
+
color: #f8f8f2;
|
22
|
+
padding: 0.2em 0.4em;
|
23
|
+
border-radius: 4px;
|
24
|
+
font-family: 'Courier New', monospace;
|
25
|
+
}
|
26
|
+
|
27
|
+
blockquote {
|
28
|
+
border-left: 4px solid #555;
|
29
|
+
padding-left: 1em;
|
30
|
+
color: #aaa;
|
31
|
+
}
|
32
|
+
|
33
|
+
table {
|
34
|
+
border-collapse: collapse;
|
35
|
+
width: 100%;
|
36
|
+
}
|
37
|
+
|
38
|
+
th, td {
|
39
|
+
border: 1px solid #444;
|
40
|
+
padding: 0.5em;
|
41
|
+
}
|
@@ -0,0 +1,20 @@
|
|
1
|
+
body {
|
2
|
+
font-family: sans-serif;
|
3
|
+
padding: 40px;
|
4
|
+
max-width: 720px;
|
5
|
+
margin: auto;
|
6
|
+
background-color: #f5f5f5;
|
7
|
+
color: #333;
|
8
|
+
}
|
9
|
+
|
10
|
+
h1, h2, h3 {
|
11
|
+
border-bottom: 1px solid #ddd;
|
12
|
+
padding-bottom: 4px;
|
13
|
+
}
|
14
|
+
|
15
|
+
pre {
|
16
|
+
background-color: #2d2d2d;
|
17
|
+
color: #f8f8f2;
|
18
|
+
padding: 12px;
|
19
|
+
overflow-x: auto;
|
20
|
+
}
|