diff --git a/.air.toml b/.air.toml new file mode 100644 index 0000000..061113f --- /dev/null +++ b/.air.toml @@ -0,0 +1,51 @@ +root = "." +testdata_dir = "testdata" +tmp_dir = "tmp" + +[build] +args_bin = [] +bin = "./tmp/main" +cmd = "go build -o ./tmp/main ." +delay = 1000 +exclude_dir = ["assets", "tmp", "vendor", "testdata"] +exclude_file = [] +exclude_regex = ["_test.go"] +exclude_unchanged = false +follow_symlink = false +full_bin = "./tmp/main picgo ./deploy/picgo-dev.yml" +include_dir = [] +include_ext = ["go", "html", "js", "css", "yml"] +include_file = [] +kill_delay = "0s" +log = "build-errors.log" +poll = false +poll_interval = 0 +post_cmd = [] +pre_cmd = [] +rerun = false +rerun_delay = 500 +send_interrupt = false +stop_on_error = false + +[color] +app = "" +build = "yellow" +main = "magenta" +runner = "green" +watcher = "cyan" + +[log] +main_only = false +time = false + +[misc] +clean_on_exit = false + +[proxy] +app_port = 0 +enabled = false +proxy_port = 0 + +[screen] +clear_on_rebuild = false +keep_scroll = true diff --git a/.gitignore b/.gitignore index 8b00ae1..011bc6e 100644 --- a/.gitignore +++ b/.gitignore @@ -220,3 +220,5 @@ $RECYCLE.BIN/ *.lnk .idea + +tmp diff --git a/configs/captcha.go b/configs/captcha.go new file mode 100644 index 0000000..912842b --- /dev/null +++ b/configs/captcha.go @@ -0,0 +1,8 @@ +package configs + +type Captcha struct { + Expire int `yaml:"expire"` + Width int `yaml:"width"` + Hight int `yaml:"hight"` + Length int `yaml:"length"` +} diff --git a/configs/mysql.go b/configs/mysql.go new file mode 100644 index 0000000..779c015 --- /dev/null +++ b/configs/mysql.go @@ -0,0 +1,7 @@ +package configs + +type Mysql struct { + MysqlDNS string `yaml:"mysql_dns"` + MaxIdleConns int `yaml:"max_idle_conns"` + MaxOpenConns int `yaml:"max_open_conns"` +} diff --git a/configs/redis.go b/configs/redis.go new file mode 100644 index 0000000..69d2e1b --- /dev/null +++ b/configs/redis.go @@ -0,0 +1,9 @@ +package configs + +type Redis struct { + Addr string `yaml:"addr"` + Password string `yaml:"password"` + DB int `yaml:"db"` + PoolSize int `yaml:"pool_size"` + MinidleConns int `yaml:"minidle_conns"` +} diff --git a/configs/server.go b/configs/server.go new file mode 100644 index 0000000..5165322 --- /dev/null +++ b/configs/server.go @@ -0,0 +1,14 @@ +package configs + +type Server struct { + Port int64 `yaml:"port"` + Host string `yaml:"host"` + NodeId int64 `yaml:"node_id"` + LogPath string `yaml:"log_path"` + Environment string `yaml:"environment"` + StaticPath string `yaml:"static_path"` + UploadPath string `yaml:"upload_path"` + UploadMaxSize int64 `yaml:"upload_max_size"` + SessionsKey string `yaml:"sessions_key"` + SessionName string `yaml:"session_name"` +} diff --git a/corelib/captcha/captcha.go b/corelib/captcha/captcha.go new file mode 100644 index 0000000..2632f19 --- /dev/null +++ b/corelib/captcha/captcha.go @@ -0,0 +1,68 @@ +package captcha + +import ( + "image" + "os" + "path/filepath" + "picgo/configs" + "picgo/corelib" + "strings" +) + +// GenerateCaptcha 图形验证码 +func GenerateCaptcha(cid string) (img *image.RGBA, err error) { + // 字体路径 + dir, err := os.Getwd() + if err != nil { + return + } + var fontPath = filepath.Join(dir, "corelib", "captcha", "fonts") + // 随机字体 + var fontNames = []string{"DENNEthree-dee", "3Dumb"} + fontName := RandChooseOne(fontNames) + // 随机模式 + var modes = []int{0, 1} + mode := RandChooseOne(modes) + // 生成图片验证码 + captchaLen := configs.Settings.Captcha.Length + w := configs.Settings.Captcha.Width + h := configs.Settings.Captcha.Hight + cp := NewCaptcha(w, h, captchaLen) + cp.SetFontPath(fontPath) //指定字体目录 + cp.SetFontName(fontName) //指定字体名字 + cp.SetMode(mode) //1:设置为简单的数学算术运算公式; 其他为普通字符串 + code, img := cp.OutPut() + // 备注:code 存储到redis,并在使用时取出验证 + corelib.Logger.Infoln("图片验证码: ", code) + // 配置文件中获取过期时间 + exp := configs.Settings.Captcha.Expire + // 缓存生成的验证码 + if err = SetCode(cid, code, int64(exp)); err != nil { + return + } + //core.Logger.Infoln("验证结果:", Verify(cid, code)) + return +} + +// Verify 验证 +func Verify(id string, code string) (ok bool) { + ok = false + var ( + err error + redisCode string + ) + // 验证字符串是否合法 + if code == "" || !corelib.IsCaptcha(code) { + return + } + code = strings.ToLower(code) + // redis取验证码 + if redisCode, err = GetCode(id); err != nil { + return + } + // redis存储验证码和用户输入验证码对比 + if strings.ToLower(redisCode) == code { + return true + } + return +} diff --git a/corelib/captcha/fonts/3Dumb.ttf b/corelib/captcha/fonts/3Dumb.ttf new file mode 100644 index 0000000..978409a Binary files /dev/null and b/corelib/captcha/fonts/3Dumb.ttf differ diff --git a/corelib/captcha/fonts/DENNEthree-dee.ttf b/corelib/captcha/fonts/DENNEthree-dee.ttf new file mode 100644 index 0000000..0350b4d Binary files /dev/null and b/corelib/captcha/fonts/DENNEthree-dee.ttf differ diff --git a/corelib/captcha/image.go b/corelib/captcha/image.go new file mode 100644 index 0000000..4975730 --- /dev/null +++ b/corelib/captcha/image.go @@ -0,0 +1,339 @@ +package captcha + +import ( + "crypto/rand" + "github.com/golang/freetype" + "github.com/llgcode/draw2d" + "github.com/llgcode/draw2d/draw2dimg" + "image" + "image/color" + "log" + "math" + "math/big" + "os" + "path/filepath" + "strconv" +) + +const ( + chars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" + operator = "+-*/" + defaultLen = 4 + defaultFontSize = 20 + defaultDpi = 72 +) + +// Captcha 图形验证码 使用字体默认ttf格式 +// w 图片宽度, h图片高度,CodeLen验证码的个数 +// FontSize 字体大小, Dpi 清晰度,FontPath 字体目录, FontName 字体名字 +// mode 验证模式 0:普通字符串,1:10以内简单数学公式 +type Captcha struct { + W, H, CodeLen int + FontSize float64 + Dpi int + FontPath, FontName string + mode int +} + +// NewCaptcha 实例化验证码 +func NewCaptcha(w, h, CodeLen int) *Captcha { + return &Captcha{W: w, H: h, CodeLen: CodeLen} +} + +// OutPut 输出 +func (captcha *Captcha) OutPut() (string, *image.RGBA) { + img := captcha.initCanvas() + return captcha.doImage(img) +} + +// RangeRand 获取区间[-m, n]的随机数 +func (captcha *Captcha) RangeRand(min, max int64) int64 { + if min > max { + panic("the min is greater than max!") + } + + if min < 0 { + f64Min := math.Abs(float64(min)) + i64Min := int64(f64Min) + result, _ := rand.Int(rand.Reader, big.NewInt(max+1+i64Min)) + + return result.Int64() - i64Min + } else { + result, _ := rand.Int(rand.Reader, big.NewInt(max-min+1)) + + return min + result.Int64() + } +} + +// 随机字符串 +func (captcha *Captcha) getRandCode() string { + if captcha.CodeLen <= 0 { + captcha.CodeLen = defaultLen + } + + var code = "" + for l := 0; l < captcha.CodeLen; l++ { + charsPos := captcha.RangeRand(0, int64(len(chars)-1)) + code += string(chars[charsPos]) + } + + return code +} + +// 获取算术运算公式 +func (captcha *Captcha) getFormulaMixData() (string, []string) { + num1 := int(captcha.RangeRand(11, 20)) + num2 := int(captcha.RangeRand(1, 10)) + opArr := []rune(operator) + opRand := opArr[captcha.RangeRand(0, 3)] + + strNum1 := strconv.Itoa(num1) + strNum2 := strconv.Itoa(num2) + + var ret int + var opRet string + switch string(opRand) { + case "+": + ret = num1 + num2 + opRet = "+" + case "-": + if num1-num2 > 0 { + ret = num1 - num2 + opRet = "-" + } else { + ret = num1 + num2 + opRet = "+" + } + + case "*": + if num1*num2 < 100 { + ret = num1 * num2 + //opRet = "×" + opRet = "×" + } else { + ret = num1 + num2 + opRet = "+" + } + case "/": + if num1%num2 == 0 { + ret = num1 / num2 + opRet = "÷" + } else { + ret = num1 + num2 + opRet = "+" + } + + } + + return strconv.Itoa(ret), []string{strNum1, opRet, strNum2, "=", "?"} +} + +// 初始化画布 +func (captcha *Captcha) initCanvas() *image.RGBA { + dest := image.NewRGBA(image.Rect(0, 0, captcha.W, captcha.H)) + // 随机色 + r := uint8(255) // uint8(captcha.RangeRand(50, 250)) + g := uint8(255) // uint8(captcha.RangeRand(50, 250)) + b := uint8(255) // uint8(captcha.RangeRand(50, 250)) + // 填充背景色 + for x := 0; x < captcha.W; x++ { + for y := 0; y < captcha.H; y++ { + dest.Set(x, y, color.RGBA{R: r, G: g, B: b, A: 255}) //设定alpha图片的透明度 + } + } + return dest +} + +// 处理图像 +func (captcha *Captcha) doImage(dest *image.RGBA) (string, *image.RGBA) { + gc := draw2dimg.NewGraphicContext(dest) + defer gc.Close() + defer gc.FillStroke() + captcha.setFont(gc) + captcha.doPoint(gc) + captcha.doLine(gc) + captcha.doSinLine(gc) + + var codeStr string + if captcha.mode == 1 { + ret, formula := captcha.getFormulaMixData() + // log.Println("算数验证码: ", formula) + codeStr = ret + captcha.doFormula(gc, formula) + } else { + codeStr = captcha.getRandCode() + captcha.doCode(gc, codeStr) + } + + return codeStr, dest +} + +// 验证码字符设置到图像上 +func (captcha *Captcha) doCode(gc *draw2dimg.GraphicContext, code string) { + for l := 0; l < len(code); l++ { + y := captcha.RangeRand(int64(captcha.FontSize)-1, int64(captcha.H)+6) + x := captcha.RangeRand(1, 20) + // 随机色 + r := uint8(captcha.RangeRand(0, 200)) + g := uint8(captcha.RangeRand(0, 200)) + b := uint8(captcha.RangeRand(0, 200)) + gc.SetFillColor(color.RGBA{R: r, G: g, B: b, A: 255}) + gc.FillStringAt(string(code[l]), float64(x)+captcha.FontSize*float64(l), float64(int64(captcha.H)-y)+captcha.FontSize) + gc.Stroke() + } +} + +// 验证码字符设置到图像上 +func (captcha *Captcha) doFormula(gc *draw2dimg.GraphicContext, formulaArr []string) { + for l := 0; l < len(formulaArr); l++ { + y := captcha.RangeRand(0, 10) + x := captcha.RangeRand(5, 10) + + // 随机色 + r := uint8(captcha.RangeRand(10, 200)) + g := uint8(captcha.RangeRand(10, 200)) + b := uint8(captcha.RangeRand(10, 200)) + + gc.SetFillColor(color.RGBA{R: r, G: g, B: b, A: 255}) + + gc.FillStringAt(formulaArr[l], float64(x)+captcha.FontSize*float64(l), captcha.FontSize+float64(y)) + gc.Stroke() + } +} + +// 增加干扰线 +func (captcha *Captcha) doLine(gc *draw2dimg.GraphicContext) { + // 设置干扰线 + for n := 0; n < 3; n++ { + gc.SetLineWidth(float64(captcha.RangeRand(1, 2))) + //gc.SetLineWidth(1) + + // 随机背景色 + r := uint8(captcha.RangeRand(0, 255)) + g := uint8(captcha.RangeRand(0, 255)) + b := uint8(captcha.RangeRand(0, 255)) + + gc.SetStrokeColor(color.RGBA{R: r, G: g, B: b, A: 255}) + + // 初始化位置 + gc.MoveTo(float64(captcha.RangeRand(0, int64(captcha.W)+10)), float64(captcha.RangeRand(0, int64(captcha.H)+5))) + gc.LineTo(float64(captcha.RangeRand(0, int64(captcha.W)+10)), float64(captcha.RangeRand(0, int64(captcha.H)+5))) + + gc.Stroke() + } +} + +// 增加干扰点 +func (captcha *Captcha) doPoint(gc *draw2dimg.GraphicContext) { + for n := 0; n < 10; n++ { + gc.SetLineWidth(float64(captcha.RangeRand(1, 3))) + + // 随机色 + r := uint8(captcha.RangeRand(0, 255)) + g := uint8(captcha.RangeRand(0, 255)) + b := uint8(captcha.RangeRand(0, 255)) + + gc.SetStrokeColor(color.RGBA{R: r, G: g, B: b, A: 255}) + + x := captcha.RangeRand(0, int64(captcha.W)+10) + 1 + y := captcha.RangeRand(0, int64(captcha.H)+5) + 1 + + gc.MoveTo(float64(x), float64(y)) + gc.LineTo(float64(x+captcha.RangeRand(1, 2)), float64(y+captcha.RangeRand(1, 2))) + + gc.Stroke() + } +} + +// 增加正弦干扰线 +func (captcha *Captcha) doSinLine(gc *draw2dimg.GraphicContext) { + h1 := captcha.RangeRand(-12, 12) + h2 := captcha.RangeRand(-1, 1) + w2 := captcha.RangeRand(5, 20) + h3 := captcha.RangeRand(5, 10) + + h := float64(captcha.H) + w := float64(captcha.W) + + // 随机色 + r := uint8(captcha.RangeRand(128, 255)) + g := uint8(captcha.RangeRand(128, 255)) + b := uint8(captcha.RangeRand(128, 255)) + + gc.SetStrokeColor(color.RGBA{R: r, G: g, B: b, A: 255}) + gc.SetLineWidth(float64(captcha.RangeRand(1, 2))) + + var i float64 + for i = -w / 2; i < w/2; i = i + 0.1 { + y := h/float64(h3)*math.Sin(i/float64(w2)) + h/2 + float64(h1) + + gc.LineTo(i+w/2, y) + + if h2 == 0 { + gc.LineTo(i+w/2, y+float64(h2)) + } + } + + gc.Stroke() +} + +// SetMode 设置模式 +func (captcha *Captcha) SetMode(mode int) { + captcha.mode = mode +} + +// SetFontPath 设置字体路径 +func (captcha *Captcha) SetFontPath(FontPath string) { + captcha.FontPath = FontPath +} + +// SetFontName 设置字体名称 +func (captcha *Captcha) SetFontName(FontName string) { + captcha.FontName = FontName +} + +// SetFontSize 设置字体大小 +func (captcha *Captcha) SetFontSize(fontSize float64) { + captcha.FontSize = fontSize +} + +// 设置相关字体 +func (captcha *Captcha) setFont(gc *draw2dimg.GraphicContext) { + if captcha.FontPath == "" { + panic("the font path is empty!") + } + + if captcha.FontName == "" { + panic("the font name is empty!") + } + // 字体文件 + fontFile := filepath.Join(captcha.FontPath, captcha.FontName+".ttf") + fontBytes, err := os.ReadFile(fontFile) + if err != nil { + log.Println(err) + return + } + + font, err := freetype.ParseFont(fontBytes) + if err != nil { + log.Println(err) + return + } + + // 设置自定义字体相关信息 + gc.FontCache = draw2d.NewSyncFolderFontCache(captcha.FontPath) + gc.FontCache.Store(draw2d.FontData{Name: captcha.FontName, Family: 0, Style: draw2d.FontStyleNormal}, font) + gc.SetFontData(draw2d.FontData{Name: captcha.FontName, Style: draw2d.FontStyleNormal}) + + //设置清晰度 + if captcha.Dpi <= 0 { + captcha.Dpi = defaultDpi + } + gc.SetDPI(captcha.Dpi) + + // 设置字体大小 + if captcha.FontSize <= 0 { + captcha.FontSize = defaultFontSize + } + gc.SetFontSize(captcha.FontSize) +} diff --git a/corelib/captcha/random.go b/corelib/captcha/random.go new file mode 100644 index 0000000..28b286b --- /dev/null +++ b/corelib/captcha/random.go @@ -0,0 +1,24 @@ +package captcha + +import ( + "math/rand" + "time" +) + +// RandDigit 随机数 +func RandDigit(max int) int { + r := rand.New(rand.NewSource(time.Now().UnixNano())) + return r.Intn(max) +} + +// RandDigitRange 随机数范围 +func RandDigitRange(max int, min int) int { + r := rand.New(rand.NewSource(time.Now().UnixNano())) + return r.Intn(max-min) + min +} + +// RandChooseOne 数组随机选一个元素 +func RandChooseOne[T any](reasons []T) T { + r := rand.New(rand.NewSource(time.Now().UnixNano())) + return reasons[r.Intn(len(reasons))] +} diff --git a/corelib/captcha/store.go b/corelib/captcha/store.go new file mode 100644 index 0000000..2aa1736 --- /dev/null +++ b/corelib/captcha/store.go @@ -0,0 +1,46 @@ +package captcha + +import ( + "context" + "picgo/corelib" + "time" +) + +// Store 存储验证码接口 +type Store interface { + // SetCode cid 当前id + // oid 旧id + // code 验证码 + // expired 过期时间(毫秒) + // return nil/error + SetCode(cid string, oid string, code string, expired int) + // GetCode cid 当前id + // return code + GetCode(cid string) string +} + +// SetCode 验证码保存到redis +func SetCode(cid string, code string, expired int64) (err error) { + ctx := context.Background() + cid = genRedisKey(cid) + if err = corelib.RdbClient.Set(ctx, cid, code, time.Duration(expired)*time.Second).Err(); err != nil { + return + } + return +} + +// GetCode 从redis中查询验证码,查询之后删除 +func GetCode(cid string) (code string, err error) { + ctx := context.Background() + cid = genRedisKey(cid) + if code, err = corelib.RdbClient.Get(ctx, cid).Result(); err != nil { + return + } + //rdb.Del(ctx, cid) + return +} + +// captcha缓存key拼接 +func genRedisKey(id string) (res string) { + return corelib.AdminCaptchaKey + id +} diff --git a/deploy/picgo-dev.yml b/deploy/picgo-dev.yml new file mode 100644 index 0000000..13765cb --- /dev/null +++ b/deploy/picgo-dev.yml @@ -0,0 +1,52 @@ +#------------------------ +# ---- 公共配置 start ---- +#------------------------ +# 服务 +server: + port: 8181 + host: localhost + node_id: 1 + log_path: tmp/admin-server.log + environment: dev + static_path: static + upload_path: static/pic + upload_max_size: 3 + sessions_key: eyjhbgcioijiuzi1niisinr5cci6ikpx + session_name: picgo +# redis +redis: + addr: "localhost:6379" + password: Develop_123 + db: 3 + pool_size: 10 + minidle_conns: 5 +# 数据库 +mysql: + mysql_dns: "root:Mysql_123@tcp(localhost:3306)/picgo?charset=utf8mb4&parseTime=True&loc=Local" + max_idle_conns: 10 + max_open_conns: 100 +#---------------------- +# ---- 公共配置 end ---- +#---------------------- + + +#------------------------------- +# ---- 管理后台相关配置 start ---- +#------------------------------- +# 验证码 +captcha: + # 过期时间(s) + expire: 305000 + width: 96 + hight: 32 + length: 4 +# 鉴权 +#jwt: +# signing_key: eyjhbgcioijiuzi1niisinr5cci6ikpxvcj9 +# expires_time: 7d +# issuer: cnnm +# aes_key: 23624e296707b1eb20311c29969bc46e +# aes_iv: 29ca233cd1f42147 +#----------------------------- +# ---- 管理后台相关配置 end ---- +#----------------------------- diff --git a/static/js/feather-icons.js b/static/js/feather-icons.js new file mode 100644 index 0000000..1781ad8 --- /dev/null +++ b/static/js/feather-icons.js @@ -0,0 +1,13 @@ +!function(e,n){"object"==typeof exports&&"object"==typeof module?module.exports=n():"function"==typeof define&&define.amd?define([],n):"object"==typeof exports?exports.feather=n():e.feather=n()}("undefined"!=typeof self?self:this,function(){return function(e){var n={};function i(t){if(n[t])return n[t].exports;var l=n[t]={i:t,l:!1,exports:{}};return e[t].call(l.exports,l,l.exports,i),l.l=!0,l.exports}return i.m=e,i.c=n,i.d=function(e,n,t){i.o(e,n)||Object.defineProperty(e,n,{configurable:!1,enumerable:!0,get:t})},i.r=function(e){Object.defineProperty(e,"__esModule",{value:!0})},i.n=function(e){var n=e&&e.__esModule?function(){return e.default}:function(){return e};return i.d(n,"a",n),n},i.o=function(e,n){return Object.prototype.hasOwnProperty.call(e,n)},i.p="",i(i.s=80)}([function(e,n,i){(function(n){var i="object",t=function(e){return e&&e.Math==Math&&e};e.exports=t(typeof globalThis==i&&globalThis)||t(typeof window==i&&window)||t(typeof self==i&&self)||t(typeof n==i&&n)||Function("return this")()}).call(this,i(75))},function(e,n){var i={}.hasOwnProperty;e.exports=function(e,n){return i.call(e,n)}},function(e,n,i){var t=i(0),l=i(11),r=i(33),o=i(62),a=t.Symbol,c=l("wks");e.exports=function(e){return c[e]||(c[e]=o&&a[e]||(o?a:r)("Symbol."+e))}},function(e,n,i){var t=i(6);e.exports=function(e){if(!t(e))throw TypeError(String(e)+" is not an object");return e}},function(e,n){e.exports=function(e){try{return!!e()}catch(e){return!0}}},function(e,n,i){var t=i(8),l=i(7),r=i(10);e.exports=t?function(e,n,i){return l.f(e,n,r(1,i))}:function(e,n,i){return e[n]=i,e}},function(e,n){e.exports=function(e){return"object"==typeof e?null!==e:"function"==typeof e}},function(e,n,i){var t=i(8),l=i(35),r=i(3),o=i(18),a=Object.defineProperty;n.f=t?a:function(e,n,i){if(r(e),n=o(n,!0),r(i),l)try{return a(e,n,i)}catch(e){}if("get"in i||"set"in i)throw TypeError("Accessors not supported");return"value"in i&&(e[n]=i.value),e}},function(e,n,i){var t=i(4);e.exports=!t(function(){return 7!=Object.defineProperty({},"a",{get:function(){return 7}}).a})},function(e,n){e.exports={}},function(e,n){e.exports=function(e,n){return{enumerable:!(1&e),configurable:!(2&e),writable:!(4&e),value:n}}},function(e,n,i){var t=i(0),l=i(19),r=i(17),o=t["__core-js_shared__"]||l("__core-js_shared__",{});(e.exports=function(e,n){return o[e]||(o[e]=void 0!==n?n:{})})("versions",[]).push({version:"3.1.3",mode:r?"pure":"global",copyright:"© 2019 Denis Pushkarev (zloirock.ru)"})},function(e,n,i){"use strict";Object.defineProperty(n,"__esModule",{value:!0});var t=o(i(43)),l=o(i(41)),r=o(i(40));function o(e){return e&&e.__esModule?e:{default:e}}n.default=Object.keys(l.default).map(function(e){return new t.default(e,l.default[e],r.default[e])}).reduce(function(e,n){return e[n.name]=n,e},{})},function(e,n){e.exports=["constructor","hasOwnProperty","isPrototypeOf","propertyIsEnumerable","toLocaleString","toString","valueOf"]},function(e,n,i){var t=i(72),l=i(20);e.exports=function(e){return t(l(e))}},function(e,n){e.exports={}},function(e,n,i){var t=i(11),l=i(33),r=t("keys");e.exports=function(e){return r[e]||(r[e]=l(e))}},function(e,n){e.exports=!1},function(e,n,i){var t=i(6);e.exports=function(e,n){if(!t(e))return e;var i,l;if(n&&"function"==typeof(i=e.toString)&&!t(l=i.call(e)))return l;if("function"==typeof(i=e.valueOf)&&!t(l=i.call(e)))return l;if(!n&&"function"==typeof(i=e.toString)&&!t(l=i.call(e)))return l;throw TypeError("Can't convert object to primitive value")}},function(e,n,i){var t=i(0),l=i(5);e.exports=function(e,n){try{l(t,e,n)}catch(i){t[e]=n}return n}},function(e,n){e.exports=function(e){if(void 0==e)throw TypeError("Can't call method on "+e);return e}},function(e,n){var i=Math.ceil,t=Math.floor;e.exports=function(e){return isNaN(e=+e)?0:(e>0?t:i)(e)}},function(e,n,i){var t; +/*! + Copyright (c) 2016 Jed Watson. + Licensed under the MIT License (MIT), see + http://jedwatson.github.io/classnames +*/ +/*! + Copyright (c) 2016 Jed Watson. + Licensed under the MIT License (MIT), see + http://jedwatson.github.io/classnames +*/ +!function(){"use strict";var i=function(){function e(){}function n(e,n){for(var i=n.length,t=0;t0?l(t(e),9007199254740991):0}},function(e,n,i){var t=i(1),l=i(14),r=i(68),o=i(15),a=r(!1);e.exports=function(e,n){var i,r=l(e),c=0,p=[];for(i in r)!t(o,i)&&t(r,i)&&p.push(i);for(;n.length>c;)t(r,i=n[c++])&&(~a(p,i)||p.push(i));return p}},function(e,n,i){var t=i(0),l=i(11),r=i(5),o=i(1),a=i(19),c=i(36),p=i(37),y=p.get,h=p.enforce,x=String(c).split("toString");l("inspectSource",function(e){return c.call(e)}),(e.exports=function(e,n,i,l){var c=!!l&&!!l.unsafe,p=!!l&&!!l.enumerable,y=!!l&&!!l.noTargetGet;"function"==typeof i&&("string"!=typeof n||o(i,"name")||r(i,"name",n),h(i).source=x.join("string"==typeof n?n:"")),e!==t?(c?!y&&e[n]&&(p=!0):delete e[n],p?e[n]=i:r(e,n,i)):p?e[n]=i:a(n,i)})(Function.prototype,"toString",function(){return"function"==typeof this&&y(this).source||c.call(this)})},function(e,n){var i={}.toString;e.exports=function(e){return i.call(e).slice(8,-1)}},function(e,n,i){var t=i(8),l=i(73),r=i(10),o=i(14),a=i(18),c=i(1),p=i(35),y=Object.getOwnPropertyDescriptor;n.f=t?y:function(e,n){if(e=o(e),n=a(n,!0),p)try{return y(e,n)}catch(e){}if(c(e,n))return r(!l.f.call(e,n),e[n])}},function(e,n,i){var t=i(0),l=i(31).f,r=i(5),o=i(29),a=i(19),c=i(71),p=i(65);e.exports=function(e,n){var i,y,h,x,s,u=e.target,d=e.global,f=e.stat;if(i=d?t:f?t[u]||a(u,{}):(t[u]||{}).prototype)for(y in n){if(x=n[y],h=e.noTargetGet?(s=l(i,y))&&s.value:i[y],!p(d?y:u+(f?".":"#")+y,e.forced)&&void 0!==h){if(typeof x==typeof h)continue;c(x,h)}(e.sham||h&&h.sham)&&r(x,"sham",!0),o(i,y,x,e)}}},function(e,n){var i=0,t=Math.random();e.exports=function(e){return"Symbol(".concat(void 0===e?"":e,")_",(++i+t).toString(36))}},function(e,n,i){var t=i(0),l=i(6),r=t.document,o=l(r)&&l(r.createElement);e.exports=function(e){return o?r.createElement(e):{}}},function(e,n,i){var t=i(8),l=i(4),r=i(34);e.exports=!t&&!l(function(){return 7!=Object.defineProperty(r("div"),"a",{get:function(){return 7}}).a})},function(e,n,i){var t=i(11);e.exports=t("native-function-to-string",Function.toString)},function(e,n,i){var t,l,r,o=i(76),a=i(0),c=i(6),p=i(5),y=i(1),h=i(16),x=i(15),s=a.WeakMap;if(o){var u=new s,d=u.get,f=u.has,g=u.set;t=function(e,n){return g.call(u,e,n),n},l=function(e){return d.call(u,e)||{}},r=function(e){return f.call(u,e)}}else{var v=h("state");x[v]=!0,t=function(e,n){return p(e,v,n),n},l=function(e){return y(e,v)?e[v]:{}},r=function(e){return y(e,v)}}e.exports={set:t,get:l,has:r,enforce:function(e){return r(e)?l(e):t(e,{})},getterFor:function(e){return function(n){var i;if(!c(n)||(i=l(n)).type!==e)throw TypeError("Incompatible receiver, "+e+" required");return i}}}},function(e,n,i){"use strict";Object.defineProperty(n,"__esModule",{value:!0});var t=Object.assign||function(e){for(var n=1;n0&&void 0!==arguments[0]?arguments[0]:{};if("undefined"==typeof document)throw new Error("`feather.replace()` only works in a browser environment.");var n=document.querySelectorAll("[data-feather]");Array.from(n).forEach(function(n){return function(e){var n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},i=function(e){return Array.from(e.attributes).reduce(function(e,n){return e[n.name]=n.value,e},{})}(e),o=i["data-feather"];if(delete i["data-feather"],void 0!==r.default[o]){var a=r.default[o].toSvg(t({},n,i,{class:(0,l.default)(n.class,i.class)})),c=(new DOMParser).parseFromString(a,"image/svg+xml").querySelector("svg");e.parentNode.replaceChild(c,e)}else console.warn("feather: '"+o+"' is not a valid icon")}(n,e)})}},function(e,n,i){"use strict";Object.defineProperty(n,"__esModule",{value:!0});var t,l=i(12),r=(t=l)&&t.__esModule?t:{default:t};n.default=function(e){var n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};if(console.warn("feather.toSvg() is deprecated. Please use feather.icons[name].toSvg() instead."),!e)throw new Error("The required `key` (icon name) parameter is missing.");if(!r.default[e])throw new Error("No icon matching '"+e+"'. See the complete list of icons at https://feathericons.com");return r.default[e].toSvg(n)}},function(e){e.exports={activity:["pulse","health","action","motion"],airplay:["stream","cast","mirroring"],"alert-circle":["warning","alert","danger"],"alert-octagon":["warning","alert","danger"],"alert-triangle":["warning","alert","danger"],"align-center":["text alignment","center"],"align-justify":["text alignment","justified"],"align-left":["text alignment","left"],"align-right":["text alignment","right"],anchor:[],archive:["index","box"],"at-sign":["mention","at","email","message"],award:["achievement","badge"],aperture:["camera","photo"],"bar-chart":["statistics","diagram","graph"],"bar-chart-2":["statistics","diagram","graph"],battery:["power","electricity"],"battery-charging":["power","electricity"],bell:["alarm","notification","sound"],"bell-off":["alarm","notification","silent"],bluetooth:["wireless"],"book-open":["read","library"],book:["read","dictionary","booklet","magazine","library"],bookmark:["read","clip","marker","tag"],box:["cube"],briefcase:["work","bag","baggage","folder"],calendar:["date"],camera:["photo"],cast:["chromecast","airplay"],"chevron-down":["expand"],"chevron-up":["collapse"],circle:["off","zero","record"],clipboard:["copy"],clock:["time","watch","alarm"],"cloud-drizzle":["weather","shower"],"cloud-lightning":["weather","bolt"],"cloud-rain":["weather"],"cloud-snow":["weather","blizzard"],cloud:["weather"],codepen:["logo"],codesandbox:["logo"],code:["source","programming"],coffee:["drink","cup","mug","tea","cafe","hot","beverage"],columns:["layout"],command:["keyboard","cmd","terminal","prompt"],compass:["navigation","safari","travel","direction"],copy:["clone","duplicate"],"corner-down-left":["arrow","return"],"corner-down-right":["arrow"],"corner-left-down":["arrow"],"corner-left-up":["arrow"],"corner-right-down":["arrow"],"corner-right-up":["arrow"],"corner-up-left":["arrow"],"corner-up-right":["arrow"],cpu:["processor","technology"],"credit-card":["purchase","payment","cc"],crop:["photo","image"],crosshair:["aim","target"],database:["storage","memory"],delete:["remove"],disc:["album","cd","dvd","music"],"dollar-sign":["currency","money","payment"],droplet:["water"],edit:["pencil","change"],"edit-2":["pencil","change"],"edit-3":["pencil","change"],eye:["view","watch"],"eye-off":["view","watch","hide","hidden"],"external-link":["outbound"],facebook:["logo","social"],"fast-forward":["music"],figma:["logo","design","tool"],"file-minus":["delete","remove","erase"],"file-plus":["add","create","new"],"file-text":["data","txt","pdf"],film:["movie","video"],filter:["funnel","hopper"],flag:["report"],"folder-minus":["directory"],"folder-plus":["directory"],folder:["directory"],framer:["logo","design","tool"],frown:["emoji","face","bad","sad","emotion"],gift:["present","box","birthday","party"],"git-branch":["code","version control"],"git-commit":["code","version control"],"git-merge":["code","version control"],"git-pull-request":["code","version control"],github:["logo","version control"],gitlab:["logo","version control"],globe:["world","browser","language","translate"],"hard-drive":["computer","server","memory","data"],hash:["hashtag","number","pound"],headphones:["music","audio","sound"],heart:["like","love","emotion"],"help-circle":["question mark"],hexagon:["shape","node.js","logo"],home:["house","living"],image:["picture"],inbox:["email"],instagram:["logo","camera"],key:["password","login","authentication","secure"],layers:["stack"],layout:["window","webpage"],"life-buoy":["help","life ring","support"],link:["chain","url"],"link-2":["chain","url"],linkedin:["logo","social media"],list:["options"],lock:["security","password","secure"],"log-in":["sign in","arrow","enter"],"log-out":["sign out","arrow","exit"],mail:["email","message"],"map-pin":["location","navigation","travel","marker"],map:["location","navigation","travel"],maximize:["fullscreen"],"maximize-2":["fullscreen","arrows","expand"],meh:["emoji","face","neutral","emotion"],menu:["bars","navigation","hamburger"],"message-circle":["comment","chat"],"message-square":["comment","chat"],"mic-off":["record","sound","mute"],mic:["record","sound","listen"],minimize:["exit fullscreen","close"],"minimize-2":["exit fullscreen","arrows","close"],minus:["subtract"],monitor:["tv","screen","display"],moon:["dark","night"],"more-horizontal":["ellipsis"],"more-vertical":["ellipsis"],"mouse-pointer":["arrow","cursor"],move:["arrows"],music:["note"],navigation:["location","travel"],"navigation-2":["location","travel"],octagon:["stop"],package:["box","container"],paperclip:["attachment"],pause:["music","stop"],"pause-circle":["music","audio","stop"],"pen-tool":["vector","drawing"],percent:["discount"],"phone-call":["ring"],"phone-forwarded":["call"],"phone-incoming":["call"],"phone-missed":["call"],"phone-off":["call","mute"],"phone-outgoing":["call"],phone:["call"],play:["music","start"],"pie-chart":["statistics","diagram"],"play-circle":["music","start"],plus:["add","new"],"plus-circle":["add","new"],"plus-square":["add","new"],pocket:["logo","save"],power:["on","off"],printer:["fax","office","device"],radio:["signal"],"refresh-cw":["synchronise","arrows"],"refresh-ccw":["arrows"],repeat:["loop","arrows"],rewind:["music"],"rotate-ccw":["arrow"],"rotate-cw":["arrow"],rss:["feed","subscribe"],save:["floppy disk"],scissors:["cut"],search:["find","magnifier","magnifying glass"],send:["message","mail","email","paper airplane","paper aeroplane"],settings:["cog","edit","gear","preferences"],"share-2":["network","connections"],shield:["security","secure"],"shield-off":["security","insecure"],"shopping-bag":["ecommerce","cart","purchase","store"],"shopping-cart":["ecommerce","cart","purchase","store"],shuffle:["music"],"skip-back":["music"],"skip-forward":["music"],slack:["logo"],slash:["ban","no"],sliders:["settings","controls"],smartphone:["cellphone","device"],smile:["emoji","face","happy","good","emotion"],speaker:["audio","music"],star:["bookmark","favorite","like"],"stop-circle":["media","music"],sun:["brightness","weather","light"],sunrise:["weather","time","morning","day"],sunset:["weather","time","evening","night"],tablet:["device"],tag:["label"],target:["logo","bullseye"],terminal:["code","command line","prompt"],thermometer:["temperature","celsius","fahrenheit","weather"],"thumbs-down":["dislike","bad","emotion"],"thumbs-up":["like","good","emotion"],"toggle-left":["on","off","switch"],"toggle-right":["on","off","switch"],tool:["settings","spanner"],trash:["garbage","delete","remove","bin"],"trash-2":["garbage","delete","remove","bin"],triangle:["delta"],truck:["delivery","van","shipping","transport","lorry"],tv:["television","stream"],twitch:["logo"],twitter:["logo","social"],type:["text"],umbrella:["rain","weather"],unlock:["security"],"user-check":["followed","subscribed"],"user-minus":["delete","remove","unfollow","unsubscribe"],"user-plus":["new","add","create","follow","subscribe"],"user-x":["delete","remove","unfollow","unsubscribe","unavailable"],user:["person","account"],users:["group"],"video-off":["camera","movie","film"],video:["camera","movie","film"],voicemail:["phone"],volume:["music","sound","mute"],"volume-1":["music","sound"],"volume-2":["music","sound"],"volume-x":["music","sound","mute"],watch:["clock","time"],"wifi-off":["disabled"],wifi:["connection","signal","wireless"],wind:["weather","air"],"x-circle":["cancel","close","delete","remove","times","clear"],"x-octagon":["delete","stop","alert","warning","times","clear"],"x-square":["cancel","close","delete","remove","times","clear"],x:["cancel","close","delete","remove","times","clear"],youtube:["logo","video","play"],"zap-off":["flash","camera","lightning"],zap:["flash","camera","lightning"],"zoom-in":["magnifying glass"],"zoom-out":["magnifying glass"]}},function(e){e.exports={activity:'',airplay:'',"alert-circle":'',"alert-octagon":'',"alert-triangle":'',"align-center":'',"align-justify":'',"align-left":'',"align-right":'',anchor:'',aperture:'',archive:'',"arrow-down-circle":'',"arrow-down-left":'',"arrow-down-right":'',"arrow-down":'',"arrow-left-circle":'',"arrow-left":'',"arrow-right-circle":'',"arrow-right":'',"arrow-up-circle":'',"arrow-up-left":'',"arrow-up-right":'',"arrow-up":'',"at-sign":'',award:'',"bar-chart-2":'',"bar-chart":'',"battery-charging":'',battery:'',"bell-off":'',bell:'',bluetooth:'',bold:'',"book-open":'',book:'',bookmark:'',box:'',briefcase:'',calendar:'',"camera-off":'',camera:'',cast:'',"check-circle":'',"check-square":'',check:'',"chevron-down":'',"chevron-left":'',"chevron-right":'',"chevron-up":'',"chevrons-down":'',"chevrons-left":'',"chevrons-right":'',"chevrons-up":'',chrome:'',circle:'',clipboard:'',clock:'',"cloud-drizzle":'',"cloud-lightning":'',"cloud-off":'',"cloud-rain":'',"cloud-snow":'',cloud:'',code:'',codepen:'',codesandbox:'',coffee:'',columns:'',command:'',compass:'',copy:'',"corner-down-left":'',"corner-down-right":'',"corner-left-down":'',"corner-left-up":'',"corner-right-down":'',"corner-right-up":'',"corner-up-left":'',"corner-up-right":'',cpu:'',"credit-card":'',crop:'',crosshair:'',database:'',delete:'',disc:'',"divide-circle":'',"divide-square":'',divide:'',"dollar-sign":'',"download-cloud":'',download:'',dribbble:'',droplet:'',"edit-2":'',"edit-3":'',edit:'',"external-link":'',"eye-off":'',eye:'',facebook:'',"fast-forward":'',feather:'',figma:'',"file-minus":'',"file-plus":'',"file-text":'',file:'',film:'',filter:'',flag:'',"folder-minus":'',"folder-plus":'',folder:'',framer:'',frown:'',gift:'',"git-branch":'',"git-commit":'',"git-merge":'',"git-pull-request":'',github:'',gitlab:'',globe:'',grid:'',"hard-drive":'',hash:'',headphones:'',heart:'',"help-circle":'',hexagon:'',home:'',image:'',inbox:'',info:'',instagram:'',italic:'',key:'',layers:'',layout:'',"life-buoy":'',"link-2":'',link:'',linkedin:'',list:'',loader:'',lock:'',"log-in":'',"log-out":'',mail:'',"map-pin":'',map:'',"maximize-2":'',maximize:'',meh:'',menu:'',"message-circle":'',"message-square":'',"mic-off":'',mic:'',"minimize-2":'',minimize:'',"minus-circle":'',"minus-square":'',minus:'',monitor:'',moon:'',"more-horizontal":'',"more-vertical":'',"mouse-pointer":'',move:'',music:'',"navigation-2":'',navigation:'',octagon:'',package:'',paperclip:'',"pause-circle":'',pause:'',"pen-tool":'',percent:'',"phone-call":'',"phone-forwarded":'',"phone-incoming":'',"phone-missed":'',"phone-off":'',"phone-outgoing":'',phone:'',"pie-chart":'',"play-circle":'',play:'',"plus-circle":'',"plus-square":'',plus:'',pocket:'',power:'',printer:'',radio:'',"refresh-ccw":'',"refresh-cw":'',repeat:'',rewind:'',"rotate-ccw":'',"rotate-cw":'',rss:'',save:'',scissors:'',search:'',send:'',server:'',settings:'',"share-2":'',share:'',"shield-off":'',shield:'',"shopping-bag":'',"shopping-cart":'',shuffle:'',sidebar:'',"skip-back":'',"skip-forward":'',slack:'',slash:'',sliders:'',smartphone:'',smile:'',speaker:'',square:'',star:'',"stop-circle":'',sun:'',sunrise:'',sunset:'',table:'',tablet:'',tag:'',target:'',terminal:'',thermometer:'',"thumbs-down":'',"thumbs-up":'',"toggle-left":'',"toggle-right":'',tool:'',"trash-2":'',trash:'',trello:'',"trending-down":'',"trending-up":'',triangle:'',truck:'',tv:'',twitch:'',twitter:'',type:'',umbrella:'',underline:'',unlock:'',"upload-cloud":'',upload:'',"user-check":'',"user-minus":'',"user-plus":'',"user-x":'',user:'',users:'',"video-off":'',video:'',voicemail:'',"volume-1":'',"volume-2":'',"volume-x":'',volume:'',watch:'',"wifi-off":'',wifi:'',wind:'',"x-circle":'',"x-octagon":'',"x-square":'',x:'',youtube:'',"zap-off":'',zap:'',"zoom-in":'',"zoom-out":''}},function(e){e.exports={xmlns:"http://www.w3.org/2000/svg",width:24,height:24,viewBox:"0 0 24 24",fill:"none",stroke:"currentColor","stroke-width":2,"stroke-linecap":"round","stroke-linejoin":"round"}},function(e,n,i){"use strict";Object.defineProperty(n,"__esModule",{value:!0});var t=Object.assign||function(e){for(var n=1;n2&&void 0!==arguments[2]?arguments[2]:[];!function(e,n){if(!(e instanceof n))throw new TypeError("Cannot call a class as a function")}(this,e),this.name=n,this.contents=i,this.tags=l,this.attrs=t({},o.default,{class:"feather feather-"+n})}return l(e,[{key:"toSvg",value:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};return""+this.contents+""}},{key:"toString",value:function(){return this.contents}}]),e}();n.default=c},function(e,n,i){"use strict";var t=o(i(12)),l=o(i(39)),r=o(i(38));function o(e){return e&&e.__esModule?e:{default:e}}e.exports={icons:t.default,toSvg:l.default,replace:r.default}},function(e,n,i){e.exports=i(0)},function(e,n,i){var t=i(2)("iterator"),l=!1;try{var r=0,o={next:function(){return{done:!!r++}},return:function(){l=!0}};o[t]=function(){return this},Array.from(o,function(){throw 2})}catch(e){}e.exports=function(e,n){if(!n&&!l)return!1;var i=!1;try{var r={};r[t]=function(){return{next:function(){return{done:i=!0}}}},e(r)}catch(e){}return i}},function(e,n,i){var t=i(30),l=i(2)("toStringTag"),r="Arguments"==t(function(){return arguments}());e.exports=function(e){var n,i,o;return void 0===e?"Undefined":null===e?"Null":"string"==typeof(i=function(e,n){try{return e[n]}catch(e){}}(n=Object(e),l))?i:r?t(n):"Object"==(o=t(n))&&"function"==typeof n.callee?"Arguments":o}},function(e,n,i){var t=i(47),l=i(9),r=i(2)("iterator");e.exports=function(e){if(void 0!=e)return e[r]||e["@@iterator"]||l[t(e)]}},function(e,n,i){"use strict";var t=i(18),l=i(7),r=i(10);e.exports=function(e,n,i){var o=t(n);o in e?l.f(e,o,r(0,i)):e[o]=i}},function(e,n,i){var t=i(2),l=i(9),r=t("iterator"),o=Array.prototype;e.exports=function(e){return void 0!==e&&(l.Array===e||o[r]===e)}},function(e,n,i){var t=i(3);e.exports=function(e,n,i,l){try{return l?n(t(i)[0],i[1]):n(i)}catch(n){var r=e.return;throw void 0!==r&&t(r.call(e)),n}}},function(e,n){e.exports=function(e){if("function"!=typeof e)throw TypeError(String(e)+" is not a function");return e}},function(e,n,i){var t=i(52);e.exports=function(e,n,i){if(t(e),void 0===n)return e;switch(i){case 0:return function(){return e.call(n)};case 1:return function(i){return e.call(n,i)};case 2:return function(i,t){return e.call(n,i,t)};case 3:return function(i,t,l){return e.call(n,i,t,l)}}return function(){return e.apply(n,arguments)}}},function(e,n,i){"use strict";var t=i(53),l=i(24),r=i(51),o=i(50),a=i(27),c=i(49),p=i(48);e.exports=function(e){var n,i,y,h,x=l(e),s="function"==typeof this?this:Array,u=arguments.length,d=u>1?arguments[1]:void 0,f=void 0!==d,g=0,v=p(x);if(f&&(d=t(d,u>2?arguments[2]:void 0,2)),void 0==v||s==Array&&o(v))for(i=new s(n=a(x.length));n>g;g++)c(i,g,f?d(x[g],g):x[g]);else for(h=v.call(x),i=new s;!(y=h.next()).done;g++)c(i,g,f?r(h,d,[y.value,g],!0):y.value);return i.length=g,i}},function(e,n,i){var t=i(32),l=i(54);t({target:"Array",stat:!0,forced:!i(46)(function(e){Array.from(e)})},{from:l})},function(e,n,i){var t=i(6),l=i(3);e.exports=function(e,n){if(l(e),!t(n)&&null!==n)throw TypeError("Can't set "+String(n)+" as a prototype")}},function(e,n,i){var t=i(56);e.exports=Object.setPrototypeOf||("__proto__"in{}?function(){var e,n=!1,i={};try{(e=Object.getOwnPropertyDescriptor(Object.prototype,"__proto__").set).call(i,[]),n=i instanceof Array}catch(e){}return function(i,l){return t(i,l),n?e.call(i,l):i.__proto__=l,i}}():void 0)},function(e,n,i){var t=i(0).document;e.exports=t&&t.documentElement},function(e,n,i){var t=i(28),l=i(13);e.exports=Object.keys||function(e){return t(e,l)}},function(e,n,i){var t=i(8),l=i(7),r=i(3),o=i(59);e.exports=t?Object.defineProperties:function(e,n){r(e);for(var i,t=o(n),a=t.length,c=0;a>c;)l.f(e,i=t[c++],n[i]);return e}},function(e,n,i){var t=i(3),l=i(60),r=i(13),o=i(15),a=i(58),c=i(34),p=i(16)("IE_PROTO"),y=function(){},h=function(){var e,n=c("iframe"),i=r.length;for(n.style.display="none",a.appendChild(n),n.src=String("javascript:"),(e=n.contentWindow.document).open(),e.write("