├── demo ├── level1-1.md ├── level2B │ ├── README.md │ └── levelB │ │ └── readme.md ├── level1-hello.md ├── level2A │ ├── abc.md │ ├── efg.md │ ├── README.md │ └── assets │ │ └── img.png ├── assets │ ├── ranshu.png │ └── baidu_tongji.js ├── level1-0.md ├── README.md └── template │ └── App.vue ├── src ├── menu │ ├── __tests__ │ │ ├── test.md │ │ └── index.spec.js │ └── index.js ├── markdown │ ├── provider │ │ ├── __test_files__ │ │ │ ├── test.md │ │ │ ├── abc │ │ │ │ ├── 123 │ │ │ │ │ ├── test.md │ │ │ │ │ └── README.md │ │ │ │ ├── 456 │ │ │ │ │ └── README.md │ │ │ │ └── README.md │ │ │ ├── xyz │ │ │ │ └── ad.md │ │ │ ├── index.js │ │ │ └── README.md │ │ ├── index.js │ │ ├── FileNode.js │ │ ├── TreeNode.js │ │ ├── Provider.js │ │ └── __test__ │ │ │ └── index.spec.js │ ├── title │ │ ├── index.js │ │ └── __test__ │ │ │ └── index.spec.js │ ├── autoNumber │ │ ├── __tests__ │ │ │ ├── convertBefore.md │ │ │ ├── convertAfter.md │ │ │ └── index.spec.js │ │ └── index.js │ ├── index.js │ ├── prefix │ │ ├── index.js │ │ └── __test__ │ │ │ └── index.spec.js │ ├── marked │ │ ├── index.js │ │ └── __tests__ │ │ │ └── index.spec.js │ ├── themes │ │ ├── index.js │ │ └── __test__ │ │ │ └── index.spec.js │ └── breadcrumb │ │ ├── index.js │ │ └── __test__ │ │ └── index.spec.js ├── assets │ ├── logo.png │ ├── responsive.css │ ├── themes │ │ ├── mark │ │ │ └── style.css │ │ └── techo │ │ │ └── style.css │ └── adminlte.min.js ├── template │ ├── seed │ │ ├── dist │ │ │ ├── img │ │ │ │ └── AdminLTELogo.png │ │ │ └── js │ │ │ │ ├── .eslintrc.json │ │ │ │ ├── pages │ │ │ │ ├── dashboard3.js │ │ │ │ ├── dashboard2.js │ │ │ │ └── dashboard.js │ │ │ │ ├── demo.js │ │ │ │ └── adminlte.min.js │ │ └── index.html │ └── App.vue ├── ssr │ ├── App.vue │ ├── HelloWorld.vue │ ├── __tests__ │ │ └── index.spec.js │ ├── index.html │ ├── server.js │ ├── index.js │ └── list.js ├── components │ └── HelloWorld.vue ├── util │ ├── progress.js │ └── available-port.js ├── devserver │ └── index.js ├── index.js └── index.html ├── .vscode └── settings.json ├── index.js ├── .gitignore ├── publish.sh ├── package.json ├── bin ├── index.js └── build.js ├── README.md └── README.en-US.md /demo/level1-1.md: -------------------------------------------------------------------------------- 1 | # 一级目录 1 -------------------------------------------------------------------------------- /src/menu/__tests__/test.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /demo/level2B/README.md: -------------------------------------------------------------------------------- 1 | # 二级目录 B -------------------------------------------------------------------------------- /src/markdown/provider/__test_files__/test.md: -------------------------------------------------------------------------------- 1 | # Test -------------------------------------------------------------------------------- /demo/level1-hello.md: -------------------------------------------------------------------------------- 1 | # 一级目录 Hello 2 | 3 | 本文件 根据排序规则排在二级目录 A上面 -------------------------------------------------------------------------------- /demo/level2A/abc.md: -------------------------------------------------------------------------------- 1 | # 三级目录1 2 | write your content detail here -------------------------------------------------------------------------------- /demo/level2A/efg.md: -------------------------------------------------------------------------------- 1 | # 三级目录2 2 | write your content detail here -------------------------------------------------------------------------------- /demo/level2B/levelB/readme.md: -------------------------------------------------------------------------------- 1 | # Cat Level3 title 2 | 3 | 三级目录文件内容 -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "liveServer.settings.port": 5501 3 | } -------------------------------------------------------------------------------- /src/markdown/provider/__test_files__/abc/456/README.md: -------------------------------------------------------------------------------- 1 | # ABC_456_README -------------------------------------------------------------------------------- /demo/level2A/README.md: -------------------------------------------------------------------------------- 1 | # 二级目录 2 | 3 | ![image-20190426195259877](assets/img.png) -------------------------------------------------------------------------------- /src/markdown/provider/__test_files__/abc/123/test.md: -------------------------------------------------------------------------------- 1 | 不存在一级标题 2 | 3 | su37josephxia 是一个高手 -------------------------------------------------------------------------------- /src/markdown/provider/__test_files__/abc/README.md: -------------------------------------------------------------------------------- 1 | # ABC_README 2 | 3 | smarty-press 这个项目不错 -------------------------------------------------------------------------------- /src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smarty-team/smarty-press/HEAD/src/assets/logo.png -------------------------------------------------------------------------------- /demo/assets/ranshu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smarty-team/smarty-press/HEAD/demo/assets/ranshu.png -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const {startDev} = require('./src/index') 2 | 3 | startDev({ 4 | root : __dirname + '/demo' 5 | }) -------------------------------------------------------------------------------- /demo/level2A/assets/img.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smarty-team/smarty-press/HEAD/demo/level2A/assets/img.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | **/node_modules/ 2 | package-lock.json 3 | .DS_Store 4 | .spress 5 | .vercel 6 | dist 7 | .idea 8 | /test 9 | -------------------------------------------------------------------------------- /src/template/seed/dist/img/AdminLTELogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smarty-team/smarty-press/HEAD/src/template/seed/dist/img/AdminLTELogo.png -------------------------------------------------------------------------------- /src/markdown/provider/__test_files__/abc/123/README.md: -------------------------------------------------------------------------------- 1 | # ABC_123_README 2 | 3 | [compose-awesome竟然有这么多实现方式](https://github.com/su37josephxia/compose-awesome) -------------------------------------------------------------------------------- /src/menu/__tests__/index.spec.js: -------------------------------------------------------------------------------- 1 | test ('menu getMdList',() => { 2 | const {getFolder} = require('../index') 3 | expect(getFolder(__dirname)).toContain( 4 | 'test.md' 5 | ) 6 | }) -------------------------------------------------------------------------------- /src/ssr/App.vue: -------------------------------------------------------------------------------- 1 | 9 | -------------------------------------------------------------------------------- /src/markdown/title/index.js: -------------------------------------------------------------------------------- 1 | // 提取Markdown标题 2 | module.exports = async ({ fileNode }, next) => { 3 | const match = /^#\s?([^#\n\r]*)/.exec(fileNode.body) 4 | fileNode.title = match ? match[1].trim() : fileNode.path 5 | await next() 6 | } -------------------------------------------------------------------------------- /src/markdown/autoNumber/__tests__/convertBefore.md: -------------------------------------------------------------------------------- 1 | ## 标题一 2 | #### 小标题一 3 | ### 小标题一 4 | ### 小标题二 5 | ### 小标题三 6 | #### 超小标题四 7 | ## 标题二 8 | ### 小标题一 9 | ### 小标题二 10 | ### 小标题三 11 | ## 标题三 12 | ### 小标题一 13 | ### 小标题二 14 | ### 小标题三 15 | ###### 超小标题四 -------------------------------------------------------------------------------- /demo/level1-0.md: -------------------------------------------------------------------------------- 1 | # 一级目录 0 2 | 3 | 根据排序规则,会排在二级目录 A上面 4 | ## aaa 5 | ### aaaaaa 6 | ### aaaaaa 7 | ### aaaaaa 8 | ### aaaaaa 9 | ## bbb 10 | ### aaaaaa 11 | ### aaaaaa 12 | ### aaaaaa 13 | ### aaaaaa 14 | ## ccc 15 | ### aaaaaa 16 | ### aaaaaa 17 | ### aaaaaa 18 | ### aaaaaa 19 | ## ddd -------------------------------------------------------------------------------- /src/markdown/autoNumber/__tests__/convertAfter.md: -------------------------------------------------------------------------------- 1 | ## 一、标题一 2 | ### 1. 小标题一 3 | ### 2. 小标题一 4 | ### 3. 小标题二 5 | ### 4. 小标题三 6 | #### 1. 超小标题四 7 | ## 二、标题二 8 | ### 1. 小标题一 9 | ### 2. 小标题二 10 | ### 3. 小标题三 11 | ## 三、标题三 12 | ### 1. 小标题一 13 | ### 2. 小标题二 14 | ### 3. 小标题三 15 | #### 1. 超小标题四 -------------------------------------------------------------------------------- /demo/assets/baidu_tongji.js: -------------------------------------------------------------------------------- 1 | var _hmt = _hmt || []; 2 | (function () { 3 | var hm = document.createElement("script"); 4 | hm.src = "https://hm.baidu.com/hm.js?83882d256e3353ab5f39c27a3fa50b23"; 5 | var s = document.getElementsByTagName("script")[0]; 6 | s.parentNode.insertBefore(hm, s); 7 | })(); 8 | -------------------------------------------------------------------------------- /src/markdown/index.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./provider')( 2 | require('./title'), // 解析标题 3 | require('./prefix'), // 标题缩进 4 | require('./breadcrumb'), // 计算面包屑 5 | require('./autoNumber'), // 自动生成序号 6 | require('./marked'), // markdown转html 7 | require('./themes') // 添加样式 8 | ) -------------------------------------------------------------------------------- /publish.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | npm config get registry # 检查仓库镜像库 3 | npm config set registry=https://registry.npmjs.org 4 | echo '请进行登录相关操作:' 5 | npm login # 登陆 6 | echo "-------publishing-------" 7 | npm publish # 发布 8 | npm config set registry=https://registry.npm.taobao.org # 设置为淘宝镜像 9 | echo "发布完成" 10 | exit -------------------------------------------------------------------------------- /src/markdown/provider/index.js: -------------------------------------------------------------------------------- 1 | const Provider = require('./Provider') 2 | 3 | module.exports = function(){ 4 | const provider = new Provider() 5 | Array.from(arguments).forEach(middleware=>{ 6 | provider.useMiddleware(middleware) 7 | }) 8 | provider.useMiddleware(require('../title')) //解析标题 9 | return provider 10 | } -------------------------------------------------------------------------------- /src/markdown/prefix/index.js: -------------------------------------------------------------------------------- 1 | // 标题缩进 2 | const prefixText = ' ' 3 | 4 | module.exports = async ({ provider, fileNode }, next) => { 5 | fileNode.prefix = '' 6 | let parentNode = fileNode.parent 7 | while (parentNode && parentNode != provider.root) { 8 | fileNode.prefix += prefixText 9 | parentNode = parentNode.parent 10 | } 11 | await next() 12 | } -------------------------------------------------------------------------------- /src/ssr/HelloWorld.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 20 | -------------------------------------------------------------------------------- /src/components/HelloWorld.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 20 | -------------------------------------------------------------------------------- /src/ssr/__tests__/index.spec.js: -------------------------------------------------------------------------------- 1 | const { assert } = require('@vue/compiler-dom') 2 | const path = require('path') 3 | it('sfc ssr render', async () => { 4 | const { createRender } = require('../index') 5 | const render = createRender(path.resolve(__dirname,'../App.vue')) 6 | const data = { 7 | todos: ['吃饭', '睡觉'] 8 | } 9 | const html = await render(data) 10 | expect(html).toBe('
') 11 | }) -------------------------------------------------------------------------------- /src/menu/index.js: -------------------------------------------------------------------------------- 1 | 2 | const glob = require('glob') 3 | const path = require('path') 4 | function getFolder(scanPath) { 5 | return glob.sync( 6 | path.join(scanPath,'/**/*.md'), 7 | { 8 | absolute: false, 9 | 10 | } 11 | ) 12 | .map( 13 | v => path.relative(scanPath, v) 14 | ) 15 | .filter(v => !v.startsWith('node_modules')) 16 | } 17 | module.exports.getFolder = getFolder 18 | module.exports.createMiddleware = (option) => async (ctx,next) => { 19 | ctx.menu = getFolder(option.root) 20 | await next() 21 | } -------------------------------------------------------------------------------- /src/util/progress.js: -------------------------------------------------------------------------------- 1 | const term = require('terminal-kit').terminal; 2 | 3 | // TODO: 强行退出程序 + 使用watch 会造成命令行出现莫名其妙的字符 4 | // const progressBar = term.progressBar({ 5 | // width: 80, 6 | // title: '🚀 Reloading the page...', 7 | // percent: true, 8 | // barHeadChar: '█', 9 | // barChar: '█', 10 | // barStyle: term.green, 11 | // }); 12 | 13 | let progress = 0 14 | 15 | function step() { 16 | // Add random progress 17 | progress += Math.random() / 5; 18 | // progressBar.update(progress); 19 | 20 | if (progress < 1) { 21 | setTimeout(step, 100 + Math.random() * 100); 22 | } 23 | } 24 | 25 | module.exports = { 26 | step, 27 | init: () => { 28 | progress = 0; 29 | } 30 | } 31 | 32 | 33 | -------------------------------------------------------------------------------- /src/markdown/marked/index.js: -------------------------------------------------------------------------------- 1 | const marked = require('marked') 2 | 3 | let indexData = {} 4 | 5 | // Markdown 转 HTML 6 | module.exports = async ({ fileNode }, next) => { 7 | 8 | // 菜单处理 9 | if (fileNode.catalogs instanceof Array) { 10 | catelogs = {} 11 | setCatelogs(fileNode.catalogs) 12 | } 13 | 14 | // HTML处理 15 | fileNode.html = marked(fileNode.body) 16 | 17 | await next() 18 | } 19 | 20 | function setCatelogs(catelogs) { 21 | catelogs.forEach(item => { 22 | if (item.hash.indexOf('#') == 0) { 23 | const matched = marked(item.hash).match(/ 1) { 29 | item.hash += '-' + (index - 1) 30 | } 31 | } 32 | } 33 | item.children && setCatelogs(item.children) 34 | }) 35 | } -------------------------------------------------------------------------------- /src/markdown/themes/index.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | const path = require('path') 3 | 4 | const styles = {} // 样式 5 | 6 | const themes = [ // 皮肤列表 7 | { name: '默认样式', file: 'mark' }, 8 | { name: 'techo', file: 'techo' }, 9 | ] 10 | 11 | // 样式常驻内存,只在第一次读取 12 | // TODO: 修改CSS后,重新读取样式并显示 13 | themes.forEach(theme => addTheme(theme)) 14 | 15 | // Markdown 转 HTML 16 | module.exports = async ({ fileNode }, next) => { 17 | //只有不存在的时候才添加属性 18 | if (!fileNode.themes) { 19 | fileNode.themes = themes 20 | fileNode.getTheme = (name) => { 21 | return styles[name in styles ? name : themes[0].name] 22 | } 23 | } 24 | await next() 25 | } 26 | 27 | function addTheme(theme) { 28 | const linkPath = `/assets/themes/${theme.file}/style.css`; 29 | const css = fs.readFileSync(path.join(__dirname, '../..' + linkPath), { 30 | encoding: 'utf-8' 31 | }) 32 | styles[theme.name] = { 33 | ...theme, 34 | css: css, 35 | html: ``, 36 | path: linkPath 37 | } 38 | } -------------------------------------------------------------------------------- /demo/README.md: -------------------------------------------------------------------------------- 1 | # 欢迎使用 SMARTY-PRESS 2 | 3 | 4 | ## 简单说明 5 | 6 | 7 | 8 | ### 文件目录 9 | * demo 根目录下的.md文件为一级目录: 10 | * 如:level1-0.md、level1-1.md 11 | * demo 根目录下的文件夹为二级目录: 12 | * 如:文件夹level2A、文件夹level2B 13 | * 以此类推在二级目录下创建的文件夹为三级目录 14 | 15 | ### 实时预览 16 | 17 | * 添加、修改、删除 demo 目录的文件,将在浏览器中可以实时显示修改结果 18 | 19 | ### 支持链接 20 | 21 | * 链接到 [二级目录 A](/level2A/README.md) 22 | 23 | * ' /level2A/README.md ' 路径中的 README.md 可以省略 24 | * 链接到 [二级目录 A](/level2A) 25 | 26 | ### 自动识别标题 27 | 28 | * 自动识别 ”一级标题 # “ 为菜单中的链接 29 | 30 | * 如果文件中不存在标题,则会显示 文件路径 31 | 32 | ### 文档自动排序 33 | 34 | * 文档会按“路径”进行排序 35 | 36 | * **同级** 目录中," README.md " 会排在第一个 37 | 38 | ### 支持多级目录显示 39 | 40 | 41 | ### assets文件拷贝 42 | 自动拷贝assets文件中的图片内容 43 | 44 | ## 插件开发说明 45 | 46 | 查看[插件开发说明](https://github.com/su37josephxia/smarty-press/tree/master/src/markdown/provider/__test_files__/) 47 | -------------------------------------------------------------------------------- /src/ssr/index.html: -------------------------------------------------------------------------------- 1 | 2 | 4 |
5 | 39 | -------------------------------------------------------------------------------- /src/template/seed/dist/js/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "parserOptions": { 4 | "ecmaVersion": 5, 5 | "sourceType": "script" 6 | }, 7 | "env": { 8 | "jquery": true 9 | }, 10 | "extends": [ 11 | "plugin:unicorn/recommended", 12 | "xo", 13 | "xo/browser" 14 | ], 15 | "rules": { 16 | "capitalized-comments": "off", 17 | "indent": [ 18 | "error", 19 | 2, 20 | { 21 | "MemberExpression": "off", 22 | "SwitchCase": 1 23 | } 24 | ], 25 | "multiline-ternary": [ 26 | "error", 27 | "always-multiline" 28 | ], 29 | "object-curly-spacing": [ 30 | "error", 31 | "always" 32 | ], 33 | "semi": [ 34 | "error", 35 | "never" 36 | ], 37 | "strict": "error", 38 | "unicorn/no-for-loop": "off", 39 | "unicorn/no-null": "off", 40 | "unicorn/prefer-dataset": "off", 41 | "unicorn/prefer-includes": "off", 42 | "unicorn/prefer-node-append": "off", 43 | "unicorn/prefer-query-selector": "off", 44 | "unicorn/prevent-abbreviations": "off" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/util/available-port.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 检测端口号 3 | * @param { number } port 端口号 4 | * @returns Promise 5 | */ 6 | function listenPort(port) { 7 | if (port < 0 || port > 65535) { 8 | throw new RangeError("Invalid port"); 9 | } 10 | return new Promise((resolve) => { 11 | const service = require("net").createServer().listen(port); 12 | service.on("listening", (client) => { 13 | service.close(); 14 | resolve(port); 15 | }); 16 | 17 | service.on("error", (err) => { 18 | if (err.code == "EADDRINUSE") { 19 | resolve(err); 20 | } 21 | }); 22 | }); 23 | } 24 | 25 | /** 26 | * 获取一个可用的端口号 27 | * @param { number } port 端口号 28 | * @param { () => void } callback 回调函数 29 | */ 30 | async function getAvailablePora(port, callback) { 31 | let avaliable = port; 32 | let isAvailable = await listenPort(port); 33 | if (isAvailable instanceof Error) { 34 | port++; 35 | avaliable = getAvailablePora(port, callback); 36 | } else { 37 | callback(port); 38 | } 39 | return avaliable; 40 | } 41 | 42 | module.exports = getAvailablePora; 43 | -------------------------------------------------------------------------------- /src/markdown/autoNumber/__tests__/index.spec.js: -------------------------------------------------------------------------------- 1 | const { FileNode, next } = require('../../provider/__test_files__') 2 | const autoNumberMiddleware = require('../index') 3 | 4 | const options = { 5 | resolvePath: filePath => require('path').join(__dirname, filePath) 6 | } 7 | 8 | it('测试##/###自动添加序号', async () => { 9 | const fileNode = new FileNode('convertBefore.md', options) 10 | const afterNode = new FileNode('convertAfter.md', options) 11 | await autoNumberMiddleware({ fileNode }, next) 12 | expect(fileNode.body).toBe(afterNode.body) 13 | }) 14 | 15 | it('测试##/###文章目录', async () => { 16 | const fileNode = new FileNode('convertBefore.md', options) 17 | await autoNumberMiddleware({ fileNode }, next) 18 | 19 | expect(fileNode.catalogs.length).toBe(3) 20 | expect(fileNode.catalogs[0].title).toBe('一、标题一') 21 | expect(fileNode.catalogs[0].hash).toBe('## 一、标题一') 22 | expect(fileNode.catalogs[0].children.length).toBe(5) 23 | expect(fileNode.catalogs[0].children[1].title).toBe('2. 小标题一') 24 | expect(fileNode.catalogs[0].children[1].hash).toBe('### 2. 小标题一') 25 | expect(fileNode.catalogs[1].title).toBe('二、标题二') 26 | }) 27 | 28 | -------------------------------------------------------------------------------- /src/markdown/provider/FileNode.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | const path = require('path') 3 | 4 | class FileNode { 5 | 6 | constructor(_path, options) { 7 | this.resolvePath = options && options.resolvePath ? 8 | options.resolvePath : (filePath) => path.join(__dirname, filePath) 9 | this.path = _path 10 | this.isFileNode = true 11 | this.parent = null 12 | this.init() 13 | } 14 | 15 | // 是否存在更新 16 | get hasChanged() { 17 | if (this.getLastModified() != this.lastModified) { 18 | this.init() 19 | return true 20 | } 21 | return false 22 | } 23 | 24 | // 完整路径 25 | get realPath() { 26 | return this.resolvePath(this.path) 27 | } 28 | 29 | // 数据初始化 30 | init() { 31 | this.body = this.getFileBody() 32 | this.lastModified = this.getLastModified() 33 | } 34 | 35 | // 获取文件更新日期 36 | getLastModified() { 37 | return fs.statSync(this.realPath).mtime.getTime() 38 | } 39 | 40 | // 获取文件内容 41 | getFileBody() { 42 | return fs.readFileSync(this.realPath, { 43 | encoding: 'utf-8' 44 | }).replace(/\r\n/g, '\n') 45 | } 46 | 47 | } 48 | 49 | module.exports = FileNode 50 | -------------------------------------------------------------------------------- /src/ssr/server.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const app = express() 3 | const vueapp = require('./list') 4 | const Vue = require('vue') // vue@next 5 | const serverRenderer = require('@vue/server-renderer') 6 | const compilerSsr = require('@vue/compiler-ssr') 7 | const compilerSfc = require('@vue/compiler-sfc') 8 | const fs = require('fs') 9 | vueapp.ssrRender = new Function('require', compilerSsr.compile(vueapp.template).code)(require) 10 | 11 | app.get('/', async function (req, res) { 12 | const { descriptor } = compilerSfc.parse(fs.readFileSync('./HelloWorld.vue', 'utf-8')) 13 | console.log(descriptor.template.content) 14 | 15 | const data = () => ({ 16 | count :0, 17 | msg: 'hello ssr' 18 | }) 19 | 20 | const render = compilerSsr.compile(descriptor.template.content).code 21 | 22 | let vapp = Vue.createApp({ 23 | // template: descriptor.template.content, // 写法一 24 | ssrRender: new Function('require',render)(require), // 写法二 25 | data 26 | }) 27 | let html = await serverRenderer.renderToString(vapp) 28 | 29 | res.send(html) 30 | }) 31 | 32 | app.listen(9093, () => { 33 | console.log('listen 9093') 34 | }) 35 | -------------------------------------------------------------------------------- /src/markdown/provider/TreeNode.js: -------------------------------------------------------------------------------- 1 | const IndexFile = 'README.md' 2 | 3 | class TreeNode { 4 | constructor(path) { 5 | this.path = path 6 | this.children = [] 7 | this.parent = null 8 | } 9 | 10 | // 添加节点 11 | addChild(child) { 12 | child.parent = this 13 | this.children.push(child) 14 | this.sort() 15 | return child 16 | } 17 | 18 | // 删除节点 19 | removeChild(child) { 20 | const index = this.children.indexOf(child) 21 | if (index != -1) { 22 | this.children.splice(index, 1) 23 | } 24 | } 25 | 26 | // 排序 27 | sort() { 28 | this.children.sort((aNode, bNode) => { 29 | const aFileName = this.getFileName(aNode) 30 | const bFileName = this.getFileName(bNode) 31 | if (aFileName == IndexFile) { 32 | //README.md放在最前面 33 | return -1 34 | } else if (bFileName == IndexFile) { 35 | return 1 36 | } else { 37 | return aFileName.localeCompare(bFileName) 38 | } 39 | }); 40 | } 41 | 42 | // 获取不包含路径(/)的文件名 43 | getFileName(fileNode) { 44 | return fileNode.path.replace(this.path, '').replace(/(\/|\\)/g, '').split(/(\/|\\)/g)[0] 45 | } 46 | } 47 | 48 | module.exports = TreeNode 49 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "smarty-press", 3 | "version": "0.0.12", 4 | "description": "", 5 | "main": "index.js", 6 | "bin": { 7 | "spress": "./bin/index.js" 8 | }, 9 | "scripts": { 10 | "start": "node bin/index.js start demo", 11 | "build": "node bin/index.js build demo", 12 | "dev": "nodemon index.js", 13 | "test": "jest --watch" 14 | }, 15 | "keywords": [], 16 | "author": "", 17 | "license": "ISC", 18 | "dependencies": { 19 | "@vue/compiler-dom": "^3.2.20", 20 | "@vue/compiler-sfc": "^3.2.20", 21 | "@vue/compiler-ssr": "^3.2.20", 22 | "@vue/server-renderer": "^3.2.20", 23 | "chalk": "^4.1.0", 24 | "chalk-animation": "^1.6.0", 25 | "cheerio": "^1.0.0-rc.3", 26 | "clear": "^0.1.0", 27 | "commander": "^6.0.0", 28 | "figlet": "^1.5.0", 29 | "glob": "^7.2.0", 30 | "koa": "^2.12.0", 31 | "koa-static": "^5.0.0", 32 | "marked": "^1.1.1", 33 | "nodemon": "^2.0.4", 34 | "open": "^7.2.0", 35 | "ora": "^4.0.5", 36 | "socket.io": "^2.3.0", 37 | "terminal-kit": "^1.43.0", 38 | "vue": "3.2.20", 39 | "watch": "^1.0.2" 40 | }, 41 | "devDependencies": { 42 | "express": "^4.17.1", 43 | "jest": "^27.2.5", 44 | "vite": "^0.20.0" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/assets/responsive.css: -------------------------------------------------------------------------------- 1 | /* 414px iphone6/7/8 Plus宽度 */ 2 | @media screen and (max-width: 414px) { 3 | .markdown-catalogs { 4 | display: none; 5 | } 6 | } 7 | @media screen and (min-width: 992px) { 8 | .markdown-catalogs { 9 | position: fixed; 10 | right: 10px; 11 | background: #FFF; 12 | border-radius: 5px; 13 | padding: 15px; 14 | width: 280px; 15 | } 16 | 17 | .body-catalogs { 18 | padding-right: 290px 19 | } 20 | 21 | .control-sidebar { 22 | display: block; 23 | } 24 | 25 | .toolkits { 26 | display: none; 27 | } 28 | 29 | iframe { 30 | width: 670px; 31 | aspect-ratio: 3/2; 32 | } 33 | } 34 | 35 | @media screen and (max-width: 992px) { 36 | .markdown-catalogs { 37 | right: 10px; 38 | background: #FFF; 39 | border-radius: 5px; 40 | padding: 10px; 41 | margin: 0 20px 40px; 42 | } 43 | 44 | .toolkits { 45 | position: fixed; 46 | bottom: 10px; 47 | right: 10px; 48 | } 49 | 50 | .toolkits-item { 51 | background: rgba(100, 100, 100, 0.4); 52 | width: 40px; 53 | height: 40px; 54 | text-align: center; 55 | line-height: 40px; 56 | border-radius: 5px; 57 | margin: 5px; 58 | } 59 | 60 | .toolkits-item:active { 61 | background: rgba(100, 100, 100, 0.9); 62 | width: 40px; 63 | } 64 | 65 | .toolkits a { 66 | color: #FFF; 67 | } 68 | .toolkits a svg{ 69 | width:30px; 70 | height:30px; 71 | } 72 | 73 | iframe { 74 | width: 100%; 75 | aspect-ratio: 3/2; 76 | } 77 | } -------------------------------------------------------------------------------- /src/markdown/provider/__test_files__/xyz/ad.md: -------------------------------------------------------------------------------- 1 | # 广告页面 2 | 3 | # 这个页面存在多个一级标题,并且不存在 README.md 4 | 5 | # 会了吧 6 | 7 | *还在因为 变量中 包含不认识的单词 头大吗?* 8 | 9 | *还在因为 看不懂 英文注释/文档 掉头发吗 ?* 10 | 11 | *还在因为 各种机器翻译的内容 云里雾里吗 ?* 12 | 13 | **您的救星来了!!!用了“会了吧”,轻松“学会啦”** 14 | 15 | 16 | # 使用流程 17 | 18 | 安装后,点击源码文件,会自动分析所有包含的单词,不在 **已掌握单词列表** 中的单词会自动添加到 **陌生单词** 列表 19 | 20 | ![使用教程](https://www.miaoqiyuan.cn/products/vscode-huile8/help/help.gif) 21 | 22 | 单词后面可以显示 单词解释 23 | 24 | 鼠标悬停可以显示 音标和解释 25 | 26 | 点击单词可以朗读本单词 27 | 28 | ![使用教程](https://www.miaoqiyuan.cn/products/vscode-huile8/help/tips.gif) 29 | 30 | # 已掌握单词列表文件 31 | 32 | 在 **已掌握单词列表文件** 中的单词,不会在 *陌生单词* 列表中显示 33 | 34 | ## 自动处理 35 | 36 | 在 **陌生单词** 中的单词,点击 图标 可以 将 单词添加到 **已掌握单词列表文件** 37 | 38 | 在 **已掌握单词** 中的单词,点击 图标 可以 将 单词 从 **已掌握单词列表文件** 中 删除 39 | 40 | ![使用教程](https://www.miaoqiyuan.cn/products/vscode-huile8/help/edit.gif) 41 | 42 | 43 | 44 | ## 手工设置 45 | 46 | 也可以手工编辑 **[用户目录]/.vscode/huile8-mastered-list.txt** ,设置已掌握单词: 47 | 48 | ```text 49 | console 50 | log 51 | hello 52 | world 53 | ``` 54 | 55 | 56 | 57 | ## 离线词库 58 | 59 | [skywind3000/ECDICT](https://github.com/skywind3000/ECDICT) 60 | 61 | [fxsjy/diaosi](https://github.com/fxsjy/diaosi) 62 | 63 | 64 | 65 | ## 功能实现 66 | 67 | - [x] 源码单词分析 68 | - [x] 点击播放读音 69 | - [x] 悬停显示单词解释 70 | - [ ] 禅模式(循环播放陌生单词) 71 | - [ ] 陌生单词列表 72 | 73 | 74 | 75 | ## 二次开发 76 | 77 | ```bash 78 | # 1、代码克隆 79 | git clone https://github.com/mqycn/huile8.git 80 | 81 | # 2、更新词库 82 | npm run dict.init 83 | npm run dict.update 84 | 85 | # 3、vscode 调试 86 | 87 | # 4、打包 88 | npm run publish 89 | ``` 90 | -------------------------------------------------------------------------------- /src/markdown/prefix/__test__/index.spec.js: -------------------------------------------------------------------------------- 1 | const { 2 | Provider, 3 | FileNode, 4 | resolvePath, 5 | testFile, 6 | options, 7 | next, 8 | updateTestFile 9 | } = require('../../provider/__test_files__') 10 | const prefixMiddleware = require('../index') 11 | const prefixText = ' ' 12 | 13 | // 必须挂到 provider树,所以没法做但文件测试 14 | 15 | it('注册中间件 方式测试', async () => { 16 | const provider = new Provider() 17 | const testFiles = [testFile] 18 | provider.resolvePath = resolvePath 19 | 20 | provider.useMiddleware(prefixMiddleware) 21 | 22 | // 初始化 23 | await provider.patch(testFiles) 24 | expect(provider 25 | .fileNodes() 26 | .map(item => item.prefix) 27 | .join(', ') 28 | ).toBe("") 29 | 30 | // 增加文件 31 | testFiles.push('abc/123/README.md') // 无标题文件 32 | testFiles.push('xyz/ad.md') // 有标题文件 33 | await provider.patch(testFiles) 34 | expect(provider 35 | .fileNodes() 36 | .map(item => item.prefix) 37 | .join(', ') 38 | ).toBe(`${prefixText}${prefixText}, , ${prefixText}`) 39 | 40 | // 删除文件 41 | testFiles.pop() 42 | await provider.patch(testFiles) 43 | expect(provider 44 | .fileNodes() 45 | .map(item => item.prefix) 46 | .join(', ') 47 | ).toBe(`${prefixText}${prefixText}, `) 48 | 49 | // 文件修改 50 | await updateTestFile('# Hello World\nFoo Bar!') 51 | await provider.patch(testFiles) 52 | expect(provider 53 | .fileNodes() 54 | .map(item => item.prefix) 55 | .join(', ') 56 | ).toBe(`${prefixText}${prefixText}, `) 57 | 58 | // 文件复原 59 | await updateTestFile() 60 | }) 61 | -------------------------------------------------------------------------------- /src/markdown/themes/__test__/index.spec.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | const path = require('path') 3 | const assetsPath = '../../../assets/' 4 | 5 | const { 6 | Provider, 7 | FileNode, 8 | resolvePath, 9 | testFile, 10 | options, 11 | next } = require('../../provider/__test_files__') 12 | const themeMiddleware = require('../index') 13 | 14 | it('themes 单文件', async () => { 15 | const fileNode = new FileNode(testFile, options) 16 | await themeMiddleware({ 17 | fileNode 18 | }, next) 19 | expect(fileNode.themes[0].name).toBe('默认样式') 20 | expect(fileNode.getTheme('默认样式').name).toBe('默认样式') 21 | expect(fileNode.getTheme('不存在的样式').file).toBe('mark') 22 | expect(fileNode.getTheme('默认样式').css) 23 | .toBe(fs.readFileSync(path.join(__dirname, assetsPath, 'themes/mark/style.css'), { 24 | encoding: 'utf-8' 25 | })) 26 | }) 27 | 28 | it('themes middleware', async () => { 29 | const provider = new Provider() 30 | provider.resolvePath = resolvePath 31 | provider.useMiddleware(themeMiddleware) 32 | 33 | await provider.patch([testFile]) 34 | const fileNode = provider.nodes[testFile] 35 | 36 | expect(fileNode.themes[0].name).toBe('默认样式') 37 | expect(fileNode.getTheme('默认样式').name).toBe('默认样式') 38 | expect(fileNode.getTheme('不存在的样式').file).toBe('mark') 39 | expect(fileNode.getTheme('默认样式').css) 40 | .toBe(fs.readFileSync(path.join(__dirname, assetsPath, 'themes/mark/style.css'), { 41 | encoding: 'utf-8' 42 | })) 43 | 44 | 45 | // // 初始化 46 | // await provider.patch(testFiles) 47 | // expect(provider 48 | // .fileNodes() 49 | // .map(item => item.html) 50 | // .join(', ') 51 | // ).toBe(testHtml) 52 | 53 | // 增加文件:和文件无关,无需测试 54 | // 删除文件:和文件无关,无需测试 55 | // 文件修改:和文件无关,无需测试 56 | // 文件复原:和文件无关,无需测试 57 | }) -------------------------------------------------------------------------------- /src/devserver/index.js: -------------------------------------------------------------------------------- 1 | 2 | const http = require('http') 3 | const Koa = require('koa') 4 | const io = require('socket.io') 5 | const watch = require('watch') 6 | const path = require('path') 7 | const progress = require('../util/progress') 8 | 9 | const createServer = (options = { 10 | watchFolder: '.' 11 | }) => { 12 | const app = new Koa() 13 | const server = http.createServer(app.callback()); 14 | var socket = io.listen(server); 15 | 16 | socket.on('connection', function () { 17 | // console.log('网页监听....') 18 | }); 19 | 20 | watch.watchTree(options.watchFolder, f => { 21 | progress.init(); 22 | progress.step(); 23 | 24 | socket.emit('reload', f) 25 | }) 26 | 27 | app.use(async (ctx, next) => { 28 | await next() 29 | 30 | if (ctx.type === ('text/html')) { 31 | ctx.body = ` 32 | 33 | 34 | 35 | 42 | ${ctx.body} 43 | 44 | ` 45 | } 46 | } 47 | ) 48 | 49 | return { 50 | start: (port = 3000) => { 51 | server.listen(port, () => { 52 | console.log('Smarty Press Start At ' + port) 53 | console.log('🚚Listen Dir : ' + path.resolve(options.watchFolder)) 54 | }) 55 | }, 56 | use: (...args) => { 57 | app.use(...args) 58 | } 59 | } 60 | 61 | } 62 | module.exports.createServer = createServer -------------------------------------------------------------------------------- /src/markdown/breadcrumb/index.js: -------------------------------------------------------------------------------- 1 | // 计算面包屑 2 | 3 | const indexFiles = [ 4 | 'README.md', 5 | 'index.html' 6 | ] 7 | 8 | module.exports = async ({ fileNode }, next) => { 9 | const breadcrumb = fileNode.breadcrumb = { 10 | nodes: [fileNode], //父子关系, 11 | toHtml: toHtml, //传入 格式化函数,返回结果 12 | get html() { //如果使用默认方法,可以直接使用 .html 方式调用 13 | return this.toHtml() 14 | } 15 | } 16 | let parentNode = fileNode.parent 17 | while (parentNode) { 18 | // parentNode 为 TreeNode 节点 19 | breadcrumb.nodes.unshift(parentNode) 20 | parentNode = parentNode.parent 21 | } 22 | if (fileNode.path.indexOf('README.md') != -1) { 23 | breadcrumb.nodes.pop() 24 | } 25 | await next() 26 | } 27 | 28 | function toHtml(formatNode, separator) { 29 | const formatFunc = formatNode || formatDefault 30 | return this.nodes.map(treeNode => { 31 | return formatFunc( 32 | treeNode.isFileNode ? 33 | null : // 最后一个节点为 fileNode,不存在 treeNode 34 | treeNode, // 其他节点 为 treeNode 节点 35 | treeNode.isFileNode ? 36 | treeNode : // 如果节点是 fileNode,直接传入 37 | treeNode.children.length > 0 && indexFiles.indexOf(treeNode.getFileName(treeNode.children[0])) != -1 ? 38 | treeNode.children[0] : // 如果 当前 treeNode 节点存在子节点,并且第一个为 README.md,传入 39 | null 40 | ) 41 | }).join(separator || '') 42 | } 43 | 44 | // 默认格式画 45 | function formatDefault(treeNode, indexFileNode) { 46 | let itemHtml = '' 47 | if (indexFileNode) { 48 | // treeNode存在 首页( README.md),显示链接 49 | let path = indexFileNode.path 50 | indexFiles.forEach(item=>{ 51 | path = path.replace(item, '') 52 | }) 53 | itemHtml = `${indexFileNode.title || indexFileNode.path}` 54 | } else { 55 | // treeNode 无首页,只显示 路径文本 56 | itemHtml = treeNode.path 57 | } 58 | return `` 59 | } -------------------------------------------------------------------------------- /src/markdown/provider/__test_files__/index.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const fs = require('fs') 3 | const FileNode = require('../FileNode') 4 | const Provider = require('../Provider') 5 | 6 | // 返回 FileNodes 的键 7 | Provider.prototype.fileNodeKeys = function () { 8 | const treeFlags = Provider.prototype.treeKey('') 9 | return Object.keys(this.nodes) 10 | .filter(filePath => filePath.indexOf(treeFlags) == -1) 11 | .sort((a, b) => a.localeCompare(b)) 12 | } 13 | 14 | // 获取 FileNodes 实例 15 | Provider.prototype.fileNodes = function () { 16 | return this.fileNodeKeys().map(filePath => this.nodes[filePath]) 17 | } 18 | 19 | // 获取当前 Nodes 节点中的文件 的功能 20 | Provider.prototype.getNodeFiles = function () { 21 | return this.fileNodeKeys().join(', ') 22 | } 23 | 24 | // 解析路径 25 | const resolvePath = (filePath) => { 26 | return path.join(__dirname, filePath) 27 | } 28 | 29 | // 读取测试文件内容 30 | const openFileAsText = (filePath) => { 31 | return fs.readFileSync(resolvePath(filePath), { 32 | encoding: 'utf-8' 33 | }).replace(/\r\n/g, '\n') 34 | } 35 | 36 | // 测试文件 37 | const testFile = 'test.md' 38 | 39 | // 测试文件内容 40 | const testBody = '# Test' 41 | 42 | // 异步写入文件 43 | const updateTestFile = (text) => { 44 | return new Promise(resolve => { 45 | setTimeout(() => { 46 | resolve(updateTestFileSync(text)) 47 | }, 1) 48 | }) 49 | } 50 | 51 | // 同步写入文件 52 | const updateTestFileSync = (text) => { 53 | fs.writeFileSync(resolvePath(testFile), text || testBody) 54 | } 55 | 56 | // 恢复test.md内容,防止之前被测试脚本改成其他内容 57 | updateTestFileSync() 58 | 59 | // 公共参数 60 | const options = { 61 | resolvePath 62 | } 63 | 64 | // 中间件结束 65 | const next = () => { } 66 | 67 | // 导出测试用的公共数据,欢迎补充 68 | module.exports = { 69 | FileNode, 70 | Provider, 71 | options, 72 | resolvePath: options.resolvePath, 73 | testFile, 74 | testBody, 75 | openFileAsText, 76 | updateTestFile, 77 | next 78 | } -------------------------------------------------------------------------------- /src/ssr/index.js: -------------------------------------------------------------------------------- 1 | const Vue = require('vue') // vue@next 2 | const serverRenderer = require('@vue/server-renderer') 3 | const compilerSsr = require('@vue/compiler-ssr') 4 | const compilerSfc = require('@vue/compiler-sfc') 5 | const fs = require('fs') 6 | const path = require('path') 7 | 8 | const createRender = path => { 9 | 10 | const { descriptor } = compilerSfc.parse(fs.readFileSync(path, 'utf-8')) 11 | const render = compilerSsr.compile(descriptor.template.content).code 12 | return async (data) => { 13 | const app = Vue.createApp({ 14 | ssrRender: new Function('require', render)(require), // 写法二 15 | data: () => data 16 | }) 17 | return serverRenderer.renderToString(app) 18 | } 19 | } 20 | 21 | const renderMarkdown = async ({ reqFile, template, provider, options }) => { 22 | const skin = options.theme || '默认皮肤' 23 | const data = { 24 | menu: provider.toArray(fileNode => ({ 25 | path: fileNode.path, 26 | name: fileNode.title, 27 | prefix: fileNode.prefix || '' 28 | })), 29 | skinPath: '', //样式 30 | breadcrumb: null, //面包屑导航 31 | catalogs: [], //目录 32 | markdown: '' //正文 33 | } 34 | await provider.getItem(reqFile, fileNode => { 35 | if (!fileNode) { 36 | data.markdown = `

文件不存在: ${reqFile}

` 37 | } else { 38 | // 面包屑也可以通过 fileNode.breadcrumb.toHtml(treeNode, indexFileNode) 方式获取更加复杂的样式 39 | data.breadcrumb = fileNode.breadcrumb.html 40 | data.catalogs = fileNode.catalogs 41 | data.skinPath = fileNode.getTheme(skin).path // 样式代码 42 | data.markdown = [ 43 | fileNode.html, // 解析后的 html 44 | ].join('') 45 | } 46 | }); 47 | return await createRender(template)(data) 48 | } 49 | 50 | module.exports = { 51 | createRender, 52 | renderMarkdown, 53 | template: path.resolve(__dirname, '../template/App.vue') 54 | } -------------------------------------------------------------------------------- /bin/index.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | const program = require("commander"); 3 | program.version(require("../package").version); 4 | const path = require("path"); 5 | const { promisify } = require("util"); 6 | const figlet = promisify(require("figlet")); 7 | const clear = require("clear"); 8 | const { startDev } = require("../src/index"); 9 | const open = require("open"); 10 | const chalkAnimation = require("chalk-animation"); 11 | const build = require("./build"); 12 | 13 | program 14 | .command("start") 15 | .description("启动本地开发环境") 16 | .usage("[options] root-path") 17 | .option("-t, --theme [theme]", "Markdown样式,可选 default、techo") 18 | .option("-p, --port [port]", "监听端口") 19 | .action(async (options) => { 20 | clear(); 21 | console.log(""); 22 | console.log(""); 23 | 24 | const data = await figlet("Smart Press"); 25 | chalkAnimation.rainbow(data).start(); 26 | 27 | // 启动开发服务器 28 | startDev( 29 | { 30 | theme: options.theme || "default", 31 | port: options.port || 3000, 32 | root: path.resolve(options.args.length > 0 ? options.args[0] : "."), 33 | }, 34 | (port) => { 35 | // 打开浏览器 36 | open(`http://localhost:${port}`); 37 | } 38 | ); 39 | }); 40 | 41 | program 42 | .command("build") 43 | .description("编译页面文件(生成html)") 44 | .option("-t, --theme [theme]", "Markdown样式,可选 default、techo") 45 | .option("-o, --output [output]", "输出目录") 46 | .action(async (options) => { 47 | console.log(""); 48 | 49 | // 生成静态 50 | await build({ 51 | theme: options.theme || "default", 52 | root: path.resolve(options.args.length > 0 ? options.args[0] : "."), 53 | output: path.resolve(options.output || "dist"), 54 | }); 55 | 56 | // TODO: 强行退出程序 + 使用watch 会造成命令行出现莫名其妙的字符 57 | // process.exit(0) 58 | }); 59 | 60 | program 61 | .command("publish") 62 | .description("发布文件到服务器") 63 | .action(async () => { 64 | console.log("发布文件到服务器"); 65 | }); 66 | 67 | program.parse(process.argv); 68 | -------------------------------------------------------------------------------- /src/markdown/title/__test__/index.spec.js: -------------------------------------------------------------------------------- 1 | const { 2 | Provider, 3 | FileNode, 4 | resolvePath, 5 | testFile, 6 | options, 7 | next, 8 | updateTestFile 9 | } = require('../../provider/__test_files__') 10 | const titleMiddleware = require('../index') 11 | 12 | it('单文件 直接调用测试', async () => { 13 | const fileNode = new FileNode(testFile, options) 14 | await titleMiddleware({ 15 | fileNode 16 | }, next) 17 | expect(fileNode.title) 18 | .toBe('Test') 19 | }) 20 | 21 | it('单文件 无标题测试', async () => { 22 | const fileNode = new FileNode('abc/123/test.md', options) 23 | await titleMiddleware({ 24 | fileNode 25 | }, next) 26 | expect(fileNode.title) 27 | .toBe('abc/123/test.md') 28 | }) 29 | 30 | it('注册中间件 方式测试', async () => { 31 | const provider = new Provider() 32 | const testFiles = [testFile] 33 | provider.resolvePath = resolvePath 34 | 35 | provider.useMiddleware(titleMiddleware) 36 | 37 | // 初始化 38 | await provider.patch(testFiles) 39 | expect(provider 40 | .fileNodes() 41 | .map(item => item.title) 42 | .join(', ') 43 | ).toBe("Test") 44 | 45 | // 增加文件 46 | testFiles.push('abc/123/README.md') // 有标题文件 47 | testFiles.push('abc/123/test.md') // 无标题文件 48 | await provider.patch(testFiles) 49 | expect(provider 50 | .fileNodes() 51 | .map(item => item.title) 52 | .join(', ') 53 | ).toBe("ABC_123_README, abc/123/test.md, Test") 54 | 55 | // 删除文件 56 | testFiles.pop() 57 | await provider.patch(testFiles) 58 | expect(provider 59 | .fileNodes() 60 | .map(item => item.title) 61 | .join(', ') 62 | ).toBe("ABC_123_README, Test") 63 | 64 | // 文件修改 65 | await updateTestFile('# Hello World\nFoo Bar!') 66 | await provider.patch(testFiles) 67 | expect(provider 68 | .fileNodes() 69 | .map(item => item.title) 70 | .join(', ') 71 | ).toBe("ABC_123_README, Hello World") 72 | 73 | // 文件复原 74 | await updateTestFile() 75 | }) 76 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 简体中文| [English](./README.en-US.md) 2 | # Smart-Press 3 | - 快速高效的 Markdown 网站制作工具 4 | - 基于 Vue3.0 SSR 技术 5 | 6 | ## 钉钉交流群 7 | 8 | 9 | ## 起步 10 | 11 | Manually 12 | ```bash 13 | # install 14 | npm install smarty-press -g 15 | 16 | # start to writing 17 | spress start 18 | 19 | # build to html 20 | spress build 21 | 22 | ``` 23 | 24 | ## 使用 25 | 26 | ```bash 27 | # 在项目文件夹中新建主页文件README.md,其它的md文件会当作它的次级目录,内容的编译使用markdown语法,并且是实时的 28 | 29 | # 生成网页的目录数量与md文件的数量相对应,有几个目录就有几个md文件 30 | 31 | # 目录的名称是对应文件的一级标题 32 | ``` 33 | 34 | ## 开发 35 | 36 | ```js 37 | # clone first 38 | npm link 39 | cd demo 40 | spress start 41 | ``` 42 | 43 | ```bash 44 | yarn add vue@3.0.0 45 | 46 | ``` 47 | 48 | ### 插件开发 49 | 50 | 请[参阅插件开发文档。](https://github.com/su37josephxia/smarty-press/tree/master/src/markdown/provider/__test_files__/) 51 | 52 | ## 开发功能列表 53 | ### 开发完成 54 | - [x] 展示DEMO 55 | 56 | ### 待开发 57 | - [ ] 菜单自由配置功能 58 | 59 | 60 | # 主题库 61 | 62 | ## MarkDown 63 | 64 | http://zhongce.sina.com.cn/article/view/18867 65 | 66 | ## 菜单模板 67 | 68 | * 默认:采用 AdminLTE 69 | * 开发中: 70 | * 菜单自由配置功能 71 | 72 | ## 文档 73 | 74 | ### 使用 75 | * Step. 1 新建并进入文件夹 76 | ```bash 77 | mkdir hello-smarty-press && cd hello-smarty-press 78 | ``` 79 | 80 | * Step. 2 使用你喜欢的包管理器进行初始化 81 | ```bash 82 | yarn init 83 | ``` 84 | 85 | * Step. 3 本地安装`smarty-press` (**如果已经全局安装过了,可以跳过这一步**) 86 | ```bash 87 | yarn add --dev smarty-press 88 | ``` 89 | 90 | * Step. 4 创建`README.md`文件 91 | ```bash 92 | touch README.md 93 | ``` 94 | 95 | * Step. 5 写入文档内容 96 | ```markdown 97 | # SmartyPress 入门 98 | ## 安装 99 | 100 | ## 使用 101 | ``` 102 | 103 | * Step. 6 在`package.json`中添加脚本 (**如果是全局安装,可以跳过这一步**) 104 | ```json 105 | { 106 | "scripts": { 107 | "start": "spress start", 108 | "build": "spress build" 109 | } 110 | } 111 | ``` 112 | 113 | * Step. 7 启动本地开发服务 114 | ```bash 115 | yarn start 116 | ``` 117 | **如果是全局安装,则使用以下命令** 118 | ```bash 119 | spress start 120 | ``` 121 | 122 | * Step. 8 构建文档 123 | ```bash 124 | yarn build 125 | ``` 126 | **如果是全局安装,则使用以下命令** 127 | ```bash 128 | spress build 129 | ``` 130 | -------------------------------------------------------------------------------- /README.en-US.md: -------------------------------------------------------------------------------- 1 | English | [简体中文](./README.md) 2 | # Smart-Press 3 | - Fast and efficient Markdown website making tool 4 | - Based on Vue3.0 SSR technology 5 | ## Weixin 6 | 7 | ![qrcode](assets/qrcode-2216750.JPG) 8 | 9 | 10 | ## Getting Started 11 | Manually 12 | ```bash 13 | # install 14 | npm install smarty-press -g 15 | 16 | # start to writing 17 | spress start 18 | 19 | # build to html 20 | spress build 21 | 22 | ``` 23 | 24 | 25 | ## Development 26 | 27 | ```js 28 | # clone first 29 | npm link 30 | cd demo 31 | spress start 32 | ``` 33 | 34 | ```bash 35 | yarn add vue@3.0.0 36 | 37 | ``` 38 | ### Plugin Development 39 | see[dedicated section in docs.](https://github.com/su37josephxia/smarty-press/tree/master/src/markdown/provider/__test_files__/) 40 | 41 | # Subject library 42 | 43 | ## MarkDown 44 | 45 | http://zhongce.sina.com.cn/article/view/18867 46 | 47 | ## The menu template 48 | 49 | * default: AdminLTE 50 | * todo: 51 | * Menu free configuration function 52 | 53 | ## Documentation 54 | 55 | ### Usage 56 | * Step. 1 Create and change into a new directory. 57 | ```bash 58 | mkdir hello-smarty-press && cd hello-smarty-press 59 | ``` 60 | 61 | * Step. 2 Initialize with your preferred package manager. 62 | ```bash 63 | yarn init 64 | ``` 65 | 66 | * Step. 3 Install `smarty-press` locally (**if you already install by global, can skip this step**) 67 | ```bash 68 | yarn add --dev smarty-press 69 | ``` 70 | 71 | * Step. 4 create `README.md` file 72 | ```bash 73 | touch README.md 74 | ``` 75 | 76 | * Step. 5 write content to `README.md` 77 | ```markdown 78 | # How to use SmartyPress 79 | ## Install 80 | 81 | ## Usage 82 | ``` 83 | 84 | * Step. 6 Add some scripts to package.json (**if install by global,you can skip this step**) 85 | ```json 86 | { 87 | "scripts": { 88 | "start": "spress start", 89 | "build": "spress build" 90 | } 91 | } 92 | ``` 93 | 94 | * Step. 7 start local server 95 | ```bash 96 | yarn start 97 | ``` 98 | **if install by global,you should use the following command** 99 | ```bash 100 | spress start 101 | ``` 102 | 103 | * Step. 8 building The Docs 104 | ```bash 105 | yarn build 106 | ``` 107 | **if install by global,you should use the following command** 108 | ```bash 109 | spress build 110 | ``` 111 | -------------------------------------------------------------------------------- /src/ssr/list.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | module.exports = { 4 | template: `
5 |
6 |
    7 |
  • kkb
  • 8 |
  • kkb
  • 9 |
  • kkb
  • 10 |
  • kkb
  • 11 |
  • kkb
  • 12 |
  • kkb
  • 13 |
  • {{n}}--{{todo}}
  • 14 |
15 |
16 |
`, 17 | data(){ 18 | return { 19 | todos:['吃饭','睡觉'] 20 | } 21 | } 22 | } 23 | 24 | 25 | // const self = (global || root) 26 | 27 | // self.performance = { 28 | // now: function () { 29 | // var hrtime = process.hrtime() 30 | // return ((hrtime[0] * 1000000 + hrtime[1] / 1000) / 1000) 31 | // } 32 | // } 33 | 34 | // function generateGrid (rowCount, columnCount) { 35 | // var grid = [] 36 | 37 | // for (var r = 0; r < rowCount; r++) { 38 | // var row = { id: r, items: [] } 39 | // for (var c = 0; c < columnCount; c++) { 40 | // row.items.push({ id: (r + '-' + c) }) 41 | // } 42 | // grid.push(row) 43 | // } 44 | 45 | // return grid 46 | // } 47 | 48 | // const gridData = generateGrid(100, 5) 49 | 50 | // module.exports = { 51 | // template: '

{{ Math.random() }}

', 52 | // components: { 53 | // myTable: { 54 | // data: function () { 55 | // return { 56 | // grid: gridData 57 | // } 58 | // }, 59 | // // template: '
123{{ item.id }}
', 60 | // template: '
', 61 | // components: { 62 | // row: { 63 | // props: ['row'], 64 | // template: '{{ Math.random() }}', 65 | // components: { 66 | // column: { 67 | // template: '' + 68 | // // 25 plain elements for each cell 69 | // '' + 74 | // '' 75 | // } 76 | // } 77 | // } 78 | // } 79 | // } 80 | // } 81 | // } 82 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | const { createServer } = require("./devserver"); 2 | const { createMiddleware } = require("./menu"); 3 | const path = require("path"); 4 | const ssr = require("./ssr"); 5 | const fs = require("fs"); 6 | const provider = require("./markdown"); 7 | const availablePort = require("./util/available-port"); 8 | 9 | module.exports.startDev = async ( 10 | options = { 11 | theme: "default", 12 | root: path.resolve("."), 13 | port: 3000, 14 | }, 15 | callback = (port) => {} 16 | ) => { 17 | const app = createServer({ 18 | watchFolder: options.root, 19 | }); 20 | 21 | // 获取文件目录 22 | provider.resolvePath = (filePath) => 23 | path.resolve(options.root, "./" + filePath); 24 | 25 | app.use(createMiddleware(options)); 26 | 27 | // 静态服务 28 | // app.use(KoaStatic('./assets')) 29 | app.use(async (ctx, next) => { 30 | if (ctx.url.indexOf("/assets") > -1) { 31 | try { 32 | // 判断是否存在此文件 33 | let assetsPath = path.resolve(__dirname, "./" + ctx.url) 34 | if(!fs.existsSync(assetsPath)) { 35 | assetsPath = path.resolve(options.root, "./" + ctx.url) 36 | } 37 | const buffer = fs.readFileSync(assetsPath); 38 | 39 | ctx.type = path.extname(ctx.url).slice(1); 40 | ctx.body = buffer; 41 | } catch (e) { 42 | ctx.body = ""; 43 | } 44 | } else { 45 | await next(); 46 | } 47 | }); 48 | 49 | app.use(async (ctx, next) => { 50 | // 忽略favicon 51 | if (ctx.url === "/favicon.ico") { 52 | ctx.body = ""; 53 | return; 54 | } 55 | await next(); 56 | }); 57 | 58 | app.use(async (ctx, next) => { 59 | await provider.patch(ctx.menu); 60 | const { 61 | request: { url, query }, 62 | } = ctx; 63 | const reqPath = url.split("?")[0]; 64 | 65 | // 判断是否存在自定义模板 66 | let template = path.resolve(options.root, "./template/App.vue"); 67 | if (fs.existsSync(template)) { 68 | } else { 69 | template = ssr.template; 70 | } 71 | // console.log('使用自定义模板:'+template) 72 | 73 | // markdown文件位置 74 | const reqFile = 75 | path.extname(reqPath) === "" ? reqPath + "/README.md" : reqPath; 76 | 77 | ctx.body = await ssr.renderMarkdown({ 78 | reqFile, 79 | provider, 80 | template, 81 | options, 82 | }); 83 | 84 | await next(); 85 | }); 86 | 87 | // 端口号被占用时,获取一个可用的端口号 88 | const port = await availablePort(options.port || 3000, callback); 89 | 90 | app.start(port, () => { 91 | console.log("app start at " + port); 92 | }); 93 | }; 94 | -------------------------------------------------------------------------------- /src/markdown/breadcrumb/__test__/index.spec.js: -------------------------------------------------------------------------------- 1 | const { 2 | Provider, 3 | resolvePath, 4 | testFile, 5 | updateTestFile 6 | } = require('../../provider/__test_files__') 7 | 8 | const titleMiddleware = require('../../title') 9 | const breadcrumbMiddleware = require('../index') 10 | 11 | // 必须挂到 provider树,所以没法做但文件测试 12 | 13 | it('注册中间件 方式测试', async () => { 14 | const provider = new Provider() 15 | const testFiles = ['README.md', testFile] 16 | provider.resolvePath = resolvePath 17 | 18 | provider.useMiddleware(titleMiddleware) 19 | provider.useMiddleware(breadcrumbMiddleware) 20 | 21 | 22 | // 预测结果: 23 | const warpper = html => `` 24 | const breadcrumbLinkResult = {} 25 | breadcrumbLinkResult.readme = warpper('开发 SMARTY_PRESS 测试文件') 26 | breadcrumbLinkResult.test = warpper('Test') 27 | breadcrumbLinkResult.xyzRoot = warpper('xyz') // 不存在 README.md,使用 treeNode 的路径 28 | breadcrumbLinkResult.xyzAd = warpper('广告页面') 29 | 30 | // 初始化 31 | await provider.patch(testFiles) 32 | expect(provider 33 | .fileNodes() 34 | .map(item => item.breadcrumb.html) 35 | .join(', ') 36 | ).toBe([ 37 | breadcrumbLinkResult.readme, 38 | `${breadcrumbLinkResult.readme}${breadcrumbLinkResult.test}` 39 | ].join(', ')) 40 | 41 | // 增加文件 42 | testFiles.push('xyz/ad.md') // 有标题文件 43 | await provider.patch(testFiles) 44 | expect(provider 45 | .fileNodes() 46 | .map(item => item.breadcrumb.html) 47 | .join(', ') 48 | ).toBe([ 49 | breadcrumbLinkResult.readme, 50 | `${breadcrumbLinkResult.readme}${breadcrumbLinkResult.test}`, 51 | `${breadcrumbLinkResult.readme}${breadcrumbLinkResult.xyzRoot}${breadcrumbLinkResult.xyzAd}` 52 | ].join(', ')) 53 | 54 | // 删除文件 55 | testFiles.pop() 56 | await provider.patch(testFiles) 57 | expect(provider 58 | .fileNodes() 59 | .map(item => item.breadcrumb.html) 60 | .join(', ') 61 | ).toBe([ 62 | breadcrumbLinkResult.readme, 63 | `${breadcrumbLinkResult.readme}${breadcrumbLinkResult.test}` 64 | ].join(', ')) 65 | 66 | // 文件修改 67 | await updateTestFile('# Hello World\nFoo Bar!') 68 | await provider.patch(testFiles) 69 | expect(provider 70 | .fileNodes() 71 | .map(item => item.breadcrumb.html) 72 | .join(', ') 73 | ).toBe([ 74 | breadcrumbLinkResult.readme, 75 | `${breadcrumbLinkResult.readme}${warpper('Hello World')}` 76 | ].join(', ')) 77 | 78 | // 文件复原 79 | await updateTestFile() 80 | }) 81 | -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | AdminLTE 3 | Dashboard 3 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 56 | 57 | 58 |
59 | 60 |
61 | 62 |
63 | 64 |
65 | Copyright © 2020-2080 67 | Smart Press. 69 | All rights reserved. 70 |
71 | Version 0.0.1 72 |
73 |
74 |
75 | 76 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /src/markdown/autoNumber/index.js: -------------------------------------------------------------------------------- 1 | module.exports = async ({ fileNode }, next) => { 2 | const { body, list } = analysisTitle(fileNode.body) 3 | fileNode.catalogs = list 4 | fileNode.body = body 5 | await next() 6 | } 7 | 8 | function analysisTitle(body) { 9 | const arr = body.split('\n') 10 | const isHeadingReg = /^#+\x20/ 11 | 12 | let numbers = {} 13 | let previousLevel = 1 14 | let contentsData = { 15 | list: [], 16 | last: null 17 | } 18 | 19 | const ret = arr.map((line, index, lines) => { 20 | if (!isHeadingReg.test(line)) { 21 | return line 22 | } 23 | 24 | let level = getHeadingLevel(line); 25 | if (level === 1) { 26 | return line 27 | } 28 | 29 | const diff = level - previousLevel; 30 | 31 | if (diff > 1) { 32 | level -= diff - 1 33 | } 34 | 35 | const levelKey = `h${level}` 36 | 37 | if (diff < 0) { 38 | Object.keys(numbers) 39 | .filter(key => Number(key.split('')[1]) > level) 40 | .forEach(key => { 41 | numbers[key] = 0 42 | }) 43 | } 44 | if (typeof numbers[levelKey] === 'undefined') { 45 | numbers[levelKey] = 0 46 | } 47 | 48 | numbers[levelKey] += 1 49 | 50 | const serialNumber = level === 2 51 | ? customChieseNumber(numbers[levelKey]) + '、' 52 | : numbers[levelKey] + '. ' 53 | line = line.replace(isHeadingReg, `${'#'.repeat(level)} ${serialNumber}`) 54 | 55 | if (level === 2) { 56 | contentsData.list.push(createCatalogNode(line)) 57 | contentsData.last = contentsData.list[contentsData.list.length - 1].children 58 | } else { 59 | contentsData.last.push(createCatalogNode(line)) 60 | } 61 | 62 | previousLevel = level 63 | 64 | return line 65 | }) 66 | 67 | return { 68 | list: contentsData.list, 69 | body: ret.join('\n') 70 | } 71 | } 72 | 73 | function customChieseNumber(number) { 74 | if (number > 999) { 75 | throw new Error('Numbers less than 1000 are supported') 76 | } 77 | const base = { 78 | 0: '零', 79 | 1: '一', 80 | 2: '二', 81 | 3: '三', 82 | 4: '四', 83 | 5: '五', 84 | 6: '六', 85 | 7: '七', 86 | 8: '八', 87 | 9: '九', 88 | } 89 | // 将数字转为数字字符串 90 | let numString = number.toString() 91 | 92 | // 解析10一下的序号 93 | if (number < 10) { 94 | switch (number.length) { 95 | case 2: 96 | return `零${base[Number(number)]}` 97 | case 3: 98 | return `千${base[Number(number)]}` 99 | default: 100 | return base[Number(number)] 101 | } 102 | } 103 | // 解析10到20的序号 104 | if (number >= 10 && number < 20) { 105 | return ('十' + base[numString[1]]).replace(/(.*)零$/, '$1') 106 | } 107 | // 解析20到100的序号 108 | if (number >= 20 && number < 100) { 109 | return numString 110 | .split('') 111 | .map(v => customChieseNumber(v)) 112 | .join('十') 113 | .replace(/(.*)零$/, '$1') 114 | } 115 | // 解析100到1000的序号 116 | if (number >= 100 && number < 1000) { 117 | let prefix = customChieseNumber(numString.substr(0, 1)) + '百' 118 | let suffix = 119 | numString.substr(1) == 0 120 | ? '' 121 | : numString.substr(1) >= 10 && numString.substr(1) < 20 122 | ? '一' + customChieseNumber(numString.substr(1)) 123 | : customChieseNumber(numString.substr(1)) 124 | return prefix + suffix 125 | } 126 | } 127 | 128 | function createCatalogNode(title) { 129 | return { 130 | title: title.replace(/#/g, '').trim(), 131 | children: [], 132 | hash: title 133 | } 134 | } 135 | 136 | function getHeadingLevel(line) { 137 | const head = line.split(' ') 138 | return head[0].length 139 | } -------------------------------------------------------------------------------- /src/markdown/marked/__tests__/index.spec.js: -------------------------------------------------------------------------------- 1 | const { 2 | Provider, 3 | FileNode, 4 | resolvePath, 5 | testFile, 6 | options, 7 | next, 8 | updateTestFile, 9 | testBody, 10 | openFileAsText 11 | } = require('../../provider/__test_files__') 12 | const marked = require('marked') 13 | const markedMiddleware = require('../index') 14 | 15 | it('测试 marked', () => { 16 | expect(marked(testBody).trim()).toBe('

Test

') 17 | }) 18 | 19 | it('markdown transfrer 单文件', async () => { 20 | const testHtml = marked(testBody) 21 | const fileNode = new FileNode(testFile, options) 22 | await markedMiddleware({ 23 | fileNode 24 | }, next) 25 | expect(fileNode.html).toBe(testHtml) 26 | }) 27 | 28 | it('markdown transfrer middleware', async () => { 29 | const testHtml = marked(testBody) 30 | const provider = new Provider() 31 | const testFiles = [testFile] 32 | provider.resolvePath = resolvePath 33 | 34 | provider.useMiddleware(markedMiddleware) 35 | 36 | // 初始化 37 | await provider.patch(testFiles) 38 | expect(provider 39 | .fileNodes() 40 | .map(item => item.html) 41 | .join(', ') 42 | ).toBe(testHtml) 43 | 44 | // 增加文件 45 | testFiles.push('abc/123/README.md') 46 | await provider.patch(testFiles) 47 | expect(provider 48 | .fileNodes() 49 | .map(item => item.html) 50 | .join('') 51 | ).toBe([ 52 | marked(openFileAsText('abc/123/README.md')), 53 | testHtml 54 | ].join('')) 55 | 56 | // 删除文件 57 | testFiles.pop() 58 | await provider.patch(testFiles) 59 | expect(provider 60 | .fileNodes() 61 | .map(item => item.html) 62 | .join(', ') 63 | ).toBe(testHtml) 64 | 65 | // 文件修改 66 | await updateTestFile('# Hello World\nFoo Bar!') 67 | await provider.patch(testFiles) 68 | expect(provider 69 | .fileNodes() 70 | .map(item => item.html) 71 | .join(', ') 72 | ).toBe(marked(openFileAsText(testFile))) 73 | 74 | // 文件复原 75 | await updateTestFile() 76 | 77 | }) 78 | 79 | it('测试 markdown,包含菜单', async () => { 80 | const testHtml = marked(testBody) 81 | const fileNode = new FileNode(testFile, options) 82 | 83 | //添加测试数据 84 | fileNode.catalogs = [ 85 | { 86 | title: '一、标题', 87 | hash: '## 一、标题', 88 | children: [{ 89 | title: '1. 二级标题', 90 | hash: '### 1. 二级标题', 91 | }, { 92 | title: '2. 二级标题', 93 | hash: '### 2. 二级标题', 94 | }] 95 | }, { 96 | title: '二、标题', 97 | hash: '## 二、标题', 98 | children: [{ 99 | title: '1. 二级标题', 100 | hash: '### 1. 二级标题', 101 | }, { 102 | title: '2. 不存在重名', 103 | hash: '### 2. 不存在重名', 104 | }] 105 | }, { 106 | title: '三、标题', 107 | hash: '## 三、标题', 108 | children: [{ 109 | title: '1. 二级标题', 110 | hash: '### 1. 二级标题', 111 | }] 112 | } 113 | ] 114 | 115 | await markedMiddleware({ 116 | fileNode 117 | }, next) 118 | 119 | // 顶级标题测试 120 | expect(fileNode.catalogs[0].hash).toBe('一、标题') 121 | expect(fileNode.catalogs[1].hash).toBe('二、标题') 122 | 123 | // 二级标题测试 124 | expect(fileNode.catalogs[0].children[0].hash).toBe('1-二级标题') 125 | expect(fileNode.catalogs[0].children[1].hash).toBe('2-二级标题') 126 | 127 | // 二级标题重名测试 128 | expect(fileNode.catalogs[1].children[0].hash).toBe('1-二级标题-1') 129 | expect(fileNode.catalogs[1].children[1].hash).toBe('2-不存在重名') 130 | expect(fileNode.catalogs[2].children[0].hash).toBe('1-二级标题-2') 131 | }) 132 | -------------------------------------------------------------------------------- /src/markdown/provider/__test_files__/README.md: -------------------------------------------------------------------------------- 1 | # 开发 SMARTY_PRESS 测试文件 2 | 3 | 如果涉及文件修改,请测试 test.md 4 | 5 | ## 关于文件 6 | 7 | ### 文件目录 8 | 9 | 请在 src/markdown/ 目录下新建文件夹,每个文件夹对应一个中间件 10 | 11 | ``` 12 | src 13 | |-- markdown/ Markdown文件相关 14 | | |-- index.js Provider定义文件,用于导出和注册中间件 15 | | |-- provider Markdown Provider 16 | | | |-- index.js 中间件在这里注册 17 | | | |-- __test_files__ 测试数据 18 | | | | |-- index.js 测试函数封装 19 | | | | |-- *.md、*/*.md 各种不规则的Markdown格式的文件测试 20 | | |-- title Title中间件 21 | | | |-- title Title中间件 22 | | | |-- __test__ jest测试文件 23 | 24 | ``` 25 | 26 | 27 | 28 | ## 插件开发说明 29 | 30 | ### 中间件代码 31 | 32 | 比如:获取标题中间件( src/markdown/title/index.js ),代码如下: 33 | 34 | ```javascript 35 | // src/markdown/title/index.js 36 | 37 | module.exports = async ({ fileNode }, next) => { 38 | const match = /^#\s?([^#\n\r]*)/.exec(fileNode.body) 39 | fileNode.title = match ? match[1].trim() : fileNode.path 40 | await next() 41 | } 42 | ``` 43 | 44 | ### 注册markdown中间件 45 | 46 | 在 src/markdown/index.js 中添加 中间件即可,比如:title中间件 47 | 48 | ```javascript 49 | // src/markdown/index.js 50 | module.exports = require('./provider')( 51 | require('./title'), // 解析标题 52 | ) 53 | ``` 54 | 55 | ### jest 测试文件 56 | 57 | 为了避免测试文件过多,md测试文件请使用 `../../provider__test_files__` 中的文件 58 | 59 | 如果符合条件的测试文件不存在,可以直接在 `../../provider__test_files__` 中创建 60 | 61 | 需要的方法直接从 `../../provider/__test_files__/index.js` 引入,比如 解析 文件路径 的resolvePath 62 | 63 | 测试文件必须至少包含 单文件调用测试、注册中间件 方式测试两种方式,其中注册中间件方式必须包含:初始化、增加文件、删除文件、文件修改等场景的测试代码 64 | 65 | 下面是一个简单的测试例子:获取标题中间件 `src/markdown/title/__test__/index.spec.js` 66 | 67 | ```javascript 68 | // src/markdown/title/__test__/index.spec.js 69 | 70 | const { 71 | Provider, 72 | FileNode, 73 | resolvePath, 74 | testFile, 75 | options, 76 | next, 77 | updateTestFile 78 | } = require('../../provider/__test_files__') 79 | const titleMiddleware = require('../index') 80 | 81 | it('单文件 直接调用测试', async () => { 82 | const fileNode = new FileNode(testFile, options) 83 | await titleMiddleware({ 84 | fileNode 85 | }, next) 86 | expect(fileNode.title) 87 | .toBe('Test') 88 | }) 89 | 90 | it('单文件 无标题测试', async () => { 91 | const fileNode = new FileNode('abc/123/test.md', options) 92 | await titleMiddleware({ 93 | fileNode 94 | }, next) 95 | expect(fileNode.title) 96 | .toBe('abc/123/test.md') 97 | }) 98 | 99 | it('注册中间件 方式测试', async () => { 100 | const provider = new Provider() 101 | const testFiles = [testFile] 102 | provider.resolvePath = resolvePath 103 | 104 | provider.useMiddleware(titleMiddleware) 105 | 106 | // 初始化 107 | await provider.patch(testFiles) 108 | expect(provider 109 | .fileNodes() 110 | .map(item => item.title) 111 | .join(', ') 112 | ).toBe("Test") 113 | 114 | // 增加文件 115 | testFiles.push('abc/123/README.md') // 有标题文件 116 | testFiles.push('abc/123/test.md') // 无标题文件 117 | await provider.patch(testFiles) 118 | expect(provider 119 | .fileNodes() 120 | .map(item => item.title) 121 | .join(', ') 122 | ).toBe("ABC_123_README, abc/123/test.md, Test") 123 | 124 | // 删除文件 125 | testFiles.pop() 126 | await provider.patch(testFiles) 127 | expect(provider 128 | .fileNodes() 129 | .map(item => item.title) 130 | .join(', ') 131 | ).toBe("ABC_123_README, Test") 132 | 133 | // 文件修改 134 | await updateTestFile('# Hello World\nFoo Bar!') 135 | await provider.patch(testFiles) 136 | expect(provider 137 | .fileNodes() 138 | .map(item => item.title) 139 | .join(', ') 140 | ).toBe("ABC_123_README, Hello World") 141 | 142 | // 文件复原 143 | await updateTestFile() 144 | }) 145 | 146 | 147 | ``` 148 | 149 | -------------------------------------------------------------------------------- /src/template/seed/dist/js/pages/dashboard3.js: -------------------------------------------------------------------------------- 1 | /* global Chart:false */ 2 | 3 | $(function () { 4 | 'use strict' 5 | 6 | var ticksStyle = { 7 | fontColor: '#495057', 8 | fontStyle: 'bold' 9 | } 10 | 11 | var mode = 'index' 12 | var intersect = true 13 | 14 | var $salesChart = $('#sales-chart') 15 | // eslint-disable-next-line no-unused-vars 16 | var salesChart = new Chart($salesChart, { 17 | type: 'bar', 18 | data: { 19 | labels: ['JUN', 'JUL', 'AUG', 'SEP', 'OCT', 'NOV', 'DEC'], 20 | datasets: [ 21 | { 22 | backgroundColor: '#007bff', 23 | borderColor: '#007bff', 24 | data: [1000, 2000, 3000, 2500, 2700, 2500, 3000] 25 | }, 26 | { 27 | backgroundColor: '#ced4da', 28 | borderColor: '#ced4da', 29 | data: [700, 1700, 2700, 2000, 1800, 1500, 2000] 30 | } 31 | ] 32 | }, 33 | options: { 34 | maintainAspectRatio: false, 35 | tooltips: { 36 | mode: mode, 37 | intersect: intersect 38 | }, 39 | hover: { 40 | mode: mode, 41 | intersect: intersect 42 | }, 43 | legend: { 44 | display: false 45 | }, 46 | scales: { 47 | yAxes: [{ 48 | // display: false, 49 | gridLines: { 50 | display: true, 51 | lineWidth: '4px', 52 | color: 'rgba(0, 0, 0, .2)', 53 | zeroLineColor: 'transparent' 54 | }, 55 | ticks: $.extend({ 56 | beginAtZero: true, 57 | 58 | // Include a dollar sign in the ticks 59 | callback: function (value) { 60 | if (value >= 1000) { 61 | value /= 1000 62 | value += 'k' 63 | } 64 | 65 | return '$' + value 66 | } 67 | }, ticksStyle) 68 | }], 69 | xAxes: [{ 70 | display: true, 71 | gridLines: { 72 | display: false 73 | }, 74 | ticks: ticksStyle 75 | }] 76 | } 77 | } 78 | }) 79 | 80 | var $visitorsChart = $('#visitors-chart') 81 | // eslint-disable-next-line no-unused-vars 82 | var visitorsChart = new Chart($visitorsChart, { 83 | data: { 84 | labels: ['18th', '20th', '22nd', '24th', '26th', '28th', '30th'], 85 | datasets: [{ 86 | type: 'line', 87 | data: [100, 120, 170, 167, 180, 177, 160], 88 | backgroundColor: 'transparent', 89 | borderColor: '#007bff', 90 | pointBorderColor: '#007bff', 91 | pointBackgroundColor: '#007bff', 92 | fill: false 93 | // pointHoverBackgroundColor: '#007bff', 94 | // pointHoverBorderColor : '#007bff' 95 | }, 96 | { 97 | type: 'line', 98 | data: [60, 80, 70, 67, 80, 77, 100], 99 | backgroundColor: 'tansparent', 100 | borderColor: '#ced4da', 101 | pointBorderColor: '#ced4da', 102 | pointBackgroundColor: '#ced4da', 103 | fill: false 104 | // pointHoverBackgroundColor: '#ced4da', 105 | // pointHoverBorderColor : '#ced4da' 106 | }] 107 | }, 108 | options: { 109 | maintainAspectRatio: false, 110 | tooltips: { 111 | mode: mode, 112 | intersect: intersect 113 | }, 114 | hover: { 115 | mode: mode, 116 | intersect: intersect 117 | }, 118 | legend: { 119 | display: false 120 | }, 121 | scales: { 122 | yAxes: [{ 123 | // display: false, 124 | gridLines: { 125 | display: true, 126 | lineWidth: '4px', 127 | color: 'rgba(0, 0, 0, .2)', 128 | zeroLineColor: 'transparent' 129 | }, 130 | ticks: $.extend({ 131 | beginAtZero: true, 132 | suggestedMax: 200 133 | }, ticksStyle) 134 | }], 135 | xAxes: [{ 136 | display: true, 137 | gridLines: { 138 | display: false 139 | }, 140 | ticks: ticksStyle 141 | }] 142 | } 143 | } 144 | }) 145 | }) 146 | -------------------------------------------------------------------------------- /src/template/seed/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | AdminLTE 3 | Dashboard 8 | 9 | 10 | 11 | 12 | 13 |
14 | 15 | 16 | 30 | 31 | 32 | 33 | 84 | 85 | 86 |
87 | 88 |
89 | 90 |
91 | 92 | 93 | 94 |
95 |
96 |
97 | 98 |
99 | 100 |
101 | Copyright © 2014-2020 AdminLTE.io. 102 | All rights reserved. 103 |
104 | Version 3.1.0-pre 105 |
106 |
107 | 108 | 109 | 112 | 113 |
114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | -------------------------------------------------------------------------------- /bin/build.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | const ssr = require("../src/ssr"); 3 | const fs = require("fs"); 4 | const { getFolder } = require("../src/menu"); 5 | const provider = require("../src/markdown"); 6 | const glob = require("glob"); 7 | 8 | // 替换 a href=**/README.md /index.html 9 | const replaceKeyword = (body) => { 10 | return body.replace(/(.*?<\/a>)/g, (math, $1, $2, $3) => { 11 | return `${$1}${$2 12 | .replace("/README.", "/index.") 13 | .replace(".md", ".html")}${$3}`; 14 | }); 15 | }; 16 | 17 | const makeFiles = async (provider, options) => { 18 | // 获取所有源码文件 19 | const files = []; 20 | Object.keys(provider.nodes).forEach((nodeName) => { 21 | const node = provider.nodes[nodeName]; 22 | if (node.isFileNode) { 23 | // 文件节点 24 | delete provider.nodes[node.path]; 25 | node.path = node.path 26 | .replace("README.", "index.") 27 | .replace(".md", ".html"); 28 | provider.nodes[node.path] = node; 29 | files.push(node.path); 30 | } else { 31 | // 目录节点,依次创建目录结构 32 | const folderPath = provider.distPath(node.path); 33 | !fs.existsSync(folderPath) && fs.mkdirSync(folderPath); 34 | } 35 | }); 36 | 37 | // 根据文件记录,依次生成静态 38 | let reqFile = files.shift(); 39 | 40 | while (reqFile) { 41 | // 判断是否存在自定义模板 42 | let template = path.resolve(options.root, "./template/App.vue"); 43 | if (fs.existsSync(template)) { 44 | } else { 45 | template = ssr.template; 46 | } 47 | console.log("使用自定义模板:" + template); 48 | 49 | const body = await ssr.renderMarkdown({ 50 | reqFile, 51 | provider, 52 | template, 53 | options, 54 | }); 55 | fs.writeFileSync( 56 | provider.distPath(reqFile), 57 | `${replaceKeyword(body)}`, 58 | { 59 | encoding: "utf-8", 60 | } 61 | ); 62 | console.log(` ${reqFile}`); 63 | reqFile = files.shift(); 64 | } 65 | }; 66 | 67 | /** 68 | * 目录拷贝 69 | * @param {*} from 源文件目录 70 | * @param {*} to 目标文件目录 71 | */ 72 | function copyDir(from, to) { 73 | // 目录不存在,则创建 74 | !fs.existsSync(to) && fs.mkdirSync(to); 75 | // 遍历拷贝 76 | fs.readdirSync(from).forEach((file) => { 77 | // 遍历目录 78 | const relativePath = `${from}/${file}`; 79 | const toFile = `${to + "/" + file}`; 80 | if (fs.lstatSync(relativePath).isDirectory()) { 81 | // 如果是目录,则继续遍历 82 | copyDir(relativePath, `${to + "/" + file}`); 83 | } else { 84 | // 复制资源文件 85 | console.log(` ${relativePath}`); 86 | fs.copyFileSync(relativePath, toFile); 87 | } 88 | }); 89 | } 90 | 91 | /** 92 | * Assets文件拷贝 93 | * @param {*} sourcePath source文件位置 94 | * @param {*} param1 95 | */ 96 | function copyAssets(sourcePath, { assetsPath, distPath, resolvePath }) { 97 | // 复制src文件到dist 98 | const from = assetsPath(sourcePath); 99 | const to = distPath(sourcePath); 100 | 101 | console.log("复制模板中的assets文件:"); 102 | copyDir(from, to); 103 | console.log("复制doc中的assets文件:"); 104 | // 复制markdown文件到dist 105 | glob.sync(`${resolvePath("./")}/**/assets/*`).forEach((item) => { 106 | // 计算copy目标 107 | const relative = path.relative(resolvePath("./"), item); 108 | const dist = path.resolve(distPath("./"), "./" + relative); 109 | const dirPath = path.dirname(dist); 110 | !fs.existsSync(dirPath) && fs.mkdirSync(dirPath); 111 | fs.copyFileSync(item, dist); 112 | }); 113 | } 114 | 115 | module.exports = async function ( 116 | options = { 117 | theme: "default", 118 | root: path.resolve("."), 119 | output: path.resolve("dist"), 120 | } 121 | ) { 122 | console.log( 123 | [ 124 | "生成静态文件:", 125 | `源码目录: ${options.root}`, 126 | `输出目录: ${options.output}`, 127 | ].join("\n ") 128 | ); 129 | 130 | // 分析源码 131 | 132 | provider.resolvePath = (filePath) => path.resolve(options.root, filePath); 133 | provider.distPath = (filePath) => path.resolve(options.output, filePath); 134 | provider.assetsPath = (filePath) => 135 | path.resolve(__dirname, "../src/", filePath); 136 | 137 | await provider.patch(await getFolder(options.root)); 138 | 139 | // 如果不存在输出目录,则创建 140 | !fs.existsSync(options.output) && fs.mkdirSync(options.output); 141 | 142 | // 复制 asssets 文件 143 | console.log("文件复制:"); 144 | 145 | console.log("代码模板文件copy"); 146 | 147 | // 复制源码仓库中的assets文件 148 | copyAssets("assets", provider); 149 | 150 | // 生成静态 151 | console.log("生成静态HTML文件:"); 152 | await makeFiles(provider, options); 153 | 154 | console.log("生成完毕"); 155 | }; 156 | -------------------------------------------------------------------------------- /src/markdown/provider/Provider.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const FileNode = require('./FileNode') 3 | const TreeNode = require('./TreeNode') 4 | 5 | class Provider { 6 | 7 | constructor() { 8 | this.root = new TreeNode('') // 从这里开始可以得到所有子节点 9 | this.nodes = { // 缓存下,方便 遍历执行 中间件 10 | [this.treeKey('')]: this.root 11 | } 12 | this.middlewares = [] 13 | this.resolvePath = (filePath) => path.join(__dirname, filePath) 14 | } 15 | 16 | toArray(formatNode, parentNode) { 17 | const datas = [] 18 | const _nodes = parentNode || this.root 19 | _nodes.children.forEach(childNode => { 20 | if (childNode.isFileNode) { 21 | datas.push(formatNode(childNode)) 22 | } else if (childNode.children && childNode.children.length > 0) { 23 | this.toArray(formatNode, childNode).forEach(item => datas.push(item)) 24 | } 25 | }) 26 | return datas 27 | } 28 | 29 | getItem(reqFile, formatNode) { 30 | const filePath = this.formatFilePath(reqFile) 31 | return formatNode(filePath in this.nodes ? this.nodes[filePath] : null) 32 | } 33 | 34 | formatFilePath(filePath) { 35 | return filePath.replace(/(\\\\*|\/\/*)/g, '\/').replace(/^\//, '') 36 | } 37 | 38 | // patch 39 | async patch(filePaths) { 40 | const treeFlags = this.treeKey('') 41 | const newFiles = {} 42 | filePaths.forEach(filePath => { 43 | newFiles[this.formatFilePath(filePath)] = null 44 | }) 45 | Object.keys(this.nodes) 46 | .filter(filePath => filePath.indexOf(treeFlags) == -1) 47 | .forEach(async (filePath) => { 48 | if (filePath in newFiles) { 49 | await this.updateFile(filePath) //文件存在,更新 50 | delete newFiles[filePath] 51 | } else { 52 | await this.removeFile(filePath) //如果新文件不存在,删除 53 | } 54 | }) 55 | Object.keys(newFiles).forEach(async (filePath) => { 56 | await this.addFile(filePath) 57 | }) 58 | } 59 | 60 | // 获取tree节点key 61 | // 1、不能和 FileName的path重名 62 | // 2、方便和 .md文件 区分 63 | // 3、不能影响排序 64 | treeKey(path) { 65 | return `${path}/?` 66 | } 67 | 68 | //获取父节点 69 | getParent(filePath) { 70 | const filePaths = this.formatFilePath(filePath).split(/\//) 71 | filePaths.pop() 72 | const parentPath = filePaths.join('/') 73 | if (!(this.treeKey(parentPath) in this.nodes)) { 74 | this.nodes[this.treeKey(parentPath)] = this.getParent(parentPath).addChild(new TreeNode(parentPath)) 75 | } 76 | return this.nodes[this.treeKey(parentPath)] 77 | } 78 | 79 | // 添加 文件 80 | //! 81 | // |- 82 | //! |- 83 | // | |- 84 | // | |- 85 | async addFile(_filePath) { 86 | const filePath = this.formatFilePath(_filePath) 87 | if (!(filePath in this.nodes)) { 88 | const fileNode = this.nodes[filePath] = new FileNode(filePath, { 89 | resolvePath: this.resolvePath 90 | }) 91 | this.getParent(filePath).addChild(fileNode) 92 | await this.applyMiddleware(fileNode, 'add') 93 | } 94 | } 95 | 96 | // 删除 vFile 97 | async removeFile(filePath) { 98 | if (filePath in this.nodes) { 99 | const fileNode = this.nodes[filePath] 100 | fileNode.parent.removeChild(fileNode) 101 | delete this.nodes[filePath] 102 | } 103 | } 104 | 105 | //更新 vFile 106 | async updateFile(filePath) { 107 | const fileNode = this.nodes[filePath] 108 | if (fileNode.hasChanged) { 109 | await this.applyMiddleware(fileNode, 'update') 110 | } 111 | } 112 | 113 | // 增加 中间件 114 | useMiddleware(middleware) { 115 | this.middlewares.push(middleware) 116 | } 117 | 118 | // 批量对 vFile 执行中间件 119 | async applyMiddleware(fileNode) { 120 | fileNode.init() 121 | const _middlewares = [...this.middlewares] 122 | const next = async () => { 123 | if (_middlewares.length > 0) { 124 | return await _middlewares.shift()({ 125 | provider: this, 126 | fileNode 127 | }, next) 128 | } 129 | } 130 | return await next() 131 | } 132 | } 133 | 134 | module.exports = Provider -------------------------------------------------------------------------------- /demo/template/App.vue: -------------------------------------------------------------------------------- 1 | 185 | 186 | -------------------------------------------------------------------------------- /src/template/App.vue: -------------------------------------------------------------------------------- 1 | 186 | 187 | -------------------------------------------------------------------------------- /src/template/seed/dist/js/pages/dashboard2.js: -------------------------------------------------------------------------------- 1 | /* global Chart:false */ 2 | 3 | $(function () { 4 | 'use strict' 5 | 6 | /* ChartJS 7 | * ------- 8 | * Here we will create a few charts using ChartJS 9 | */ 10 | 11 | //----------------------- 12 | // - MONTHLY SALES CHART - 13 | //----------------------- 14 | 15 | // Get context with jQuery - using jQuery's .get() method. 16 | var salesChartCanvas = $('#salesChart').get(0).getContext('2d') 17 | 18 | var salesChartData = { 19 | labels: ['January', 'February', 'March', 'April', 'May', 'June', 'July'], 20 | datasets: [ 21 | { 22 | label: 'Digital Goods', 23 | backgroundColor: 'rgba(60,141,188,0.9)', 24 | borderColor: 'rgba(60,141,188,0.8)', 25 | pointRadius: false, 26 | pointColor: '#3b8bba', 27 | pointStrokeColor: 'rgba(60,141,188,1)', 28 | pointHighlightFill: '#fff', 29 | pointHighlightStroke: 'rgba(60,141,188,1)', 30 | data: [28, 48, 40, 19, 86, 27, 90] 31 | }, 32 | { 33 | label: 'Electronics', 34 | backgroundColor: 'rgba(210, 214, 222, 1)', 35 | borderColor: 'rgba(210, 214, 222, 1)', 36 | pointRadius: false, 37 | pointColor: 'rgba(210, 214, 222, 1)', 38 | pointStrokeColor: '#c1c7d1', 39 | pointHighlightFill: '#fff', 40 | pointHighlightStroke: 'rgba(220,220,220,1)', 41 | data: [65, 59, 80, 81, 56, 55, 40] 42 | } 43 | ] 44 | } 45 | 46 | var salesChartOptions = { 47 | maintainAspectRatio: false, 48 | responsive: true, 49 | legend: { 50 | display: false 51 | }, 52 | scales: { 53 | xAxes: [{ 54 | gridLines: { 55 | display: false 56 | } 57 | }], 58 | yAxes: [{ 59 | gridLines: { 60 | display: false 61 | } 62 | }] 63 | } 64 | } 65 | 66 | // This will get the first returned node in the jQuery collection. 67 | // eslint-disable-next-line no-unused-vars 68 | var salesChart = new Chart(salesChartCanvas, { 69 | type: 'line', 70 | data: salesChartData, 71 | options: salesChartOptions 72 | } 73 | ) 74 | 75 | //--------------------------- 76 | // - END MONTHLY SALES CHART - 77 | //--------------------------- 78 | 79 | //------------- 80 | // - PIE CHART - 81 | //------------- 82 | // Get context with jQuery - using jQuery's .get() method. 83 | var pieChartCanvas = $('#pieChart').get(0).getContext('2d') 84 | var pieData = { 85 | labels: [ 86 | 'Chrome', 87 | 'IE', 88 | 'FireFox', 89 | 'Safari', 90 | 'Opera', 91 | 'Navigator' 92 | ], 93 | datasets: [ 94 | { 95 | data: [700, 500, 400, 600, 300, 100], 96 | backgroundColor: ['#f56954', '#00a65a', '#f39c12', '#00c0ef', '#3c8dbc', '#d2d6de'] 97 | } 98 | ] 99 | } 100 | var pieOptions = { 101 | legend: { 102 | display: false 103 | } 104 | } 105 | // Create pie or douhnut chart 106 | // You can switch between pie and douhnut using the method below. 107 | // eslint-disable-next-line no-unused-vars 108 | var pieChart = new Chart(pieChartCanvas, { 109 | type: 'doughnut', 110 | data: pieData, 111 | options: pieOptions 112 | }) 113 | 114 | //----------------- 115 | // - END PIE CHART - 116 | //----------------- 117 | 118 | /* jVector Maps 119 | * ------------ 120 | * Create a world map with markers 121 | */ 122 | $('#world-map-markers').mapael({ 123 | map: { 124 | name: 'usa_states', 125 | zoom: { 126 | enabled: true, 127 | maxLevel: 10 128 | } 129 | } 130 | }) 131 | 132 | // $('#world-map-markers').vectorMap({ 133 | // map : 'world_en', 134 | // normalizeFunction: 'polynomial', 135 | // hoverOpacity : 0.7, 136 | // hoverColor : false, 137 | // backgroundColor : 'transparent', 138 | // regionStyle : { 139 | // initial : { 140 | // fill : 'rgba(210, 214, 222, 1)', 141 | // 'fill-opacity' : 1, 142 | // stroke : 'none', 143 | // 'stroke-width' : 0, 144 | // 'stroke-opacity': 1 145 | // }, 146 | // hover : { 147 | // 'fill-opacity': 0.7, 148 | // cursor : 'pointer' 149 | // }, 150 | // selected : { 151 | // fill: 'yellow' 152 | // }, 153 | // selectedHover: {} 154 | // }, 155 | // markerStyle : { 156 | // initial: { 157 | // fill : '#00a65a', 158 | // stroke: '#111' 159 | // } 160 | // }, 161 | // markers : [ 162 | // { 163 | // latLng: [41.90, 12.45], 164 | // name : 'Vatican City' 165 | // }, 166 | // { 167 | // latLng: [43.73, 7.41], 168 | // name : 'Monaco' 169 | // }, 170 | // { 171 | // latLng: [-0.52, 166.93], 172 | // name : 'Nauru' 173 | // }, 174 | // { 175 | // latLng: [-8.51, 179.21], 176 | // name : 'Tuvalu' 177 | // }, 178 | // { 179 | // latLng: [43.93, 12.46], 180 | // name : 'San Marino' 181 | // }, 182 | // { 183 | // latLng: [47.14, 9.52], 184 | // name : 'Liechtenstein' 185 | // }, 186 | // { 187 | // latLng: [7.11, 171.06], 188 | // name : 'Marshall Islands' 189 | // }, 190 | // { 191 | // latLng: [17.3, -62.73], 192 | // name : 'Saint Kitts and Nevis' 193 | // }, 194 | // { 195 | // latLng: [3.2, 73.22], 196 | // name : 'Maldives' 197 | // }, 198 | // { 199 | // latLng: [35.88, 14.5], 200 | // name : 'Malta' 201 | // }, 202 | // { 203 | // latLng: [12.05, -61.75], 204 | // name : 'Grenada' 205 | // }, 206 | // { 207 | // latLng: [13.16, -61.23], 208 | // name : 'Saint Vincent and the Grenadines' 209 | // }, 210 | // { 211 | // latLng: [13.16, -59.55], 212 | // name : 'Barbados' 213 | // }, 214 | // { 215 | // latLng: [17.11, -61.85], 216 | // name : 'Antigua and Barbuda' 217 | // }, 218 | // { 219 | // latLng: [-4.61, 55.45], 220 | // name : 'Seychelles' 221 | // }, 222 | // { 223 | // latLng: [7.35, 134.46], 224 | // name : 'Palau' 225 | // }, 226 | // { 227 | // latLng: [42.5, 1.51], 228 | // name : 'Andorra' 229 | // }, 230 | // { 231 | // latLng: [14.01, -60.98], 232 | // name : 'Saint Lucia' 233 | // }, 234 | // { 235 | // latLng: [6.91, 158.18], 236 | // name : 'Federated States of Micronesia' 237 | // }, 238 | // { 239 | // latLng: [1.3, 103.8], 240 | // name : 'Singapore' 241 | // }, 242 | // { 243 | // latLng: [1.46, 173.03], 244 | // name : 'Kiribati' 245 | // }, 246 | // { 247 | // latLng: [-21.13, -175.2], 248 | // name : 'Tonga' 249 | // }, 250 | // { 251 | // latLng: [15.3, -61.38], 252 | // name : 'Dominica' 253 | // }, 254 | // { 255 | // latLng: [-20.2, 57.5], 256 | // name : 'Mauritius' 257 | // }, 258 | // { 259 | // latLng: [26.02, 50.55], 260 | // name : 'Bahrain' 261 | // }, 262 | // { 263 | // latLng: [0.33, 6.73], 264 | // name : 'São Tomé and Príncipe' 265 | // } 266 | // ] 267 | // }) 268 | }) 269 | -------------------------------------------------------------------------------- /src/template/seed/dist/js/pages/dashboard.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Author: Abdullah A Almsaeed 3 | * Date: 4 Jan 2014 4 | * Description: 5 | * This is a demo file used only for the main dashboard (index.html) 6 | **/ 7 | 8 | /* global moment:false, Chart:false, Sparkline:false */ 9 | 10 | $(function () { 11 | 'use strict' 12 | 13 | // Make the dashboard widgets sortable Using jquery UI 14 | $('.connectedSortable').sortable({ 15 | placeholder: 'sort-highlight', 16 | connectWith: '.connectedSortable', 17 | handle: '.card-header, .nav-tabs', 18 | forcePlaceholderSize: true, 19 | zIndex: 999999 20 | }) 21 | $('.connectedSortable .card-header').css('cursor', 'move') 22 | 23 | // jQuery UI sortable for the todo list 24 | $('.todo-list').sortable({ 25 | placeholder: 'sort-highlight', 26 | handle: '.handle', 27 | forcePlaceholderSize: true, 28 | zIndex: 999999 29 | }) 30 | 31 | // bootstrap WYSIHTML5 - text editor 32 | $('.textarea').summernote() 33 | 34 | $('.daterange').daterangepicker({ 35 | ranges: { 36 | Today: [moment(), moment()], 37 | Yesterday: [moment().subtract(1, 'days'), moment().subtract(1, 'days')], 38 | 'Last 7 Days': [moment().subtract(6, 'days'), moment()], 39 | 'Last 30 Days': [moment().subtract(29, 'days'), moment()], 40 | 'This Month': [moment().startOf('month'), moment().endOf('month')], 41 | 'Last Month': [moment().subtract(1, 'month').startOf('month'), moment().subtract(1, 'month').endOf('month')] 42 | }, 43 | startDate: moment().subtract(29, 'days'), 44 | endDate: moment() 45 | }, function (start, end) { 46 | // eslint-disable-next-line no-alert 47 | alert('You chose: ' + start.format('MMMM D, YYYY') + ' - ' + end.format('MMMM D, YYYY')) 48 | }) 49 | 50 | /* jQueryKnob */ 51 | $('.knob').knob() 52 | 53 | // jvectormap data 54 | var visitorsData = { 55 | US: 398, // USA 56 | SA: 400, // Saudi Arabia 57 | CA: 1000, // Canada 58 | DE: 500, // Germany 59 | FR: 760, // France 60 | CN: 300, // China 61 | AU: 700, // Australia 62 | BR: 600, // Brazil 63 | IN: 800, // India 64 | GB: 320, // Great Britain 65 | RU: 3000 // Russia 66 | } 67 | // World map by jvectormap 68 | $('#world-map').vectorMap({ 69 | map: 'usa_en', 70 | backgroundColor: 'transparent', 71 | regionStyle: { 72 | initial: { 73 | fill: 'rgba(255, 255, 255, 0.7)', 74 | 'fill-opacity': 1, 75 | stroke: 'rgba(0,0,0,.2)', 76 | 'stroke-width': 1, 77 | 'stroke-opacity': 1 78 | } 79 | }, 80 | series: { 81 | regions: [{ 82 | values: visitorsData, 83 | scale: ['#ffffff', '#0154ad'], 84 | normalizeFunction: 'polynomial' 85 | }] 86 | }, 87 | onRegionLabelShow: function (e, el, code) { 88 | if (typeof visitorsData[code] !== 'undefined') { 89 | el.html(el.html() + ': ' + visitorsData[code] + ' new visitors') 90 | } 91 | } 92 | }) 93 | 94 | // Sparkline charts 95 | var sparkline1 = new Sparkline($('#sparkline-1')[0], { width: 80, height: 50, lineColor: '#92c1dc', endColor: '#ebf4f9' }) 96 | var sparkline2 = new Sparkline($('#sparkline-2')[0], { width: 80, height: 50, lineColor: '#92c1dc', endColor: '#ebf4f9' }) 97 | var sparkline3 = new Sparkline($('#sparkline-3')[0], { width: 80, height: 50, lineColor: '#92c1dc', endColor: '#ebf4f9' }) 98 | 99 | sparkline1.draw([1000, 1200, 920, 927, 931, 1027, 819, 930, 1021]) 100 | sparkline2.draw([515, 519, 520, 522, 652, 810, 370, 627, 319, 630, 921]) 101 | sparkline3.draw([15, 19, 20, 22, 33, 27, 31, 27, 19, 30, 21]) 102 | 103 | // The Calender 104 | $('#calendar').datetimepicker({ 105 | format: 'L', 106 | inline: true 107 | }) 108 | 109 | // SLIMSCROLL FOR CHAT WIDGET 110 | $('#chat-box').overlayScrollbars({ 111 | height: '250px' 112 | }) 113 | 114 | /* Chart.js Charts */ 115 | // Sales chart 116 | var salesChartCanvas = document.getElementById('revenue-chart-canvas').getContext('2d') 117 | // $('#revenue-chart').get(0).getContext('2d'); 118 | 119 | var salesChartData = { 120 | labels: ['January', 'February', 'March', 'April', 'May', 'June', 'July'], 121 | datasets: [ 122 | { 123 | label: 'Digital Goods', 124 | backgroundColor: 'rgba(60,141,188,0.9)', 125 | borderColor: 'rgba(60,141,188,0.8)', 126 | pointRadius: false, 127 | pointColor: '#3b8bba', 128 | pointStrokeColor: 'rgba(60,141,188,1)', 129 | pointHighlightFill: '#fff', 130 | pointHighlightStroke: 'rgba(60,141,188,1)', 131 | data: [28, 48, 40, 19, 86, 27, 90] 132 | }, 133 | { 134 | label: 'Electronics', 135 | backgroundColor: 'rgba(210, 214, 222, 1)', 136 | borderColor: 'rgba(210, 214, 222, 1)', 137 | pointRadius: false, 138 | pointColor: 'rgba(210, 214, 222, 1)', 139 | pointStrokeColor: '#c1c7d1', 140 | pointHighlightFill: '#fff', 141 | pointHighlightStroke: 'rgba(220,220,220,1)', 142 | data: [65, 59, 80, 81, 56, 55, 40] 143 | } 144 | ] 145 | } 146 | 147 | var salesChartOptions = { 148 | maintainAspectRatio: false, 149 | responsive: true, 150 | legend: { 151 | display: false 152 | }, 153 | scales: { 154 | xAxes: [{ 155 | gridLines: { 156 | display: false 157 | } 158 | }], 159 | yAxes: [{ 160 | gridLines: { 161 | display: false 162 | } 163 | }] 164 | } 165 | } 166 | 167 | // This will get the first returned node in the jQuery collection. 168 | // eslint-disable-next-line no-unused-vars 169 | var salesChart = new Chart(salesChartCanvas, { 170 | type: 'line', 171 | data: salesChartData, 172 | options: salesChartOptions 173 | }) 174 | 175 | // Donut Chart 176 | var pieChartCanvas = $('#sales-chart-canvas').get(0).getContext('2d') 177 | var pieData = { 178 | labels: [ 179 | 'Instore Sales', 180 | 'Download Sales', 181 | 'Mail-Order Sales' 182 | ], 183 | datasets: [ 184 | { 185 | data: [30, 12, 20], 186 | backgroundColor: ['#f56954', '#00a65a', '#f39c12'] 187 | } 188 | ] 189 | } 190 | var pieOptions = { 191 | legend: { 192 | display: false 193 | }, 194 | maintainAspectRatio: false, 195 | responsive: true 196 | } 197 | // Create pie or douhnut chart 198 | // You can switch between pie and douhnut using the method below. 199 | // eslint-disable-next-line no-unused-vars 200 | var pieChart = new Chart(pieChartCanvas, { 201 | type: 'doughnut', 202 | data: pieData, 203 | options: pieOptions 204 | }) 205 | 206 | // Sales graph chart 207 | var salesGraphChartCanvas = $('#line-chart').get(0).getContext('2d') 208 | // $('#revenue-chart').get(0).getContext('2d'); 209 | 210 | var salesGraphChartData = { 211 | labels: ['2011 Q1', '2011 Q2', '2011 Q3', '2011 Q4', '2012 Q1', '2012 Q2', '2012 Q3', '2012 Q4', '2013 Q1', '2013 Q2'], 212 | datasets: [ 213 | { 214 | label: 'Digital Goods', 215 | fill: false, 216 | borderWidth: 2, 217 | lineTension: 0, 218 | spanGaps: true, 219 | borderColor: '#efefef', 220 | pointRadius: 3, 221 | pointHoverRadius: 7, 222 | pointColor: '#efefef', 223 | pointBackgroundColor: '#efefef', 224 | data: [2666, 2778, 4912, 3767, 6810, 5670, 4820, 15073, 10687, 8432] 225 | } 226 | ] 227 | } 228 | 229 | var salesGraphChartOptions = { 230 | maintainAspectRatio: false, 231 | responsive: true, 232 | legend: { 233 | display: false 234 | }, 235 | scales: { 236 | xAxes: [{ 237 | ticks: { 238 | fontColor: '#efefef' 239 | }, 240 | gridLines: { 241 | display: false, 242 | color: '#efefef', 243 | drawBorder: false 244 | } 245 | }], 246 | yAxes: [{ 247 | ticks: { 248 | stepSize: 5000, 249 | fontColor: '#efefef' 250 | }, 251 | gridLines: { 252 | display: true, 253 | color: '#efefef', 254 | drawBorder: false 255 | } 256 | }] 257 | } 258 | } 259 | 260 | // This will get the first returned node in the jQuery collection. 261 | // eslint-disable-next-line no-unused-vars 262 | var salesGraphChart = new Chart(salesGraphChartCanvas, { 263 | type: 'line', 264 | data: salesGraphChartData, 265 | options: salesGraphChartOptions 266 | }) 267 | }) 268 | -------------------------------------------------------------------------------- /src/markdown/provider/__test__/index.spec.js: -------------------------------------------------------------------------------- 1 | const { 2 | FileNode, 3 | Provider, 4 | options, 5 | resolvePath, 6 | testFile, 7 | testBody, 8 | openFileAsText, 9 | updateTestFile 10 | } = require('../../provider/__test_files__') 11 | 12 | it('FileNode 路径测试', () => { 13 | expect(new FileNode(testFile, options).realPath) 14 | .toBe(resolvePath(testFile)) 15 | }) 16 | 17 | it('FileNode 读取文件内容', () => { 18 | expect(new FileNode(testFile, options).body).toBe(testBody) 19 | }) 20 | 21 | it('FileNode 文件修改测试', async () => { 22 | 23 | const vFile = new FileNode(testFile, options) 24 | const newBody = '# Foobar' 25 | 26 | // 修改前 27 | expect(vFile.hasChanged).toBe(false) 28 | expect(vFile.body).toBe(testBody) 29 | 30 | // 修改测试 31 | await updateTestFile(newBody) 32 | expect(vFile.body).toBe(testBody) // 修改后,因为没有判断 hasChange,所以不会重新读取文件 33 | expect(vFile.hasChanged).toBe(true) // 监测到更新 34 | expect(vFile.body).toBe(newBody) // 监测到更新 ,会自动读取文件内容 35 | expect(vFile.hasChanged).toBe(false) // 数据已经同步,再次监测不存在更新 36 | 37 | // 测试完修改回来 38 | await updateTestFile(testBody) 39 | expect(vFile.hasChanged).toBe(true) //同样会监测到更新 40 | expect(vFile.body).toBe(testBody) //内容也变回来了 41 | 42 | }) 43 | 44 | it('Provider 获取父节点(TreeNode)', () => { 45 | const provider = new Provider() 46 | 47 | // 没父节点的父节点不存在时,会自动创建 48 | const treeNode1 = provider.getParent('abc/456/README.md') // macOS、 Linux 路径 49 | const treeNode2 = provider.getParent('abc\\123\\README.md') // Window 路径 50 | 51 | // 测试README.md排序,同级 README.md 排在最前面 52 | provider.getParent('abc/README.md/README排在最前') 53 | 54 | // 从子节点查找父级 55 | expect(treeNode1.path).toBe('abc/456') 56 | expect(treeNode1.parent.path).toBe('abc') 57 | expect(treeNode1.parent.parent.path).toBe('') 58 | expect(treeNode2.path).toBe('abc/123') 59 | 60 | // 从根节点向下查找 61 | expect(provider.root.path).toBe('') 62 | expect(provider.root.children.length).toBe(1) 63 | expect(provider.root.children[0].path).toBe('abc') 64 | expect(provider.root.children[0].children.length).toBe(3) 65 | 66 | // 同级别treeNode会按文件名排序:需要abc/123、abc/456在后,和插入顺序无关 67 | expect(provider.root.children[0].children[0].path).toBe('abc/README.md') 68 | expect(provider.root.children[0].children[1].path).toBe('abc/123') 69 | expect(provider.root.children[0].children[2].path).toBe('abc/456') 70 | }) 71 | 72 | it('Provider 添加文件和排序', async () => { 73 | const provider = new Provider() 74 | provider.resolvePath = resolvePath 75 | 76 | // 根节点添加文件 77 | await provider.addFile(testFile) 78 | const vFile1 = provider.nodes[testFile] 79 | expect(vFile1.path).toBe(testFile) 80 | expect(vFile1.body).toBe(testBody) 81 | 82 | // 子节点添加节点 83 | await provider.addFile('abc/456/README.md') // macOS、 Linux 路径 84 | const vFile2 = provider.nodes['abc/456/README.md'] 85 | expect(vFile2.path).toBe('abc/456/README.md') 86 | expect(vFile2.body).toBe(openFileAsText('abc/456/README.md')) 87 | 88 | // 从根节点向下查找 89 | await provider.addFile('abc\\123\\README.md') // Window 路径 90 | const vFile3 = provider.root // [abc/, test.md] 91 | .children[0] // [abc/123/, abc/456/] 92 | .children[0] // [abc/123/README.md] 93 | .children[0] 94 | expect(vFile3.body).toBe(openFileAsText('abc/123/README.md')) 95 | 96 | // 自动排序测试 97 | expect(vFile1 === provider.root.children[1]).toBe(true) // [abc/, test.md] 98 | expect(vFile2.parent === vFile3.parent.parent.children[1]).toBe(true) // [abc/123/, abc/456/] 99 | 100 | // 增加更多文件,测试排序 101 | await provider.addFile('xyz/ad.md') // macOS、 Linux 路径 102 | await provider.addFile('README.md') // Window 路径 103 | await provider.addFile('abc\\123\\test.md') 104 | await provider.addFile('abc/README.md') 105 | expect(provider 106 | .toArray(fileNode => fileNode.path) 107 | .join(', ')) 108 | .toBe([ 109 | 'README.md', //根目录参与排序结果 [README.md, abc/, test.md, xyz] 110 | 'abc/README.md', // abc目录参与排序结果 [abc/README.md, abc/123/, 456/] 111 | 'abc/123/README.md', // abc/123目录参与排序结果 [abc/123/README.md, abc/123/test.md] 112 | 'abc/123/test.md', 113 | 'abc/456/README.md', 114 | 'test.md', 115 | 'xyz/ad.md' 116 | ].join(', ')) 117 | }) 118 | 119 | it('Provider 删除文件', async () => { 120 | const provider = new Provider() 121 | provider.resolvePath = resolvePath 122 | 123 | // 先添加测试文件 124 | await provider.addFile(testFile) 125 | await provider.addFile('abc/123/README.md') // macOS、 Linux 路径 126 | await provider.addFile('abc\\456\\README.md') // Window 路径 127 | expect(provider.getNodeFiles()).toBe(`abc/123/README.md, abc/456/README.md, ${testFile}`) 128 | 129 | // 删除文件 130 | await provider.removeFile('abc/123/README.md') 131 | expect(provider.getNodeFiles()).toBe(`abc/456/README.md, ${testFile}`) 132 | }) 133 | 134 | it('Provider 修改文件', async () => { 135 | const provider = new Provider() 136 | provider.resolvePath = resolvePath 137 | 138 | // 添加测试文件 139 | await provider.addFile(testFile) 140 | const vFile = provider.nodes[testFile] 141 | expect(vFile.body).toBe(testBody) 142 | 143 | // 修改文件 144 | await updateTestFile('来点广告, vscode插件 会了吧 非常好用') 145 | expect(vFile.body).toBe(testBody) // patdh之前为老内容 146 | await provider.updateFile(testFile) 147 | expect(vFile.body).toBe('来点广告, vscode插件 会了吧 非常好用') // patch后更新 148 | 149 | // 恢复原样 150 | await updateTestFile(testBody) 151 | }) 152 | 153 | it('Provider Patcher vFileNode测试', async () => { 154 | const provider = new Provider() 155 | provider.resolvePath = resolvePath 156 | 157 | // 数据从 glob.sync 传入,每次文件改动,会传入所有路径 158 | const testFiles = [testFile] 159 | 160 | // 初始化测试文件 161 | await provider.patch(testFiles) 162 | expect(provider.getNodeFiles()).toBe(testFile) 163 | 164 | // 模拟用户新建文件 165 | testFiles.push('abc\\456\\README.md') // Window 路径 166 | testFiles.push('abc/123/README.md') // macOS、 Linux 路径 167 | await provider.patch(testFiles) 168 | expect(provider.getNodeFiles()).toBe(`abc/123/README.md, abc/456/README.md, ${testFile}`) 169 | 170 | // 模拟用户删除文件 171 | testFiles.pop() 172 | await provider.patch(testFiles) 173 | expect(provider.getNodeFiles()).toBe(`abc/456/README.md, ${testFile}`) 174 | 175 | // 修改文件 176 | const vFile = provider.nodes[testFile] 177 | const newBody = '测试文件陌生单词太多,试试 会了吧!' 178 | expect(vFile.body).toBe(testBody) // vFile文件内容 179 | await updateTestFile(newBody) 180 | expect(vFile.body).toBe(testBody) // patch前,没有调用 updateFile,所以内容不变 181 | await provider.patch(testFiles) 182 | expect(vFile.body).toBe(newBody) //patch后,内容被更新 183 | 184 | // 恢复原样 185 | await updateTestFile(testBody) 186 | expect(vFile.body).toBe(newBody) // 和上面一样,patch前,内容不变 187 | await provider.patch(testFiles) 188 | expect(vFile.body).toBe(testBody) // patch后,内容恢复 189 | }) 190 | 191 | it('Provider getItem', async () => { 192 | const provider = new Provider() 193 | provider.resolvePath = resolvePath 194 | 195 | // 获取文件内容 196 | const testFiles = [ 197 | testFile, 198 | 'abc\\123\\README.md', // Window 路径 199 | 'abc/456/README.md' // macOS、 Linux 路径 200 | ] 201 | await provider.patch(testFiles) 202 | const result = provider.getItem(testFile, (fileNode) => { 203 | return fileNode.body 204 | }) 205 | expect(result).toBe(testBody) 206 | // Window 路径 207 | const result1 = provider.getItem('abc\\123\\README.md', (fileNode) => { 208 | return fileNode.body 209 | }) 210 | expect(result1).toBe(openFileAsText('abc/123/README.md')) 211 | // macOS、 Linux 路径 212 | const result2 = provider.getItem('abc/456/README.md', (fileNode) => { 213 | return fileNode.body 214 | }) 215 | expect(result2).toBe(openFileAsText('abc/456/README.md')) 216 | }) 217 | 218 | it('Provider getItem with middleware', async () => { 219 | const provider = new Provider() 220 | provider.resolvePath = resolvePath 221 | 222 | //添加中间件 223 | provider.useMiddleware(({ fileNode }, next) => { 224 | fileNode.body += '' 225 | next() 226 | }) 227 | 228 | // 获取文件内容 229 | const testFiles = [ 230 | testFile, 231 | 'abc\\123\\README.md', // Window 路径 232 | 'abc/456/README.md' // macOS、 Linux 路径 233 | ] 234 | await provider.patch(testFiles) 235 | const result = provider.getItem(testFile, (fileNode) => { 236 | return fileNode.body 237 | }) 238 | expect(result).toBe(`${testBody}`) 239 | // Window 路径 240 | const result1 = provider.getItem('abc\\123\\README.md', (fileNode) => { 241 | return fileNode.body 242 | }) 243 | expect(result1).toBe(`${openFileAsText('abc/123/README.md')}`) 244 | // macOS、 Linux 路径 245 | const result2 = provider.getItem('abc/456/README.md', (fileNode) => { 246 | return fileNode.body 247 | }) 248 | expect(result2).toBe(`${openFileAsText('abc/456/README.md')}`) 249 | }) 250 | 251 | it('Provider toArray', async () => { 252 | const provider = new Provider() 253 | provider.resolvePath = resolvePath 254 | await provider.patch([ 255 | testFile, 256 | 'abc\\123\\README.md', // Window 路径 257 | 'abc/456/README.md' // macOS、 Linux 路径 258 | ]) 259 | const result = provider.toArray(fileNode => { 260 | return fileNode.path 261 | }).join(', ') 262 | expect(result).toBe(`abc/123/README.md, abc/456/README.md, ${testFile}`) 263 | }) 264 | 265 | it('Provider toArray with middleware', async () => { 266 | const provider = new Provider() 267 | provider.resolvePath = resolvePath 268 | //添加中间件 269 | provider.useMiddleware(({ fileNode }, next) => { 270 | fileNode.title = `<-- title:${fileNode.path} -->` 271 | next() 272 | }) 273 | await provider.patch([ 274 | testFile, 275 | 'abc\\123\\README.md', // Window 路径 276 | 'abc/456/README.md' // macOS、 Linux 路径 277 | ]) 278 | const result = provider.toArray(fileNode => { 279 | return fileNode.title 280 | }) 281 | expect(result.join(', ')).toBe(`<-- title:abc/123/README.md -->, <-- title:abc/456/README.md -->, <-- title:${testFile} -->`) 282 | }) -------------------------------------------------------------------------------- /src/assets/themes/mark/style.css: -------------------------------------------------------------------------------- 1 | /* body{ 2 | margin: 0 auto; 3 | font-family: "Microsoft YaHei", arial,sans-serif; 4 | color: #444444; 5 | line-height: 1; 6 | padding: 30px; 7 | } 8 | @media screen and (min-width: 768px) { 9 | body { 10 | width: 748px; 11 | margin: 10px auto; 12 | } 13 | } */ 14 | h1, h2, h3, h4 { 15 | color: #111111; 16 | font-weight: 400; 17 | margin-top: 1em; 18 | } 19 | 20 | h1, h2, h3, h4, h5 { 21 | font-family: Georgia, Palatino, serif; 22 | } 23 | h1, h2, h3, h4, h5, p , dl{ 24 | margin-bottom: 16px; 25 | padding: 0; 26 | } 27 | h1 { 28 | font-size: 48px; 29 | line-height: 54px; 30 | } 31 | h2 { 32 | font-size: 36px; 33 | line-height: 42px; 34 | } 35 | h1, h2 { 36 | border-bottom: 1px solid #EFEAEA; 37 | padding-bottom: 10px; 38 | } 39 | h3 { 40 | font-size: 24px; 41 | line-height: 30px; 42 | } 43 | h4 { 44 | font-size: 21px; 45 | line-height: 26px; 46 | } 47 | h5 { 48 | font-size: 18px; 49 | list-style: 23px; 50 | } 51 | a { 52 | color: #0099ff; 53 | margin: 0; 54 | padding: 0; 55 | vertical-align: baseline; 56 | } 57 | a:hover { 58 | text-decoration: none; 59 | color: #ff6600; 60 | } 61 | a:visited { 62 | /*color: purple;*/ 63 | } 64 | ul, ol { 65 | padding: 0; 66 | padding-left: 24px; 67 | margin: 0; 68 | } 69 | li { 70 | line-height: 24px; 71 | } 72 | p, ul, ol { 73 | font-size: 16px; 74 | line-height: 24px; 75 | } 76 | 77 | ol ol, ul ol { 78 | list-style-type: lower-roman; 79 | } 80 | 81 | /*pre { 82 | padding: 0px 24px; 83 | max-width: 800px; 84 | white-space: pre-wrap; 85 | } 86 | code { 87 | font-family: Consolas, Monaco, Andale Mono, monospace; 88 | line-height: 1.5; 89 | font-size: 13px; 90 | }*/ 91 | 92 | code, pre { 93 | border-radius: 3px; 94 | background-color:#f7f7f7; 95 | color: inherit; 96 | } 97 | 98 | code { 99 | font-family: Consolas, Monaco, Andale Mono, monospace; 100 | margin: 0 2px; 101 | } 102 | 103 | pre { 104 | line-height: 1.7em; 105 | overflow: auto; 106 | padding: 6px 10px; 107 | border-left: 5px solid #6CE26C; 108 | } 109 | 110 | pre > code { 111 | border: 0; 112 | display: inline; 113 | max-width: initial; 114 | padding: 0; 115 | margin: 0; 116 | overflow: initial; 117 | line-height: inherit; 118 | font-size: .85em; 119 | white-space: pre; 120 | background: 0 0; 121 | 122 | } 123 | 124 | code { 125 | color: #666555; 126 | } 127 | 128 | 129 | /** markdown preview plus 对于代码块的处理有些问题, 所以使用统一的颜色 */ 130 | /*code .keyword { 131 | color: #8959a8; 132 | } 133 | 134 | code .number { 135 | color: #f5871f; 136 | } 137 | 138 | code .comment { 139 | color: #998 140 | }*/ 141 | 142 | aside { 143 | display: block; 144 | float: right; 145 | width: 390px; 146 | } 147 | blockquote { 148 | border-left:.5em solid #eee; 149 | padding: 0 0 0 2em; 150 | margin-left:0; 151 | } 152 | blockquote cite { 153 | font-size:14px; 154 | line-height:20px; 155 | color:#bfbfbf; 156 | } 157 | blockquote cite:before { 158 | content: '\2014 \00A0'; 159 | } 160 | 161 | blockquote p { 162 | color: #666; 163 | } 164 | hr { 165 | text-align: left; 166 | color: #999; 167 | height: 2px; 168 | padding: 0; 169 | margin: 16px 0; 170 | background-color: #e7e7e7; 171 | border: 0 none; 172 | } 173 | 174 | dl { 175 | padding: 0; 176 | } 177 | 178 | dl dt { 179 | padding: 10px 0; 180 | margin-top: 16px; 181 | font-size: 1em; 182 | font-style: italic; 183 | font-weight: bold; 184 | } 185 | 186 | dl dd { 187 | padding: 0 16px; 188 | margin-bottom: 16px; 189 | } 190 | 191 | dd { 192 | margin-left: 0; 193 | } 194 | 195 | /* Code below this line is copyright Twitter Inc. */ 196 | 197 | button, 198 | input, 199 | select, 200 | textarea { 201 | font-size: 100%; 202 | margin: 0; 203 | vertical-align: baseline; 204 | *vertical-align: middle; 205 | } 206 | button, input { 207 | line-height: normal; 208 | *overflow: visible; 209 | } 210 | button::-moz-focus-inner, input::-moz-focus-inner { 211 | border: 0; 212 | padding: 0; 213 | } 214 | button, 215 | input[type="button"], 216 | input[type="reset"], 217 | input[type="submit"] { 218 | cursor: pointer; 219 | -webkit-appearance: button; 220 | } 221 | input[type=checkbox], input[type=radio] { 222 | cursor: pointer; 223 | } 224 | /* override default chrome & firefox settings */ 225 | input:not([type="image"]), textarea { 226 | -webkit-box-sizing: content-box; 227 | -moz-box-sizing: content-box; 228 | box-sizing: content-box; 229 | } 230 | 231 | input[type="search"] { 232 | -webkit-appearance: textfield; 233 | -webkit-box-sizing: content-box; 234 | -moz-box-sizing: content-box; 235 | box-sizing: content-box; 236 | } 237 | input[type="search"]::-webkit-search-decoration { 238 | -webkit-appearance: none; 239 | } 240 | label, 241 | input, 242 | select, 243 | textarea { 244 | font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; 245 | font-size: 13px; 246 | font-weight: normal; 247 | line-height: normal; 248 | margin-bottom: 18px; 249 | } 250 | input[type=checkbox], input[type=radio] { 251 | cursor: pointer; 252 | margin-bottom: 0; 253 | } 254 | input[type=text], 255 | input[type=password], 256 | textarea, 257 | select { 258 | display: inline-block; 259 | width: 210px; 260 | padding: 4px; 261 | font-size: 13px; 262 | font-weight: normal; 263 | line-height: 18px; 264 | height: 18px; 265 | color: #808080; 266 | border: 1px solid #ccc; 267 | -webkit-border-radius: 3px; 268 | -moz-border-radius: 3px; 269 | border-radius: 3px; 270 | } 271 | select, input[type=file] { 272 | height: 27px; 273 | line-height: 27px; 274 | } 275 | textarea { 276 | height: auto; 277 | } 278 | /* grey out placeholders */ 279 | :-moz-placeholder { 280 | color: #bfbfbf; 281 | } 282 | ::-webkit-input-placeholder { 283 | color: #bfbfbf; 284 | } 285 | input[type=text], 286 | input[type=password], 287 | select, 288 | textarea { 289 | -webkit-transition: border linear 0.2s, box-shadow linear 0.2s; 290 | -moz-transition: border linear 0.2s, box-shadow linear 0.2s; 291 | transition: border linear 0.2s, box-shadow linear 0.2s; 292 | -webkit-box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.1); 293 | -moz-box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.1); 294 | box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.1); 295 | } 296 | input[type=text]:focus, input[type=password]:focus, textarea:focus { 297 | outline: none; 298 | border-color: rgba(82, 168, 236, 0.8); 299 | -webkit-box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.1), 0 0 8px rgba(82, 168, 236, 0.6); 300 | -moz-box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.1), 0 0 8px rgba(82, 168, 236, 0.6); 301 | box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.1), 0 0 8px rgba(82, 168, 236, 0.6); 302 | } 303 | /* buttons */ 304 | button { 305 | display: inline-block; 306 | padding: 4px 14px; 307 | font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; 308 | font-size: 13px; 309 | line-height: 18px; 310 | -webkit-border-radius: 4px; 311 | -moz-border-radius: 4px; 312 | border-radius: 4px; 313 | -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05); 314 | -moz-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05); 315 | box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05); 316 | background-color: #0064cd; 317 | background-repeat: repeat-x; 318 | background-image: -khtml-gradient(linear, left top, left bottom, from(#049cdb), to(#0064cd)); 319 | background-image: -moz-linear-gradient(top, #049cdb, #0064cd); 320 | background-image: -ms-linear-gradient(top, #049cdb, #0064cd); 321 | background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #049cdb), color-stop(100%, #0064cd)); 322 | background-image: -webkit-linear-gradient(top, #049cdb, #0064cd); 323 | background-image: -o-linear-gradient(top, #049cdb, #0064cd); 324 | background-image: linear-gradient(top, #049cdb, #0064cd); 325 | color: #fff; 326 | text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); 327 | border: 1px solid #004b9a; 328 | border-bottom-color: #003f81; 329 | -webkit-transition: 0.1s linear all; 330 | -moz-transition: 0.1s linear all; 331 | transition: 0.1s linear all; 332 | border-color: #0064cd #0064cd #003f81; 333 | border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); 334 | } 335 | button:hover { 336 | color: #fff; 337 | background-position: 0 -15px; 338 | text-decoration: none; 339 | } 340 | button:active { 341 | -webkit-box-shadow: inset 0 3px 7px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05); 342 | -moz-box-shadow: inset 0 3px 7px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05); 343 | box-shadow: inset 0 3px 7px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05); 344 | } 345 | button::-moz-focus-inner { 346 | padding: 0; 347 | border: 0; 348 | } 349 | table { 350 | *border-collapse: collapse; /* IE7 and lower */ 351 | border-spacing: 0; 352 | width: 100%; 353 | } 354 | table { 355 | border: solid #ccc 1px; 356 | -moz-border-radius: 6px; 357 | -webkit-border-radius: 6px; 358 | border-radius: 6px; 359 | /*-webkit-box-shadow: 0 1px 1px #ccc; 360 | -moz-box-shadow: 0 1px 1px #ccc; 361 | box-shadow: 0 1px 1px #ccc; */ 362 | } 363 | table tr:hover { 364 | background: #fbf8e9; 365 | -o-transition: all 0.1s ease-in-out; 366 | -webkit-transition: all 0.1s ease-in-out; 367 | -moz-transition: all 0.1s ease-in-out; 368 | -ms-transition: all 0.1s ease-in-out; 369 | transition: all 0.1s ease-in-out; 370 | } 371 | table td, .table th { 372 | border-left: 1px solid #ccc; 373 | border-top: 1px solid #ccc; 374 | padding: 10px; 375 | text-align: left; 376 | } 377 | 378 | table th { 379 | background-color: #dce9f9; 380 | background-image: -webkit-gradient(linear, left top, left bottom, from(#ebf3fc), to(#dce9f9)); 381 | background-image: -webkit-linear-gradient(top, #ebf3fc, #dce9f9); 382 | background-image: -moz-linear-gradient(top, #ebf3fc, #dce9f9); 383 | background-image: -ms-linear-gradient(top, #ebf3fc, #dce9f9); 384 | background-image: -o-linear-gradient(top, #ebf3fc, #dce9f9); 385 | background-image: linear-gradient(top, #ebf3fc, #dce9f9); 386 | /*-webkit-box-shadow: 0 1px 0 rgba(255,255,255,.8) inset; 387 | -moz-box-shadow:0 1px 0 rgba(255,255,255,.8) inset; 388 | box-shadow: 0 1px 0 rgba(255,255,255,.8) inset;*/ 389 | border-top: none; 390 | text-shadow: 0 1px 0 rgba(255,255,255,.5); 391 | padding: 5px; 392 | } 393 | 394 | table td:first-child, table th:first-child { 395 | border-left: none; 396 | } 397 | 398 | table th:first-child { 399 | -moz-border-radius: 6px 0 0 0; 400 | -webkit-border-radius: 6px 0 0 0; 401 | border-radius: 6px 0 0 0; 402 | } 403 | table th:last-child { 404 | -moz-border-radius: 0 6px 0 0; 405 | -webkit-border-radius: 0 6px 0 0; 406 | border-radius: 0 6px 0 0; 407 | } 408 | table th:only-child{ 409 | -moz-border-radius: 6px 6px 0 0; 410 | -webkit-border-radius: 6px 6px 0 0; 411 | border-radius: 6px 6px 0 0; 412 | } 413 | table tr:last-child td:first-child { 414 | -moz-border-radius: 0 0 0 6px; 415 | -webkit-border-radius: 0 0 0 6px; 416 | border-radius: 0 0 0 6px; 417 | } 418 | table tr:last-child td:last-child { 419 | -moz-border-radius: 0 0 6px 0; 420 | -webkit-border-radius: 0 0 6px 0; 421 | border-radius: 0 0 6px 0; 422 | } 423 | 424 | /* smart press */ 425 | .content { 426 | font-family: "PingFangSC-Regular", "Microsoft YaHei", arial, sans-serif; 427 | padding: 0 40px !important; 428 | } 429 | 430 | .content p { 431 | color: #666666; 432 | } 433 | -------------------------------------------------------------------------------- /src/assets/themes/techo/style.css: -------------------------------------------------------------------------------- 1 | @charset "utf-8"; 2 | 3 | /* techo-typora style */ 4 | :root { 5 | --active-file-bg-color: #dadada; 6 | --active-file-bg-color: rgba(32, 43, 51, 0.63); 7 | --active-file-text-color: white; 8 | --bg-color: #f9f5ed; 9 | --text-color: #6e4832; 10 | --side-bar-bg-color: #f5f5f5; 11 | --control-text-color: #666; 12 | --primary-color: #6e4832; 13 | } 14 | 15 | html { 16 | color: #6e4832; 17 | background: #f9f5ed; 18 | -webkit-text-size-adjust: 100%; 19 | -ms-text-size-adjust: 100%; 20 | text-rendering: optimizelegibility; 21 | font-size: 16px; 22 | -webkit-font-smoothing: initial; 23 | } 24 | 25 | content { 26 | background: #f9f5ed; 27 | } 28 | 29 | /* input mode */ 30 | #write { 31 | max-width: 960px; 32 | padding-top: 2em; 33 | padding-left: 60px; 34 | padding-right: 60px; 35 | min-height: calc(100vh - 6em); 36 | -webkit-font-smoothing: antialiased; 37 | font-size: 16px; 38 | } 39 | 40 | .typora-node #write { 41 | min-height: calc(100% - 6em); 42 | } 43 | 44 | /* meta - message - block */ 45 | pre.md-meta-block { 46 | color: rgba(146, 115, 91, 0.81); 47 | padding: 1em; 48 | border-radius: 2px; 49 | font-size: 14px; 50 | background: rgba(251, 246, 222, 0.5); 51 | } 52 | 53 | @media screen and (max-width: 800px) { 54 | html { 55 | font-size: 14px; 56 | } 57 | 58 | #write { 59 | padding-left: 30px; 60 | background: #f9f5ed; 61 | padding-right: 30px; 62 | font-size: 14px; 63 | } 64 | } 65 | 66 | @media screen and (min-width: 1100px) { 67 | 68 | body, 69 | #footer-word-count-info { 70 | background: #f5f5f5; 71 | } 72 | 73 | body.pin-outline, 74 | .pin-outline #footer-word-count-info, 75 | .pin-outline footer { 76 | background: #f9f5ed; 77 | } 78 | 79 | #write { 80 | max-width: 1000px; 81 | padding: 40px 60px; 82 | background: #f9f5ed; 83 | margin: 3em auto 3em; 84 | } 85 | 86 | /* paper outline */ 87 | .pin-outline #write { 88 | max-width: 1000px; 89 | background: #f9f5ed; 90 | margin: 0 0 0; 91 | border: 0; 92 | padding-left: 60px; 93 | padding-right: 60px; 94 | } 95 | 96 | footer { 97 | background-color: transparent; 98 | } 99 | } 100 | 101 | @media screen and (min-width: 1300px) { 102 | 103 | body.pin-outline, 104 | .pin-outline #footer-word-count-info, 105 | .pin-outline footer { 106 | background: #f9f5ed; 107 | } 108 | 109 | .pin-outline #write { 110 | max-width: 1000px; 111 | padding: 40px 60px; 112 | background: #f9f5ed; 113 | margin: 3em auto 3em; 114 | } 115 | 116 | .pin-outline footer { 117 | background-color: transparent; 118 | } 119 | 120 | #footer-word-count-info { 121 | background: #f5f5f5; 122 | } 123 | } 124 | 125 | /* clear margin and padding */ 126 | body, 127 | dl, 128 | dt, 129 | dd, 130 | ul, 131 | ol, 132 | li, 133 | h1, 134 | h2, 135 | h3, 136 | h4, 137 | h5, 138 | h6, 139 | code, 140 | form, 141 | fieldset, 142 | legend, 143 | input, 144 | textarea, 145 | p, 146 | blockquote, 147 | th, 148 | td, 149 | hr, 150 | button, 151 | article, 152 | aside, 153 | details, 154 | figcaption, 155 | figure, 156 | footer, 157 | header, 158 | menu, 159 | nav, 160 | section { 161 | margin: 0; 162 | padding: 0; 163 | } 164 | 165 | article, 166 | aside, 167 | details, 168 | figcaption, 169 | figure, 170 | footer, 171 | header, 172 | menu, 173 | nav, 174 | section { 175 | display: block; 176 | } 177 | 178 | audio, 179 | canvas, 180 | video { 181 | display: inline-block; 182 | } 183 | 184 | body, 185 | button, 186 | input, 187 | select, 188 | textarea { 189 | font: 400 1em/1.8 "font/han.otf", "PingFang SC", "Lantinghei SC", "Microsoft Yahei", "Hiragino Sans GB", "Microsoft Sans Serif", "WenQuanYi Micro Hei", sans; 190 | } 191 | 192 | body { 193 | font-family: "font/han.otf", "PingFang SC", "Lantinghei SC", "Microsoft Yahei", "Hiragino Sans GB", "Microsoft Sans Serif", "WenQuanYi Micro Hei", sans; 194 | } 195 | 196 | h1, 197 | h2, 198 | h3, 199 | h4, 200 | h5, 201 | h6 { 202 | font-family: "font/han.otf", "PingFang SC", "Lantinghei SC", "Microsoft Yahei", "Hiragino Sans GB", "Microsoft Sans Serif", "WenQuanYi Micro Hei", sans; 203 | /*font-family: "PingFang SC", "Lantinghei SC", "Microsoft Yahei", "Hiragino Sans GB", "Microsoft Sans Serif", "WenQuanYi Micro Hei", sans;*/ 204 | -webkit-font-smoothing: initial; 205 | font-weight: 100; 206 | color: var(--text-color); 207 | line-height: 1.35; 208 | font-variant-numeric: lining-nums; 209 | margin-bottom: 1em; 210 | } 211 | 212 | em { 213 | font-family: "font/han.otf", Georgia-Italic, STSongti-SC-Light, serif; 214 | } 215 | 216 | strong em, 217 | em strong { 218 | font-family: "font/han.otf", Georgia-BoldItalic, STSongti-SC-Regular, serif; 219 | } 220 | 221 | button::-moz-focus-inner, 222 | input::-moz-focus-inner { 223 | padding: 0; 224 | border: 0; 225 | } 226 | 227 | table { 228 | border-collapse: collapse; 229 | border-spacing: 0; 230 | } 231 | 232 | fieldset, 233 | img { 234 | border: 0; 235 | } 236 | 237 | /* img 添加边框 */ 238 | img { 239 | background: white; 240 | border-radius: .2rem; 241 | padding: 0.3rem; 242 | box-shadow: 1px 2px 4px gray; 243 | text-align: center; 244 | } 245 | 246 | blockquote { 247 | position: relative; 248 | color: rgba(146, 115, 91, 0.81); 249 | font-weight: 400; 250 | border-left: 2px solid #6e4832; 251 | padding-left: 1rem; 252 | margin: 1em 3em 1em 1em; 253 | } 254 | 255 | @media only screen and (max-width: 640px) { 256 | blockquote { 257 | margin: 1em 0; 258 | } 259 | } 260 | 261 | acronym, 262 | abbr { 263 | border-bottom: 1px dotted; 264 | font-variant: normal; 265 | } 266 | 267 | abbr { 268 | cursor: help; 269 | } 270 | 271 | address, 272 | caption, 273 | cite, 274 | code, 275 | dfn, 276 | th, 277 | var { 278 | font-style: normal; 279 | font-weight: 400; 280 | } 281 | 282 | ul, 283 | ol { 284 | list-style: none; 285 | } 286 | 287 | caption, 288 | th { 289 | text-align: left; 290 | } 291 | 292 | q:before, 293 | q:after { 294 | content: ''; 295 | } 296 | 297 | sub, 298 | sup { 299 | font-size: 75%; 300 | line-height: 0; 301 | position: relative; 302 | } 303 | 304 | :root sub, 305 | :root sup { 306 | vertical-align: baseline; 307 | /* for ie9 and other modern browsers */ 308 | } 309 | 310 | sup { 311 | top: -0.5em; 312 | } 313 | 314 | sub { 315 | bottom: -0.25em; 316 | } 317 | 318 | /* a border bottom styles */ 319 | a { 320 | color: #4484c2; 321 | } 322 | 323 | a:hover { 324 | text-decoration: underline; 325 | } 326 | 327 | #write a { 328 | font-weight: normal; 329 | } 330 | 331 | #write a:hover { 332 | text-decoration: none; 333 | border-bottom: 1px solid #4484c2; 334 | } 335 | 336 | ins, 337 | a { 338 | text-decoration: none; 339 | } 340 | 341 | /* mark */ 342 | mark { 343 | background: #fffdd1; 344 | border-bottom: 1px solid #ffedce; 345 | padding: 2px; 346 | margin: 0 5px; 347 | } 348 | 349 | pre, 350 | code, 351 | pre tt { 352 | font-family: Courier, 'Courier New', monospace; 353 | } 354 | 355 | #write .md-fences { 356 | border: 1px solid #ddd; 357 | padding: 1em 0.5em; 358 | display: block; 359 | -webkit-overflow-scrolling: touch; 360 | } 361 | 362 | hr { 363 | border: none; 364 | border-bottom: 1px solid #cfcfcf; 365 | margin-bottom: 0.8em; 366 | height: 10px; 367 | } 368 | 369 | strong, 370 | b { 371 | font-weight: 700; 372 | /*color: #000;*/ 373 | } 374 | 375 | /* 段落之间的间隔 */ 376 | #write p, 377 | #write .md-fences, 378 | #write ul, 379 | #write ol, 380 | #write dl, 381 | #write form, 382 | #write hr, 383 | #write figure, 384 | #write-p, 385 | #write-pre, 386 | #write-ul, 387 | #write-ol, 388 | #write-dl, 389 | #write-form, 390 | #write-hr, 391 | #write-table, 392 | blockquote { 393 | margin-bottom: 1.2em 394 | } 395 | 396 | html { 397 | font-family: "font/han.otf", PingFang SC, Verdana, Helvetica Neue, Microsoft Yahei, Hiragino Sans GB, Microsoft Sans Serif, WenQuanYi Micro Hei, sans-serif; 398 | } 399 | 400 | #write h1, 401 | #write h2, 402 | #write h3, 403 | #write h4, 404 | #write h5, 405 | #write h6, 406 | #write-h1, 407 | #write-h2, 408 | #write-h3, 409 | #write-h4, 410 | #write-h5, 411 | #write-h6 { 412 | margin-bottom: 0.6em; 413 | line-height: 1.35; 414 | color: #6e4832; 415 | font-weight: bolder; 416 | } 417 | 418 | #write h1, 419 | #write-h1 { 420 | font-size: 2.4em; 421 | padding-bottom: 0.3em; 422 | border-bottom: 3px double #eee; 423 | } 424 | 425 | #write h2, 426 | #write-h2 { 427 | font-size: 1.8em; 428 | } 429 | 430 | #write h3, 431 | #write-h3 { 432 | font-size: 1.6em; 433 | } 434 | 435 | #write h4, 436 | #write-h4 { 437 | font-size: 1.4em; 438 | } 439 | 440 | #write h5, 441 | #write h6, 442 | #write-h5, 443 | #write-h6 { 444 | font-size: 1.2em; 445 | } 446 | 447 | /* 在文章中的 ul 和 ol 的样式 */ 448 | #write ul, 449 | #write-ul { 450 | margin-left: 1.3em; 451 | list-style: disc; 452 | } 453 | 454 | #write ol, 455 | #write-ol { 456 | list-style: decimal; 457 | margin-left: 1.9em; 458 | } 459 | 460 | #write li ul, 461 | #write li ol, 462 | #write-ul ul, 463 | #write-ul ol, 464 | #write-ol ul, 465 | #write-ol ol { 466 | margin-bottom: 0.8em; 467 | margin-left: 2em; 468 | } 469 | 470 | #write li ul, 471 | #write-ul ul, 472 | #write-ol ul { 473 | list-style: circle; 474 | } 475 | 476 | [mdtype="table_row"]>th, 477 | [mdtype="table_row"]>td { 478 | border: 1px solid #ddd; 479 | padding: 0.5em 1em; 480 | color: rgba(110, 72, 50, 0.74); 481 | } 482 | 483 | #write table th, 484 | #write-table th { 485 | background: #fbfbfb; 486 | } 487 | 488 | #write table thead th, 489 | #write-table thead th { 490 | background: rgba(251, 240, 211, 0.76); 491 | } 492 | 493 | #write table caption { 494 | border-bottom: none; 495 | } 496 | 497 | #write em { 498 | font-weight: inherit; 499 | font-style: inherit; 500 | } 501 | 502 | li>p { 503 | margin-bottom: 0 !important; 504 | } 505 | 506 | /* Responsive images */ 507 | #write img { 508 | max-width: 100%; 509 | } 510 | 511 | a.md-toc-inner { 512 | border-bottom: 0 !important; 513 | } 514 | 515 | .md-toc-h1:first-of-type:last-of-type { 516 | display: none; 517 | } 518 | 519 | .md-toc { 520 | font-size: inherit; 521 | } 522 | 523 | .md-toc-h1 .md-toc-inner { 524 | font-weight: normal; 525 | } 526 | 527 | .md-table-edit th { 528 | padding: 0 !important; 529 | border: 0 !important; 530 | } 531 | 532 | .mac-seamless-mode #write { 533 | min-height: calc(100vh - 6em - 20px); 534 | } 535 | 536 | .typora-quick-open-item.active { 537 | color: var(--active-file-text-color); 538 | } 539 | 540 | *.in-text-selection, 541 | ::selection { 542 | background: var(--active-file-bg-color); 543 | text-shadow: none; 544 | color: white; 545 | } 546 | 547 | .btn-primary { 548 | background-color: #2d2d2d; 549 | border-color: #020202; 550 | } 551 | 552 | .btn-primary:hover, 553 | .btn-primary:focus, 554 | .btn-primary.focus, 555 | .btn-primary:active, 556 | .btn-primary.active, 557 | .open>.dropdown-toggle.btn-primary { 558 | background-color: #4e4c4e; 559 | border: #4e4c4e; 560 | } 561 | 562 | #preference-dialog .modal-content { 563 | background: #6e757a; 564 | --bg-color: #6e757a; 565 | --text-color: #f1f1f1; 566 | color: #f1f1f1; 567 | } 568 | 569 | /* 角标颜色 */ 570 | sup.md-footnote { 571 | color: #6e4832; 572 | } 573 | 574 | .task-list { 575 | list-style: none !important; 576 | } 577 | 578 | /* checked box 效果 */ 579 | .task-list-item input { 580 | width: 1.25rem; 581 | height: 1.25rem; 582 | display: block; 583 | -webkit-appearance: initial; 584 | left: -18px; 585 | } 586 | 587 | .task-list-item input:focus { 588 | outline: none; 589 | box-shadow: none; 590 | } 591 | 592 | .task-list-item input:before { 593 | border: 2px solid #6e4832; 594 | border-radius: 1.2rem; 595 | width: 1.2rem; 596 | height: 1.2rem; 597 | content: ' '; 598 | transition: background-color 200ms ease-in-out; 599 | display: block; 600 | } 601 | 602 | .task-list-item input:checked:before, 603 | .task-list-item input[checked]:before { 604 | -webkit-appearance: initial; 605 | background: #6e4832; 606 | border-width: 2px; 607 | display: inline-block; 608 | transition: background-color 200ms ease-in-out; 609 | } 610 | 611 | .task-list-item input:checked:after, 612 | .task-list-item input[checked]:after { 613 | opacity: 1; 614 | } 615 | 616 | .task-list-item input:after { 617 | opacity: 1; 618 | -webkit-transition: opacity 0.05s ease-in-out; 619 | -moz-transition: opacity 0.05s ease-in-out; 620 | transition: opacity 0.05s ease-in-out; 621 | -webkit-transform: rotate(-45deg); 622 | -moz-transform: rotate(-45deg); 623 | transform: rotate(-45deg); 624 | position: absolute; 625 | top: 0.325rem; 626 | left: 0.28125rem; 627 | width: 0.6375rem; 628 | height: 0.4rem; 629 | border: 3px solid #f9f5ed; 630 | border-top: 0; 631 | border-right: 0; 632 | content: ' '; 633 | opacity: 0; 634 | } 635 | 636 | .task-list-done { 637 | text-decoration: line-through; 638 | } 639 | 640 | .md-grid-board a:hover, 641 | .md-grid-board a.md-active { 642 | border-color: #a1a1a1; 643 | background: #f9edc5; 644 | } 645 | 646 | .md-grid-board tr[row='1'] a:hover, 647 | .md-grid-board tr[row='1'] a.md-active { 648 | background: #5e3e2b; 649 | } 650 | 651 | /* smart-press */ 652 | .content-wrapper { 653 | background: #f9f5ed; 654 | } 655 | 656 | .content { 657 | font-family: "PingFangSC-Regular", "Microsoft YaHei", arial, sans-serif; 658 | padding: 0 40px !important; 659 | } 660 | 661 | .content p { 662 | color: #666666; 663 | } 664 | 665 | h1, 666 | h2, 667 | h3, 668 | h4, 669 | h5, 670 | h6 { 671 | margin-top: 0.5em; 672 | margin-bottom: 0.1em; 673 | } -------------------------------------------------------------------------------- /src/template/seed/dist/js/demo.js: -------------------------------------------------------------------------------- 1 | /** 2 | * AdminLTE Demo Menu 3 | * ------------------ 4 | * You should not use this file in production. 5 | * This file is for demo purposes only. 6 | */ 7 | 8 | /* eslint-disable camelcase */ 9 | 10 | (function ($) { 11 | 'use strict' 12 | 13 | var $sidebar = $('.control-sidebar') 14 | var $container = $('
', { 15 | class: 'p-3 control-sidebar-content' 16 | }) 17 | 18 | $sidebar.append($container) 19 | 20 | var navbar_dark_skins = [ 21 | 'navbar-primary', 22 | 'navbar-secondary', 23 | 'navbar-info', 24 | 'navbar-success', 25 | 'navbar-danger', 26 | 'navbar-indigo', 27 | 'navbar-purple', 28 | 'navbar-pink', 29 | 'navbar-navy', 30 | 'navbar-lightblue', 31 | 'navbar-teal', 32 | 'navbar-cyan', 33 | 'navbar-dark', 34 | 'navbar-gray-dark', 35 | 'navbar-gray' 36 | ] 37 | 38 | var navbar_light_skins = [ 39 | 'navbar-light', 40 | 'navbar-warning', 41 | 'navbar-white', 42 | 'navbar-orange' 43 | ] 44 | 45 | $container.append( 46 | '
Customize AdminLTE

' 47 | ) 48 | 49 | var $no_border_checkbox = $('', { 50 | type: 'checkbox', 51 | value: 1, 52 | checked: $('.main-header').hasClass('border-bottom-0'), 53 | class: 'mr-1' 54 | }).on('click', function () { 55 | if ($(this).is(':checked')) { 56 | $('.main-header').addClass('border-bottom-0') 57 | } else { 58 | $('.main-header').removeClass('border-bottom-0') 59 | } 60 | }) 61 | var $no_border_container = $('
', { class: 'mb-1' }).append($no_border_checkbox).append('No Navbar border') 62 | $container.append($no_border_container) 63 | 64 | var $text_sm_body_checkbox = $('', { 65 | type: 'checkbox', 66 | value: 1, 67 | checked: $('body').hasClass('text-sm'), 68 | class: 'mr-1' 69 | }).on('click', function () { 70 | if ($(this).is(':checked')) { 71 | $('body').addClass('text-sm') 72 | } else { 73 | $('body').removeClass('text-sm') 74 | } 75 | }) 76 | var $text_sm_body_container = $('
', { class: 'mb-1' }).append($text_sm_body_checkbox).append('Body small text') 77 | $container.append($text_sm_body_container) 78 | 79 | var $text_sm_header_checkbox = $('', { 80 | type: 'checkbox', 81 | value: 1, 82 | checked: $('.main-header').hasClass('text-sm'), 83 | class: 'mr-1' 84 | }).on('click', function () { 85 | if ($(this).is(':checked')) { 86 | $('.main-header').addClass('text-sm') 87 | } else { 88 | $('.main-header').removeClass('text-sm') 89 | } 90 | }) 91 | var $text_sm_header_container = $('
', { class: 'mb-1' }).append($text_sm_header_checkbox).append('Navbar small text') 92 | $container.append($text_sm_header_container) 93 | 94 | var $text_sm_sidebar_checkbox = $('', { 95 | type: 'checkbox', 96 | value: 1, 97 | checked: $('.nav-sidebar').hasClass('text-sm'), 98 | class: 'mr-1' 99 | }).on('click', function () { 100 | if ($(this).is(':checked')) { 101 | $('.nav-sidebar').addClass('text-sm') 102 | } else { 103 | $('.nav-sidebar').removeClass('text-sm') 104 | } 105 | }) 106 | var $text_sm_sidebar_container = $('
', { class: 'mb-1' }).append($text_sm_sidebar_checkbox).append('Sidebar nav small text') 107 | $container.append($text_sm_sidebar_container) 108 | 109 | var $text_sm_footer_checkbox = $('', { 110 | type: 'checkbox', 111 | value: 1, 112 | checked: $('.main-footer').hasClass('text-sm'), 113 | class: 'mr-1' 114 | }).on('click', function () { 115 | if ($(this).is(':checked')) { 116 | $('.main-footer').addClass('text-sm') 117 | } else { 118 | $('.main-footer').removeClass('text-sm') 119 | } 120 | }) 121 | var $text_sm_footer_container = $('
', { class: 'mb-1' }).append($text_sm_footer_checkbox).append('Footer small text') 122 | $container.append($text_sm_footer_container) 123 | 124 | var $flat_sidebar_checkbox = $('', { 125 | type: 'checkbox', 126 | value: 1, 127 | checked: $('.nav-sidebar').hasClass('nav-flat'), 128 | class: 'mr-1' 129 | }).on('click', function () { 130 | if ($(this).is(':checked')) { 131 | $('.nav-sidebar').addClass('nav-flat') 132 | } else { 133 | $('.nav-sidebar').removeClass('nav-flat') 134 | } 135 | }) 136 | var $flat_sidebar_container = $('
', { class: 'mb-1' }).append($flat_sidebar_checkbox).append('Sidebar nav flat style') 137 | $container.append($flat_sidebar_container) 138 | 139 | var $legacy_sidebar_checkbox = $('', { 140 | type: 'checkbox', 141 | value: 1, 142 | checked: $('.nav-sidebar').hasClass('nav-legacy'), 143 | class: 'mr-1' 144 | }).on('click', function () { 145 | if ($(this).is(':checked')) { 146 | $('.nav-sidebar').addClass('nav-legacy') 147 | } else { 148 | $('.nav-sidebar').removeClass('nav-legacy') 149 | } 150 | }) 151 | var $legacy_sidebar_container = $('
', { class: 'mb-1' }).append($legacy_sidebar_checkbox).append('Sidebar nav legacy style') 152 | $container.append($legacy_sidebar_container) 153 | 154 | var $compact_sidebar_checkbox = $('', { 155 | type: 'checkbox', 156 | value: 1, 157 | checked: $('.nav-sidebar').hasClass('nav-compact'), 158 | class: 'mr-1' 159 | }).on('click', function () { 160 | if ($(this).is(':checked')) { 161 | $('.nav-sidebar').addClass('nav-compact') 162 | } else { 163 | $('.nav-sidebar').removeClass('nav-compact') 164 | } 165 | }) 166 | var $compact_sidebar_container = $('
', { class: 'mb-1' }).append($compact_sidebar_checkbox).append('Sidebar nav compact') 167 | $container.append($compact_sidebar_container) 168 | 169 | var $child_indent_sidebar_checkbox = $('', { 170 | type: 'checkbox', 171 | value: 1, 172 | checked: $('.nav-sidebar').hasClass('nav-child-indent'), 173 | class: 'mr-1' 174 | }).on('click', function () { 175 | if ($(this).is(':checked')) { 176 | $('.nav-sidebar').addClass('nav-child-indent') 177 | } else { 178 | $('.nav-sidebar').removeClass('nav-child-indent') 179 | } 180 | }) 181 | var $child_indent_sidebar_container = $('
', { class: 'mb-1' }).append($child_indent_sidebar_checkbox).append('Sidebar nav child indent') 182 | $container.append($child_indent_sidebar_container) 183 | 184 | var $child_hide_sidebar_checkbox = $('', { 185 | type: 'checkbox', 186 | value: 1, 187 | checked: $('.nav-sidebar').hasClass('nav-collapse-hide-child'), 188 | class: 'mr-1' 189 | }).on('click', function () { 190 | if ($(this).is(':checked')) { 191 | $('.nav-sidebar').addClass('nav-collapse-hide-child') 192 | } else { 193 | $('.nav-sidebar').removeClass('nav-collapse-hide-child') 194 | } 195 | }) 196 | var $child_hide_sidebar_container = $('
', { class: 'mb-1' }).append($child_hide_sidebar_checkbox).append('Sidebar nav child hide on collapse') 197 | $container.append($child_hide_sidebar_container) 198 | 199 | var $no_expand_sidebar_checkbox = $('', { 200 | type: 'checkbox', 201 | value: 1, 202 | checked: $('.main-sidebar').hasClass('sidebar-no-expand'), 203 | class: 'mr-1' 204 | }).on('click', function () { 205 | if ($(this).is(':checked')) { 206 | $('.main-sidebar').addClass('sidebar-no-expand') 207 | } else { 208 | $('.main-sidebar').removeClass('sidebar-no-expand') 209 | } 210 | }) 211 | var $no_expand_sidebar_container = $('
', { class: 'mb-1' }).append($no_expand_sidebar_checkbox).append('Main Sidebar disable hover/focus auto expand') 212 | $container.append($no_expand_sidebar_container) 213 | 214 | var $text_sm_brand_checkbox = $('', { 215 | type: 'checkbox', 216 | value: 1, 217 | checked: $('.brand-link').hasClass('text-sm'), 218 | class: 'mr-1' 219 | }).on('click', function () { 220 | if ($(this).is(':checked')) { 221 | $('.brand-link').addClass('text-sm') 222 | } else { 223 | $('.brand-link').removeClass('text-sm') 224 | } 225 | }) 226 | var $text_sm_brand_container = $('
', { class: 'mb-4' }).append($text_sm_brand_checkbox).append('Brand small text') 227 | $container.append($text_sm_brand_container) 228 | 229 | $container.append('
Navbar Variants
') 230 | 231 | var $navbar_variants = $('
', { 232 | class: 'd-flex' 233 | }) 234 | var navbar_all_colors = navbar_dark_skins.concat(navbar_light_skins) 235 | var $navbar_variants_colors = createSkinBlock(navbar_all_colors, function () { 236 | var color = $(this).data('color') 237 | var $main_header = $('.main-header') 238 | $main_header.removeClass('navbar-dark').removeClass('navbar-light') 239 | navbar_all_colors.forEach(function (color) { 240 | $main_header.removeClass(color) 241 | }) 242 | 243 | if (navbar_dark_skins.indexOf(color) > -1) { 244 | $main_header.addClass('navbar-dark') 245 | } else { 246 | $main_header.addClass('navbar-light') 247 | } 248 | 249 | $main_header.addClass(color) 250 | }) 251 | 252 | $navbar_variants.append($navbar_variants_colors) 253 | 254 | $container.append($navbar_variants) 255 | 256 | var sidebar_colors = [ 257 | 'bg-primary', 258 | 'bg-warning', 259 | 'bg-info', 260 | 'bg-danger', 261 | 'bg-success', 262 | 'bg-indigo', 263 | 'bg-lightblue', 264 | 'bg-navy', 265 | 'bg-purple', 266 | 'bg-fuchsia', 267 | 'bg-pink', 268 | 'bg-maroon', 269 | 'bg-orange', 270 | 'bg-lime', 271 | 'bg-teal', 272 | 'bg-olive' 273 | ] 274 | 275 | var accent_colors = [ 276 | 'accent-primary', 277 | 'accent-warning', 278 | 'accent-info', 279 | 'accent-danger', 280 | 'accent-success', 281 | 'accent-indigo', 282 | 'accent-lightblue', 283 | 'accent-navy', 284 | 'accent-purple', 285 | 'accent-fuchsia', 286 | 'accent-pink', 287 | 'accent-maroon', 288 | 'accent-orange', 289 | 'accent-lime', 290 | 'accent-teal', 291 | 'accent-olive' 292 | ] 293 | 294 | var sidebar_skins = [ 295 | 'sidebar-dark-primary', 296 | 'sidebar-dark-warning', 297 | 'sidebar-dark-info', 298 | 'sidebar-dark-danger', 299 | 'sidebar-dark-success', 300 | 'sidebar-dark-indigo', 301 | 'sidebar-dark-lightblue', 302 | 'sidebar-dark-navy', 303 | 'sidebar-dark-purple', 304 | 'sidebar-dark-fuchsia', 305 | 'sidebar-dark-pink', 306 | 'sidebar-dark-maroon', 307 | 'sidebar-dark-orange', 308 | 'sidebar-dark-lime', 309 | 'sidebar-dark-teal', 310 | 'sidebar-dark-olive', 311 | 'sidebar-light-primary', 312 | 'sidebar-light-warning', 313 | 'sidebar-light-info', 314 | 'sidebar-light-danger', 315 | 'sidebar-light-success', 316 | 'sidebar-light-indigo', 317 | 'sidebar-light-lightblue', 318 | 'sidebar-light-navy', 319 | 'sidebar-light-purple', 320 | 'sidebar-light-fuchsia', 321 | 'sidebar-light-pink', 322 | 'sidebar-light-maroon', 323 | 'sidebar-light-orange', 324 | 'sidebar-light-lime', 325 | 'sidebar-light-teal', 326 | 'sidebar-light-olive' 327 | ] 328 | 329 | $container.append('
Accent Color Variants
') 330 | var $accent_variants = $('
', { 331 | class: 'd-flex' 332 | }) 333 | $container.append($accent_variants) 334 | $container.append(createSkinBlock(accent_colors, function () { 335 | var color = $(this).data('color') 336 | var accent_class = color 337 | var $body = $('body') 338 | accent_colors.forEach(function (skin) { 339 | $body.removeClass(skin) 340 | }) 341 | 342 | $body.addClass(accent_class) 343 | })) 344 | 345 | $container.append('
Dark Sidebar Variants
') 346 | var $sidebar_variants_dark = $('
', { 347 | class: 'd-flex' 348 | }) 349 | $container.append($sidebar_variants_dark) 350 | $container.append(createSkinBlock(sidebar_colors, function () { 351 | var color = $(this).data('color') 352 | var sidebar_class = 'sidebar-dark-' + color.replace('bg-', '') 353 | var $sidebar = $('.main-sidebar') 354 | sidebar_skins.forEach(function (skin) { 355 | $sidebar.removeClass(skin) 356 | }) 357 | 358 | $sidebar.addClass(sidebar_class) 359 | })) 360 | 361 | $container.append('
Light Sidebar Variants
') 362 | var $sidebar_variants_light = $('
', { 363 | class: 'd-flex' 364 | }) 365 | $container.append($sidebar_variants_light) 366 | $container.append(createSkinBlock(sidebar_colors, function () { 367 | var color = $(this).data('color') 368 | var sidebar_class = 'sidebar-light-' + color.replace('bg-', '') 369 | var $sidebar = $('.main-sidebar') 370 | sidebar_skins.forEach(function (skin) { 371 | $sidebar.removeClass(skin) 372 | }) 373 | 374 | $sidebar.addClass(sidebar_class) 375 | })) 376 | 377 | var logo_skins = navbar_all_colors 378 | $container.append('
Brand Logo Variants
') 379 | var $logo_variants = $('
', { 380 | class: 'd-flex' 381 | }) 382 | $container.append($logo_variants) 383 | var $clear_btn = $('', { 384 | href: '#' 385 | }).text('clear').on('click', function (e) { 386 | e.preventDefault() 387 | var $logo = $('.brand-link') 388 | logo_skins.forEach(function (skin) { 389 | $logo.removeClass(skin) 390 | }) 391 | }) 392 | $container.append(createSkinBlock(logo_skins, function () { 393 | var color = $(this).data('color') 394 | var $logo = $('.brand-link') 395 | logo_skins.forEach(function (skin) { 396 | $logo.removeClass(skin) 397 | }) 398 | $logo.addClass(color) 399 | }).append($clear_btn)) 400 | 401 | function createSkinBlock(colors, callback) { 402 | var $block = $('
', { 403 | class: 'd-flex flex-wrap mb-3' 404 | }) 405 | 406 | colors.forEach(function (color) { 407 | var $color = $('
', { 408 | class: (typeof color === 'object' ? color.join(' ') : color).replace('navbar-', 'bg-').replace('accent-', 'bg-') + ' elevation-2' 409 | }) 410 | 411 | $block.append($color) 412 | 413 | $color.data('color', color) 414 | 415 | $color.css({ 416 | width: '40px', 417 | height: '20px', 418 | borderRadius: '25px', 419 | marginRight: 10, 420 | marginBottom: 10, 421 | opacity: 0.8, 422 | cursor: 'pointer' 423 | }) 424 | 425 | $color.hover(function () { 426 | $(this).css({ opacity: 1 }).removeClass('elevation-2').addClass('elevation-4') 427 | }, function () { 428 | $(this).css({ opacity: 0.8 }).removeClass('elevation-4').addClass('elevation-2') 429 | }) 430 | 431 | if (callback) { 432 | $color.on('click', callback) 433 | } 434 | }) 435 | 436 | return $block 437 | } 438 | 439 | $('.product-image-thumb').on('click', function () { 440 | var image_element = $(this).find('img') 441 | $('.product-image').prop('src', $(image_element).attr('src')) 442 | $('.product-image-thumb.active').removeClass('active') 443 | $(this).addClass('active') 444 | }) 445 | })(jQuery) 446 | -------------------------------------------------------------------------------- /src/assets/adminlte.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * AdminLTE v3.0.6-pre (https://adminlte.io) 3 | * Copyright 2014-2020 Colorlib 4 | * Licensed under MIT (https://github.com/ColorlibHQ/AdminLTE/blob/master/LICENSE) 5 | */ 6 | !function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports):"function"==typeof define&&define.amd?define(["exports"],e):e((t=t||self).adminlte={})}(this,(function(t){"use strict";var e=function(t){var e="ControlSidebar",i="lte.controlsidebar",n=t.fn[e],s={COLLAPSED:"collapsed.lte.controlsidebar",EXPANDED:"expanded.lte.controlsidebar"},o=".control-sidebar",a=".control-sidebar-content",r='[data-widget="control-sidebar"]',l=".main-header",c=".main-footer",d="control-sidebar-animate",h="control-sidebar-open",f="control-sidebar-slide-open",u="layout-fixed",g="layout-navbar-fixed",p="layout-sm-navbar-fixed",_="layout-md-navbar-fixed",m="layout-lg-navbar-fixed",v="layout-xl-navbar-fixed",C="layout-footer-fixed",y="layout-sm-footer-fixed",b="layout-md-footer-fixed",w="layout-lg-footer-fixed",x="layout-xl-footer-fixed",E={controlsidebarSlide:!0,scrollbarTheme:"os-theme-light",scrollbarAutoHide:"l"},A=function(){function e(t,e){this._element=t,this._config=e,this._init()}var n=e.prototype;return n.collapse=function(){this._config.controlsidebarSlide?(t("html").addClass(d),t("body").removeClass(f).delay(300).queue((function(){t(o).hide(),t("html").removeClass(d),t(this).dequeue()}))):t("body").removeClass(h);var e=t.Event(s.COLLAPSED);t(this._element).trigger(e)},n.show=function(){this._config.controlsidebarSlide?(t("html").addClass(d),t(o).show().delay(10).queue((function(){t("body").addClass(f).delay(300).queue((function(){t("html").removeClass(d),t(this).dequeue()})),t(this).dequeue()}))):t("body").addClass(h);var e=t.Event(s.EXPANDED);t(this._element).trigger(e)},n.toggle=function(){t("body").hasClass(h)||t("body").hasClass(f)?this.collapse():this.show()},n._init=function(){var e=this;this._fixHeight(),this._fixScrollHeight(),t(window).resize((function(){e._fixHeight(),e._fixScrollHeight()})),t(window).scroll((function(){(t("body").hasClass(h)||t("body").hasClass(f))&&e._fixScrollHeight()}))},n._fixScrollHeight=function(){var e={scroll:t(document).height(),window:t(window).height(),header:t(l).outerHeight(),footer:t(c).outerHeight()},i=Math.abs(e.window+t(window).scrollTop()-e.scroll),n=t(window).scrollTop(),s=!1,r=!1;t("body").hasClass(u)&&((t("body").hasClass(g)||t("body").hasClass(p)||t("body").hasClass(_)||t("body").hasClass(m)||t("body").hasClass(v))&&"fixed"===t(l).css("position")&&(s=!0),(t("body").hasClass(C)||t("body").hasClass(y)||t("body").hasClass(b)||t("body").hasClass(w)||t("body").hasClass(x))&&"fixed"===t(c).css("position")&&(r=!0),0===n&&0===i?(t(o).css("bottom",e.footer),t(o).css("top",e.header),t(o+", "+o+" "+a).css("height",e.window-(e.header+e.footer))):i<=e.footer?!1===r?(t(o).css("bottom",e.footer-i),t(o+", "+o+" "+a).css("height",e.window-(e.footer-i))):t(o).css("bottom",e.footer):n<=e.header?!1===s?(t(o).css("top",e.header-n),t(o+", "+o+" "+a).css("height",e.window-(e.header-n))):t(o).css("top",e.header):!1===s?(t(o).css("top",0),t(o+", "+o+" "+a).css("height",e.window)):t(o).css("top",e.header))},n._fixHeight=function(){var e=t(window).height(),i=t(l).outerHeight(),n=t(c).outerHeight();if(t("body").hasClass(u)){var s=e-i;(t("body").hasClass(C)||t("body").hasClass(y)||t("body").hasClass(b)||t("body").hasClass(w)||t("body").hasClass(x))&&"fixed"===t(c).css("position")&&(s=e-i-n),t(o+" "+a).css("height",s),"undefined"!=typeof t.fn.overlayScrollbars&&t(o+" "+a).overlayScrollbars({className:this._config.scrollbarTheme,sizeAutoCapable:!0,scrollbars:{autoHide:this._config.scrollbarAutoHide,clickScrolling:!0}})}},e._jQueryInterface=function(n){return this.each((function(){var s=t(this).data(i),o=t.extend({},E,t(this).data());if(s||(s=new e(this,o),t(this).data(i,s)),"undefined"===s[n])throw new Error(n+" is not a function");s[n]()}))},e}();return t(document).on("click",r,(function(e){e.preventDefault(),A._jQueryInterface.call(t(this),"toggle")})),t.fn[e]=A._jQueryInterface,t.fn[e].Constructor=A,t.fn[e].noConflict=function(){return t.fn[e]=n,A._jQueryInterface},A}(jQuery),i=function(t){var e="Layout",i=t.fn[e],n=".main-header",s=".main-sidebar",o=".main-sidebar .sidebar",a=".content-wrapper",r=".control-sidebar-content",l='[data-widget="control-sidebar"]',c=".main-footer",d='[data-widget="pushmenu"]',h=".login-box",f=".register-box",u="sidebar-focused",g="layout-fixed",p="control-sidebar-slide-open",_="control-sidebar-open",m={scrollbarTheme:"os-theme-light",scrollbarAutoHide:"l",panelAutoHeight:!0,loginRegisterAutoHeight:!0},v=function(){function e(t,e){this._config=e,this._element=t,this._init()}var i=e.prototype;return i.fixLayoutHeight=function(e){void 0===e&&(e=null);var i=0;(t("body").hasClass(p)||t("body").hasClass(_)||"control_sidebar"==e)&&(i=t(r).height());var s={window:t(window).height(),header:0!==t(n).length?t(n).outerHeight():0,footer:0!==t(c).length?t(c).outerHeight():0,sidebar:0!==t(o).length?t(o).height():0,control_sidebar:i},l=this._max(s),d=this._config.panelAutoHeight;!0===d&&(d=0),!1!==d&&(l==s.control_sidebar?t(a).css("min-height",l+d):l==s.window?t(a).css("min-height",l+d-s.header-s.footer):t(a).css("min-height",l+d-s.header),this._isFooterFixed()&&t(a).css("min-height",parseFloat(t(a).css("min-height"))+s.footer)),t("body").hasClass(g)&&(!1!==d&&t(a).css("min-height",l+d-s.header-s.footer),"undefined"!=typeof t.fn.overlayScrollbars&&t(o).overlayScrollbars({className:this._config.scrollbarTheme,sizeAutoCapable:!0,scrollbars:{autoHide:this._config.scrollbarAutoHide,clickScrolling:!0}}))},i.fixLoginRegisterHeight=function(){if(0===t(h+", "+f).length)t("body, html").css("height","auto");else if(0!==t(h+", "+f).length){var e=t(h+", "+f).height();t("body").css("min-height")!==e&&t("body").css("min-height",e)}},i._init=function(){var e=this;this.fixLayoutHeight(),!0===this._config.loginRegisterAutoHeight?this.fixLoginRegisterHeight():Number.isInteger(this._config.loginRegisterAutoHeight)&&setInterval(this.fixLoginRegisterHeight,this._config.loginRegisterAutoHeight),t(o).on("collapsed.lte.treeview expanded.lte.treeview",(function(){e.fixLayoutHeight()})),t(d).on("collapsed.lte.pushmenu shown.lte.pushmenu",(function(){e.fixLayoutHeight()})),t(l).on("collapsed.lte.controlsidebar",(function(){e.fixLayoutHeight()})).on("expanded.lte.controlsidebar",(function(){e.fixLayoutHeight("control_sidebar")})),t(window).resize((function(){e.fixLayoutHeight()})),setTimeout((function(){t("body.hold-transition").removeClass("hold-transition")}),50)},i._max=function(t){var e=0;return Object.keys(t).forEach((function(i){t[i]>e&&(e=t[i])})),e},i._isFooterFixed=function(){return"fixed"===t(".main-footer").css("position")},e._jQueryInterface=function(i){return void 0===i&&(i=""),this.each((function(){var n=t(this).data("lte.layout"),s=t.extend({},m,t(this).data());n||(n=new e(t(this),s),t(this).data("lte.layout",n)),"init"===i||""===i?n._init():"fixLayoutHeight"!==i&&"fixLoginRegisterHeight"!==i||n[i]()}))},e}();return t(window).on("load",(function(){v._jQueryInterface.call(t("body"))})),t(o+" a").on("focusin",(function(){t(s).addClass(u)})),t(o+" a").on("focusout",(function(){t(s).removeClass(u)})),t.fn[e]=v._jQueryInterface,t.fn[e].Constructor=v,t.fn[e].noConflict=function(){return t.fn[e]=i,v._jQueryInterface},v}(jQuery),n=function(t){var e="PushMenu",i=".lte.pushmenu",n=t.fn[e],s={COLLAPSED:"collapsed"+i,SHOWN:"shown"+i},o={autoCollapseSize:992,enableRemember:!1,noTransitionAfterReload:!0},a='[data-widget="pushmenu"]',r="body",l="#sidebar-overlay",c=".wrapper",d="sidebar-collapse",h="sidebar-open",f="sidebar-closed",u=function(){function e(e,i){this._element=e,this._options=t.extend({},o,i),t(l).length||this._addOverlay(),this._init()}var n=e.prototype;return n.expand=function(){this._options.autoCollapseSize&&t(window).width()<=this._options.autoCollapseSize&&t(r).addClass(h),t(r).removeClass(d).removeClass(f),this._options.enableRemember&&localStorage.setItem("remember"+i,h);var e=t.Event(s.SHOWN);t(this._element).trigger(e)},n.collapse=function(){this._options.autoCollapseSize&&t(window).width()<=this._options.autoCollapseSize&&t(r).removeClass(h).addClass(f),t(r).addClass(d),this._options.enableRemember&&localStorage.setItem("remember"+i,d);var e=t.Event(s.COLLAPSED);t(this._element).trigger(e)},n.toggle=function(){t(r).hasClass(d)?this.expand():this.collapse()},n.autoCollapse=function(e){void 0===e&&(e=!1),this._options.autoCollapseSize&&(t(window).width()<=this._options.autoCollapseSize?t(r).hasClass(h)||this.collapse():1==e&&(t(r).hasClass(h)?t(r).removeClass(h):t(r).hasClass(f)&&this.expand()))},n.remember=function(){this._options.enableRemember&&(localStorage.getItem("remember"+i)==d?this._options.noTransitionAfterReload?t("body").addClass("hold-transition").addClass(d).delay(50).queue((function(){t(this).removeClass("hold-transition"),t(this).dequeue()})):t("body").addClass(d):this._options.noTransitionAfterReload?t("body").addClass("hold-transition").removeClass(d).delay(50).queue((function(){t(this).removeClass("hold-transition"),t(this).dequeue()})):t("body").removeClass(d))},n._init=function(){var e=this;this.remember(),this.autoCollapse(),t(window).resize((function(){e.autoCollapse(!0)}))},n._addOverlay=function(){var e=this,i=t("
",{id:"sidebar-overlay"});i.on("click",(function(){e.collapse()})),t(c).append(i)},e._jQueryInterface=function(i){return this.each((function(){var n=t(this).data("lte.pushmenu"),s=t.extend({},o,t(this).data());n||(n=new e(this,s),t(this).data("lte.pushmenu",n)),"string"==typeof i&&i.match(/collapse|expand|toggle/)&&n[i]()}))},e}();return t(document).on("click",a,(function(e){e.preventDefault();var i=e.currentTarget;"pushmenu"!==t(i).data("widget")&&(i=t(i).closest(a)),u._jQueryInterface.call(t(i),"toggle")})),t(window).on("load",(function(){u._jQueryInterface.call(t(a))})),t.fn[e]=u._jQueryInterface,t.fn[e].Constructor=u,t.fn[e].noConflict=function(){return t.fn[e]=n,u._jQueryInterface},u}(jQuery),s=function(t){var e="Treeview",i=t.fn[e],n={SELECTED:"selected.lte.treeview",EXPANDED:"expanded.lte.treeview",COLLAPSED:"collapsed.lte.treeview",LOAD_DATA_API:"load.lte.treeview"},s=".nav-item",o=".nav-treeview",a=".menu-open",r='[data-widget="treeview"]',l="menu-open",c="sidebar-collapse",d={trigger:r+" "+".nav-link",animationSpeed:300,accordion:!0,expandSidebar:!1,sidebarButtonSelector:'[data-widget="pushmenu"]'},h=function(){function e(t,e){this._config=e,this._element=t}var i=e.prototype;return i.init=function(){this._setupListeners()},i.expand=function(e,i){var s=this,r=t.Event(n.EXPANDED);if(this._config.accordion){var c=i.siblings(a).first(),d=c.find(o).first();this.collapse(d,c)}e.stop().slideDown(this._config.animationSpeed,(function(){i.addClass(l),t(s._element).trigger(r)})),this._config.expandSidebar&&this._expandSidebar()},i.collapse=function(e,i){var s=this,r=t.Event(n.COLLAPSED);e.stop().slideUp(this._config.animationSpeed,(function(){i.removeClass(l),t(s._element).trigger(r),e.find(a+" > "+o).slideUp(),e.find(a).removeClass(l)}))},i.toggle=function(e){var i=t(e.currentTarget),n=i.parent(),a=n.find("> "+o);if(a.is(o)||(n.is(s)||(a=n.parent().find("> "+o)),a.is(o))){e.preventDefault();var r=i.parents(s).first();r.hasClass(l)?this.collapse(t(a),r):this.expand(t(a),r)}},i._setupListeners=function(){var e=this;t(document).on("click",this._config.trigger,(function(t){e.toggle(t)}))},i._expandSidebar=function(){t("body").hasClass(c)&&t(this._config.sidebarButtonSelector).PushMenu("expand")},e._jQueryInterface=function(i){return this.each((function(){var n=t(this).data("lte.treeview"),s=t.extend({},d,t(this).data());n||(n=new e(t(this),s),t(this).data("lte.treeview",n)),"init"===i&&n[i]()}))},e}();return t(window).on(n.LOAD_DATA_API,(function(){t(r).each((function(){h._jQueryInterface.call(t(this),"init")}))})),t.fn[e]=h._jQueryInterface,t.fn[e].Constructor=h,t.fn[e].noConflict=function(){return t.fn[e]=i,h._jQueryInterface},h}(jQuery),o=function(t){var e="DirectChat",i=t.fn[e],n="toggled{EVENT_KEY}",s='[data-widget="chat-pane-toggle"]',o=".direct-chat",a="direct-chat-contacts-open",r=function(){function e(t,e){this._element=t}return e.prototype.toggle=function(){t(this._element).parents(o).first().toggleClass(a);var e=t.Event(n);t(this._element).trigger(e)},e._jQueryInterface=function(i){return this.each((function(){var n=t(this).data("lte.directchat");n||(n=new e(t(this)),t(this).data("lte.directchat",n)),n[i]()}))},e}();return t(document).on("click",s,(function(e){e&&e.preventDefault(),r._jQueryInterface.call(t(this),"toggle")})),t.fn[e]=r._jQueryInterface,t.fn[e].Constructor=r,t.fn[e].noConflict=function(){return t.fn[e]=i,r._jQueryInterface},r}(jQuery),a=function(t){var e="TodoList",i=t.fn[e],n='[data-widget="todo-list"]',s="done",o={onCheck:function(t){return t},onUnCheck:function(t){return t}},a=function(){function e(t,e){this._config=e,this._element=t,this._init()}var i=e.prototype;return i.toggle=function(e){e.parents("li").toggleClass(s),t(e).prop("checked")?this.check(e):this.unCheck(t(e))},i.check=function(t){this._config.onCheck.call(t)},i.unCheck=function(t){this._config.onUnCheck.call(t)},i._init=function(){var e=this;t(n).find("input:checkbox:checked").parents("li").toggleClass(s),t(n).on("change","input:checkbox",(function(i){e.toggle(t(i.target))}))},e._jQueryInterface=function(i){return this.each((function(){var n=t(this).data("lte.todolist"),s=t.extend({},o,t(this).data());n||(n=new e(t(this),s),t(this).data("lte.todolist",n)),"init"===i&&n[i]()}))},e}();return t(window).on("load",(function(){a._jQueryInterface.call(t(n))})),t.fn[e]=a._jQueryInterface,t.fn[e].Constructor=a,t.fn[e].noConflict=function(){return t.fn[e]=i,a._jQueryInterface},a}(jQuery),r=function(t){var e="CardWidget",i=".lte.cardwidget",n=t.fn[e],s={EXPANDED:"expanded"+i,COLLAPSED:"collapsed"+i,MAXIMIZED:"maximized"+i,MINIMIZED:"minimized"+i,REMOVED:"removed"+i},o="card",a="collapsed-card",r="collapsing-card",l="expanding-card",c="was-collapsed",d="maximized-card",h={DATA_REMOVE:'[data-card-widget="remove"]',DATA_COLLAPSE:'[data-card-widget="collapse"]',DATA_MAXIMIZE:'[data-card-widget="maximize"]',CARD:"."+o,CARD_HEADER:".card-header",CARD_BODY:".card-body",CARD_FOOTER:".card-footer",COLLAPSED:"."+a},f={animationSpeed:"normal",collapseTrigger:h.DATA_COLLAPSE,removeTrigger:h.DATA_REMOVE,maximizeTrigger:h.DATA_MAXIMIZE,collapseIcon:"fa-minus",expandIcon:"fa-plus",maximizeIcon:"fa-expand",minimizeIcon:"fa-compress"},u=function(){function e(e,i){this._element=e,this._parent=e.parents(h.CARD).first(),e.hasClass(o)&&(this._parent=e),this._settings=t.extend({},f,i)}var i=e.prototype;return i.collapse=function(){var e=this;this._parent.addClass(r).children(h.CARD_BODY+", "+h.CARD_FOOTER).slideUp(this._settings.animationSpeed,(function(){e._parent.addClass(a).removeClass(r)})),this._parent.find("> "+h.CARD_HEADER+" "+this._settings.collapseTrigger+" ."+this._settings.collapseIcon).addClass(this._settings.expandIcon).removeClass(this._settings.collapseIcon);var i=t.Event(s.COLLAPSED);this._element.trigger(i,this._parent)},i.expand=function(){var e=this;this._parent.addClass(l).children(h.CARD_BODY+", "+h.CARD_FOOTER).slideDown(this._settings.animationSpeed,(function(){e._parent.removeClass(a).removeClass(l)})),this._parent.find("> "+h.CARD_HEADER+" "+this._settings.collapseTrigger+" ."+this._settings.expandIcon).addClass(this._settings.collapseIcon).removeClass(this._settings.expandIcon);var i=t.Event(s.EXPANDED);this._element.trigger(i,this._parent)},i.remove=function(){this._parent.slideUp();var e=t.Event(s.REMOVED);this._element.trigger(e,this._parent)},i.toggle=function(){this._parent.hasClass(a)?this.expand():this.collapse()},i.maximize=function(){this._parent.find(this._settings.maximizeTrigger+" ."+this._settings.maximizeIcon).addClass(this._settings.minimizeIcon).removeClass(this._settings.maximizeIcon),this._parent.css({height:this._parent.height(),width:this._parent.width(),transition:"all .15s"}).delay(150).queue((function(){t(this).addClass(d),t("html").addClass(d),t(this).hasClass(a)&&t(this).addClass(c),t(this).dequeue()}));var e=t.Event(s.MAXIMIZED);this._element.trigger(e,this._parent)},i.minimize=function(){this._parent.find(this._settings.maximizeTrigger+" ."+this._settings.minimizeIcon).addClass(this._settings.maximizeIcon).removeClass(this._settings.minimizeIcon),this._parent.css("cssText","height:"+this._parent[0].style.height+" !important;width:"+this._parent[0].style.width+" !important; transition: all .15s;").delay(10).queue((function(){t(this).removeClass(d),t("html").removeClass(d),t(this).css({height:"inherit",width:"inherit"}),t(this).hasClass(c)&&t(this).removeClass(c),t(this).dequeue()}));var e=t.Event(s.MINIMIZED);this._element.trigger(e,this._parent)},i.toggleMaximize=function(){this._parent.hasClass(d)?this.minimize():this.maximize()},i._init=function(e){var i=this;this._parent=e,t(this).find(this._settings.collapseTrigger).click((function(){i.toggle()})),t(this).find(this._settings.maximizeTrigger).click((function(){i.toggleMaximize()})),t(this).find(this._settings.removeTrigger).click((function(){i.remove()}))},e._jQueryInterface=function(i){var n=t(this).data("lte.cardwidget"),s=t.extend({},f,t(this).data());n||(n=new e(t(this),s),t(this).data("lte.cardwidget","string"==typeof i?n:i)),"string"==typeof i&&i.match(/collapse|expand|remove|toggle|maximize|minimize|toggleMaximize/)?n[i]():"object"==typeof i&&n._init(t(this))},e}();return t(document).on("click",h.DATA_COLLAPSE,(function(e){e&&e.preventDefault(),u._jQueryInterface.call(t(this),"toggle")})),t(document).on("click",h.DATA_REMOVE,(function(e){e&&e.preventDefault(),u._jQueryInterface.call(t(this),"remove")})),t(document).on("click",h.DATA_MAXIMIZE,(function(e){e&&e.preventDefault(),u._jQueryInterface.call(t(this),"toggleMaximize")})),t.fn[e]=u._jQueryInterface,t.fn[e].Constructor=u,t.fn[e].noConflict=function(){return t.fn[e]=n,u._jQueryInterface},u}(jQuery),l=function(t){var e="CardRefresh",i=t.fn[e],n={LOADED:"loaded.lte.cardrefresh",OVERLAY_ADDED:"overlay.added.lte.cardrefresh",OVERLAY_REMOVED:"overlay.removed.lte.cardrefresh"},s="card",o={CARD:"."+s,DATA_REFRESH:'[data-card-widget="card-refresh"]'},a={source:"",sourceSelector:"",params:{},trigger:o.DATA_REFRESH,content:".card-body",loadInContent:!0,loadOnInit:!0,responseType:"",overlayTemplate:'
',onLoadStart:function(){},onLoadDone:function(t){return t}},r=function(){function e(e,i){if(this._element=e,this._parent=e.parents(o.CARD).first(),this._settings=t.extend({},a,i),this._overlay=t(this._settings.overlayTemplate),e.hasClass(s)&&(this._parent=e),""===this._settings.source)throw new Error("Source url was not defined. Please specify a url in your CardRefresh source option.")}var i=e.prototype;return i.load=function(){this._addOverlay(),this._settings.onLoadStart.call(t(this)),t.get(this._settings.source,this._settings.params,function(e){this._settings.loadInContent&&(""!=this._settings.sourceSelector&&(e=t(e).find(this._settings.sourceSelector).html()),this._parent.find(this._settings.content).html(e)),this._settings.onLoadDone.call(t(this),e),this._removeOverlay()}.bind(this),""!==this._settings.responseType&&this._settings.responseType);var e=t.Event(n.LOADED);t(this._element).trigger(e)},i._addOverlay=function(){this._parent.append(this._overlay);var e=t.Event(n.OVERLAY_ADDED);t(this._element).trigger(e)},i._removeOverlay=function(){this._parent.find(this._overlay).remove();var e=t.Event(n.OVERLAY_REMOVED);t(this._element).trigger(e)},i._init=function(e){var i=this;t(this).find(this._settings.trigger).on("click",(function(){i.load()})),this._settings.loadOnInit&&this.load()},e._jQueryInterface=function(i){var n=t(this).data("lte.cardrefresh"),s=t.extend({},a,t(this).data());n||(n=new e(t(this),s),t(this).data("lte.cardrefresh","string"==typeof i?n:i)),"string"==typeof i&&i.match(/load/)?n[i]():n._init(t(this))},e}();return t(document).on("click",o.DATA_REFRESH,(function(e){e&&e.preventDefault(),r._jQueryInterface.call(t(this),"load")})),t(document).ready((function(){t(o.DATA_REFRESH).each((function(){r._jQueryInterface.call(t(this))}))})),t.fn[e]=r._jQueryInterface,t.fn[e].Constructor=r,t.fn[e].noConflict=function(){return t.fn[e]=i,r._jQueryInterface},r}(jQuery),c=function(t){var e="Dropdown",i=t.fn[e],n=".navbar",s=".dropdown-menu",o=".dropdown-menu.show",a='[data-toggle="dropdown"]',r="dropdown-menu-right",l={},c=function(){function e(t,e){this._config=e,this._element=t}var i=e.prototype;return i.toggleSubmenu=function(){this._element.siblings().show().toggleClass("show"),this._element.next().hasClass("show")||this._element.parents(".dropdown-menu").first().find(".show").removeClass("show").hide(),this._element.parents("li.nav-item.dropdown.show").on("hidden.bs.dropdown",(function(e){t(".dropdown-submenu .show").removeClass("show").hide()}))},i.fixPosition=function(){var e=t(o);if(0!==e.length){e.hasClass(r)?(e.css("left","inherit"),e.css("right",0)):(e.css("left",0),e.css("right","inherit"));var i=e.offset(),n=e.width(),s=t(window).width()-i.left;i.left<0?(e.css("left","inherit"),e.css("right",i.left-5)):s');e.data("autohide",this._config.autohide),e.data("animation",this._config.fade),this._config.class&&e.addClass(this._config.class),this._config.delay&&500!=this._config.delay&&e.data("delay",this._config.delay);var i=t('
');if(null!=this._config.image){var s=t("").addClass("rounded mr-2").attr("src",this._config.image).attr("alt",this._config.imageAlt);null!=this._config.imageHeight&&s.height(this._config.imageHeight).width("auto"),i.append(s)}if(null!=this._config.icon&&i.append(t("").addClass("mr-2").addClass(this._config.icon)),null!=this._config.title&&i.append(t("").addClass("mr-auto").html(this._config.title)),null!=this._config.subtitle&&i.append(t("").html(this._config.subtitle)),1==this._config.close){var o=t('