diff --git a/core/checker.go b/core/checker.go index bf0df359..60795fb9 100644 --- a/core/checker.go +++ b/core/checker.go @@ -165,7 +165,7 @@ func (s *Service) checkHttp(record bool) *Service { var response *http.Response if s.Method == "POST" { - response, err = client.Post(s.Domain, "application/json", bytes.NewBuffer([]byte(s.PostData))) + response, err = client.Post(s.Domain, "application/json", bytes.NewBuffer([]byte(s.PostData.String))) } else { response, err = client.Get(s.Domain) } @@ -190,11 +190,11 @@ func (s *Service) checkHttp(record bool) *Service { s.LastResponse = string(contents) s.LastStatusCode = response.StatusCode - if s.Expected != "" { + if s.Expected.String != "" { if err != nil { utils.Log(2, err) } - match, err := regexp.MatchString(s.Expected, string(contents)) + match, err := regexp.MatchString(s.Expected.String, string(contents)) if err != nil { utils.Log(2, err) } diff --git a/core/configs.go b/core/configs.go index d5db8368..20cb4d44 100644 --- a/core/configs.go +++ b/core/configs.go @@ -16,6 +16,7 @@ package core import ( + "database/sql" "errors" "fmt" "github.com/go-yaml/yaml" @@ -71,9 +72,8 @@ func LoadUsingEnv() (*DbConfig, error) { CoreApp.Name = os.Getenv("NAME") CoreApp.Domain = os.Getenv("DOMAIN") CoreApp.DbConnection = Configs.DbConn - if os.Getenv("USE_CDN") == "true" { - CoreApp.UseCdn = true - } + CoreApp.UseCdn = sql.NullBool{os.Getenv("USE_CDN") == "true", true} + err := Configs.Connect(true, utils.Directory) if err != nil { utils.Log(4, err) @@ -94,7 +94,7 @@ func LoadUsingEnv() (*DbConfig, error) { Username: "admin", Password: "admin", Email: "info@admin.com", - Admin: true, + Admin: sql.NullBool{true, true}, }) _, err := admin.Create() diff --git a/core/core.go b/core/core.go index 2a6908f9..599f8397 100644 --- a/core/core.go +++ b/core/core.go @@ -16,6 +16,7 @@ package core import ( + "database/sql" "errors" "github.com/hunterlong/statup/core/notifier" "github.com/hunterlong/statup/source" @@ -147,9 +148,7 @@ func SelectCore() (*Core, error) { } CoreApp.DbConnection = Configs.DbConn CoreApp.Version = VERSION - if os.Getenv("USE_CDN") == "true" { - CoreApp.UseCdn = true - } + CoreApp.UseCdn = sql.NullBool{os.Getenv("USE_CDN") == "true", true} return CoreApp, db.Error } diff --git a/core/database.go b/core/database.go index e4bf9f77..bc8aa690 100644 --- a/core/database.go +++ b/core/database.go @@ -68,6 +68,11 @@ func checkinDB() *gorm.DB { return DbSession.Model(&types.Checkin{}) } +// messagesDb returns the Checkin records for a service +func messagesDb() *gorm.DB { + return DbSession.Model(&types.Message{}) +} + // checkinHitsDB returns the 'hits' from the Checkin record func checkinHitsDB() *gorm.DB { return DbSession.Model(&types.CheckinHit{}) @@ -329,6 +334,7 @@ func (db *DbConfig) DropDatabase() error { err = DbSession.DropTableIfExists("hits") err = DbSession.DropTableIfExists("services") err = DbSession.DropTableIfExists("users") + err = DbSession.DropTableIfExists("messages") return err.Error } @@ -343,6 +349,7 @@ func (db *DbConfig) CreateDatabase() error { err = DbSession.CreateTable(&types.Hit{}) err = DbSession.CreateTable(&types.Service{}) err = DbSession.CreateTable(&types.User{}) + err = DbSession.CreateTable(&types.Message{}) utils.Log(1, "Statup Database Created") return err.Error } @@ -361,7 +368,7 @@ func (db *DbConfig) MigrateDatabase() error { if tx.Error != nil { return tx.Error } - tx = tx.AutoMigrate(&types.Service{}, &types.User{}, &types.Hit{}, &types.Failure{}, &types.Checkin{}, &types.CheckinHit{}, ¬ifier.Notification{}).Table("core").AutoMigrate(&types.Core{}) + tx = tx.AutoMigrate(&types.Service{}, &types.User{}, &types.Hit{}, &types.Failure{}, &types.Message{}, &types.Checkin{}, &types.CheckinHit{}, ¬ifier.Notification{}).Table("core").AutoMigrate(&types.Core{}) if tx.Error != nil { tx.Rollback() utils.Log(3, fmt.Sprintf("Statup Database could not be migrated: %v", tx.Error)) diff --git a/core/export.go b/core/export.go index b6c9bf79..32826868 100644 --- a/core/export.go +++ b/core/export.go @@ -17,6 +17,7 @@ package core import ( "bytes" + "database/sql" "fmt" "github.com/hunterlong/statup/source" "github.com/hunterlong/statup/utils" @@ -32,7 +33,7 @@ func ExportIndexHTML() string { source.Assets() injectDatabase() CoreApp.SelectAllServices(false) - CoreApp.UseCdn = true + CoreApp.UseCdn = sql.NullBool{true, true} for _, srv := range CoreApp.Services { service := srv.(*Service) service.Check(true) @@ -60,7 +61,7 @@ func ExportIndexHTML() string { return CoreApp }, "USE_CDN": func() bool { - return CoreApp.UseCdn + return CoreApp.UseCdn.Bool }, "underscore": func(html string) string { return utils.UnderScoreString(html) diff --git a/core/messages.go b/core/messages.go new file mode 100644 index 00000000..6ec3c62f --- /dev/null +++ b/core/messages.go @@ -0,0 +1,80 @@ +// Statup +// Copyright (C) 2018. Hunter Long and the project contributors +// Written by Hunter Long and the project contributors +// +// https://github.com/hunterlong/statup +// +// The licenses for most software and other practical works are designed +// to take away your freedom to share and change the works. By contrast, +// the GNU General Public License is intended to guarantee your freedom to +// share and change all versions of a program--to make sure it remains free +// software for all its users. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +package core + +import ( + "fmt" + "github.com/hunterlong/statup/types" + "github.com/hunterlong/statup/utils" + "time" +) + +type Message struct { + *types.Message +} + +// SelectServiceMessages returns all messages for a service +func SelectServiceMessages(id int64) []*Message { + var message []*Message + messagesDb().Where("service = ?", id).Limit(10).Find(&message) + return message +} + +// ReturnMessage will convert *types.Message to *core.Message +func ReturnMessage(m *types.Message) *Message { + return &Message{m} +} + +// SelectMessages returns all messages +func SelectMessages() ([]*Message, error) { + var messages []*Message + db := messagesDb().Find(&messages).Order("id desc") + return messages, db.Error +} + +// SelectMessage returns a Message based on the ID passed +func SelectMessage(id int64) (*Message, error) { + var message Message + db := messagesDb().Where("id = ?", id).Find(&message) + return &message, db.Error +} + +// Create will create a Message and insert it into the database +func (m *Message) Create() (int64, error) { + m.CreatedAt = time.Now().UTC() + db := messagesDb().Create(m) + if db.Error != nil { + utils.Log(3, fmt.Sprintf("Failed to create message %v #%v: %v", m.Title, m.Id, db.Error)) + return 0, db.Error + } + return m.Id, nil +} + +// Delete will delete a Message from database +func (m *Message) Delete() error { + db := messagesDb().Delete(m) + return db.Error +} + +// Update will update a Message in the database +func (m *Message) Update() (*Message, error) { + db := messagesDb().Update(m) + if db.Error != nil { + utils.Log(3, fmt.Sprintf("Failed to update message %v #%v: %v", m.Title, m.Id, db.Error)) + return nil, db.Error + } + return m, nil +} diff --git a/core/sample.go b/core/sample.go index 1dbabe91..02158041 100644 --- a/core/sample.go +++ b/core/sample.go @@ -16,6 +16,7 @@ package core import ( + "database/sql" "fmt" "github.com/hunterlong/statup/types" "github.com/hunterlong/statup/utils" @@ -60,11 +61,11 @@ func InsertSampleData() error { Name: "JSON API Tester", Domain: "https://jsonplaceholder.typicode.com/posts", ExpectedStatus: 201, - Expected: `(title)": "((\\"|[statup])*)"`, + Expected: utils.NullString(`(title)": "((\\"|[statup])*)"`), Interval: 30, Type: "http", Method: "POST", - PostData: `{ "title": "statup", "body": "bar", "userId": 19999 }`, + PostData: utils.NullString(`{ "title": "statup", "body": "bar", "userId": 19999 }`), Timeout: 30, Order: 4, }) @@ -155,7 +156,7 @@ func insertSampleCore() error { Domain: "http://localhost:8080", Version: "test", CreatedAt: time.Now(), - UseCdn: false, + UseCdn: sql.NullBool{false, true}, } query := coreDB().Create(core) return query.Error @@ -167,14 +168,14 @@ func insertSampleUsers() { Username: "testadmin", Password: "password123", Email: "info@betatude.com", - Admin: true, + Admin: sql.NullBool{true, true}, }) u3 := ReturnUser(&types.User{ Username: "testadmin2", Password: "password123", Email: "info@adminhere.com", - Admin: true, + Admin: sql.NullBool{true, true}, }) u2.Create() diff --git a/core/services.go b/core/services.go index a78b0f62..82c9b94e 100644 --- a/core/services.go +++ b/core/services.go @@ -394,6 +394,12 @@ func (s *Service) Create(check bool) (int64, error) { return s.Id, nil } +// Messages returns all Messages for a Service +func (s *Service) Messages() []*Message { + messages := SelectServiceMessages(s.Id) + return messages +} + // ServicesCount returns the amount of services inside the []*core.Services slice func (c *Core) ServicesCount() int { return len(c.Services) diff --git a/core/users.go b/core/users.go index b1b3f4eb..b1a3c293 100644 --- a/core/users.go +++ b/core/users.go @@ -35,7 +35,7 @@ func ReturnUser(u *types.User) *user { // SelectUser returns the user based on the user's ID. func SelectUser(id int64) (*user, error) { var user user - err := usersDB().First(&user, id) + err := usersDB().Where("id = ?", id).First(&user) return &user, err.Error } @@ -54,7 +54,6 @@ func (u *user) Delete() error { // Update will update the user's record in database func (u *user) Update() error { - u.Password = utils.HashPassword(u.Password) u.ApiKey = utils.NewSHA1Hash(5) u.ApiSecret = utils.NewSHA1Hash(10) return usersDB().Update(u).Error diff --git a/handlers/handlers.go b/handlers/handlers.go index 1620658e..f3899416 100644 --- a/handlers/handlers.go +++ b/handlers/handlers.go @@ -108,8 +108,11 @@ var handlerFuncs = func(w http.ResponseWriter, r *http.Request) template.FuncMap "len": func(g []types.ServiceInterface) int { return len(g) }, + "IsNil": func(g interface{}) bool { + return g == nil + }, "USE_CDN": func() bool { - return core.CoreApp.UseCdn + return core.CoreApp.UseCdn.Bool }, "Type": func(g interface{}) []string { fooType := reflect.TypeOf(g) @@ -163,6 +166,9 @@ var handlerFuncs = func(w http.ResponseWriter, r *http.Request) template.FuncMap "NewCheckin": func() *types.Checkin { return new(types.Checkin) }, + "NewMessage": func() *types.Message { + return new(types.Message) + }, } } @@ -176,7 +182,7 @@ func executeResponse(w http.ResponseWriter, r *http.Request, file string, data i return } - templates := []string{"base.html", "head.html", "nav.html", "footer.html", "scripts.html", "form_service.html", "form_notifier.html", "form_user.html", "form_checkin.html"} + templates := []string{"base.html", "head.html", "nav.html", "footer.html", "scripts.html", "form_service.html", "form_notifier.html", "form_user.html", "form_checkin.html", "form_message.html"} javascripts := []string{"charts.js", "chart_index.js"} diff --git a/handlers/index.go b/handlers/index.go index e108519e..5bbf58a9 100644 --- a/handlers/index.go +++ b/handlers/index.go @@ -16,6 +16,7 @@ package handlers import ( + "database/sql" "encoding/json" "github.com/hunterlong/statup/core" "github.com/hunterlong/statup/types" @@ -108,7 +109,7 @@ func DesktopInit(ip string, port int) { Username: config.Username, Password: config.Password, Email: config.Email, - Admin: true, + Admin: sql.NullBool{true, true}, }) admin.Create() diff --git a/handlers/messages.go b/handlers/messages.go new file mode 100644 index 00000000..4ecffc9c --- /dev/null +++ b/handlers/messages.go @@ -0,0 +1,147 @@ +// Statup +// Copyright (C) 2018. Hunter Long and the project contributors +// Written by Hunter Long and the project contributors +// +// https://github.com/hunterlong/statup +// +// The licenses for most software and other practical works are designed +// to take away your freedom to share and change the works. By contrast, +// the GNU General Public License is intended to guarantee your freedom to +// share and change all versions of a program--to make sure it remains free +// software for all its users. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +package handlers + +import ( + "fmt" + "github.com/gorilla/mux" + "github.com/hunterlong/statup/core" + "github.com/hunterlong/statup/types" + "github.com/hunterlong/statup/utils" + "net/http" + "time" +) + +func messagesHandler(w http.ResponseWriter, r *http.Request) { + if !IsAuthenticated(r) { + http.Redirect(w, r, "/", http.StatusSeeOther) + return + } + messages, _ := core.SelectMessages() + executeResponse(w, r, "messages.html", messages, nil) +} + +func deleteMessageHandler(w http.ResponseWriter, r *http.Request) { + if !IsAuthenticated(r) { + http.Redirect(w, r, "/", http.StatusSeeOther) + return + } + vars := mux.Vars(r) + id := utils.StringInt(vars["id"]) + message, err := core.SelectMessage(id) + if err != nil { + http.Redirect(w, r, "/messages", http.StatusSeeOther) + return + } + message.Delete() + http.Redirect(w, r, "/messages", http.StatusSeeOther) +} + +func viewMessageHandler(w http.ResponseWriter, r *http.Request) { + if !IsAuthenticated(r) { + http.Redirect(w, r, "/", http.StatusSeeOther) + return + } + vars := mux.Vars(r) + id := utils.StringInt(vars["id"]) + message, err := core.SelectMessage(id) + if err != nil { + w.WriteHeader(http.StatusNotFound) + return + } + executeResponse(w, r, "message.html", message, nil) +} + +func updateMessageHandler(w http.ResponseWriter, r *http.Request) { + if !IsAuthenticated(r) { + http.Redirect(w, r, "/", http.StatusSeeOther) + return + } + vars := mux.Vars(r) + id := utils.StringInt(vars["id"]) + message, err := core.SelectMessage(id) + if err != nil { + w.WriteHeader(http.StatusNotFound) + return + } + r.ParseForm() + + title := r.PostForm.Get("title") + description := r.PostForm.Get("description") + notifyMethod := r.PostForm.Get("notify_method") + notifyUsers := r.PostForm.Get("notify_users") + startOn := r.PostForm.Get("start_on") + endOn := r.PostForm.Get("end_on") + notifyBefore := r.PostForm.Get("notify_before") + serviceId := utils.StringInt(r.PostForm.Get("service_id")) + + start, err := time.Parse(utils.FlatpickrTime, startOn) + if err != nil { + utils.Log(3, err) + } + end, _ := time.Parse(utils.FlatpickrTime, endOn) + before, _ := time.ParseDuration(notifyBefore) + + message.Title = title + message.Description = description + message.NotifyUsers = utils.NullBool(notifyUsers == "on") + message.NotifyMethod = notifyMethod + message.StartOn = start.UTC() + message.EndOn = end.UTC() + message.NotifyBefore = before + message.ServiceId = serviceId + + message.Update() + executeResponse(w, r, "messages.html", message, "/messages") +} + +func createMessageHandler(w http.ResponseWriter, r *http.Request) { + if !IsAuthenticated(r) { + http.Redirect(w, r, "/", http.StatusSeeOther) + return + } + r.ParseForm() + + title := r.PostForm.Get("title") + description := r.PostForm.Get("description") + notifyMethod := r.PostForm.Get("notify_method") + notifyUsers := r.PostForm.Get("notify_users") + startOn := r.PostForm.Get("start_on") + endOn := r.PostForm.Get("end_on") + notifyBefore := r.PostForm.Get("notify_before") + serviceId := utils.StringInt(r.PostForm.Get("service_id")) + + start, _ := time.Parse(utils.FlatpickrTime, startOn) + end, _ := time.Parse(utils.FlatpickrTime, endOn) + before, _ := time.ParseDuration(notifyBefore) + + message := core.ReturnMessage(&types.Message{ + Title: title, + Description: description, + StartOn: start.UTC(), + EndOn: end.UTC(), + ServiceId: serviceId, + NotifyUsers: utils.NullBool(notifyUsers == "on"), + NotifyMethod: notifyMethod, + NotifyBefore: before, + }) + _, err := message.Create() + if err != nil { + utils.Log(3, fmt.Sprintf("Error creating message %v", err)) + } + messages, _ := core.SelectMessages() + executeResponse(w, r, "messages.html", messages, "/messages") +} diff --git a/handlers/routes.go b/handlers/routes.go index f3413566..11dc30fb 100644 --- a/handlers/routes.go +++ b/handlers/routes.go @@ -70,6 +70,13 @@ func Router() *mux.Router { r.Handle("/user/{id}", http.HandlerFunc(updateUserHandler)).Methods("POST") r.Handle("/user/{id}/delete", http.HandlerFunc(usersDeleteHandler)).Methods("GET") + // MESSAGES Routes + r.Handle("/messages", http.HandlerFunc(messagesHandler)).Methods("GET") + r.Handle("/messages", http.HandlerFunc(createMessageHandler)).Methods("POST") + r.Handle("/message/{id}", http.HandlerFunc(viewMessageHandler)).Methods("GET") + r.Handle("/message/{id}", http.HandlerFunc(updateMessageHandler)).Methods("POST") + r.Handle("/message/{id}/delete", http.HandlerFunc(deleteMessageHandler)).Methods("GET") + // SETTINGS Routes r.Handle("/settings", http.HandlerFunc(settingsHandler)).Methods("GET") r.Handle("/settings", http.HandlerFunc(saveSettingsHandler)).Methods("POST") diff --git a/handlers/services.go b/handlers/services.go index 801f1847..a9939cb9 100644 --- a/handlers/services.go +++ b/handlers/services.go @@ -104,12 +104,12 @@ func createServiceHandler(w http.ResponseWriter, r *http.Request) { Name: name, Domain: domain, Method: method, - Expected: expected, + Expected: utils.NullString(expected), ExpectedStatus: status, Interval: interval, Type: checkType, Port: port, - PostData: postData, + PostData: utils.NullString(postData), Timeout: timeout, Order: order, }) @@ -139,8 +139,11 @@ func servicesDeleteHandler(w http.ResponseWriter, r *http.Request) { func servicesViewHandler(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) fields := parseGet(r) + r.ParseForm() + startField := utils.StringInt(fields.Get("start")) endField := utils.StringInt(fields.Get("end")) + group := r.Form.Get("group") serv := core.SelectService(utils.StringInt(vars["id"])) if serv == nil { w.WriteHeader(http.StatusNotFound) @@ -156,15 +159,18 @@ func servicesViewHandler(w http.ResponseWriter, r *http.Request) { if endField != 0 { end = time.Unix(endField, 0) } + if group == "" { + group = "hour" + } - data := core.GraphDataRaw(serv, start, end, "hour", "latency") + data := core.GraphDataRaw(serv, start, end, group, "latency") out := struct { Service *core.Service - Start int64 - End int64 + Start string + End string Data string - }{serv, start.Unix(), end.Unix(), data.ToString()} + }{serv, start.Format(utils.FlatpickrReadable), end.Format(utils.FlatpickrReadable), data.ToString()} executeResponse(w, r, "service.html", out, nil) } @@ -193,11 +199,11 @@ func servicesUpdateHandler(w http.ResponseWriter, r *http.Request) { service.Domain = domain service.Method = method service.ExpectedStatus = status - service.Expected = expected + service.Expected = utils.NullString(expected) service.Interval = interval service.Type = checkType service.Port = port - service.PostData = postData + service.PostData = utils.NullString(postData) service.Timeout = timeout service.Order = order diff --git a/handlers/settings.go b/handlers/settings.go index 7e0ac893..d11ed9b7 100644 --- a/handlers/settings.go +++ b/handlers/settings.go @@ -56,8 +56,8 @@ func saveSettingsHandler(w http.ResponseWriter, r *http.Request) { app.Style = style } footer := r.PostForm.Get("footer") - if footer != app.Footer { - app.Footer = footer + if footer != app.Footer.String { + app.Footer = utils.NullString(footer) } domain := r.PostForm.Get("domain") if domain != app.Domain { @@ -67,7 +67,7 @@ func saveSettingsHandler(w http.ResponseWriter, r *http.Request) { timeFloat, _ := strconv.ParseFloat(timezone, 10) app.Timezone = float32(timeFloat) - app.UseCdn = (r.PostForm.Get("enable_cdn") == "on") + app.UseCdn = utils.NullBool(r.PostForm.Get("enable_cdn") == "on") core.CoreApp, _ = core.UpdateCore(app) //notifiers.OnSettingsSaved(core.CoreApp.ToCore()) executeResponse(w, r, "settings.html", core.CoreApp, "/settings") diff --git a/handlers/setup.go b/handlers/setup.go index 2cb28004..836070ec 100644 --- a/handlers/setup.go +++ b/handlers/setup.go @@ -16,6 +16,7 @@ package handlers import ( + "database/sql" "github.com/hunterlong/statup/core" "github.com/hunterlong/statup/types" "github.com/hunterlong/statup/utils" @@ -113,7 +114,7 @@ func processSetupHandler(w http.ResponseWriter, r *http.Request) { Username: config.Username, Password: config.Password, Email: config.Email, - Admin: true, + Admin: sql.NullBool{true, true}, }) admin.Create() diff --git a/handlers/users.go b/handlers/users.go index c885c30a..f233f6ed 100644 --- a/handlers/users.go +++ b/handlers/users.go @@ -16,6 +16,7 @@ package handlers import ( + "database/sql" "fmt" "github.com/gorilla/mux" "github.com/hunterlong/statup/core" @@ -52,8 +53,8 @@ func updateUserHandler(w http.ResponseWriter, r *http.Request) { } r.ParseForm() vars := mux.Vars(r) - id, _ := strconv.Atoi(vars["id"]) - user, err := core.SelectUser(int64(id)) + id := utils.StringInt(vars["id"]) + user, err := core.SelectUser(id) if err != nil { utils.Log(3, fmt.Sprintf("user error: %v", err)) w.WriteHeader(http.StatusInternalServerError) @@ -62,12 +63,21 @@ func updateUserHandler(w http.ResponseWriter, r *http.Request) { user.Username = r.PostForm.Get("username") user.Email = r.PostForm.Get("email") - user.Admin = (r.PostForm.Get("admin") == "on") + isAdmin := r.PostForm.Get("admin") == "on" + user.Admin = sql.NullBool{isAdmin, true} password := r.PostForm.Get("password") if password != "##########" { user.Password = utils.HashPassword(password) } - user.Update() + err = user.Update() + if err != nil { + utils.Log(3, err) + } + + fmt.Println(user.Id) + fmt.Println(user.Password) + fmt.Println(user.Admin.Bool) + users, _ := core.SelectAllUsers() executeResponse(w, r, "users.html", users, "/users") } @@ -87,7 +97,7 @@ func createUserHandler(w http.ResponseWriter, r *http.Request) { Username: username, Password: password, Email: email, - Admin: (admin == "on"), + Admin: sql.NullBool{admin == "on", true}, }) _, err := user.Create() if err != nil { diff --git a/notifiers/email.go b/notifiers/email.go index 881ab2fe..09edab74 100644 --- a/notifiers/email.go +++ b/notifiers/email.go @@ -246,7 +246,7 @@ func (u *email) OnTest() error { Method: "GET", Timeout: 20, LastStatusCode: 200, - Expected: "test example", + Expected: utils.NullString("test example"), LastResponse: "this is an example response", CreatedAt: time.Now().Add(-24 * time.Hour), } diff --git a/notifiers/webhook.go b/notifiers/webhook.go index 607cb73b..8da58cec 100644 --- a/notifiers/webhook.go +++ b/notifiers/webhook.go @@ -152,7 +152,7 @@ func (w *webhooker) OnTest() error { Method: "GET", Timeout: 20, LastStatusCode: 404, - Expected: "test example", + Expected: utils.NullString("test example"), LastResponse: "this is an example response", CreatedAt: time.Now().Add(-24 * time.Hour), } diff --git a/source/css/base.css b/source/css/base.css index fb6dc1be..5bdd6b4b 100644 --- a/source/css/base.css +++ b/source/css/base.css @@ -1,4 +1,3 @@ -@charset "UTF-8"; /* Index Page */ /* Status Container */ /* Button Colors */ @@ -406,186 +405,6 @@ HTML, BODY { .pointer { cursor: pointer; } -/*! - * Pikaday - * Copyright © 2014 David Bushell | BSD & MIT license | http://dbushell.com/ - */ -.pika-single { - z-index: 9999; - display: block; - position: relative; - color: #333; - background: #fff; - border: 1px solid #ccc; - border-bottom-color: #bbb; - font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; } - .pika-single.is-hidden { - display: none; } - .pika-single.is-bound { - position: absolute; - box-shadow: 0 5px 15px -5px rgba(0, 0, 0, 0.5); } - -.pika-single { - *zoom: 1; } - .pika-single:before, .pika-single:after { - content: " "; - display: table; } - .pika-single:after { - clear: both; } - -.pika-lendar { - float: left; - width: 240px; - margin: 8px; } - -.pika-title { - position: relative; - text-align: center; } - .pika-title select { - cursor: pointer; - position: absolute; - z-index: 9998; - margin: 0; - left: 0; - top: 5px; - filter: alpha(opacity=0); - opacity: 0; } - -.pika-label { - display: inline-block; - *display: inline; - position: relative; - z-index: 9999; - overflow: hidden; - margin: 0; - padding: 5px 3px; - font-size: 14px; - line-height: 20px; - font-weight: bold; - color: #333; - background-color: #fff; } - -.pika-prev, -.pika-next { - display: block; - cursor: pointer; - position: relative; - outline: none; - border: 0; - padding: 0; - width: 20px; - height: 30px; - text-indent: 20px; - white-space: nowrap; - overflow: hidden; - background-color: transparent; - background-position: center center; - background-repeat: no-repeat; - background-size: 75% 75%; - opacity: .5; - *position: absolute; - *top: 0; } - .pika-prev:hover, - .pika-next:hover { - opacity: 1; } - .pika-prev.is-disabled, - .pika-next.is-disabled { - cursor: default; - opacity: .2; } - -.pika-prev, -.is-rtl .pika-next { - float: left; - background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAeCAYAAAAsEj5rAAAAUklEQVR42u3VMQoAIBADQf8Pgj+OD9hG2CtONJB2ymQkKe0HbwAP0xucDiQWARITIDEBEnMgMQ8S8+AqBIl6kKgHiXqQqAeJepBo/z38J/U0uAHlaBkBl9I4GwAAAABJRU5ErkJggg=="); - *left: 0; } - -.pika-next, -.is-rtl .pika-prev { - float: right; - background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAeCAYAAAAsEj5rAAAAU0lEQVR42u3VOwoAMAgE0dwfAnNjU26bYkBCFGwfiL9VVWoO+BJ4Gf3gtsEKKoFBNTCoCAYVwaAiGNQGMUHMkjGbgjk2mIONuXo0nC8XnCf1JXgArVIZAQh5TKYAAAAASUVORK5CYII="); - *right: 0; } - -.pika-select { - display: inline-block; - *display: inline; } - -.pika-table { - width: 100%; - border-collapse: collapse; - border-spacing: 0; - border: 0; } - .pika-table th, - .pika-table td { - width: 14.285714285714286%; - padding: 0; } - .pika-table th { - color: #999; - font-size: 12px; - line-height: 25px; - font-weight: bold; - text-align: center; } - .pika-table abbr { - border-bottom: none; - cursor: help; } - -.pika-button { - cursor: pointer; - display: block; - -moz-box-sizing: border-box; - box-sizing: border-box; - outline: none; - border: 0; - margin: 0; - width: 100%; - padding: 5px; - color: #666; - font-size: 12px; - line-height: 15px; - text-align: right; - background: #f5f5f5; } - .is-today .pika-button { - color: #33aaff; - font-weight: bold; } - .is-selected .pika-button { - color: #fff; - font-weight: bold; - background: #33aaff; - box-shadow: inset 0 1px 3px #178fe5; - border-radius: 3px; } - .is-disabled .pika-button, .is-outside-current-month .pika-button { - color: #999; - opacity: .3; } - .is-disabled .pika-button { - pointer-events: none; - cursor: default; } - .pika-button:hover { - color: #fff; - background: #ff8000; - box-shadow: none; - border-radius: 3px; } - .pika-button .is-selection-disabled { - pointer-events: none; - cursor: default; } - -.pika-week { - font-size: 11px; - color: #999; } - -.is-inrange .pika-button { - background: #D5E9F7; } - -.is-startrange .pika-button { - color: #fff; - background: #6CB31D; - box-shadow: none; - border-radius: 3px; } - -.is-endrange .pika-button { - color: #fff; - background: #33aaff; - box-shadow: none; - border-radius: 3px; } - @media (max-width: 767px) { HTML, BODY { background-color: #fcfcfc; } diff --git a/source/css/flatpickr.min.css b/source/css/flatpickr.min.css new file mode 100644 index 00000000..5bcd1ff8 --- /dev/null +++ b/source/css/flatpickr.min.css @@ -0,0 +1,13 @@ +.flatpickr-calendar{background:transparent;opacity:0;display:none;text-align:center;visibility:hidden;padding:0;-webkit-animation:none;animation:none;direction:ltr;border:0;font-size:14px;line-height:24px;border-radius:5px;position:absolute;width:307.875px;-webkit-box-sizing:border-box;box-sizing:border-box;-ms-touch-action:manipulation;touch-action:manipulation;background:#fff;-webkit-box-shadow:1px 0 0 #e6e6e6,-1px 0 0 #e6e6e6,0 1px 0 #e6e6e6,0 -1px 0 #e6e6e6,0 3px 13px rgba(0,0,0,0.08);box-shadow:1px 0 0 #e6e6e6,-1px 0 0 #e6e6e6,0 1px 0 #e6e6e6,0 -1px 0 #e6e6e6,0 3px 13px rgba(0,0,0,0.08);}.flatpickr-calendar.open,.flatpickr-calendar.inline{opacity:1;max-height:640px;visibility:visible}.flatpickr-calendar.open{display:inline-block;z-index:99999}.flatpickr-calendar.animate.open{-webkit-animation:fpFadeInDown 300ms cubic-bezier(.23,1,.32,1);animation:fpFadeInDown 300ms cubic-bezier(.23,1,.32,1)}.flatpickr-calendar.inline{display:block;position:relative;top:2px}.flatpickr-calendar.static{position:absolute;top:calc(100% + 2px);}.flatpickr-calendar.static.open{z-index:999;display:block}.flatpickr-calendar.multiMonth .flatpickr-days .dayContainer:nth-child(n+1) .flatpickr-day.inRange:nth-child(7n+7){-webkit-box-shadow:none !important;box-shadow:none !important}.flatpickr-calendar.multiMonth .flatpickr-days .dayContainer:nth-child(n+2) .flatpickr-day.inRange:nth-child(7n+1){-webkit-box-shadow:-2px 0 0 #e6e6e6,5px 0 0 #e6e6e6;box-shadow:-2px 0 0 #e6e6e6,5px 0 0 #e6e6e6}.flatpickr-calendar .hasWeeks .dayContainer,.flatpickr-calendar .hasTime .dayContainer{border-bottom:0;border-bottom-right-radius:0;border-bottom-left-radius:0}.flatpickr-calendar .hasWeeks .dayContainer{border-left:0}.flatpickr-calendar.showTimeInput.hasTime .flatpickr-time{height:40px;border-top:1px solid #e6e6e6}.flatpickr-calendar.noCalendar.hasTime .flatpickr-time{height:auto}.flatpickr-calendar:before,.flatpickr-calendar:after{position:absolute;display:block;pointer-events:none;border:solid transparent;content:'';height:0;width:0;left:22px}.flatpickr-calendar.rightMost:before,.flatpickr-calendar.rightMost:after{left:auto;right:22px}.flatpickr-calendar:before{border-width:5px;margin:0 -5px}.flatpickr-calendar:after{border-width:4px;margin:0 -4px}.flatpickr-calendar.arrowTop:before,.flatpickr-calendar.arrowTop:after{bottom:100%}.flatpickr-calendar.arrowTop:before{border-bottom-color:#e6e6e6}.flatpickr-calendar.arrowTop:after{border-bottom-color:#fff}.flatpickr-calendar.arrowBottom:before,.flatpickr-calendar.arrowBottom:after{top:100%}.flatpickr-calendar.arrowBottom:before{border-top-color:#e6e6e6}.flatpickr-calendar.arrowBottom:after{border-top-color:#fff}.flatpickr-calendar:focus{outline:0}.flatpickr-wrapper{position:relative;display:inline-block}.flatpickr-months{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;}.flatpickr-months .flatpickr-month{background:transparent;color:rgba(0,0,0,0.9);fill:rgba(0,0,0,0.9);height:28px;line-height:1;text-align:center;position:relative;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;overflow:hidden;-webkit-box-flex:1;-webkit-flex:1;-ms-flex:1;flex:1}.flatpickr-months .flatpickr-prev-month,.flatpickr-months .flatpickr-next-month{text-decoration:none;cursor:pointer;position:absolute;top:0;line-height:16px;height:28px;padding:10px;z-index:3;color:rgba(0,0,0,0.9);fill:rgba(0,0,0,0.9);}.flatpickr-months .flatpickr-prev-month.disabled,.flatpickr-months .flatpickr-next-month.disabled{display:none}.flatpickr-months .flatpickr-prev-month i,.flatpickr-months .flatpickr-next-month i{position:relative}.flatpickr-months .flatpickr-prev-month.flatpickr-prev-month,.flatpickr-months .flatpickr-next-month.flatpickr-prev-month{/* + /*rtl:begin:ignore*/left:0;/* + /*rtl:end:ignore*/}/* + /*rtl:begin:ignore*/ +/* + /*rtl:end:ignore*/ +.flatpickr-months .flatpickr-prev-month.flatpickr-next-month,.flatpickr-months .flatpickr-next-month.flatpickr-next-month{/* + /*rtl:begin:ignore*/right:0;/* + /*rtl:end:ignore*/}/* + /*rtl:begin:ignore*/ +/* + /*rtl:end:ignore*/ +.flatpickr-months .flatpickr-prev-month:hover,.flatpickr-months .flatpickr-next-month:hover{color:#959ea9;}.flatpickr-months .flatpickr-prev-month:hover svg,.flatpickr-months .flatpickr-next-month:hover svg{fill:#f64747}.flatpickr-months .flatpickr-prev-month svg,.flatpickr-months .flatpickr-next-month svg{width:14px;height:14px;}.flatpickr-months .flatpickr-prev-month svg path,.flatpickr-months .flatpickr-next-month svg path{-webkit-transition:fill .1s;transition:fill .1s;fill:inherit}.numInputWrapper{position:relative;height:auto;}.numInputWrapper input,.numInputWrapper span{display:inline-block}.numInputWrapper input{width:100%;}.numInputWrapper input::-ms-clear{display:none}.numInputWrapper span{position:absolute;right:0;width:14px;padding:0 4px 0 2px;height:50%;line-height:50%;opacity:0;cursor:pointer;border:1px solid rgba(57,57,57,0.15);-webkit-box-sizing:border-box;box-sizing:border-box;}.numInputWrapper span:hover{background:rgba(0,0,0,0.1)}.numInputWrapper span:active{background:rgba(0,0,0,0.2)}.numInputWrapper span:after{display:block;content:"";position:absolute}.numInputWrapper span.arrowUp{top:0;border-bottom:0;}.numInputWrapper span.arrowUp:after{border-left:4px solid transparent;border-right:4px solid transparent;border-bottom:4px solid rgba(57,57,57,0.6);top:26%}.numInputWrapper span.arrowDown{top:50%;}.numInputWrapper span.arrowDown:after{border-left:4px solid transparent;border-right:4px solid transparent;border-top:4px solid rgba(57,57,57,0.6);top:40%}.numInputWrapper span svg{width:inherit;height:auto;}.numInputWrapper span svg path{fill:rgba(0,0,0,0.5)}.numInputWrapper:hover{background:rgba(0,0,0,0.05);}.numInputWrapper:hover span{opacity:1}.flatpickr-current-month{font-size:135%;line-height:inherit;font-weight:300;color:inherit;position:absolute;width:75%;left:12.5%;padding:6.16px 0 0 0;line-height:1;height:28px;display:inline-block;text-align:center;-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0);}.flatpickr-current-month span.cur-month{font-family:inherit;font-weight:700;color:inherit;display:inline-block;margin-left:.5ch;padding:0;}.flatpickr-current-month span.cur-month:hover{background:rgba(0,0,0,0.05)}.flatpickr-current-month .numInputWrapper{width:6ch;width:7ch\0;display:inline-block;}.flatpickr-current-month .numInputWrapper span.arrowUp:after{border-bottom-color:rgba(0,0,0,0.9)}.flatpickr-current-month .numInputWrapper span.arrowDown:after{border-top-color:rgba(0,0,0,0.9)}.flatpickr-current-month input.cur-year{background:transparent;-webkit-box-sizing:border-box;box-sizing:border-box;color:inherit;cursor:text;padding:0 0 0 .5ch;margin:0;display:inline-block;font-size:inherit;font-family:inherit;font-weight:300;line-height:inherit;height:auto;border:0;border-radius:0;vertical-align:initial;}.flatpickr-current-month input.cur-year:focus{outline:0}.flatpickr-current-month input.cur-year[disabled],.flatpickr-current-month input.cur-year[disabled]:hover{font-size:100%;color:rgba(0,0,0,0.5);background:transparent;pointer-events:none}.flatpickr-weekdays{background:transparent;text-align:center;overflow:hidden;width:100%;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-align:center;-webkit-align-items:center;-ms-flex-align:center;align-items:center;height:28px;}.flatpickr-weekdays .flatpickr-weekdaycontainer{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-flex:1;-webkit-flex:1;-ms-flex:1;flex:1}span.flatpickr-weekday{cursor:default;font-size:90%;background:transparent;color:rgba(0,0,0,0.54);line-height:1;margin:0;text-align:center;display:block;-webkit-box-flex:1;-webkit-flex:1;-ms-flex:1;flex:1;font-weight:bolder}.dayContainer,.flatpickr-weeks{padding:1px 0 0 0}.flatpickr-days{position:relative;overflow:hidden;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-align:start;-webkit-align-items:flex-start;-ms-flex-align:start;align-items:flex-start;width:307.875px;}.flatpickr-days:focus{outline:0}.dayContainer{padding:0;outline:0;text-align:left;width:307.875px;min-width:307.875px;max-width:307.875px;-webkit-box-sizing:border-box;box-sizing:border-box;display:inline-block;display:-ms-flexbox;display:-webkit-box;display:-webkit-flex;display:flex;-webkit-flex-wrap:wrap;flex-wrap:wrap;-ms-flex-wrap:wrap;-ms-flex-pack:justify;-webkit-justify-content:space-around;justify-content:space-around;-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0);opacity:1;}.dayContainer + .dayContainer{-webkit-box-shadow:-1px 0 0 #e6e6e6;box-shadow:-1px 0 0 #e6e6e6}.flatpickr-day{background:none;border:1px solid transparent;border-radius:150px;-webkit-box-sizing:border-box;box-sizing:border-box;color:#393939;cursor:pointer;font-weight:400;width:14.2857143%;-webkit-flex-basis:14.2857143%;-ms-flex-preferred-size:14.2857143%;flex-basis:14.2857143%;max-width:39px;height:39px;line-height:39px;margin:0;display:inline-block;position:relative;-webkit-box-pack:center;-webkit-justify-content:center;-ms-flex-pack:center;justify-content:center;text-align:center;}.flatpickr-day.inRange,.flatpickr-day.prevMonthDay.inRange,.flatpickr-day.nextMonthDay.inRange,.flatpickr-day.today.inRange,.flatpickr-day.prevMonthDay.today.inRange,.flatpickr-day.nextMonthDay.today.inRange,.flatpickr-day:hover,.flatpickr-day.prevMonthDay:hover,.flatpickr-day.nextMonthDay:hover,.flatpickr-day:focus,.flatpickr-day.prevMonthDay:focus,.flatpickr-day.nextMonthDay:focus{cursor:pointer;outline:0;background:#e6e6e6;border-color:#e6e6e6}.flatpickr-day.today{border-color:#959ea9;}.flatpickr-day.today:hover,.flatpickr-day.today:focus{border-color:#959ea9;background:#959ea9;color:#fff}.flatpickr-day.selected,.flatpickr-day.startRange,.flatpickr-day.endRange,.flatpickr-day.selected.inRange,.flatpickr-day.startRange.inRange,.flatpickr-day.endRange.inRange,.flatpickr-day.selected:focus,.flatpickr-day.startRange:focus,.flatpickr-day.endRange:focus,.flatpickr-day.selected:hover,.flatpickr-day.startRange:hover,.flatpickr-day.endRange:hover,.flatpickr-day.selected.prevMonthDay,.flatpickr-day.startRange.prevMonthDay,.flatpickr-day.endRange.prevMonthDay,.flatpickr-day.selected.nextMonthDay,.flatpickr-day.startRange.nextMonthDay,.flatpickr-day.endRange.nextMonthDay{background:#569ff7;-webkit-box-shadow:none;box-shadow:none;color:#fff;border-color:#569ff7}.flatpickr-day.selected.startRange,.flatpickr-day.startRange.startRange,.flatpickr-day.endRange.startRange{border-radius:50px 0 0 50px}.flatpickr-day.selected.endRange,.flatpickr-day.startRange.endRange,.flatpickr-day.endRange.endRange{border-radius:0 50px 50px 0}.flatpickr-day.selected.startRange + .endRange:not(:nth-child(7n+1)),.flatpickr-day.startRange.startRange + .endRange:not(:nth-child(7n+1)),.flatpickr-day.endRange.startRange + .endRange:not(:nth-child(7n+1)){-webkit-box-shadow:-10px 0 0 #569ff7;box-shadow:-10px 0 0 #569ff7}.flatpickr-day.selected.startRange.endRange,.flatpickr-day.startRange.startRange.endRange,.flatpickr-day.endRange.startRange.endRange{border-radius:50px}.flatpickr-day.inRange{border-radius:0;-webkit-box-shadow:-5px 0 0 #e6e6e6,5px 0 0 #e6e6e6;box-shadow:-5px 0 0 #e6e6e6,5px 0 0 #e6e6e6}.flatpickr-day.disabled,.flatpickr-day.disabled:hover,.flatpickr-day.prevMonthDay,.flatpickr-day.nextMonthDay,.flatpickr-day.notAllowed,.flatpickr-day.notAllowed.prevMonthDay,.flatpickr-day.notAllowed.nextMonthDay{color:rgba(57,57,57,0.3);background:transparent;border-color:transparent;cursor:default}.flatpickr-day.disabled,.flatpickr-day.disabled:hover{cursor:not-allowed;color:rgba(57,57,57,0.1)}.flatpickr-day.week.selected{border-radius:0;-webkit-box-shadow:-5px 0 0 #569ff7,5px 0 0 #569ff7;box-shadow:-5px 0 0 #569ff7,5px 0 0 #569ff7}.flatpickr-day.hidden{visibility:hidden}.rangeMode .flatpickr-day{margin-top:1px}.flatpickr-weekwrapper{display:inline-block;float:left;}.flatpickr-weekwrapper .flatpickr-weeks{padding:0 12px;-webkit-box-shadow:1px 0 0 #e6e6e6;box-shadow:1px 0 0 #e6e6e6}.flatpickr-weekwrapper .flatpickr-weekday{float:none;width:100%;line-height:28px}.flatpickr-weekwrapper span.flatpickr-day,.flatpickr-weekwrapper span.flatpickr-day:hover{display:block;width:100%;max-width:none;color:rgba(57,57,57,0.3);background:transparent;cursor:default;border:none}.flatpickr-innerContainer{display:block;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-sizing:border-box;box-sizing:border-box;overflow:hidden;}.flatpickr-rContainer{display:inline-block;padding:0;-webkit-box-sizing:border-box;box-sizing:border-box}.flatpickr-time{text-align:center;outline:0;display:block;height:0;line-height:40px;max-height:40px;-webkit-box-sizing:border-box;box-sizing:border-box;overflow:hidden;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;}.flatpickr-time:after{content:"";display:table;clear:both}.flatpickr-time .numInputWrapper{-webkit-box-flex:1;-webkit-flex:1;-ms-flex:1;flex:1;width:40%;height:40px;float:left;}.flatpickr-time .numInputWrapper span.arrowUp:after{border-bottom-color:#393939}.flatpickr-time .numInputWrapper span.arrowDown:after{border-top-color:#393939}.flatpickr-time.hasSeconds .numInputWrapper{width:26%}.flatpickr-time.time24hr .numInputWrapper{width:49%}.flatpickr-time input{background:transparent;-webkit-box-shadow:none;box-shadow:none;border:0;border-radius:0;text-align:center;margin:0;padding:0;height:inherit;line-height:inherit;color:#393939;font-size:14px;position:relative;-webkit-box-sizing:border-box;box-sizing:border-box;}.flatpickr-time input.flatpickr-hour{font-weight:bold}.flatpickr-time input.flatpickr-minute,.flatpickr-time input.flatpickr-second{font-weight:400}.flatpickr-time input:focus{outline:0;border:0}.flatpickr-time .flatpickr-time-separator,.flatpickr-time .flatpickr-am-pm{height:inherit;display:inline-block;float:left;line-height:inherit;color:#393939;font-weight:bold;width:2%;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-webkit-align-self:center;-ms-flex-item-align:center;align-self:center}.flatpickr-time .flatpickr-am-pm{outline:0;width:18%;cursor:pointer;text-align:center;font-weight:400}.flatpickr-time input:hover,.flatpickr-time .flatpickr-am-pm:hover,.flatpickr-time input:focus,.flatpickr-time .flatpickr-am-pm:focus{background:#f3f3f3}.flatpickr-input[readonly]{cursor:pointer}@-webkit-keyframes fpFadeInDown{from{opacity:0;-webkit-transform:translate3d(0,-20px,0);transform:translate3d(0,-20px,0)}to{opacity:1;-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}}@keyframes fpFadeInDown{from{opacity:0;-webkit-transform:translate3d(0,-20px,0);transform:translate3d(0,-20px,0)}to{opacity:1;-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}} \ No newline at end of file diff --git a/source/js/flatpickr.js b/source/js/flatpickr.js new file mode 100644 index 00000000..e9602014 --- /dev/null +++ b/source/js/flatpickr.js @@ -0,0 +1,2 @@ +/* flatpickr v4.5.2,, @license MIT */ +!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):e.flatpickr=t()}(this,function(){"use strict";var X=function(e){return("0"+e).slice(-2)},ee=function(e){return!0===e?1:0};function te(n,a,i){var o;return void 0===i&&(i=!1),function(){var e=this,t=arguments;null!==o&&clearTimeout(o),o=window.setTimeout(function(){o=null,i||n.apply(e,t)},a),i&&!o&&n.apply(e,t)}}var ne=function(e){return e instanceof Array?e:[e]},e=function(){},ae=function(e,t,n){return n.months[t?"shorthand":"longhand"][e]},w={D:e,F:function(e,t,n){e.setMonth(n.months.longhand.indexOf(t))},G:function(e,t){e.setHours(parseFloat(t))},H:function(e,t){e.setHours(parseFloat(t))},J:function(e,t){e.setDate(parseFloat(t))},K:function(e,t,n){e.setHours(e.getHours()%12+12*ee(new RegExp(n.amPM[1],"i").test(t)))},M:function(e,t,n){e.setMonth(n.months.shorthand.indexOf(t))},S:function(e,t){e.setSeconds(parseFloat(t))},U:function(e,t){return new Date(1e3*parseFloat(t))},W:function(e,t){var n=parseInt(t);return new Date(e.getFullYear(),0,2+7*(n-1),0,0,0,0)},Y:function(e,t){e.setFullYear(parseFloat(t))},Z:function(e,t){return new Date(t)},d:function(e,t){e.setDate(parseFloat(t))},h:function(e,t){e.setHours(parseFloat(t))},i:function(e,t){e.setMinutes(parseFloat(t))},j:function(e,t){e.setDate(parseFloat(t))},l:e,m:function(e,t){e.setMonth(parseFloat(t)-1)},n:function(e,t){e.setMonth(parseFloat(t)-1)},s:function(e,t){e.setSeconds(parseFloat(t))},w:e,y:function(e,t){e.setFullYear(2e3+parseFloat(t))}},ie={D:"(\\w+)",F:"(\\w+)",G:"(\\d\\d|\\d)",H:"(\\d\\d|\\d)",J:"(\\d\\d|\\d)\\w+",K:"",M:"(\\w+)",S:"(\\d\\d|\\d)",U:"(.+)",W:"(\\d\\d|\\d)",Y:"(\\d{4})",Z:"(.+)",d:"(\\d\\d|\\d)",h:"(\\d\\d|\\d)",i:"(\\d\\d|\\d)",j:"(\\d\\d|\\d)",l:"(\\w+)",m:"(\\d\\d|\\d)",n:"(\\d\\d|\\d)",s:"(\\d\\d|\\d)",w:"(\\d\\d|\\d)",y:"(\\d{2})"},l={Z:function(e){return e.toISOString()},D:function(e,t,n){return t.weekdays.shorthand[l.w(e,t,n)]},F:function(e,t,n){return ae(l.n(e,t,n)-1,!1,t)},G:function(e,t,n){return X(l.h(e,t,n))},H:function(e){return X(e.getHours())},J:function(e,t){return void 0!==t.ordinal?e.getDate()+t.ordinal(e.getDate()):e.getDate()},K:function(e,t){return t.amPM[ee(11Math.min(t,n)&&e",noCalendar:!1,now:new Date,onChange:[],onClose:[],onDayCreate:[],onDestroy:[],onKeyDown:[],onMonthChange:[],onOpen:[],onParseConfig:[],onReady:[],onValueUpdate:[],onYearChange:[],onPreCalendarPosition:[],plugins:[],position:"auto",positionElement:void 0,prevArrow:"",shorthandCurrentMonth:!1,showMonths:1,static:!1,time_24hr:!1,weekNumbers:!1,wrap:!1};function fe(e,t,n){if(!0===n)return e.classList.add(t);e.classList.remove(t)}function me(e,t,n){var a=window.document.createElement(e);return t=t||"",n=n||"",a.className=t,void 0!==n&&(a.textContent=n),a}function ge(e){for(;e.firstChild;)e.removeChild(e.firstChild)}function pe(e,t){var n=me("div","numInputWrapper"),a=me("input","numInput "+e),i=me("span","arrowUp"),o=me("span","arrowDown");if(a.type="text",a.pattern="\\d*",void 0!==t)for(var r in t)a.setAttribute(r,t[r]);return n.appendChild(a),n.appendChild(i),n.appendChild(o),n}"function"!=typeof Object.assign&&(Object.assign=function(n){if(!n)throw TypeError("Cannot convert undefined or null to object");for(var e=arguments.length,a=new Array(1o)&&(h.amPM.textContent=h.l10n.amPM[ee(h.amPM.textContent===h.l10n.amPM[0])]),n.value=X(c)}}(e);var t=h._input.value;m(),Z(),h._input.value!==t&&h._debouncedChange()}}function m(){if(void 0!==h.hourElement&&void 0!==h.minuteElement){var e,t,n=(parseInt(h.hourElement.value.slice(-2),10)||0)%24,a=(parseInt(h.minuteElement.value,10)||0)%60,i=void 0!==h.secondElement?(parseInt(h.secondElement.value,10)||0)%60:0;void 0!==h.amPM&&(e=n,t=h.amPM.textContent,n=e%12+12*ee(t===h.l10n.amPM[1]));var o=void 0!==h.config.minTime||h.config.minDate&&h.minDateHasTime&&h.latestSelectedDateObj&&0===ce(h.latestSelectedDateObj,h.config.minDate,!0);if(void 0!==h.config.maxTime||h.config.maxDate&&h.maxDateHasTime&&h.latestSelectedDateObj&&0===ce(h.latestSelectedDateObj,h.config.maxDate,!0)){var r=void 0!==h.config.maxTime?h.config.maxTime:h.config.maxDate;(n=Math.min(n,r.getHours()))===r.getHours()&&(a=Math.min(a,r.getMinutes())),a===r.getMinutes()&&(i=Math.min(i,r.getSeconds()))}if(o){var l=void 0!==h.config.minTime?h.config.minTime:h.config.minDate;(n=Math.max(n,l.getHours()))===l.getHours()&&(a=Math.max(a,l.getMinutes())),a===l.getMinutes()&&(i=Math.max(i,l.getSeconds()))}c(n,a,i)}}function i(e){var t=e||h.latestSelectedDateObj;t&&c(t.getHours(),t.getMinutes(),t.getSeconds())}function a(){var e=h.config.defaultHour,t=h.config.defaultMinute,n=h.config.defaultSeconds;if(void 0!==h.config.minDate){var a=h.config.minDate.getHours(),i=h.config.minDate.getMinutes();(e=Math.max(e,a))===a&&(t=Math.max(i,t)),e===a&&t===i&&(n=h.config.minDate.getSeconds())}if(void 0!==h.config.maxDate){var o=h.config.maxDate.getHours(),r=h.config.maxDate.getMinutes();(e=Math.min(e,o))===o&&(t=Math.min(r,t)),e===o&&t===r&&(n=h.config.maxDate.getSeconds())}c(e,t,n)}function c(e,t,n){void 0!==h.latestSelectedDateObj&&h.latestSelectedDateObj.setHours(e%24,t,n||0,0),h.hourElement&&h.minuteElement&&!h.isMobile&&(h.hourElement.value=X(h.config.time_24hr?e:(12+e)%12+12*ee(e%12==0)),h.minuteElement.value=X(t),void 0!==h.amPM&&(h.amPM.textContent=h.l10n.amPM[ee(12<=e)]),void 0!==h.secondElement&&(h.secondElement.value=X(n)))}function n(e){var t=parseInt(e.target.value)+(e.delta||0);(1h.now?h.config.minDate:h.config.maxDate&&h.config.maxDate"+h.config.getWeek(t)+""),q("onDayCreate",r),r}function b(e){e.focus(),"range"===h.config.mode&&P(e)}function w(e){for(var t=0=Math.abs(t))return b(s)}h.changeMonth(i),C(w(i),0)}(a,t):b(a)}function y(e,t){for(var n=(new Date(e,t,1).getDay()-h.l10n.firstDayOfWeek+7)%7,a=h.utils.getDaysInMonth((t-1+12)%12),i=h.utils.getDaysInMonth(t),o=window.document.createDocumentFragment(),r=1\n "+t.join("")+"\n \n "}function I(e,t){void 0===t&&(t=!0);var n=t?e:e-h.currentMonth;n<0&&!0===h._hidePrevMonthArrow||0h.config.maxDate.getFullYear())){var t=e,n=h.currentYear!==t;h.currentYear=t||h.currentYear,h.config.maxDate&&h.currentYear===h.config.maxDate.getFullYear()?h.currentMonth=Math.min(h.config.maxDate.getMonth(),h.currentMonth):h.config.minDate&&h.currentYear===h.config.minDate.getFullYear()&&(h.currentMonth=Math.max(h.config.minDate.getMonth(),h.currentMonth)),n&&(h.redraw(),q("onYearChange"))}}function N(e,t){void 0===t&&(t=!0);var n=h.parseDate(e,void 0,t);if(h.config.minDate&&n&&ce(n,h.config.minDate,void 0!==t?t:!h.minDateHasTime)<0||h.config.maxDate&&n&&0=a.from.getTime()&&n.getTime()<=a.to.getTime())return i}return!i}function F(e){return void 0!==h.daysContainer&&(-1===e.className.indexOf("hidden")&&h.daysContainer.contains(e))}function A(e){var t=e.target===h._input,n=h.config.allowInput,a=h.isOpen&&(!n||!t),i=h.config.inline&&t&&!n;if(13===e.keyCode&&t){if(n)return h.setDate(h._input.value,!0,e.target===h.altInput?h.config.altFormat:h.config.dateFormat),e.target.blur();h.open()}else if(O(e.target)||a||i){var o=!!h.timeContainer&&h.timeContainer.contains(e.target);switch(e.keyCode){case 13:o?f():B(e);break;case 27:e.preventDefault(),R();break;case 8:case 46:t&&!h.config.allowInput&&(e.preventDefault(),h.clear());break;case 37:case 39:if(o)h.hourElement&&h.hourElement.focus();else if(e.preventDefault(),void 0!==h.daysContainer&&(!1===n||F(document.activeElement))){var r=39===e.keyCode?1:-1;e.ctrlKey?(I(r),C(w(1),0)):C(void 0,r)}break;case 38:case 40:e.preventDefault();var l=40===e.keyCode?1:-1;h.daysContainer&&void 0!==e.target.$i?e.ctrlKey?(_(h.currentYear-l),C(w(1),0)):o||C(void 0,7*l):h.config.enableTime&&(!o&&h.hourElement&&h.hourElement.focus(),f(e),h._debouncedChange());break;case 9:if(!o){h.element.focus();break}var c=[h.hourElement,h.minuteElement,h.secondElement,h.amPM].filter(function(e){return e}),d=c.indexOf(e.target);if(-1!==d){var s=c[d+(e.shiftKey?-1:1)];void 0!==s?(e.preventDefault(),s.focus()):h.element.focus()}}}if(void 0!==h.amPM&&e.target===h.amPM)switch(e.key){case h.l10n.amPM[0].charAt(0):case h.l10n.amPM[0].charAt(0).toLowerCase():h.amPM.textContent=h.l10n.amPM[0],m(),Z();break;case h.l10n.amPM[1].charAt(0):case h.l10n.amPM[1].charAt(0).toLowerCase():h.amPM.textContent=h.l10n.amPM[1],m(),Z()}q("onKeyDown",e)}function P(o){if(1===h.selectedDates.length&&(!o||o.classList.contains("flatpickr-day")&&!o.classList.contains("disabled"))){for(var r=o?o.dateObj.getTime():h.days.firstElementChild.dateObj.getTime(),l=h.parseDate(h.selectedDates[0],void 0,!0).getTime(),e=Math.min(r,h.selectedDates[0].getTime()),t=Math.max(r,h.selectedDates[0].getTime()),n=h.daysContainer.lastChild.lastChild.dateObj.getTime(),c=!1,d=0,s=0,a=e;a=a||(ln,s=window.pageYOffset+l.top+(d?-n-2:t.offsetHeight+2);if(fe(h.calendarContainer,"arrowTop",!d),fe(h.calendarContainer,"arrowBottom",d),!h.config.inline){var u=window.pageXOffset+l.left-(null!=r&&"center"===r?(a-l.width)/2:0),f=window.document.body.offsetWidth-l.right,m=u+a>window.document.body.offsetWidth;fe(h.calendarContainer,"rightMost",m),h.config.static||(h.calendarContainer.style.top=s+"px",m?(h.calendarContainer.style.left="auto",h.calendarContainer.style.right=f+"px"):(h.calendarContainer.style.left=u+"px",h.calendarContainer.style.right="auto"))}}}function W(){h.config.noCalendar||h.isMobile||(G(),M())}function R(){h._input.focus(),-1!==window.navigator.userAgent.indexOf("MSIE")||void 0!==navigator.msMaxTouchPoints?setTimeout(h.close,0):h.close()}function B(e){e.preventDefault(),e.stopPropagation();var t=function e(t,n){return n(t)?t:t.parentNode?e(t.parentNode,n):void 0}(e.target,function(e){return e.classList&&e.classList.contains("flatpickr-day")&&!e.classList.contains("disabled")&&!e.classList.contains("notAllowed")});if(void 0!==t){var n=t,a=h.latestSelectedDateObj=new Date(n.dateObj.getTime()),i=(a.getMonth()h.currentMonth+h.config.showMonths-1)&&"range"!==h.config.mode;if(h.selectedDateElem=n,"single"===h.config.mode)h.selectedDates=[a];else if("multiple"===h.config.mode){var o=z(a);o?h.selectedDates.splice(parseInt(o),1):h.selectedDates.push(a)}else"range"===h.config.mode&&(2===h.selectedDates.length&&h.clear(!1),h.selectedDates.push(a),0!==ce(a,h.selectedDates[0],!0)&&h.selectedDates.sort(function(e,t){return e.getTime()-t.getTime()}));if(m(),i){var r=h.currentYear!==a.getFullYear();h.currentYear=a.getFullYear(),h.currentMonth=a.getMonth(),r&&q("onYearChange"),q("onMonthChange")}if(G(),M(),Z(),h.config.enableTime&&setTimeout(function(){return h.showTimeInput=!0},50),i||"range"===h.config.mode||1!==h.config.showMonths?h.selectedDateElem&&h.selectedDateElem.focus():b(n),void 0!==h.hourElement&&setTimeout(function(){return void 0!==h.hourElement&&h.hourElement.select()},451),h.config.closeOnSelect){var l="single"===h.config.mode&&!h.config.enableTime,c="range"===h.config.mode&&2===h.selectedDates.length&&!h.config.enableTime;(l||c)&&R()}g()}}h.parseDate=le({config:h.config,l10n:h.l10n}),h._handlers=[],h._bind=o,h._setHoursFromDate=i,h._positionCalendar=L,h.changeMonth=I,h.changeYear=_,h.clear=function(e){void 0===e&&(e=!0);h.input.value="",void 0!==h.altInput&&(h.altInput.value="");void 0!==h.mobileInput&&(h.mobileInput.value="");h.selectedDates=[],h.latestSelectedDateObj=void 0,!(h.showTimeInput=!1)===h.config.enableTime&&a();h.redraw(),e&&q("onChange")},h.close=function(){h.isOpen=!1,h.isMobile||(h.calendarContainer.classList.remove("open"),h._input.classList.remove("active"));q("onClose")},h._createElement=me,h.destroy=function(){void 0!==h.config&&q("onDestroy");for(var e=h._handlers.length;e--;){var t=h._handlers[e];t.element.removeEventListener(t.event,t.handler,t.options)}if(h._handlers=[],h.mobileInput)h.mobileInput.parentNode&&h.mobileInput.parentNode.removeChild(h.mobileInput),h.mobileInput=void 0;else if(h.calendarContainer&&h.calendarContainer.parentNode)if(h.config.static&&h.calendarContainer.parentNode){var n=h.calendarContainer.parentNode;if(n.lastChild&&n.removeChild(n.lastChild),n.parentNode){for(;n.firstChild;)n.parentNode.insertBefore(n.firstChild,n);n.parentNode.removeChild(n)}}else h.calendarContainer.parentNode.removeChild(h.calendarContainer);h.altInput&&(h.input.type="text",h.altInput.parentNode&&h.altInput.parentNode.removeChild(h.altInput),delete h.altInput);h.input&&(h.input.type=h.input._type,h.input.classList.remove("flatpickr-input"),h.input.removeAttribute("readonly"),h.input.value="");["_showTimeInput","latestSelectedDateObj","_hideNextMonthArrow","_hidePrevMonthArrow","__hideNextMonthArrow","__hidePrevMonthArrow","isMobile","isOpen","selectedDateElem","minDateHasTime","maxDateHasTime","days","daysContainer","_input","_positionElement","innerContainer","rContainer","monthNav","todayDateElem","calendarContainer","weekdayContainer","prevMonthNav","nextMonthNav","currentMonthElement","currentYearElement","navigationCurrentMonth","selectedDateElem","config"].forEach(function(e){try{delete h[e]}catch(e){}})},h.isEnabled=N,h.jumpToDate=l,h.open=function(e,t){void 0===t&&(t=h._positionElement);if(!0===h.isMobile)return e&&(e.preventDefault(),e.target&&e.target.blur()),void 0!==h.mobileInput&&(h.mobileInput.focus(),h.mobileInput.click()),void q("onOpen");if(h._input.disabled||h.config.inline)return;var n=h.isOpen;h.isOpen=!0,n||(h.calendarContainer.classList.add("open"),h._input.classList.add("active"),q("onOpen"),L(t));!0===h.config.enableTime&&!0===h.config.noCalendar&&(0===h.selectedDates.length&&(h.setDate(void 0!==h.config.minDate?new Date(h.config.minDate.getTime()):new Date,!1),a(),Z()),!1!==h.config.allowInput||void 0!==e&&h.timeContainer.contains(e.relatedTarget)||setTimeout(function(){return h.hourElement.select()},50))},h.redraw=W,h.set=function(e,t){null!==e&&"object"==typeof e?Object.assign(h.config,e):(h.config[e]=t,void 0!==K[e]?K[e].forEach(function(e){return e()}):-1h.config.maxDate.getMonth():h.currentYear>h.config.maxDate.getFullYear()))}function V(t){return h.selectedDates.map(function(e){return h.formatDate(e,t)}).filter(function(e,t,n){return"range"!==h.config.mode||h.config.enableTime||n.indexOf(e)===t}).join("range"!==h.config.mode?h.config.conjunction:h.l10n.rangeSeparator)}function Z(e){if(void 0===e&&(e=!0),0===h.selectedDates.length)return h.clear(e);void 0!==h.mobileInput&&h.mobileFormatStr&&(h.mobileInput.value=void 0!==h.latestSelectedDateObj?h.formatDate(h.latestSelectedDateObj,h.mobileFormatStr):""),h.input.value=V(h.config.dateFormat),void 0!==h.altInput&&(h.altInput.value=V(h.config.altFormat)),!1!==e&&q("onValueUpdate")}function Q(e){e.preventDefault();var t=h.prevMonthNav.contains(e.target),n=h.nextMonthNav.contains(e.target);t||n?I(t?-1:1):0<=h.yearElements.indexOf(e.target)?e.target.select():e.target.classList.contains("arrowUp")?h.changeYear(h.currentYear+1):e.target.classList.contains("arrowDown")&&h.changeYear(h.currentYear-1)}return function(){h.element=h.input=d,h.isOpen=!1,function(){var e=["wrap","weekNumbers","allowInput","clickOpens","time_24hr","enableTime","noCalendar","altInput","shorthandCurrentMonth","inline","static","enableSeconds","disableMobile"],t=Object.assign({},s,JSON.parse(JSON.stringify(d.dataset||{}))),n={};h.config.parseDate=t.parseDate,h.config.formatDate=t.formatDate,Object.defineProperty(h.config,"enable",{get:function(){return h.config._enable},set:function(e){h.config._enable=U(e)}}),Object.defineProperty(h.config,"disable",{get:function(){return h.config._disable},set:function(e){h.config._disable=U(e)}});var a="time"===t.mode;t.dateFormat||!t.enableTime&&!a||(n.dateFormat=t.noCalendar||a?"H:i"+(t.enableSeconds?":S":""):ve.defaultConfig.dateFormat+" H:i"+(t.enableSeconds?":S":"")),t.altInput&&(t.enableTime||a)&&!t.altFormat&&(n.altFormat=t.noCalendar||a?"h:i"+(t.enableSeconds?":S K":" K"):ve.defaultConfig.altFormat+" h:i"+(t.enableSeconds?":S":"")+" K"),Object.defineProperty(h.config,"minDate",{get:function(){return h.config._minDate},set:Y("min")}),Object.defineProperty(h.config,"maxDate",{get:function(){return h.config._maxDate},set:Y("max")});var i=function(t){return function(e){h.config["min"===t?"_minTime":"_maxTime"]=h.parseDate(e,"H:i")}};Object.defineProperty(h.config,"minTime",{get:function(){return h.config._minTime},set:i("min")}),Object.defineProperty(h.config,"maxTime",{get:function(){return h.config._maxTime},set:i("max")}),"time"===t.mode&&(h.config.noCalendar=!0,h.config.enableTime=!0),Object.assign(h.config,n,t);for(var o=0;oh.now.getTime()?h.config.minDate:h.config.maxDate&&h.config.maxDate.getTime() 11) { - calendar.year += Math.floor(Math.abs(calendar.month)/12); - calendar.month -= 12; - } - return calendar; - }, - - /** - * defaults and localisation - */ - defaults = { - - // bind the picker to a form field - field: null, - - // automatically show/hide the picker on `field` focus (default `true` if `field` is set) - bound: undefined, - - // data-attribute on the input field with an aria assistance tekst (only applied when `bound` is set) - ariaLabel: 'Use the arrow keys to pick a date', - - // position of the datepicker, relative to the field (default to bottom & left) - // ('bottom' & 'left' keywords are not used, 'top' & 'right' are modifier on the bottom/left position) - position: 'bottom left', - - // automatically fit in the viewport even if it means repositioning from the position option - reposition: true, - - // the default output format for `.toString()` and `field` value - format: 'YYYY-MM-DD', - - // the toString function which gets passed a current date object and format - // and returns a string - toString: null, - - // used to create date object from current input string - parse: null, - - // the initial date to view when first opened - defaultDate: null, - - // make the `defaultDate` the initial selected value - setDefaultDate: false, - - // first day of week (0: Sunday, 1: Monday etc) - firstDay: 0, - - // the default flag for moment's strict date parsing - formatStrict: false, - - // the minimum/earliest date that can be selected - minDate: null, - // the maximum/latest date that can be selected - maxDate: null, - - // number of years either side, or array of upper/lower range - yearRange: 10, - - // show week numbers at head of row - showWeekNumber: false, - - // Week picker mode - pickWholeWeek: false, - - // used internally (don't config outside) - minYear: 0, - maxYear: 9999, - minMonth: undefined, - maxMonth: undefined, - - startRange: null, - endRange: null, - - isRTL: false, - - // Additional text to append to the year in the calendar title - yearSuffix: '', - - // Render the month after year in the calendar title - showMonthAfterYear: false, - - // Render days of the calendar grid that fall in the next or previous month - showDaysInNextAndPreviousMonths: false, - - // Allows user to select days that fall in the next or previous month - enableSelectionDaysInNextAndPreviousMonths: false, - - // how many months are visible - numberOfMonths: 1, - - // when numberOfMonths is used, this will help you to choose where the main calendar will be (default `left`, can be set to `right`) - // only used for the first display or when a selected date is not visible - mainCalendar: 'left', - - // Specify a DOM element to render the calendar in - container: undefined, - - // Blur field when date is selected - blurFieldOnSelect : true, - - // internationalization - i18n: { - previousMonth : 'Previous Month', - nextMonth : 'Next Month', - months : ['January','February','March','April','May','June','July','August','September','October','November','December'], - weekdays : ['Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday'], - weekdaysShort : ['Sun','Mon','Tue','Wed','Thu','Fri','Sat'] - }, - - // Theme Classname - theme: null, - - // events array - events: [], - - // callback function - onSelect: null, - onOpen: null, - onClose: null, - onDraw: null, - - // Enable keyboard input - keyboardInput: true - }, - - - /** - * templating functions to abstract HTML rendering - */ - renderDayName = function(opts, day, abbr) - { - day += opts.firstDay; - while (day >= 7) { - day -= 7; - } - return abbr ? opts.i18n.weekdaysShort[day] : opts.i18n.weekdays[day]; - }, - - renderDay = function(opts) - { - var arr = []; - var ariaSelected = 'false'; - if (opts.isEmpty) { - if (opts.showDaysInNextAndPreviousMonths) { - arr.push('is-outside-current-month'); - - if(!opts.enableSelectionDaysInNextAndPreviousMonths) { - arr.push('is-selection-disabled'); - } - - } else { - return ''; - } - } - if (opts.isDisabled) { - arr.push('is-disabled'); - } - if (opts.isToday) { - arr.push('is-today'); - } - if (opts.isSelected) { - arr.push('is-selected'); - ariaSelected = 'true'; - } - if (opts.hasEvent) { - arr.push('has-event'); - } - if (opts.isInRange) { - arr.push('is-inrange'); - } - if (opts.isStartRange) { - arr.push('is-startrange'); - } - if (opts.isEndRange) { - arr.push('is-endrange'); - } - return '' + - '' + - ''; - }, - - renderWeek = function (d, m, y) { - // Lifted from http://javascript.about.com/library/blweekyear.htm, lightly modified. - var onejan = new Date(y, 0, 1), - weekNum = Math.ceil((((new Date(y, m, d) - onejan) / 86400000) + onejan.getDay()+1)/7); - return '' + weekNum + ''; - }, - - renderRow = function(days, isRTL, pickWholeWeek, isRowSelected) - { - return '' + (isRTL ? days.reverse() : days).join('') + ''; - }, - - renderBody = function(rows) - { - return '' + rows.join('') + ''; - }, - - renderHead = function(opts) - { - var i, arr = []; - if (opts.showWeekNumber) { - arr.push(''); - } - for (i = 0; i < 7; i++) { - arr.push('' + renderDayName(opts, i, true) + ''); - } - return '' + (opts.isRTL ? arr.reverse() : arr).join('') + ''; - }, - - renderTitle = function(instance, c, year, month, refYear, randId) - { - var i, j, arr, - opts = instance._o, - isMinYear = year === opts.minYear, - isMaxYear = year === opts.maxYear, - html = '
', - monthHtml, - yearHtml, - prev = true, - next = true; - - for (arr = [], i = 0; i < 12; i++) { - arr.push(''); - } - - monthHtml = '
' + opts.i18n.months[month] + '
'; - - if (isArray(opts.yearRange)) { - i = opts.yearRange[0]; - j = opts.yearRange[1] + 1; - } else { - i = year - opts.yearRange; - j = 1 + year + opts.yearRange; - } - - for (arr = []; i < j && i <= opts.maxYear; i++) { - if (i >= opts.minYear) { - arr.push(''); - } - } - yearHtml = '
' + year + opts.yearSuffix + '
'; - - if (opts.showMonthAfterYear) { - html += yearHtml + monthHtml; - } else { - html += monthHtml + yearHtml; - } - - if (isMinYear && (month === 0 || opts.minMonth >= month)) { - prev = false; - } - - if (isMaxYear && (month === 11 || opts.maxMonth <= month)) { - next = false; - } - - if (c === 0) { - html += ''; - } - if (c === (instance._o.numberOfMonths - 1) ) { - html += ''; - } - - return html += '
'; - }, - - renderTable = function(opts, data, randId) - { - return '' + renderHead(opts) + renderBody(data) + '
'; - }, - - - /** - * Pikaday constructor - */ - Pikaday = function(options) - { - var self = this, - opts = self.config(options); - - self._onMouseDown = function(e) - { - if (!self._v) { - return; - } - e = e || window.event; - var target = e.target || e.srcElement; - if (!target) { - return; - } - - if (!hasClass(target, 'is-disabled')) { - if (hasClass(target, 'pika-button') && !hasClass(target, 'is-empty') && !hasClass(target.parentNode, 'is-disabled')) { - self.setDate(new Date(target.getAttribute('data-pika-year'), target.getAttribute('data-pika-month'), target.getAttribute('data-pika-day'))); - if (opts.bound) { - sto(function() { - self.hide(); - if (opts.blurFieldOnSelect && opts.field) { - opts.field.blur(); - } - }, 100); - } - } - else if (hasClass(target, 'pika-prev')) { - self.prevMonth(); - } - else if (hasClass(target, 'pika-next')) { - self.nextMonth(); - } - } - if (!hasClass(target, 'pika-select')) { - // if this is touch event prevent mouse events emulation - if (e.preventDefault) { - e.preventDefault(); - } else { - e.returnValue = false; - return false; - } - } else { - self._c = true; - } - }; - - self._onChange = function(e) - { - e = e || window.event; - var target = e.target || e.srcElement; - if (!target) { - return; - } - if (hasClass(target, 'pika-select-month')) { - self.gotoMonth(target.value); - } - else if (hasClass(target, 'pika-select-year')) { - self.gotoYear(target.value); - } - }; - - self._onKeyChange = function(e) - { - e = e || window.event; - - if (self.isVisible()) { - - switch(e.keyCode){ - case 13: - case 27: - if (opts.field) { - opts.field.blur(); - } - break; - case 37: - e.preventDefault(); - self.adjustDate('subtract', 1); - break; - case 38: - self.adjustDate('subtract', 7); - break; - case 39: - self.adjustDate('add', 1); - break; - case 40: - self.adjustDate('add', 7); - break; - } - } - }; - - self._onInputChange = function(e) - { - var date; - - if (e.firedBy === self) { - return; - } - if (opts.parse) { - date = opts.parse(opts.field.value, opts.format); - } else if (hasMoment) { - date = moment(opts.field.value, opts.format, opts.formatStrict); - date = (date && date.isValid()) ? date.toDate() : null; - } - else { - date = new Date(Date.parse(opts.field.value)); - } - if (isDate(date)) { - self.setDate(date); - } - if (!self._v) { - self.show(); - } - }; - - self._onInputFocus = function() - { - self.show(); - }; - - self._onInputClick = function() - { - self.show(); - }; - - self._onInputBlur = function() - { - // IE allows pika div to gain focus; catch blur the input field - var pEl = document.activeElement; - do { - if (hasClass(pEl, 'pika-single')) { - return; - } - } - while ((pEl = pEl.parentNode)); - - if (!self._c) { - self._b = sto(function() { - self.hide(); - }, 50); - } - self._c = false; - }; - - self._onClick = function(e) - { - e = e || window.event; - var target = e.target || e.srcElement, - pEl = target; - if (!target) { - return; - } - if (!hasEventListeners && hasClass(target, 'pika-select')) { - if (!target.onchange) { - target.setAttribute('onchange', 'return;'); - addEvent(target, 'change', self._onChange); - } - } - do { - if (hasClass(pEl, 'pika-single') || pEl === opts.trigger) { - return; - } - } - while ((pEl = pEl.parentNode)); - if (self._v && target !== opts.trigger && pEl !== opts.trigger) { - self.hide(); - } - }; - - self.el = document.createElement('div'); - self.el.className = 'pika-single' + (opts.isRTL ? ' is-rtl' : '') + (opts.theme ? ' ' + opts.theme : ''); - - addEvent(self.el, 'mousedown', self._onMouseDown, true); - addEvent(self.el, 'touchend', self._onMouseDown, true); - addEvent(self.el, 'change', self._onChange); - - if (opts.keyboardInput) { - addEvent(document, 'keydown', self._onKeyChange); - } - - if (opts.field) { - if (opts.container) { - opts.container.appendChild(self.el); - } else if (opts.bound) { - document.body.appendChild(self.el); - } else { - opts.field.parentNode.insertBefore(self.el, opts.field.nextSibling); - } - addEvent(opts.field, 'change', self._onInputChange); - - if (!opts.defaultDate) { - if (hasMoment && opts.field.value) { - opts.defaultDate = moment(opts.field.value, opts.format).toDate(); - } else { - opts.defaultDate = new Date(Date.parse(opts.field.value)); - } - opts.setDefaultDate = true; - } - } - - var defDate = opts.defaultDate; - - if (isDate(defDate)) { - if (opts.setDefaultDate) { - self.setDate(defDate, true); - } else { - self.gotoDate(defDate); - } - } else { - self.gotoDate(new Date()); - } - - if (opts.bound) { - this.hide(); - self.el.className += ' is-bound'; - addEvent(opts.trigger, 'click', self._onInputClick); - addEvent(opts.trigger, 'focus', self._onInputFocus); - addEvent(opts.trigger, 'blur', self._onInputBlur); - } else { - this.show(); - } - }; - - - /** - * public Pikaday API - */ - Pikaday.prototype = { - - - /** - * configure functionality - */ - config: function(options) - { - if (!this._o) { - this._o = extend({}, defaults, true); - } - - var opts = extend(this._o, options, true); - - opts.isRTL = !!opts.isRTL; - - opts.field = (opts.field && opts.field.nodeName) ? opts.field : null; - - opts.theme = (typeof opts.theme) === 'string' && opts.theme ? opts.theme : null; - - opts.bound = !!(opts.bound !== undefined ? opts.field && opts.bound : opts.field); - - opts.trigger = (opts.trigger && opts.trigger.nodeName) ? opts.trigger : opts.field; - - opts.disableWeekends = !!opts.disableWeekends; - - opts.disableDayFn = (typeof opts.disableDayFn) === 'function' ? opts.disableDayFn : null; - - var nom = parseInt(opts.numberOfMonths, 10) || 1; - opts.numberOfMonths = nom > 4 ? 4 : nom; - - if (!isDate(opts.minDate)) { - opts.minDate = false; - } - if (!isDate(opts.maxDate)) { - opts.maxDate = false; - } - if ((opts.minDate && opts.maxDate) && opts.maxDate < opts.minDate) { - opts.maxDate = opts.minDate = false; - } - if (opts.minDate) { - this.setMinDate(opts.minDate); - } - if (opts.maxDate) { - this.setMaxDate(opts.maxDate); - } - - if (isArray(opts.yearRange)) { - var fallback = new Date().getFullYear() - 10; - opts.yearRange[0] = parseInt(opts.yearRange[0], 10) || fallback; - opts.yearRange[1] = parseInt(opts.yearRange[1], 10) || fallback; - } else { - opts.yearRange = Math.abs(parseInt(opts.yearRange, 10)) || defaults.yearRange; - if (opts.yearRange > 100) { - opts.yearRange = 100; - } - } - - return opts; - }, - - /** - * return a formatted string of the current selection (using Moment.js if available) - */ - toString: function(format) - { - format = format || this._o.format; - if (!isDate(this._d)) { - return ''; - } - if (this._o.toString) { - return this._o.toString(this._d, format); - } - if (hasMoment) { - return moment(this._d).format(format); - } - return this._d.toDateString(); - }, - - /** - * return a Moment.js object of the current selection (if available) - */ - getMoment: function() - { - return hasMoment ? moment(this._d) : null; - }, - - /** - * set the current selection from a Moment.js object (if available) - */ - setMoment: function(date, preventOnSelect) - { - if (hasMoment && moment.isMoment(date)) { - this.setDate(date.toDate(), preventOnSelect); - } - }, - - /** - * return a Date object of the current selection - */ - getDate: function() - { - return isDate(this._d) ? new Date(this._d.getTime()) : null; - }, - - /** - * set the current selection - */ - setDate: function(date, preventOnSelect) - { - if (!date) { - this._d = null; - - if (this._o.field) { - this._o.field.value = ''; - fireEvent(this._o.field, 'change', { firedBy: this }); - } - - return this.draw(); - } - if (typeof date === 'string') { - date = new Date(Date.parse(date)); - } - if (!isDate(date)) { - return; - } - - var min = this._o.minDate, - max = this._o.maxDate; - - if (isDate(min) && date < min) { - date = min; - } else if (isDate(max) && date > max) { - date = max; - } - - this._d = new Date(date.getTime()); - setToStartOfDay(this._d); - this.gotoDate(this._d); - - if (this._o.field) { - this._o.field.value = this.toString(); - fireEvent(this._o.field, 'change', { firedBy: this }); - } - if (!preventOnSelect && typeof this._o.onSelect === 'function') { - this._o.onSelect.call(this, this.getDate()); - } - }, - - /** - * change view to a specific date - */ - gotoDate: function(date) - { - var newCalendar = true; - - if (!isDate(date)) { - return; - } - - if (this.calendars) { - var firstVisibleDate = new Date(this.calendars[0].year, this.calendars[0].month, 1), - lastVisibleDate = new Date(this.calendars[this.calendars.length-1].year, this.calendars[this.calendars.length-1].month, 1), - visibleDate = date.getTime(); - // get the end of the month - lastVisibleDate.setMonth(lastVisibleDate.getMonth()+1); - lastVisibleDate.setDate(lastVisibleDate.getDate()-1); - newCalendar = (visibleDate < firstVisibleDate.getTime() || lastVisibleDate.getTime() < visibleDate); - } - - if (newCalendar) { - this.calendars = [{ - month: date.getMonth(), - year: date.getFullYear() - }]; - if (this._o.mainCalendar === 'right') { - this.calendars[0].month += 1 - this._o.numberOfMonths; - } - } - - this.adjustCalendars(); - }, - - adjustDate: function(sign, days) { - - var day = this.getDate() || new Date(); - var difference = parseInt(days)*24*60*60*1000; - - var newDay; - - if (sign === 'add') { - newDay = new Date(day.valueOf() + difference); - } else if (sign === 'subtract') { - newDay = new Date(day.valueOf() - difference); - } - - this.setDate(newDay); - }, - - adjustCalendars: function() { - this.calendars[0] = adjustCalendar(this.calendars[0]); - for (var c = 1; c < this._o.numberOfMonths; c++) { - this.calendars[c] = adjustCalendar({ - month: this.calendars[0].month + c, - year: this.calendars[0].year - }); - } - this.draw(); - }, - - gotoToday: function() - { - this.gotoDate(new Date()); - }, - - /** - * change view to a specific month (zero-index, e.g. 0: January) - */ - gotoMonth: function(month) - { - if (!isNaN(month)) { - this.calendars[0].month = parseInt(month, 10); - this.adjustCalendars(); - } - }, - - nextMonth: function() - { - this.calendars[0].month++; - this.adjustCalendars(); - }, - - prevMonth: function() - { - this.calendars[0].month--; - this.adjustCalendars(); - }, - - /** - * change view to a specific full year (e.g. "2012") - */ - gotoYear: function(year) - { - if (!isNaN(year)) { - this.calendars[0].year = parseInt(year, 10); - this.adjustCalendars(); - } - }, - - /** - * change the minDate - */ - setMinDate: function(value) - { - if(value instanceof Date) { - setToStartOfDay(value); - this._o.minDate = value; - this._o.minYear = value.getFullYear(); - this._o.minMonth = value.getMonth(); - } else { - this._o.minDate = defaults.minDate; - this._o.minYear = defaults.minYear; - this._o.minMonth = defaults.minMonth; - this._o.startRange = defaults.startRange; - } - - this.draw(); - }, - - /** - * change the maxDate - */ - setMaxDate: function(value) - { - if(value instanceof Date) { - setToStartOfDay(value); - this._o.maxDate = value; - this._o.maxYear = value.getFullYear(); - this._o.maxMonth = value.getMonth(); - } else { - this._o.maxDate = defaults.maxDate; - this._o.maxYear = defaults.maxYear; - this._o.maxMonth = defaults.maxMonth; - this._o.endRange = defaults.endRange; - } - - this.draw(); - }, - - setStartRange: function(value) - { - this._o.startRange = value; - }, - - setEndRange: function(value) - { - this._o.endRange = value; - }, - - /** - * refresh the HTML - */ - draw: function(force) - { - if (!this._v && !force) { - return; - } - var opts = this._o, - minYear = opts.minYear, - maxYear = opts.maxYear, - minMonth = opts.minMonth, - maxMonth = opts.maxMonth, - html = '', - randId; - - if (this._y <= minYear) { - this._y = minYear; - if (!isNaN(minMonth) && this._m < minMonth) { - this._m = minMonth; - } - } - if (this._y >= maxYear) { - this._y = maxYear; - if (!isNaN(maxMonth) && this._m > maxMonth) { - this._m = maxMonth; - } - } - - randId = 'pika-title-' + Math.random().toString(36).replace(/[^a-z]+/g, '').substr(0, 2); - - for (var c = 0; c < opts.numberOfMonths; c++) { - html += '
' + renderTitle(this, c, this.calendars[c].year, this.calendars[c].month, this.calendars[0].year, randId) + this.render(this.calendars[c].year, this.calendars[c].month, randId) + '
'; - } - - this.el.innerHTML = html; - - if (opts.bound) { - if(opts.field.type !== 'hidden') { - sto(function() { - opts.trigger.focus(); - }, 1); - } - } - - if (typeof this._o.onDraw === 'function') { - this._o.onDraw(this); - } - - if (opts.bound) { - // let the screen reader user know to use arrow keys - opts.field.setAttribute('aria-label', opts.ariaLabel); - } - }, - - adjustPosition: function() - { - var field, pEl, width, height, viewportWidth, viewportHeight, scrollTop, left, top, clientRect, leftAligned, bottomAligned; - - if (this._o.container) return; - - this.el.style.position = 'absolute'; - - field = this._o.trigger; - pEl = field; - width = this.el.offsetWidth; - height = this.el.offsetHeight; - viewportWidth = window.innerWidth || document.documentElement.clientWidth; - viewportHeight = window.innerHeight || document.documentElement.clientHeight; - scrollTop = window.pageYOffset || document.body.scrollTop || document.documentElement.scrollTop; - leftAligned = true; - bottomAligned = true; - - if (typeof field.getBoundingClientRect === 'function') { - clientRect = field.getBoundingClientRect(); - left = clientRect.left + window.pageXOffset; - top = clientRect.bottom + window.pageYOffset; - } else { - left = pEl.offsetLeft; - top = pEl.offsetTop + pEl.offsetHeight; - while((pEl = pEl.offsetParent)) { - left += pEl.offsetLeft; - top += pEl.offsetTop; - } - } - - // default position is bottom & left - if ((this._o.reposition && left + width > viewportWidth) || - ( - this._o.position.indexOf('right') > -1 && - left - width + field.offsetWidth > 0 - ) - ) { - left = left - width + field.offsetWidth; - leftAligned = false; - } - if ((this._o.reposition && top + height > viewportHeight + scrollTop) || - ( - this._o.position.indexOf('top') > -1 && - top - height - field.offsetHeight > 0 - ) - ) { - top = top - height - field.offsetHeight; - bottomAligned = false; - } - - this.el.style.left = left + 'px'; - this.el.style.top = top + 'px'; - - addClass(this.el, leftAligned ? 'left-aligned' : 'right-aligned'); - addClass(this.el, bottomAligned ? 'bottom-aligned' : 'top-aligned'); - removeClass(this.el, !leftAligned ? 'left-aligned' : 'right-aligned'); - removeClass(this.el, !bottomAligned ? 'bottom-aligned' : 'top-aligned'); - }, - - /** - * render HTML for a particular month - */ - render: function(year, month, randId) - { - var opts = this._o, - now = new Date(), - days = getDaysInMonth(year, month), - before = new Date(year, month, 1).getDay(), - data = [], - row = []; - setToStartOfDay(now); - if (opts.firstDay > 0) { - before -= opts.firstDay; - if (before < 0) { - before += 7; - } - } - var previousMonth = month === 0 ? 11 : month - 1, - nextMonth = month === 11 ? 0 : month + 1, - yearOfPreviousMonth = month === 0 ? year - 1 : year, - yearOfNextMonth = month === 11 ? year + 1 : year, - daysInPreviousMonth = getDaysInMonth(yearOfPreviousMonth, previousMonth); - var cells = days + before, - after = cells; - while(after > 7) { - after -= 7; - } - cells += 7 - after; - var isWeekSelected = false; - for (var i = 0, r = 0; i < cells; i++) - { - var day = new Date(year, month, 1 + (i - before)), - isSelected = isDate(this._d) ? compareDates(day, this._d) : false, - isToday = compareDates(day, now), - hasEvent = opts.events.indexOf(day.toDateString()) !== -1 ? true : false, - isEmpty = i < before || i >= (days + before), - dayNumber = 1 + (i - before), - monthNumber = month, - yearNumber = year, - isStartRange = opts.startRange && compareDates(opts.startRange, day), - isEndRange = opts.endRange && compareDates(opts.endRange, day), - isInRange = opts.startRange && opts.endRange && opts.startRange < day && day < opts.endRange, - isDisabled = (opts.minDate && day < opts.minDate) || - (opts.maxDate && day > opts.maxDate) || - (opts.disableWeekends && isWeekend(day)) || - (opts.disableDayFn && opts.disableDayFn(day)); - - if (isEmpty) { - if (i < before) { - dayNumber = daysInPreviousMonth + dayNumber; - monthNumber = previousMonth; - yearNumber = yearOfPreviousMonth; - } else { - dayNumber = dayNumber - days; - monthNumber = nextMonth; - yearNumber = yearOfNextMonth; - } - } - - var dayConfig = { - day: dayNumber, - month: monthNumber, - year: yearNumber, - hasEvent: hasEvent, - isSelected: isSelected, - isToday: isToday, - isDisabled: isDisabled, - isEmpty: isEmpty, - isStartRange: isStartRange, - isEndRange: isEndRange, - isInRange: isInRange, - showDaysInNextAndPreviousMonths: opts.showDaysInNextAndPreviousMonths, - enableSelectionDaysInNextAndPreviousMonths: opts.enableSelectionDaysInNextAndPreviousMonths - }; - - if (opts.pickWholeWeek && isSelected) { - isWeekSelected = true; - } - - row.push(renderDay(dayConfig)); - - if (++r === 7) { - if (opts.showWeekNumber) { - row.unshift(renderWeek(i - before, month, year)); - } - data.push(renderRow(row, opts.isRTL, opts.pickWholeWeek, isWeekSelected)); - row = []; - r = 0; - isWeekSelected = false; - } - } - return renderTable(opts, data, randId); - }, - - isVisible: function() - { - return this._v; - }, - - show: function() - { - if (!this.isVisible()) { - this._v = true; - this.draw(); - removeClass(this.el, 'is-hidden'); - if (this._o.bound) { - addEvent(document, 'click', this._onClick); - this.adjustPosition(); - } - if (typeof this._o.onOpen === 'function') { - this._o.onOpen.call(this); - } - } - }, - - hide: function() - { - var v = this._v; - if (v !== false) { - if (this._o.bound) { - removeEvent(document, 'click', this._onClick); - } - this.el.style.position = 'static'; // reset - this.el.style.left = 'auto'; - this.el.style.top = 'auto'; - addClass(this.el, 'is-hidden'); - this._v = false; - if (v !== undefined && typeof this._o.onClose === 'function') { - this._o.onClose.call(this); - } - } - }, - - /** - * GAME OVER - */ - destroy: function() - { - var opts = this._o; - - this.hide(); - removeEvent(this.el, 'mousedown', this._onMouseDown, true); - removeEvent(this.el, 'touchend', this._onMouseDown, true); - removeEvent(this.el, 'change', this._onChange); - if (opts.keyboardInput) { - removeEvent(document, 'keydown', this._onKeyChange); - } - if (opts.field) { - removeEvent(opts.field, 'change', this._onInputChange); - if (opts.bound) { - removeEvent(opts.trigger, 'click', this._onInputClick); - removeEvent(opts.trigger, 'focus', this._onInputFocus); - removeEvent(opts.trigger, 'blur', this._onInputBlur); - } - } - if (this.el.parentNode) { - this.el.parentNode.removeChild(this.el); - } - } - - }; - - return Pikaday; -})); diff --git a/source/js/rangePlugin.js b/source/js/rangePlugin.js new file mode 100644 index 00000000..ca5a2e44 --- /dev/null +++ b/source/js/rangePlugin.js @@ -0,0 +1,147 @@ +/* flatpickr v4.5.2, @license MIT */ +(function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : + typeof define === 'function' && define.amd ? define(factory) : + (global.rangePlugin = factory()); +}(this, (function () { 'use strict'; + + function rangePlugin(config) { + if (config === void 0) { + config = {}; + } + + return function (fp) { + var dateFormat = "", + secondInput, + _secondInputFocused, + _prevDates; + + var createSecondInput = function createSecondInput() { + if (config.input) { + secondInput = config.input instanceof Element ? config.input : window.document.querySelector(config.input); + } else { + secondInput = fp._input.cloneNode(); + secondInput.removeAttribute("id"); + secondInput._flatpickr = undefined; + } + + if (secondInput.value) { + var parsedDate = fp.parseDate(secondInput.value); + if (parsedDate) fp.selectedDates.push(parsedDate); + } + + secondInput.setAttribute("data-fp-omit", ""); + + fp._bind(secondInput, ["focus", "click"], function () { + if (fp.selectedDates[1]) { + fp.latestSelectedDateObj = fp.selectedDates[1]; + + fp._setHoursFromDate(fp.selectedDates[1]); + + fp.jumpToDate(fp.selectedDates[1]); + } + _secondInputFocused = true; + fp.isOpen = false; + fp.open(undefined, secondInput); + }); + + fp._bind(fp._input, ["focus", "click"], function (e) { + e.preventDefault(); + fp.isOpen = false; + fp.open(); + }); + + if (fp.config.allowInput) fp._bind(secondInput, "keydown", function (e) { + if (e.key === "Enter") { + fp.setDate([fp.selectedDates[0], secondInput.value], true, dateFormat); + secondInput.click(); + } + }); + if (!config.input) fp._input.parentNode && fp._input.parentNode.insertBefore(secondInput, fp._input.nextSibling); + }; + + var plugin = { + onParseConfig: function onParseConfig() { + fp.config.mode = "range"; + dateFormat = fp.config.altInput ? fp.config.altFormat : fp.config.dateFormat; + }, + onReady: function onReady() { + createSecondInput(); + fp.config.ignoredFocusElements.push(secondInput); + + if (fp.config.allowInput) { + fp._input.removeAttribute("readonly"); + + secondInput.removeAttribute("readonly"); + } else { + secondInput.setAttribute("readonly", "readonly"); + } + + fp._bind(fp._input, "focus", function () { + fp.latestSelectedDateObj = fp.selectedDates[0]; + + fp._setHoursFromDate(fp.selectedDates[0]); + _secondInputFocused = false; + fp.jumpToDate(fp.selectedDates[0]); + }); + + if (fp.config.allowInput) fp._bind(fp._input, "keydown", function (e) { + if (e.key === "Enter") fp.setDate([fp._input.value, fp.selectedDates[1]], true, dateFormat); + }); + fp.setDate(fp.selectedDates, false); + plugin.onValueUpdate(fp.selectedDates); + }, + onPreCalendarPosition: function onPreCalendarPosition() { + if (_secondInputFocused) { + fp._positionElement = secondInput; + setTimeout(function () { + fp._positionElement = fp._input; + }, 0); + } + }, + onChange: function onChange() { + if (!fp.selectedDates.length) { + setTimeout(function () { + if (fp.selectedDates.length) return; + secondInput.value = ""; + _prevDates = []; + }, 10); + } + + if (_secondInputFocused) { + setTimeout(function () { + secondInput.focus(); + }, 0); + } + }, + onDestroy: function onDestroy() { + if (!config.input) secondInput.parentNode && secondInput.parentNode.removeChild(secondInput); + }, + onValueUpdate: function onValueUpdate(selDates) { + if (!secondInput) return; + _prevDates = !_prevDates || selDates.length >= _prevDates.length ? selDates.concat() : _prevDates; + + if (_prevDates.length > selDates.length) { + var newSelectedDate = selDates[0]; + var newDates = _secondInputFocused ? [_prevDates[0], newSelectedDate] : [newSelectedDate, _prevDates[1]]; + fp.setDate(newDates, false); + _prevDates = newDates.concat(); + } + + var _fp$selectedDates$map = fp.selectedDates.map(function (d) { + return fp.formatDate(d, dateFormat); + }); + + var _fp$selectedDates$map2 = _fp$selectedDates$map[0]; + fp._input.value = _fp$selectedDates$map2 === void 0 ? "" : _fp$selectedDates$map2; + var _fp$selectedDates$map3 = _fp$selectedDates$map[1]; + secondInput.value = _fp$selectedDates$map3 === void 0 ? "" : _fp$selectedDates$map3; + } + }; + return plugin; + }; + } + + return rangePlugin; + +}))); diff --git a/source/scss/base.scss b/source/scss/base.scss index a6fb0907..18aef902 100644 --- a/source/scss/base.scss +++ b/source/scss/base.scss @@ -468,6 +468,4 @@ HTML,BODY { cursor: pointer; } -@import './pikaday'; - @import './mobile'; diff --git a/source/tmpl/footer.html b/source/tmpl/footer.html index a7189393..ba7ec6ac 100644 --- a/source/tmpl/footer.html +++ b/source/tmpl/footer.html @@ -1,7 +1,7 @@ {{ define "footer"}}
- +
- +
- 10,000+ will be checked in Microseconds (1 millisecond = 1000 microseconds). + 10,000+ will be checked in Microseconds (1 millisecond = 1000 microseconds).
diff --git a/source/tmpl/form_user.html b/source/tmpl/form_user.html index b14a20d2..f49e68ad 100644 --- a/source/tmpl/form_user.html +++ b/source/tmpl/form_user.html @@ -7,7 +7,7 @@
- +
diff --git a/source/tmpl/message.html b/source/tmpl/message.html new file mode 100644 index 00000000..862f54d7 --- /dev/null +++ b/source/tmpl/message.html @@ -0,0 +1,28 @@ +{{define "title"}}Statup | {{.Title}}{{end}} +{{define "content"}} +
+{{template "nav"}} +
+

Message {{.Title}}

+ {{template "form_message" .}} +
+
+{{end}} +{{define "extra_css"}} + +{{end}} +{{define "extra_scripts"}} + + + + +{{end}} \ No newline at end of file diff --git a/source/tmpl/messages.html b/source/tmpl/messages.html new file mode 100644 index 00000000..42180bf2 --- /dev/null +++ b/source/tmpl/messages.html @@ -0,0 +1,52 @@ +{{define "title"}}Statup Messages{{end}} +{{define "content"}} +
+{{template "nav"}} +
+

Messages

+ + + + + + + + + {{range .}} + + + + + {{end}} + +
Title
{{.Title}} +
+ Edit + +
+
+ +

Create Message

+ + {{template "form_message" NewMessage}} +
+
+{{end}} +{{define "extra_css"}} + +{{end}} +{{define "extra_scripts"}} + + + + +{{end}} \ No newline at end of file diff --git a/source/tmpl/nav.html b/source/tmpl/nav.html index 08eac5de..082bebeb 100644 --- a/source/tmpl/nav.html +++ b/source/tmpl/nav.html @@ -16,6 +16,9 @@ + diff --git a/source/tmpl/service.html b/source/tmpl/service.html index 3eb11e96..404f5813 100644 --- a/source/tmpl/service.html +++ b/source/tmpl/service.html @@ -10,11 +10,11 @@
- {{if $s.Online }} - ONLINE - {{ else }} - OFFLINE - {{end}} + {{if $s.Online }} + ONLINE + {{ else }} + OFFLINE + {{end}}

{{ $s.Name }} {{if $s.Online }} @@ -46,11 +46,11 @@

- {{FromUnix .Start}} - {{FromUnix .End}} - - - + + {{.Start}} to {{.End}} + + +
@@ -59,6 +59,15 @@
{{$s.DowntimeText}}
{{end}} + {{if $s.Messages}} + {{range $s.Messages}} + + {{end}} + {{end}} + {{ if $s.LimitedFailures }}
{{ range $s.LimitedFailures }} @@ -137,14 +146,15 @@ {{end}}
{{end}} -{{define "extra_scripts"}} -{{if USE_CDN}} - -{{ else }} - +{{define "extra_css"}} + {{end}} +{{define "extra_scripts"}} {{$s := .Service}} + + {{end}} diff --git a/source/tmpl/settings.html b/source/tmpl/settings.html index 763fa5fd..d541b4ee 100644 --- a/source/tmpl/settings.html +++ b/source/tmpl/settings.html @@ -54,7 +54,7 @@
- +
diff --git a/types/core.go b/types/core.go index 6b842562..8748ddba 100644 --- a/types/core.go +++ b/types/core.go @@ -16,6 +16,7 @@ package types import ( + "database/sql" "time" ) @@ -32,11 +33,11 @@ type Core struct { ApiKey string `gorm:"column:api_key" json:"-"` ApiSecret string `gorm:"column:api_secret" json:"-"` Style string `gorm:"not null;column:style" json:"style,omitempty"` - Footer string `gorm:"not null;column:footer" json:"footer,omitempty"` - Domain string `gorm:"not null;column:domain" json:"domain,omitempty"` + Footer sql.NullString `gorm:"not null;column:footer" json:"footer"` + Domain string `gorm:"not null;column:domain" json:"domain"` Version string `gorm:"column:version" json:"version"` MigrationId int64 `gorm:"column:migration_id" json:"migration_id,omitempty"` - UseCdn bool `gorm:"column:use_cdn;default:false" json:"using_cdn,omitempty"` + UseCdn sql.NullBool `gorm:"column:use_cdn;default:false" json:"using_cdn,omitempty"` Timezone float32 `gorm:"column:timezone;default:-8.0" json:"timezone,omitempty"` CreatedAt time.Time `gorm:"column:created_at" json:"created_at"` UpdatedAt time.Time `gorm:"column:updated_at" json:"updated_at"` diff --git a/types/message.go b/types/message.go new file mode 100644 index 00000000..1273863d --- /dev/null +++ b/types/message.go @@ -0,0 +1,36 @@ +// Statup +// Copyright (C) 2018. Hunter Long and the project contributors +// Written by Hunter Long and the project contributors +// +// https://github.com/hunterlong/statup +// +// The licenses for most software and other practical works are designed +// to take away your freedom to share and change the works. By contrast, +// the GNU General Public License is intended to guarantee your freedom to +// share and change all versions of a program--to make sure it remains free +// software for all its users. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +package types + +import ( + "database/sql" + "time" +) + +// Message is for creating Announcements, Alerts and other messages for the end users +type Message struct { + Id int64 `gorm:"primary_key;column:id"` + Title string `gorm:"column:title"` + Description string `gorm:"column:description"` + StartOn time.Time `gorm:"column:start_on"` + EndOn time.Time `gorm:"column:end_on"` + ServiceId int64 `gorm:"index;column:service"` + NotifyUsers sql.NullBool `gorm:"column:notify_users"` + NotifyMethod string `gorm:"column:notify_method"` + NotifyBefore time.Duration `gorm:"column:notify_before"` + CreatedAt time.Time `gorm:"column:created_at" json:"created_at"` + UpdatedAt time.Time `gorm:"column:updated_at" json:"updated_at"` +} diff --git a/types/service.go b/types/service.go index 3c54732d..ec523a41 100644 --- a/types/service.go +++ b/types/service.go @@ -16,37 +16,39 @@ package types import ( + "database/sql" "time" ) // Service is the main struct for Services type Service struct { - Id int64 `gorm:"primary_key;column:id" json:"id"` - Name string `gorm:"column:name" json:"name"` - Domain string `gorm:"column:domain" json:"domain"` - Expected string `gorm:"not null;column:expected" json:"expected"` - ExpectedStatus int `gorm:"default:200;column:expected_status" json:"expected_status"` - Interval int `gorm:"default:30;column:check_interval" json:"check_interval"` - Type string `gorm:"column:check_type" json:"type"` - Method string `gorm:"column:method" json:"method"` - PostData string `gorm:"not null;column:post_data" json:"post_data"` - Port int `gorm:"not null;column:port" json:"port"` - Timeout int `gorm:"default:30;column:timeout" json:"timeout"` - Order int `gorm:"default:0;column:order_id" json:"order_id"` - CreatedAt time.Time `gorm:"column:created_at" json:"created_at"` - UpdatedAt time.Time `gorm:"column:updated_at" json:"updated_at"` - Online bool `gorm:"-" json:"online"` - Latency float64 `gorm:"-" json:"latency"` - PingTime float64 `gorm:"-" json:"ping_time"` - Online24Hours float32 `gorm:"-" json:"online_24_hours"` - AvgResponse string `gorm:"-" json:"avg_response"` - Running chan bool `gorm:"-" json:"-"` - Checkpoint time.Time `gorm:"-" json:"-"` - SleepDuration time.Duration `gorm:"-" json:"-"` - LastResponse string `gorm:"-" json:"-"` - LastStatusCode int `gorm:"-" json:"status_code"` - LastOnline time.Time `gorm:"-" json:"last_online"` - Failures []interface{} `gorm:"-" json:"failures,omitempty"` + Id int64 `gorm:"primary_key;column:id" json:"id"` + Name string `gorm:"column:name" json:"name"` + Domain string `gorm:"column:domain" json:"domain"` + Expected sql.NullString `gorm:"not null;column:expected" json:"expected"` + ExpectedStatus int `gorm:"default:200;column:expected_status" json:"expected_status"` + Interval int `gorm:"default:30;column:check_interval" json:"check_interval"` + Type string `gorm:"column:check_type" json:"type"` + Method string `gorm:"column:method" json:"method"` + PostData sql.NullString `gorm:"not null;column:post_data" json:"post_data"` + Port int `gorm:"not null;column:port" json:"port"` + Timeout int `gorm:"default:30;column:timeout" json:"timeout"` + Order int `gorm:"default:0;column:order_id" json:"order_id"` + AllowNotifications sql.NullBool `gorm:"default:false;column:allow_notifications" json:"allow_notifications"` + CreatedAt time.Time `gorm:"column:created_at" json:"created_at"` + UpdatedAt time.Time `gorm:"column:updated_at" json:"updated_at"` + Online bool `gorm:"-" json:"online"` + Latency float64 `gorm:"-" json:"latency"` + PingTime float64 `gorm:"-" json:"ping_time"` + Online24Hours float32 `gorm:"-" json:"online_24_hours"` + AvgResponse string `gorm:"-" json:"avg_response"` + Running chan bool `gorm:"-" json:"-"` + Checkpoint time.Time `gorm:"-" json:"-"` + SleepDuration time.Duration `gorm:"-" json:"-"` + LastResponse string `gorm:"-" json:"-"` + LastStatusCode int `gorm:"-" json:"status_code"` + LastOnline time.Time `gorm:"-" json:"last_online"` + Failures []interface{} `gorm:"-" json:"failures,omitempty"` } type ServiceInterface interface { diff --git a/types/user.go b/types/user.go index 444f5ed2..b2e2efbd 100644 --- a/types/user.go +++ b/types/user.go @@ -16,20 +16,21 @@ package types import ( + "database/sql" "time" ) // User is the main struct for Users type User struct { - Id int64 `gorm:"primary_key;column:id" json:"id"` - Username string `gorm:"type:varchar(100);unique;column:username;" json:"username"` - Password string `gorm:"column:password" json:"-"` - Email string `gorm:"type:varchar(100);unique;column:email" json:"-"` - ApiKey string `gorm:"column:api_key" json:"api_key"` - ApiSecret string `gorm:"column:api_secret" json:"-"` - Admin bool `gorm:"column:administrator" json:"admin"` - CreatedAt time.Time `gorm:"column:created_at" json:"created_at"` - UpdatedAt time.Time `gorm:"column:updated_at" json:"updated_at"` + Id int64 `gorm:"primary_key;column:id" json:"id"` + Username string `gorm:"type:varchar(100);unique;column:username;" json:"username"` + Password string `gorm:"column:password" json:"-"` + Email string `gorm:"type:varchar(100);unique;column:email" json:"-"` + ApiKey string `gorm:"column:api_key" json:"api_key"` + ApiSecret string `gorm:"column:api_secret" json:"-"` + Admin sql.NullBool `gorm:"column:administrator" json:"admin"` + CreatedAt time.Time `gorm:"column:created_at" json:"created_at"` + UpdatedAt time.Time `gorm:"column:updated_at" json:"updated_at"` UserInterface `gorm:"-" json:"-"` } diff --git a/utils/time.go b/utils/time.go index 190bd402..c365f693 100644 --- a/utils/time.go +++ b/utils/time.go @@ -20,6 +20,12 @@ import ( "time" ) +const ( + FlatpickrTime = "2006-01-02 15:04" + FlatpickrDay = "2006-01-02" + FlatpickrReadable = "Mon, 02 Jan 2006" +) + // FormatDuration converts a time.Duration into a string func FormatDuration(d time.Duration) string { var out string diff --git a/utils/utils.go b/utils/utils.go index c963544a..4332ea70 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -16,6 +16,7 @@ package utils import ( + "database/sql" "errors" "fmt" "github.com/ararog/timeago" @@ -43,6 +44,20 @@ func init() { } } +func NullString(s string) sql.NullString { + return sql.NullString{s, true} +} + +func NullBool(s bool) sql.NullBool { + return sql.NullBool{s, true} +} + +func StringPoint(s string) *string { + val := new(string) + *val = s + return val +} + // StringInt converts a string to an int64 func StringInt(s string) int64 { num, _ := strconv.Atoi(s)