├── assets ├── img │ └── templ.png ├── fonts │ └── Kanit-Regular.ttf └── js │ ├── htmx.min.js │ └── sweetalert2.min.js ├── doc ├── screenshot-1.png ├── screenshot-2.png ├── screenshot-3.png ├── screenshot-4.png └── structure.svg ├── .gitignore ├── tailwind ├── tailwind.config.js ├── package.json └── base.css ├── views ├── errors_pages │ ├── error.500.templ │ ├── error.404.templ │ └── error.401.templ ├── todo_views │ ├── todo.create.templ │ ├── todo.update.templ │ └── todo.list.templ ├── auth_views │ ├── home.templ │ ├── login.templ │ └── register.templ ├── layout │ └── base.layout.templ └── partials │ ├── flashmessages.partial.templ │ └── navbar.partial.templ ├── .air.toml ├── go.mod ├── handlers ├── routes.go ├── error.handler.go ├── flashmessages.manager.go ├── todo.handlers.go └── auth.handlers.go ├── LICENSE ├── cmd └── main.go ├── db └── db.go ├── services ├── user.services.go └── todo.services.go ├── go.sum └── README.md /assets/img/templ.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/emarifer/go-echo-templ-htmx/HEAD/assets/img/templ.png -------------------------------------------------------------------------------- /doc/screenshot-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/emarifer/go-echo-templ-htmx/HEAD/doc/screenshot-1.png -------------------------------------------------------------------------------- /doc/screenshot-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/emarifer/go-echo-templ-htmx/HEAD/doc/screenshot-2.png -------------------------------------------------------------------------------- /doc/screenshot-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/emarifer/go-echo-templ-htmx/HEAD/doc/screenshot-3.png -------------------------------------------------------------------------------- /doc/screenshot-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/emarifer/go-echo-templ-htmx/HEAD/doc/screenshot-4.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | tmp 2 | bin 3 | 4 | app_data.db 5 | **/*_templ.go 6 | assets/css/main.css 7 | 8 | tailwind/node_modules -------------------------------------------------------------------------------- /assets/fonts/Kanit-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/emarifer/go-echo-templ-htmx/HEAD/assets/fonts/Kanit-Regular.ttf -------------------------------------------------------------------------------- /tailwind/tailwind.config.js: -------------------------------------------------------------------------------- 1 | const { fontFamily } = require('tailwindcss/defaultTheme'); 2 | 3 | /** @type {import('tailwindcss').Config} */ 4 | 5 | module.exports = { 6 | content: ["../views/**/*.{templ,go}"], 7 | theme: { 8 | extend: { 9 | fontFamily: { 10 | sans: ['Kanit', ...fontFamily.sans] 11 | } 12 | }, 13 | }, 14 | plugins: [ 15 | require("daisyui") 16 | ], 17 | daisyui: { 18 | themes: ["dark"] 19 | } 20 | } 21 | 22 | -------------------------------------------------------------------------------- /views/errors_pages/error.500.templ: -------------------------------------------------------------------------------- 1 | package errors_pages 2 | 3 | templ Error500(fromProtected bool) { 4 |
5 |
6 |

7 | 500 8 |

9 |

10 | Internal Server Error 11 |

12 |
13 |

14 | An unexpected condition was encountered. 15 |

16 |
17 | } 18 | -------------------------------------------------------------------------------- /tailwind/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "go-echo-templ-htmx", 3 | "version": "0.1.0", 4 | "description": "Go/Echo+Templ+Htmx: Full stack application using Golang's Echo framework & Templ templating language with user session management + CRUD to a SQLite database (To Do List) and HTMX in the frontend", 5 | "main": "index.js", 6 | "scripts": { 7 | "watch-css": "npx tailwindcss -i base.css -o ../assets/css/main.css --watch", 8 | "build-css-prod": "npx tailwindcss -i base.css -o ../assets/css/main.css --minify" 9 | }, 10 | "keywords": [], 11 | "author": "emarifer", 12 | "license": "MIT", 13 | "devDependencies": { 14 | "daisyui": "^4.10.2", 15 | "tailwindcss": "^3.4.3" 16 | } 17 | } -------------------------------------------------------------------------------- /.air.toml: -------------------------------------------------------------------------------- 1 | root = "." 2 | testdata_dir = "testdata" 3 | tmp_dir = "tmp" 4 | 5 | [build] 6 | args_bin = [] 7 | bin = "./tmp/main" 8 | cmd = "go build -o ./tmp/main ./cmd/main.go" 9 | delay = 1000 10 | exclude_dir = ["tmp", "vendor", "testdata"] 11 | exclude_file = [] 12 | exclude_regex = ["_test.go"] 13 | exclude_unchanged = false 14 | follow_symlink = false 15 | full_bin = "" 16 | include_dir = [] 17 | include_ext = ["go", "tpl", "tmpl", "html", "templ"] 18 | include_file = [] 19 | kill_delay = "10s" 20 | log = "build-errors.log" 21 | poll = false 22 | poll_interval = 0 23 | rerun = false 24 | rerun_delay = 500 25 | send_interrupt = false 26 | stop_on_error = false 27 | 28 | [color] 29 | app = "" 30 | build = "yellow" 31 | main = "magenta" 32 | runner = "green" 33 | watcher = "cyan" 34 | 35 | [log] 36 | main_only = false 37 | time = false 38 | 39 | [misc] 40 | clean_on_exit = false 41 | 42 | [screen] 43 | clear_on_rebuild = false 44 | keep_scroll = true 45 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/emarifer/go-echo-templ-htmx 2 | 3 | go 1.21.0 4 | 5 | require ( 6 | github.com/a-h/templ v0.2.501 7 | github.com/gorilla/sessions v1.2.2 8 | github.com/labstack/echo-contrib v0.15.0 9 | github.com/labstack/echo/v4 v4.11.4 10 | ) 11 | 12 | require ( 13 | github.com/golang-jwt/jwt v3.2.2+incompatible // indirect 14 | github.com/gorilla/context v1.1.1 // indirect 15 | github.com/gorilla/securecookie v1.1.2 // indirect 16 | github.com/labstack/gommon v0.4.2 // indirect 17 | github.com/mattn/go-colorable v0.1.13 // indirect 18 | github.com/mattn/go-isatty v0.0.20 // indirect 19 | github.com/mattn/go-sqlite3 v1.14.19 20 | github.com/valyala/bytebufferpool v1.0.0 // indirect 21 | github.com/valyala/fasttemplate v1.2.2 // indirect 22 | golang.org/x/crypto v0.17.0 23 | golang.org/x/net v0.19.0 // indirect 24 | golang.org/x/sys v0.15.0 // indirect 25 | golang.org/x/text v0.14.0 26 | golang.org/x/time v0.5.0 // indirect 27 | ) 28 | -------------------------------------------------------------------------------- /views/errors_pages/error.404.templ: -------------------------------------------------------------------------------- 1 | package errors_pages 2 | 3 | templ Error404(fromProtected bool) { 4 |
5 |
6 |

7 | 404 8 |

9 |

10 | Resource not found 11 |

12 |
13 |

14 | The requested resource could not be found but may be available again in the future. 15 |

16 | if !fromProtected { 17 | 18 | Go Home Page 19 | 20 | } else { 21 | 26 | Go Todo List Page 27 | 28 | } 29 |
30 | } 31 | -------------------------------------------------------------------------------- /handlers/routes.go: -------------------------------------------------------------------------------- 1 | package handlers 2 | 3 | import ( 4 | "github.com/labstack/echo/v4" 5 | ) 6 | 7 | func SetupRoutes(e *echo.Echo, ah *AuthHandler, th *TaskHandler) { 8 | e.GET("/", ah.flagsMiddleware(ah.homeHandler)) 9 | e.GET("/login", ah.flagsMiddleware(ah.loginHandler)) 10 | e.POST("/login", ah.flagsMiddleware(ah.loginHandler)) 11 | e.GET("/register", ah.flagsMiddleware(ah.registerHandler)) 12 | e.POST("/register", ah.flagsMiddleware(ah.registerHandler)) 13 | 14 | protectedGroup := e.Group("/todo", ah.authMiddleware) 15 | /* ↓ Protected Routes ↓ */ 16 | protectedGroup.GET("/list", th.todoListHandler) 17 | protectedGroup.GET("/create", th.createTodoHandler) 18 | protectedGroup.POST("/create", th.createTodoHandler) 19 | protectedGroup.GET("/edit/:id", th.updateTodoHandler) 20 | protectedGroup.POST("/edit/:id", th.updateTodoHandler) 21 | protectedGroup.DELETE("/delete/:id", th.deleteTodoHandler) 22 | protectedGroup.POST("/logout", th.logoutHandler) 23 | 24 | /* ↓ Fallback Page ↓ */ 25 | e.GET("/*", RouteNotFoundHandler) 26 | } 27 | -------------------------------------------------------------------------------- /views/errors_pages/error.401.templ: -------------------------------------------------------------------------------- 1 | package errors_pages 2 | 3 | import "github.com/emarifer/go-echo-templ-htmx/views/layout" 4 | 5 | templ Error401(fromProtected bool) { 6 |
7 |
8 |

9 | 401 10 |

11 |

12 | Status Unauthorized 13 |

14 |
15 |

16 | Please provide valid credentials. 17 |

18 | 19 | Go Login Page 20 | 21 |
22 | } 23 | 24 | templ ErrorIndex( 25 | title, 26 | username string, 27 | fromProtected bool, 28 | isError bool, 29 | cmp templ.Component, 30 | ) { 31 | @layout.Base( 32 | title, 33 | username, 34 | fromProtected, 35 | isError, 36 | []string{}, 37 | []string{}, 38 | ) { 39 | @cmp 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Enrique Marín 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /views/todo_views/todo.create.templ: -------------------------------------------------------------------------------- 1 | package todo_views 2 | 3 | templ CreateTodo() { 4 |

5 | Enter Task 6 |

7 |
8 |
9 | 21 | 29 | 37 |
38 |
39 | } 40 | -------------------------------------------------------------------------------- /tailwind/base.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | @font-face { 6 | font-family: Kanit; 7 | font-weight: normal; 8 | src: url("../fonts/Kanit-Regular.ttf") format("truetype"); 9 | } 10 | 11 | /* --------------------------------------- */ 12 | /* ---------- View Transitions ----------- */ 13 | /* --------------------------------------- */ 14 | 15 | @keyframes fade-in { 16 | from { 17 | opacity: 0; 18 | } 19 | } 20 | 21 | @keyframes fade-out { 22 | to { 23 | opacity: 0; 24 | } 25 | } 26 | 27 | @keyframes slide-from-right { 28 | from { 29 | transform: translateX(90px); 30 | } 31 | } 32 | 33 | @keyframes slide-to-left { 34 | to { 35 | transform: translateX(-90px); 36 | } 37 | } 38 | 39 | /* define animations for the old and new content */ 40 | ::view-transition-old(slide-it) { 41 | animation: 180ms cubic-bezier(0.4, 0, 1, 1) both fade-out, 42 | 600ms cubic-bezier(0.4, 0, 0.2, 1) both slide-to-left; 43 | } 44 | 45 | ::view-transition-new(slide-it) { 46 | animation: 420ms cubic-bezier(0, 0, 0.2, 1) 90ms both fade-in, 47 | 600ms cubic-bezier(0.4, 0, 0.2, 1) both slide-from-right; 48 | } 49 | 50 | /* tie the view transition to a given CSS class */ 51 | .sample-transition { 52 | view-transition-name: slide-it; 53 | } -------------------------------------------------------------------------------- /views/auth_views/home.templ: -------------------------------------------------------------------------------- 1 | package auth_views 2 | 3 | import "github.com/emarifer/go-echo-templ-htmx/views/layout" 4 | 5 | templ Home(fromProtected bool) { 6 |
7 |

Welcome to your TodoList !!

8 |

9 | Here you can keep track of all your tasks and have an overview of your responsibilities. 10 |

11 | if !fromProtected { 12 |
13 |

You have an account?

14 | 30 | } 31 |
32 | } 33 | 34 | templ HomeIndex( 35 | title, 36 | username string, 37 | fromProtected bool, 38 | isError bool, 39 | errMsgs, sucMsgs []string, 40 | cmp templ.Component, 41 | ) { 42 | @layout.Base(title, username, fromProtected, isError, errMsgs, sucMsgs) { 43 | @cmp 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /handlers/error.handler.go: -------------------------------------------------------------------------------- 1 | package handlers 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | 7 | "github.com/emarifer/go-echo-templ-htmx/views/errors_pages" 8 | 9 | "github.com/a-h/templ" 10 | "github.com/labstack/echo/v4" 11 | ) 12 | 13 | func CustomHTTPErrorHandler(err error, c echo.Context) { 14 | code := http.StatusInternalServerError 15 | if he, ok := err.(*echo.HTTPError); ok { 16 | code = he.Code 17 | } 18 | c.Logger().Error(err) 19 | 20 | /* errorPage := fmt.Sprintf("views/%d.html", code) 21 | if err := c.File(errorPage); err != nil { 22 | c.Logger().Error(err) 23 | } */ 24 | 25 | var errorPage func(fp bool) templ.Component 26 | 27 | switch code { 28 | case 401: 29 | errorPage = errors_pages.Error401 30 | case 404: 31 | errorPage = errors_pages.Error404 32 | case 500: 33 | errorPage = errors_pages.Error500 34 | } 35 | 36 | // isError = true 37 | c.Set("ISERROR", true) 38 | 39 | renderView(c, errors_pages.ErrorIndex( 40 | fmt.Sprintf("| Error (%d)", code), 41 | "", 42 | c.Get("FROMPROTECTED").(bool), 43 | c.Get("ISERROR").(bool), 44 | errorPage(c.Get("FROMPROTECTED").(bool)), 45 | )) 46 | } 47 | 48 | func RouteNotFoundHandler(c echo.Context) error { 49 | // Hardcoded parameters 50 | 51 | return renderView(c, errors_pages.ErrorIndex( 52 | fmt.Sprintf("| Error (%d)", 404), 53 | "", 54 | false, 55 | true, 56 | errors_pages.Error404(false), 57 | )) 58 | } 59 | -------------------------------------------------------------------------------- /handlers/flashmessages.manager.go: -------------------------------------------------------------------------------- 1 | package handlers 2 | 3 | import ( 4 | "github.com/gorilla/sessions" 5 | "github.com/labstack/echo/v4" 6 | ) 7 | 8 | // cookie name & flash messages key 9 | // this should be a .env file 10 | const ( 11 | session_name string = "fmessages" 12 | session_flashmessages_key string = "flashmessages-key" 13 | ) 14 | 15 | func getCookieStore() *sessions.CookieStore { 16 | 17 | return sessions.NewCookieStore([]byte(session_flashmessages_key)) 18 | } 19 | 20 | // Set adds a new message to the cookie store 21 | func setFlashmessages(c echo.Context, kind, value string) { 22 | session, _ := getCookieStore().Get(c.Request(), session_name) 23 | 24 | session.AddFlash(value, kind) 25 | 26 | session.Save(c.Request(), c.Response()) 27 | } 28 | 29 | // Get receives flash messages from cookie store 30 | func getFlashmessages(c echo.Context, kind string) []string { 31 | session, _ := getCookieStore().Get(c.Request(), session_name) 32 | 33 | fm := session.Flashes(kind) 34 | 35 | // if there are some messages… 36 | if len(fm) > 0 { 37 | session.Save(c.Request(), c.Response()) 38 | 39 | // we start an empty strings slice that we 40 | // then return with messages 41 | var flashes []string 42 | for _, fl := range fm { 43 | // we add the messages to the slice 44 | flashes = append(flashes, fl.(string)) 45 | } 46 | 47 | return flashes 48 | } 49 | 50 | return nil 51 | } 52 | -------------------------------------------------------------------------------- /views/layout/base.layout.templ: -------------------------------------------------------------------------------- 1 | package layout 2 | 3 | import "github.com/emarifer/go-echo-templ-htmx/views/partials" 4 | 5 | templ Base(title, username string, fromProtected, isError bool, errMsgs, sucMsgs []string) { 6 | 7 | 8 | 9 | 10 | 11 | 15 | 16 | 17 | 18 | Todo List { title } 19 | 20 | 21 | 22 | 23 | 24 |
25 | if !isError { 26 | @partials.Navbar(username, fromProtected) 27 | } 28 |
29 |
30 | { children... } 31 | @partials.FlashMessages(errMsgs, sucMsgs) 32 |
33 | 34 | 35 | } 36 | -------------------------------------------------------------------------------- /views/partials/flashmessages.partial.templ: -------------------------------------------------------------------------------- 1 | package partials 2 | 3 | templ FlashMessages(errMsgs, sucMsgs []string) { 4 |
5 | if len(errMsgs) != 0 { 6 | 25 | } 26 | if len(sucMsgs) != 0 { 27 | 46 | } 47 |
48 | } 49 | -------------------------------------------------------------------------------- /cmd/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/emarifer/go-echo-templ-htmx/db" 5 | "github.com/emarifer/go-echo-templ-htmx/handlers" 6 | "github.com/emarifer/go-echo-templ-htmx/services" 7 | 8 | "github.com/gorilla/sessions" 9 | "github.com/labstack/echo-contrib/session" 10 | "github.com/labstack/echo/v4" 11 | "github.com/labstack/echo/v4/middleware" 12 | ) 13 | 14 | // In production, the secret key of the CookieStore 15 | // and database name would be obtained from a .env file 16 | const ( 17 | SECRET_KEY string = "secret" 18 | DB_NAME string = "app_data.db" 19 | ) 20 | 21 | func main() { 22 | 23 | e := echo.New() 24 | 25 | e.Static("/static", "assets") 26 | 27 | e.HTTPErrorHandler = handlers.CustomHTTPErrorHandler 28 | 29 | // Helpers Middleware 30 | // e.Use(middleware.Recover()) 31 | e.Use(middleware.Logger()) 32 | 33 | // Session Middleware 34 | e.Use(session.Middleware(sessions.NewCookieStore([]byte(SECRET_KEY)))) 35 | 36 | store, err := db.NewStore(DB_NAME) 37 | if err != nil { 38 | e.Logger.Fatalf("failed to create store: %s", err) 39 | } 40 | 41 | us := services.NewUserServices(services.User{}, store) 42 | ah := handlers.NewAuthHandler(us) 43 | 44 | ts := services.NewTodoServices(services.Todo{}, store) 45 | th := handlers.NewTaskHandler(ts) 46 | 47 | // Setting Routes 48 | handlers.SetupRoutes(e, ah, th) 49 | 50 | // Start Server 51 | e.Logger.Fatal(e.Start(":8082")) 52 | } 53 | 54 | /* 55 | https://gist.github.com/taforyou/544c60ffd072c9573971cf447c9fea44 56 | https://gist.github.com/mhewedy/4e45e04186ed9d4e3c8c86e6acff0b17 57 | 58 | https://github.com/CurtisVermeeren/gorilla-sessions-tutorial 59 | */ 60 | -------------------------------------------------------------------------------- /views/partials/navbar.partial.templ: -------------------------------------------------------------------------------- 1 | package partials 2 | 3 | templ Navbar(username string, fromProtected bool) { 4 | 54 | } 55 | -------------------------------------------------------------------------------- /db/db.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import ( 4 | "database/sql" 5 | "fmt" 6 | "log" 7 | 8 | _ "github.com/mattn/go-sqlite3" 9 | ) 10 | 11 | type Store struct { 12 | Db *sql.DB 13 | } 14 | 15 | func NewStore(dbName string) (Store, error) { 16 | Db, err := getConnection(dbName) 17 | if err != nil { 18 | return Store{}, err 19 | } 20 | 21 | if err := createMigrations(dbName, Db); err != nil { 22 | return Store{}, err 23 | } 24 | 25 | return Store{ 26 | Db, 27 | }, nil 28 | } 29 | 30 | func getConnection(dbName string) (*sql.DB, error) { 31 | var ( 32 | err error 33 | db *sql.DB 34 | ) 35 | 36 | if db != nil { 37 | return db, nil 38 | } 39 | 40 | // Init SQLite3 database 41 | db, err = sql.Open("sqlite3", dbName) 42 | if err != nil { 43 | // log.Fatalf("🔥 failed to connect to the database: %s", err.Error()) 44 | return nil, fmt.Errorf("🔥 failed to connect to the database: %s", err) 45 | } 46 | 47 | log.Println("🚀 Connected Successfully to the Database") 48 | 49 | return db, nil 50 | } 51 | 52 | func createMigrations(dbName string, db *sql.DB) error { 53 | stmt := `CREATE TABLE IF NOT EXISTS users ( 54 | id INTEGER PRIMARY KEY AUTOINCREMENT, 55 | email VARCHAR(255) NOT NULL UNIQUE, 56 | password VARCHAR(255) NOT NULL, 57 | username VARCHAR(64) NOT NULL 58 | );` 59 | 60 | _, err := db.Exec(stmt) 61 | if err != nil { 62 | return err 63 | } 64 | 65 | stmt = `CREATE TABLE IF NOT EXISTS todos ( 66 | id INTEGER PRIMARY KEY AUTOINCREMENT, 67 | created_by INTEGER NOT NULL, 68 | title VARCHAR(64) NOT NULL, 69 | description VARCHAR(255) NULL, 70 | status BOOLEAN DEFAULT(FALSE), 71 | created_at DATETIME default CURRENT_TIMESTAMP, 72 | FOREIGN KEY(created_by) REFERENCES users(id) 73 | );` 74 | 75 | _, err = db.Exec(stmt) 76 | if err != nil { 77 | return err 78 | } 79 | 80 | return nil 81 | } 82 | -------------------------------------------------------------------------------- /views/todo_views/todo.update.templ: -------------------------------------------------------------------------------- 1 | package todo_views 2 | 3 | import ( 4 | "strconv" 5 | 6 | "github.com/emarifer/go-echo-templ-htmx/services" 7 | ) 8 | 9 | templ UpdateTodo(todo services.Todo, tz string) { 10 |

11 | Update Task #{ strconv.Itoa(int(todo.ID)) } 12 |

13 |
14 |
15 | 28 | 34 | 61 |
62 |
63 | } 64 | -------------------------------------------------------------------------------- /services/user.services.go: -------------------------------------------------------------------------------- 1 | package services 2 | 3 | import ( 4 | "github.com/emarifer/go-echo-templ-htmx/db" 5 | "golang.org/x/crypto/bcrypt" 6 | ) 7 | 8 | func NewUserServices(u User, uStore db.Store) *UserServices { 9 | 10 | return &UserServices{ 11 | User: u, 12 | UserStore: uStore, 13 | } 14 | } 15 | 16 | type User struct { 17 | ID int `json:"id"` 18 | Email string `json:"email"` 19 | Password string `json:"password"` 20 | Username string `json:"username"` 21 | } 22 | 23 | type UserServices struct { 24 | User User 25 | UserStore db.Store 26 | } 27 | 28 | func (us *UserServices) CreateUser(u User) error { 29 | hashedPassword, err := bcrypt.GenerateFromPassword([]byte(u.Password), 8) 30 | if err != nil { 31 | return err 32 | } 33 | 34 | stmt := `INSERT INTO users(email, password, username) VALUES($1, $2, $3)` 35 | 36 | _, err = us.UserStore.Db.Exec( 37 | stmt, 38 | u.Email, 39 | string(hashedPassword), 40 | u.Username, 41 | ) 42 | 43 | return err 44 | } 45 | 46 | func (us *UserServices) CheckEmail(email string) (User, error) { 47 | 48 | query := `SELECT id, email, password, username FROM users 49 | WHERE email = ?` 50 | 51 | stmt, err := us.UserStore.Db.Prepare(query) 52 | if err != nil { 53 | return User{}, err 54 | } 55 | 56 | defer stmt.Close() 57 | 58 | us.User.Email = email 59 | err = stmt.QueryRow( 60 | us.User.Email, 61 | ).Scan( 62 | &us.User.ID, 63 | &us.User.Email, 64 | &us.User.Password, 65 | &us.User.Username, 66 | ) 67 | if err != nil { 68 | return User{}, err 69 | } 70 | 71 | return us.User, nil 72 | } 73 | 74 | /* func (us *UserServices) GetUserById(id int) (User, error) { 75 | 76 | query := `SELECT id, email, password, username FROM users 77 | WHERE id = ?` 78 | 79 | stmt, err := us.UserStore.Db.Prepare(query) 80 | if err != nil { 81 | return User{}, err 82 | } 83 | 84 | defer stmt.Close() 85 | 86 | us.User.ID = id 87 | err = stmt.QueryRow( 88 | us.User.ID, 89 | ).Scan( 90 | &us.User.ID, 91 | &us.User.Email, 92 | &us.User.Password, 93 | &us.User.Username, 94 | ) 95 | if err != nil { 96 | return User{}, err 97 | } 98 | 99 | return us.User, nil 100 | } */ 101 | -------------------------------------------------------------------------------- /views/auth_views/login.templ: -------------------------------------------------------------------------------- 1 | package auth_views 2 | 3 | import "github.com/emarifer/go-echo-templ-htmx/views/layout" 4 | 5 | templ Login(fromProtected bool) { 6 |
7 |
8 |

9 | Log In 10 |

11 |
18 | 32 | 62 | 67 |
68 |
69 |
70 | } 71 | 72 | templ LoginIndex( 73 | title, 74 | username string, 75 | fromProtected bool, 76 | isError bool, 77 | errMsgs, sucMsgs []string, 78 | cmp templ.Component, 79 | ) { 80 | @layout.Base(title, username, fromProtected, isError, errMsgs, sucMsgs) { 81 | @cmp 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /views/todo_views/todo.list.templ: -------------------------------------------------------------------------------- 1 | package todo_views 2 | 3 | import ( 4 | "fmt" 5 | "strconv" 6 | "strings" 7 | 8 | "github.com/emarifer/go-echo-templ-htmx/services" 9 | "github.com/emarifer/go-echo-templ-htmx/views/layout" 10 | ) 11 | 12 | templ TodoList(titlePage string, todos []services.Todo) { 13 |
14 |

15 | { strings.Trim(titlePage, "| ") } 16 |

17 | 18 | New 19 | 20 |
21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | if len(todos) != 0 { 33 | 34 | for _, todo := range todos { 35 | 36 | 37 | 38 | 45 | 79 | 80 | } 81 | 82 | } else { 83 | 84 | 85 | 88 | 89 | 90 | } 91 |
TasksStatusOptions
{ strconv.Itoa(int(todo.ID)) }{ todo.Title } 39 | if todo.Status { 40 | ✅ 41 | } else { 42 | ❌ 43 | } 44 | 46 | 51 | Edit 52 | 53 | 78 |
86 | You do not have anything to do 87 |
92 |
93 | } 94 | 95 | templ TodoIndex( 96 | title, 97 | username string, 98 | fromProtected bool, 99 | isError bool, 100 | errMsgs, sucMsgs []string, 101 | cmp templ.Component, 102 | ) { 103 | @layout.Base(title, username, fromProtected, isError, errMsgs, sucMsgs) { 104 | @cmp 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /views/auth_views/register.templ: -------------------------------------------------------------------------------- 1 | package auth_views 2 | 3 | import "github.com/emarifer/go-echo-templ-htmx/views/layout" 4 | 5 | templ Register(fromProtected bool) { 6 |
7 |
8 |

9 | Register User 10 |

11 |
17 | 31 | 61 | 76 | 81 |
82 |
83 |
84 | } 85 | 86 | templ RegisterIndex( 87 | title, 88 | username string, 89 | fromProtected bool, 90 | isError bool, 91 | errMsgs, sucMsgs []string, 92 | cmp templ.Component, 93 | ) { 94 | @layout.Base(title, username, fromProtected, isError, errMsgs, sucMsgs) { 95 | @cmp 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /services/todo.services.go: -------------------------------------------------------------------------------- 1 | package services 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "time" 7 | 8 | "github.com/emarifer/go-echo-templ-htmx/db" 9 | ) 10 | 11 | func NewTodoServices(t Todo, tStore db.Store) *TodoServices { 12 | 13 | return &TodoServices{ 14 | Todo: t, 15 | TodoStore: tStore, 16 | } 17 | } 18 | 19 | type Todo struct { 20 | ID int `json:"id"` 21 | CreatedBy int `json:"created_by"` 22 | Title string `json:"title"` 23 | Description string `json:"description,omitempty"` 24 | Status bool `json:"status,omitempty"` 25 | CreatedAt time.Time `json:"created_at,omitempty"` 26 | } 27 | 28 | type TodoServices struct { 29 | Todo Todo 30 | TodoStore db.Store 31 | } 32 | 33 | func (ts *TodoServices) CreateTodo(t Todo) (Todo, error) { 34 | 35 | query := `INSERT INTO todos (created_by, title, description) 36 | VALUES(?, ?, ?) RETURNING *` 37 | 38 | stmt, err := ts.TodoStore.Db.Prepare(query) 39 | if err != nil { 40 | return Todo{}, err 41 | } 42 | 43 | defer stmt.Close() 44 | 45 | err = stmt.QueryRow( 46 | t.CreatedBy, 47 | t.Title, 48 | t.Description, 49 | ).Scan( 50 | &ts.Todo.ID, 51 | &ts.Todo.CreatedBy, 52 | &ts.Todo.Title, 53 | &ts.Todo.Description, 54 | &ts.Todo.Status, 55 | &ts.Todo.CreatedAt, 56 | ) 57 | if err != nil { 58 | return Todo{}, err 59 | } 60 | 61 | /* if i, err := result.RowsAffected(); err != nil || i != 1 { 62 | return errors.New("error: an affected row was expected") 63 | } */ 64 | 65 | return ts.Todo, nil 66 | } 67 | 68 | func (ts *TodoServices) GetAllTodos(createdBy int) ([]Todo, error) { 69 | query := fmt.Sprintf("SELECT id, title, status FROM todos WHERE created_by = %d ORDER BY created_at DESC", createdBy) 70 | 71 | rows, err := ts.TodoStore.Db.Query(query) 72 | if err != nil { 73 | return []Todo{}, err 74 | } 75 | // We close the resource 76 | defer rows.Close() 77 | 78 | todos := []Todo{} 79 | for rows.Next() { 80 | rows.Scan(&ts.Todo.ID, &ts.Todo.Title, &ts.Todo.Status) 81 | 82 | todos = append(todos, ts.Todo) 83 | } 84 | 85 | return todos, nil 86 | } 87 | 88 | func (ts *TodoServices) GetTodoById(t Todo) (Todo, error) { 89 | 90 | query := `SELECT id, title, description, status, created_at FROM todos 91 | WHERE created_by = ? AND id=?` 92 | 93 | stmt, err := ts.TodoStore.Db.Prepare(query) 94 | if err != nil { 95 | return Todo{}, err 96 | } 97 | 98 | defer stmt.Close() 99 | 100 | err = stmt.QueryRow( 101 | t.CreatedBy, 102 | t.ID, 103 | ).Scan( 104 | &ts.Todo.ID, 105 | &ts.Todo.Title, 106 | &ts.Todo.Description, 107 | &ts.Todo.Status, 108 | &ts.Todo.CreatedAt, 109 | ) 110 | if err != nil { 111 | return Todo{}, err 112 | } 113 | 114 | return ts.Todo, nil 115 | } 116 | 117 | func (ts *TodoServices) UpdateTodo(t Todo) (Todo, error) { 118 | 119 | query := `UPDATE todos SET title = ?, description = ?, status = ? 120 | WHERE created_by = ? AND id=? RETURNING id, title, description, status` 121 | 122 | stmt, err := ts.TodoStore.Db.Prepare(query) 123 | if err != nil { 124 | return Todo{}, err 125 | } 126 | 127 | defer stmt.Close() 128 | 129 | err = stmt.QueryRow( 130 | t.Title, 131 | t.Description, 132 | t.Status, 133 | t.CreatedBy, 134 | t.ID, 135 | ).Scan( 136 | &ts.Todo.ID, 137 | &ts.Todo.Title, 138 | &ts.Todo.Description, 139 | &ts.Todo.Status, 140 | ) 141 | if err != nil { 142 | return Todo{}, err 143 | } 144 | 145 | return ts.Todo, nil 146 | } 147 | 148 | func (ts *TodoServices) DeleteTodo(t Todo) error { 149 | 150 | query := `DELETE FROM todos 151 | WHERE created_by = ? AND id=?` 152 | 153 | stmt, err := ts.TodoStore.Db.Prepare(query) 154 | if err != nil { 155 | return err 156 | } 157 | 158 | defer stmt.Close() 159 | 160 | result, err := stmt.Exec(t.CreatedBy, t.ID) 161 | if err != nil { 162 | return err 163 | } 164 | 165 | if i, err := result.RowsAffected(); err != nil || i != 1 { 166 | return errors.New("an affected row was expected") 167 | } 168 | 169 | return nil 170 | } 171 | 172 | func ConvertDateTime(tz string, dt time.Time) string { 173 | loc, _ := time.LoadLocation(tz) 174 | 175 | return dt.In(loc).Format(time.RFC822Z) 176 | } 177 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/a-h/templ v0.2.501 h1:9rIo5u+B+NDJIkbHGthckUGRguCuWKY/7ri8e2ckn9M= 2 | github.com/a-h/templ v0.2.501/go.mod h1:9gZxTLtRzM3gQxO8jr09Na0v8/jfliS97S9W5SScanM= 3 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 4 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 5 | github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= 6 | github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= 7 | github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= 8 | github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 9 | github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= 10 | github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 11 | github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8= 12 | github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= 13 | github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA= 14 | github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo= 15 | github.com/gorilla/sessions v1.2.2 h1:lqzMYz6bOfvn2WriPUjNByzeXIlVzURcPmgMczkmTjY= 16 | github.com/gorilla/sessions v1.2.2/go.mod h1:ePLdVu+jbEgHH+KWw8I1z2wqd0BAdAQh/8LRvBeoNcQ= 17 | github.com/labstack/echo-contrib v0.15.0 h1:9K+oRU265y4Mu9zpRDv3X+DGTqUALY6oRHCSZZKCRVU= 18 | github.com/labstack/echo-contrib v0.15.0/go.mod h1:lei+qt5CLB4oa7VHTE0yEfQSEB9XTJI1LUqko9UWvo4= 19 | github.com/labstack/echo/v4 v4.11.4 h1:vDZmA+qNeh1pd/cCkEicDMrjtrnMGQ1QFI9gWN1zGq8= 20 | github.com/labstack/echo/v4 v4.11.4/go.mod h1:noh7EvLwqDsmh/X/HWKPUl1AjzJrhyptRyEbQJfxen8= 21 | github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0= 22 | github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU= 23 | github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= 24 | github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= 25 | github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= 26 | github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= 27 | github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 28 | github.com/mattn/go-sqlite3 v1.14.19 h1:fhGleo2h1p8tVChob4I9HpmVFIAkKGpiukdrgQbWfGI= 29 | github.com/mattn/go-sqlite3 v1.14.19/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= 30 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 31 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 32 | github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= 33 | github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= 34 | github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= 35 | github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= 36 | github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo= 37 | github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= 38 | golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= 39 | golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= 40 | golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c= 41 | golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= 42 | golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 43 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 44 | golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= 45 | golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 46 | golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= 47 | golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= 48 | golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= 49 | golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= 50 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 51 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 52 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | # Go/Echo+Templ: Full stack Demo App with session authentication (with middleware) and centralized HTTP error handling with Golang's Echo framework, CRUD to a SQLite database (To Do List), use of *Templ* Template language and HTMx-powered frontend 4 | 5 | 6 | A full stack application using Golang's Echo framework. Requests to the backend are improved in their "aesthetic" aspect with the use [htmx](https://htmx.org/) ([hypermedia](https://hypermedia.systems/) only). 7 | 8 | ![GitHub License](https://img.shields.io/github/license/emarifer/go-echo-templ-htmx) ![Static Badge](https://img.shields.io/badge/Go-%3E=1.18-blue) 9 | 10 |

11 | 12 | >[!NOTE] 13 | >***This application is an clone of this [repository](https://github.com/emarifer/rust-axum-askama-htmx-todoapp) of mine (rust-axum-askama-htmx-todoapp), but made in `Golang`.*** 14 | 15 |
16 | 17 | ### 🤔 Explanation 18 | 19 | Demo application to use session authentication (with middleware) and centralized HTTP error handling with the Echo framework. 20 | 21 | The architecture follows a typical "onion model" where each layer doesn't know about the layer above it, and each layer is responsible for a specific thing. Although the application is extremely simple, we use this pattern to illustrate its use in more complex applications. 22 | 23 | Layering an application in this way can simplify code structure, since the responsibility of each type is clear. 24 | 25 | To ensure that each part of the application is initialized with its dependencies, each struct defines a constructor (the New function in this example). 26 | 27 | 28 | 29 |
30 | 31 | >[!IMPORTANT] 32 | >***In this application, instead of using the [html/template](https://pkg.go.dev/html/template) package (native Golang templates), we use the [a-h/templ](https://github.com/a-h/templ) library. This amazing library implements a templating language (very similar to JSX) that compiles to Go code. `Templ` will allow us to write code almost identical to Go (with expressions, control flow, if/else, for loops, etc.) and have autocompletion thanks to strong typing. This means that errors appear at compile time and any calls to these templates (which are compiled as Go functions) from the handlers side will always require the correct data, minimizing errors and thus increasing the security and speed of our coding.*** 33 | 34 |
35 | 36 | On the other hand, we use Golang's [Echo](https://echo.labstack.com/docs) web framework, which as stated on its website is a "High performance, extensible, minimalist Go web framework". 37 | 38 | The use of htmx allows behavior similar to that of a SPA, without page reloads when switching from one route to another or when making requests (via AJAX) to the backend. 39 | 40 | The styling of the views is achieved through Tailwind CSS and DaisyUI that are obtained from their respective CDNs. 41 | 42 | Finally, minimal use of [_hyperscript](https://hyperscript.org/) is made to achieve the action of closing the alerts when they are displayed or giving interactivity to the show/hide password button in its corresponding input. 43 | 44 | >[!NOTE] 45 | >***This application is very similar to several of my previous repositories ([flask-htmx-todolist](https://github.com/emarifer/flask-htmx-todolist), [gofiber-templ-htmx](https://github.com/emarifer/gofiber-templ-htmx), [go-echo-templ-project-structure](https://github.com/emarifer/go-echo-templ-project-structure), [echo-cookie-session-demo](https://github.com/emarifer/echo-cookie-session-demo)), which are developed with other frameworks (Fiber, Echo, Python's Flask framework…) and template languages (native Golang templates, Python's Jinja2 templates or [Templ](https://templ.guide/) template language). This repository is a compendium of the aforementioned repositories.*** 46 | 47 | --- 48 | 49 | ## 🖼️ Screenshots: 50 | 51 | ###### Todo List Page with success alert: 52 | 53 | 54 | 55 |
56 | 57 | ###### Sign Up Page with error alert: 58 | 59 | 60 | 61 |
62 | 63 | ###### Task update page: 64 | 65 | 66 | 67 |
68 | 69 | ###### Centralized HTTP error handling: 70 | 71 | 72 | 73 | --- 74 | 75 | ## 👨‍🚀 Setup: 76 | 77 | Before compiling the view templates, you'll need to regenerate the CSS. First, you need to install the dependencies required by `Tailwind CSS` and `daisyUI` (you must have `Node.js` installed on your system) and then run the regeneration of the `main.css` file. To do this, apply the following commands: 78 | 79 | ``` 80 | $ cd tailwind && npm i 81 | $npm run build-css-prod # `npm run watch-css` regenerate the css in watch mode for development 82 | ``` 83 | 84 | Besides the obvious prerequisite of having Go! on your machine, you must have Air installed for hot reloading when editing code. 85 | 86 | >[!TIP] 87 | >***In order to have autocompletion and syntax highlighting in VS Code for the `Templ templating language`, you will have to install the [templ-vscode](https://marketplace.visualstudio.com/items?itemName=a-h.templ) extension (for vim/nvim install this [plugin](https://github.com/joerdav/templ.vim)). To generate the Go code corresponding to these templates you will have to download this [executable binary](https://github.com/a-h/templ/releases/tag/v0.2.476) from Github and place it in the PATH of your system. The command:*** 88 | 89 | ``` 90 | $ templ generate --watch 91 | ``` 92 | 93 | >[!TIP] 94 | >***This command allows us to regenerate the `.templ` templates and, therefore, is necessary to start the application. This will also allow us to monitor changes to the `.templ` files and compile them as we save them if we make changes to them. Review the documentation on Templ [installation](https://templ.guide/quick-start/installation) and [support](https://templ.guide/commands-and-tools/ide-support/) for your IDE .*** 95 | 96 | 97 | Start the app in development mode: 98 | 99 | ``` 100 | $ air # Ctrl + C to stop the application 101 | ``` 102 | 103 | Build for production: 104 | 105 | ``` 106 | $ go build -ldflags="-s -w" -o ./bin/main . # ./bin/main to run the application / Ctrl + C to stop the application 107 | ``` 108 | --- 109 | 110 | ### Happy coding 😀!! 111 | -------------------------------------------------------------------------------- /handlers/todo.handlers.go: -------------------------------------------------------------------------------- 1 | package handlers 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "net/http" 7 | "strconv" 8 | "strings" 9 | 10 | "github.com/emarifer/go-echo-templ-htmx/services" 11 | "github.com/emarifer/go-echo-templ-htmx/views/todo_views" 12 | "github.com/labstack/echo-contrib/session" 13 | "github.com/labstack/echo/v4" 14 | "golang.org/x/text/cases" 15 | "golang.org/x/text/language" 16 | ) 17 | 18 | /********** Handlers for Todo Views **********/ 19 | 20 | type TaskService interface { 21 | CreateTodo(t services.Todo) (services.Todo, error) 22 | GetAllTodos(createdBy int) ([]services.Todo, error) 23 | GetTodoById(t services.Todo) (services.Todo, error) 24 | UpdateTodo(t services.Todo) (services.Todo, error) 25 | DeleteTodo(t services.Todo) error 26 | } 27 | 28 | func NewTaskHandler(ts TaskService) *TaskHandler { 29 | 30 | return &TaskHandler{ 31 | TodoServices: ts, 32 | } 33 | } 34 | 35 | type TaskHandler struct { 36 | TodoServices TaskService 37 | } 38 | 39 | func (th *TaskHandler) createTodoHandler(c echo.Context) error { 40 | // isError = false 41 | c.Set("ISERROR", false) 42 | fromProtected, ok := c.Get("FROMPROTECTED").(bool) 43 | if !ok { 44 | return errors.New("invalid type for key 'FROMPROTECTED'") 45 | } 46 | 47 | if c.Request().Method == "POST" { 48 | todo := services.Todo{ 49 | CreatedBy: c.Get(user_id_key).(int), 50 | Title: strings.Trim(c.FormValue("title"), " "), 51 | Description: strings.Trim(c.FormValue("description"), " "), 52 | } 53 | 54 | _, err := th.TodoServices.CreateTodo(todo) 55 | if err != nil { 56 | return err 57 | } 58 | 59 | setFlashmessages(c, "success", "Task created successfully!!") 60 | 61 | return c.Redirect(http.StatusSeeOther, "/todo/list") 62 | } 63 | 64 | return renderView(c, todo_views.TodoIndex( 65 | "| Create Todo", 66 | c.Get(username_key).(string), 67 | fromProtected, 68 | c.Get("ISERROR").(bool), 69 | getFlashmessages(c, "error"), 70 | getFlashmessages(c, "success"), 71 | todo_views.CreateTodo(), 72 | )) 73 | } 74 | 75 | func (th *TaskHandler) todoListHandler(c echo.Context) error { 76 | // isError = false 77 | c.Set("ISERROR", false) 78 | fromProtected, ok := c.Get("FROMPROTECTED").(bool) 79 | if !ok { 80 | return errors.New("invalid type for key 'FROMPROTECTED'") 81 | } 82 | 83 | userId := c.Get(user_id_key).(int) 84 | 85 | todos, err := th.TodoServices.GetAllTodos(userId) 86 | if err != nil { 87 | return err 88 | } 89 | 90 | titlePage := fmt.Sprintf( 91 | "| %s's Task List", 92 | cases.Title(language.English).String(c.Get(username_key).(string)), 93 | ) 94 | 95 | return renderView(c, todo_views.TodoIndex( 96 | titlePage, 97 | c.Get(username_key).(string), 98 | fromProtected, 99 | c.Get("ISERROR").(bool), 100 | getFlashmessages(c, "error"), 101 | getFlashmessages(c, "success"), 102 | todo_views.TodoList(titlePage, todos), 103 | )) 104 | } 105 | 106 | func (th *TaskHandler) updateTodoHandler(c echo.Context) error { 107 | // isError = false 108 | c.Set("ISERROR", false) 109 | fromProtected, ok := c.Get("FROMPROTECTED").(bool) 110 | if !ok { 111 | return errors.New("invalid type for key 'FROMPROTECTED'") 112 | } 113 | 114 | idParams, err := strconv.Atoi(c.Param("id")) 115 | if err != nil { 116 | return err 117 | } 118 | 119 | t := services.Todo{ 120 | ID: idParams, 121 | CreatedBy: c.Get(user_id_key).(int), 122 | } 123 | 124 | todo, err := th.TodoServices.GetTodoById(t) 125 | if err != nil { 126 | if strings.Contains(err.Error(), "no rows in result set") { 127 | 128 | return echo.NewHTTPError( 129 | echo.ErrNotFound.Code, 130 | fmt.Sprintf( 131 | "something went wrong: %s", 132 | err, 133 | )) 134 | } 135 | 136 | return echo.NewHTTPError( 137 | echo.ErrInternalServerError.Code, 138 | fmt.Sprintf( 139 | "something went wrong: %s", 140 | err, 141 | )) 142 | } 143 | 144 | if c.Request().Method == "POST" { 145 | var status bool 146 | if c.FormValue("status") == "on" { 147 | status = true 148 | } else { 149 | status = false 150 | } 151 | 152 | todo := services.Todo{ 153 | Title: strings.Trim(c.FormValue("title"), " "), 154 | Description: strings.Trim(c.FormValue("description"), " "), 155 | Status: status, 156 | CreatedBy: c.Get(user_id_key).(int), 157 | ID: idParams, 158 | } 159 | 160 | _, err := th.TodoServices.UpdateTodo(todo) 161 | if err != nil { 162 | return err 163 | } 164 | 165 | setFlashmessages(c, "success", "Task successfully updated!!") 166 | 167 | return c.Redirect(http.StatusSeeOther, "/todo/list") 168 | } 169 | 170 | return renderView(c, todo_views.TodoIndex( 171 | fmt.Sprintf("| Edit Todo #%d", todo.ID), 172 | c.Get(username_key).(string), 173 | fromProtected, 174 | c.Get("ISERROR").(bool), 175 | getFlashmessages(c, "error"), 176 | getFlashmessages(c, "success"), // ↓ getting time zone from context ↓ 177 | todo_views.UpdateTodo(todo, c.Get(tzone_key).(string)), 178 | )) 179 | } 180 | 181 | func (th *TaskHandler) deleteTodoHandler(c echo.Context) error { 182 | idParams, err := strconv.Atoi(c.Param("id")) 183 | if err != nil { 184 | fmt.Println(err) 185 | return err 186 | } 187 | 188 | t := services.Todo{ 189 | CreatedBy: c.Get(user_id_key).(int), 190 | ID: idParams, 191 | } 192 | 193 | err = th.TodoServices.DeleteTodo(t) 194 | if err != nil { 195 | if strings.Contains(err.Error(), "an affected row was expected") { 196 | 197 | return echo.NewHTTPError( 198 | echo.ErrNotFound.Code, 199 | fmt.Sprintf( 200 | "something went wrong: %s", 201 | err, 202 | )) 203 | } 204 | 205 | return echo.NewHTTPError( 206 | echo.ErrInternalServerError.Code, 207 | fmt.Sprintf( 208 | "something went wrong: %s", 209 | err, 210 | )) 211 | } 212 | 213 | setFlashmessages(c, "success", "Task successfully deleted!!") 214 | 215 | return c.Redirect(http.StatusSeeOther, "/todo/list") 216 | } 217 | 218 | func (th *TaskHandler) logoutHandler(c echo.Context) error { 219 | sess, _ := session.Get(auth_sessions_key, c) 220 | // Revoke users authentication 221 | sess.Values = map[interface{}]interface{}{ 222 | auth_key: false, 223 | user_id_key: "", 224 | username_key: "", 225 | tzone_key: "", 226 | } 227 | sess.Save(c.Request(), c.Response()) 228 | 229 | setFlashmessages(c, "success", "You have successfully logged out!!") 230 | 231 | // fromProtected = false 232 | c.Set("FROMPROTECTED", false) 233 | 234 | return c.Redirect(http.StatusSeeOther, "/login") 235 | } 236 | -------------------------------------------------------------------------------- /handlers/auth.handlers.go: -------------------------------------------------------------------------------- 1 | package handlers 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "net/http" 7 | "strings" 8 | 9 | "github.com/emarifer/go-echo-templ-htmx/services" 10 | "github.com/emarifer/go-echo-templ-htmx/views/auth_views" 11 | "golang.org/x/crypto/bcrypt" 12 | 13 | "github.com/a-h/templ" 14 | "github.com/gorilla/sessions" 15 | "github.com/labstack/echo-contrib/session" 16 | "github.com/labstack/echo/v4" 17 | ) 18 | 19 | const ( 20 | auth_sessions_key string = "authenticate-sessions" 21 | auth_key string = "authenticated" 22 | user_id_key string = "user_id" 23 | username_key string = "username" 24 | tzone_key string = "time_zone" 25 | ) 26 | 27 | /********** Handlers for Auth Views **********/ 28 | 29 | type AuthService interface { 30 | CreateUser(u services.User) error 31 | CheckEmail(email string) (services.User, error) 32 | // GetUserById(id int) (services.User, error) 33 | } 34 | 35 | func NewAuthHandler(us AuthService) *AuthHandler { 36 | 37 | return &AuthHandler{ 38 | UserServices: us, 39 | } 40 | } 41 | 42 | type AuthHandler struct { 43 | UserServices AuthService 44 | } 45 | 46 | func (ah *AuthHandler) homeHandler(c echo.Context) error { 47 | fromProtected, ok := c.Get("FROMPROTECTED").(bool) 48 | if !ok { 49 | return errors.New("invalid type for key 'FROMPROTECTED'") 50 | } 51 | homeView := auth_views.Home(fromProtected) 52 | // isError = false 53 | c.Set("ISERROR", false) 54 | // fmt.Printf("\033[31mFROMPROTECTED = %t\n\033[0m", fromProtected) 55 | 56 | return renderView(c, auth_views.HomeIndex( 57 | "| Home", 58 | "", 59 | fromProtected, 60 | c.Get("ISERROR").(bool), 61 | getFlashmessages(c, "error"), 62 | getFlashmessages(c, "success"), 63 | homeView, 64 | )) 65 | } 66 | 67 | func (ah *AuthHandler) registerHandler(c echo.Context) error { 68 | fromProtected, ok := c.Get("FROMPROTECTED").(bool) 69 | if !ok { 70 | return errors.New("invalid type for key 'FROMPROTECTED'") 71 | } 72 | registerView := auth_views.Register(fromProtected) 73 | // isError = false 74 | c.Set("ISERROR", false) 75 | 76 | if c.Request().Method == "POST" { 77 | user := services.User{ 78 | Email: c.FormValue("email"), 79 | Password: c.FormValue("password"), 80 | Username: c.FormValue("username"), 81 | } 82 | 83 | err := ah.UserServices.CreateUser(user) 84 | if err != nil { 85 | if strings.Contains(err.Error(), "UNIQUE constraint failed") { 86 | err = errors.New("the email is already in use") 87 | setFlashmessages(c, "error", fmt.Sprintf( 88 | "something went wrong: %s", 89 | err, 90 | )) 91 | 92 | return c.Redirect(http.StatusSeeOther, "/register") 93 | } 94 | 95 | return echo.NewHTTPError( 96 | echo.ErrInternalServerError.Code, 97 | fmt.Sprintf( 98 | "something went wrong: %s", 99 | err, 100 | )) 101 | } 102 | 103 | setFlashmessages(c, "success", "You have successfully registered!!") 104 | 105 | return c.Redirect(http.StatusSeeOther, "/login") 106 | } 107 | 108 | return renderView(c, auth_views.RegisterIndex( 109 | "| Register", 110 | "", 111 | fromProtected, 112 | c.Get("ISERROR").(bool), 113 | getFlashmessages(c, "error"), 114 | getFlashmessages(c, "success"), 115 | registerView, 116 | )) 117 | } 118 | 119 | func (ah *AuthHandler) loginHandler(c echo.Context) error { 120 | fromProtected, ok := c.Get("FROMPROTECTED").(bool) 121 | if !ok { 122 | return errors.New("invalid type for key 'FROMPROTECTED'") 123 | } 124 | loginView := auth_views.Login(fromProtected) 125 | // isError = false 126 | c.Set("ISERROR", false) 127 | 128 | if c.Request().Method == "POST" { 129 | // obtaining the time zone from the POST request of the login form 130 | tzone := "" 131 | if len(c.Request().Header["X-Timezone"]) != 0 { 132 | tzone = c.Request().Header["X-Timezone"][0] 133 | } 134 | 135 | // Authentication goes here 136 | user, err := ah.UserServices.CheckEmail(c.FormValue("email")) 137 | if err != nil { 138 | if strings.Contains(err.Error(), "no rows in result set") { 139 | setFlashmessages(c, "error", "There is no user with that email") 140 | 141 | return c.Redirect(http.StatusSeeOther, "/login") 142 | } 143 | 144 | return echo.NewHTTPError( 145 | echo.ErrInternalServerError.Code, 146 | fmt.Sprintf( 147 | "something went wrong: %s", 148 | err, 149 | )) 150 | } 151 | 152 | err = bcrypt.CompareHashAndPassword( 153 | []byte(user.Password), 154 | []byte(c.FormValue("password")), 155 | ) 156 | if err != nil { 157 | // In production you have to give the user a generic message 158 | setFlashmessages(c, "error", "Incorrect password") 159 | 160 | return c.Redirect(http.StatusSeeOther, "/login") 161 | } 162 | 163 | // Get Session and setting Cookies 164 | sess, _ := session.Get(auth_sessions_key, c) 165 | sess.Options = &sessions.Options{ 166 | Path: "/", 167 | MaxAge: 3600, // in seconds 168 | HttpOnly: true, 169 | } 170 | 171 | // Set user as authenticated, their username, 172 | // their ID and the client's time zone 173 | sess.Values = map[interface{}]interface{}{ 174 | auth_key: true, 175 | user_id_key: user.ID, 176 | username_key: user.Username, 177 | tzone_key: tzone, 178 | } 179 | sess.Save(c.Request(), c.Response()) 180 | 181 | setFlashmessages(c, "success", "You have successfully logged in!!") 182 | 183 | return c.Redirect(http.StatusSeeOther, "/todo/list") 184 | } 185 | 186 | return renderView(c, auth_views.LoginIndex( 187 | "| Login", 188 | "", 189 | fromProtected, 190 | c.Get("ISERROR").(bool), 191 | getFlashmessages(c, "error"), 192 | getFlashmessages(c, "success"), 193 | loginView, 194 | )) 195 | } 196 | 197 | func (ah *AuthHandler) flagsMiddleware(next echo.HandlerFunc) echo.HandlerFunc { 198 | return func(c echo.Context) error { 199 | sess, _ := session.Get(auth_sessions_key, c) 200 | if auth, ok := sess.Values[auth_key].(bool); !ok || !auth { 201 | // fmt.Printf("\033[36m Ok=%t, Auth=%t \n\033[0m", ok, auth) 202 | c.Set("FROMPROTECTED", false) 203 | 204 | return next(c) 205 | } 206 | 207 | c.Set("FROMPROTECTED", true) 208 | 209 | return next(c) 210 | } 211 | } 212 | 213 | func (ah *AuthHandler) authMiddleware(next echo.HandlerFunc) echo.HandlerFunc { 214 | return func(c echo.Context) error { 215 | sess, _ := session.Get(auth_sessions_key, c) 216 | if auth, ok := sess.Values[auth_key].(bool); !ok || !auth { 217 | // fmt.Printf("\033[36m Ok=%t, Auth=%t \n\033[0m", ok, auth) 218 | // fromProtected = false 219 | c.Set("FROMPROTECTED", false) 220 | 221 | return echo.NewHTTPError(echo.ErrUnauthorized.Code, "Please provide valid credentials") 222 | } 223 | 224 | if userId, ok := sess.Values[user_id_key].(int); ok && userId != 0 { 225 | c.Set(user_id_key, userId) // set the user_id in the context 226 | } 227 | 228 | if username, ok := sess.Values[username_key].(string); ok && len(username) != 0 { 229 | c.Set(username_key, username) // set the username in the context 230 | } 231 | 232 | if tzone, ok := sess.Values[tzone_key].(string); ok && len(tzone) != 0 { 233 | c.Set(tzone_key, tzone) // set the client's time zone in the context 234 | } 235 | 236 | // fromProtected = true 237 | c.Set("FROMPROTECTED", true) 238 | 239 | return next(c) 240 | } 241 | } 242 | 243 | func renderView(c echo.Context, cmp templ.Component) error { 244 | c.Response().Header().Set(echo.HeaderContentType, echo.MIMETextHTML) 245 | 246 | return cmp.Render(c.Request().Context(), c.Response().Writer) 247 | } 248 | -------------------------------------------------------------------------------- /doc/structure.svg: -------------------------------------------------------------------------------- 1 |
uses
use
uses
renders
HTTP handler
Services
Database access code
SQLite3
Components
2 | -------------------------------------------------------------------------------- /assets/js/htmx.min.js: -------------------------------------------------------------------------------- 1 | (function(e,t){if(typeof define==="function"&&define.amd){define([],t)}else if(typeof module==="object"&&module.exports){module.exports=t()}else{e.htmx=e.htmx||t()}})(typeof self!=="undefined"?self:this,function(){return function(){"use strict";var Q={onLoad:F,process:zt,on:de,off:ge,trigger:ce,ajax:Nr,find:C,findAll:f,closest:v,values:function(e,t){var r=dr(e,t||"post");return r.values},remove:_,addClass:z,removeClass:n,toggleClass:$,takeClass:W,defineExtension:Ur,removeExtension:Br,logAll:V,logNone:j,logger:null,config:{historyEnabled:true,historyCacheSize:10,refreshOnHistoryMiss:false,defaultSwapStyle:"innerHTML",defaultSwapDelay:0,defaultSettleDelay:20,includeIndicatorStyles:true,indicatorClass:"htmx-indicator",requestClass:"htmx-request",addedClass:"htmx-added",settlingClass:"htmx-settling",swappingClass:"htmx-swapping",allowEval:true,allowScriptTags:true,inlineScriptNonce:"",attributesToSettle:["class","style","width","height"],withCredentials:false,timeout:0,wsReconnectDelay:"full-jitter",wsBinaryType:"blob",disableSelector:"[hx-disable], [data-hx-disable]",useTemplateFragments:false,scrollBehavior:"smooth",defaultFocusScroll:false,getCacheBusterParam:false,globalViewTransitions:false,methodsThatUseUrlParams:["get"],selfRequestsOnly:false,ignoreTitle:false,scrollIntoViewOnBoost:true,triggerSpecsCache:null},parseInterval:d,_:t,createEventSource:function(e){return new EventSource(e,{withCredentials:true})},createWebSocket:function(e){var t=new WebSocket(e,[]);t.binaryType=Q.config.wsBinaryType;return t},version:"1.9.12"};var r={addTriggerHandler:Lt,bodyContains:se,canAccessLocalStorage:U,findThisElement:xe,filterValues:yr,hasAttribute:o,getAttributeValue:te,getClosestAttributeValue:ne,getClosestMatch:c,getExpressionVars:Hr,getHeaders:xr,getInputValues:dr,getInternalData:ae,getSwapSpecification:wr,getTriggerSpecs:it,getTarget:ye,makeFragment:l,mergeObjects:le,makeSettleInfo:T,oobSwap:Ee,querySelectorExt:ue,selectAndSwap:je,settleImmediately:nr,shouldCancel:ut,triggerEvent:ce,triggerErrorEvent:fe,withExtensions:R};var w=["get","post","put","delete","patch"];var i=w.map(function(e){return"[hx-"+e+"], [data-hx-"+e+"]"}).join(", ");var S=e("head"),q=e("title"),H=e("svg",true);function e(e,t){return new RegExp("<"+e+"(\\s[^>]*>|>)([\\s\\S]*?)<\\/"+e+">",!!t?"gim":"im")}function d(e){if(e==undefined){return undefined}let t=NaN;if(e.slice(-2)=="ms"){t=parseFloat(e.slice(0,-2))}else if(e.slice(-1)=="s"){t=parseFloat(e.slice(0,-1))*1e3}else if(e.slice(-1)=="m"){t=parseFloat(e.slice(0,-1))*1e3*60}else{t=parseFloat(e)}return isNaN(t)?undefined:t}function ee(e,t){return e.getAttribute&&e.getAttribute(t)}function o(e,t){return e.hasAttribute&&(e.hasAttribute(t)||e.hasAttribute("data-"+t))}function te(e,t){return ee(e,t)||ee(e,"data-"+t)}function u(e){return e.parentElement}function re(){return document}function c(e,t){while(e&&!t(e)){e=u(e)}return e?e:null}function L(e,t,r){var n=te(t,r);var i=te(t,"hx-disinherit");if(e!==t&&i&&(i==="*"||i.split(" ").indexOf(r)>=0)){return"unset"}else{return n}}function ne(t,r){var n=null;c(t,function(e){return n=L(t,e,r)});if(n!=="unset"){return n}}function h(e,t){var r=e.matches||e.matchesSelector||e.msMatchesSelector||e.mozMatchesSelector||e.webkitMatchesSelector||e.oMatchesSelector;return r&&r.call(e,t)}function A(e){var t=/<([a-z][^\/\0>\x20\t\r\n\f]*)/i;var r=t.exec(e);if(r){return r[1].toLowerCase()}else{return""}}function s(e,t){var r=new DOMParser;var n=r.parseFromString(e,"text/html");var i=n.body;while(t>0){t--;i=i.firstChild}if(i==null){i=re().createDocumentFragment()}return i}function N(e){return/",0);var a=i.querySelector("template").content;if(Q.config.allowScriptTags){oe(a.querySelectorAll("script"),function(e){if(Q.config.inlineScriptNonce){e.nonce=Q.config.inlineScriptNonce}e.htmxExecuted=navigator.userAgent.indexOf("Firefox")===-1})}else{oe(a.querySelectorAll("script"),function(e){_(e)})}return a}switch(r){case"thead":case"tbody":case"tfoot":case"colgroup":case"caption":return s(""+n+"
",1);case"col":return s(""+n+"
",2);case"tr":return s(""+n+"
",2);case"td":case"th":return s(""+n+"
",3);case"script":case"style":return s("
"+n+"
",1);default:return s(n,0)}}function ie(e){if(e){e()}}function I(e,t){return Object.prototype.toString.call(e)==="[object "+t+"]"}function k(e){return I(e,"Function")}function P(e){return I(e,"Object")}function ae(e){var t="htmx-internal-data";var r=e[t];if(!r){r=e[t]={}}return r}function M(e){var t=[];if(e){for(var r=0;r=0}function se(e){if(e.getRootNode&&e.getRootNode()instanceof window.ShadowRoot){return re().body.contains(e.getRootNode().host)}else{return re().body.contains(e)}}function D(e){return e.trim().split(/\s+/)}function le(e,t){for(var r in t){if(t.hasOwnProperty(r)){e[r]=t[r]}}return e}function E(e){try{return JSON.parse(e)}catch(e){b(e);return null}}function U(){var e="htmx:localStorageTest";try{localStorage.setItem(e,e);localStorage.removeItem(e);return true}catch(e){return false}}function B(t){try{var e=new URL(t);if(e){t=e.pathname+e.search}if(!/^\/$/.test(t)){t=t.replace(/\/+$/,"")}return t}catch(e){return t}}function t(e){return Tr(re().body,function(){return eval(e)})}function F(t){var e=Q.on("htmx:load",function(e){t(e.detail.elt)});return e}function V(){Q.logger=function(e,t,r){if(console){console.log(t,e,r)}}}function j(){Q.logger=null}function C(e,t){if(t){return e.querySelector(t)}else{return C(re(),e)}}function f(e,t){if(t){return e.querySelectorAll(t)}else{return f(re(),e)}}function _(e,t){e=p(e);if(t){setTimeout(function(){_(e);e=null},t)}else{e.parentElement.removeChild(e)}}function z(e,t,r){e=p(e);if(r){setTimeout(function(){z(e,t);e=null},r)}else{e.classList&&e.classList.add(t)}}function n(e,t,r){e=p(e);if(r){setTimeout(function(){n(e,t);e=null},r)}else{if(e.classList){e.classList.remove(t);if(e.classList.length===0){e.removeAttribute("class")}}}}function $(e,t){e=p(e);e.classList.toggle(t)}function W(e,t){e=p(e);oe(e.parentElement.children,function(e){n(e,t)});z(e,t)}function v(e,t){e=p(e);if(e.closest){return e.closest(t)}else{do{if(e==null||h(e,t)){return e}}while(e=e&&u(e));return null}}function g(e,t){return e.substring(0,t.length)===t}function G(e,t){return e.substring(e.length-t.length)===t}function J(e){var t=e.trim();if(g(t,"<")&&G(t,"/>")){return t.substring(1,t.length-2)}else{return t}}function Z(e,t){if(t.indexOf("closest ")===0){return[v(e,J(t.substr(8)))]}else if(t.indexOf("find ")===0){return[C(e,J(t.substr(5)))]}else if(t==="next"){return[e.nextElementSibling]}else if(t.indexOf("next ")===0){return[K(e,J(t.substr(5)))]}else if(t==="previous"){return[e.previousElementSibling]}else if(t.indexOf("previous ")===0){return[Y(e,J(t.substr(9)))]}else if(t==="document"){return[document]}else if(t==="window"){return[window]}else if(t==="body"){return[document.body]}else{return re().querySelectorAll(J(t))}}var K=function(e,t){var r=re().querySelectorAll(t);for(var n=0;n=0;n--){var i=r[n];if(i.compareDocumentPosition(e)===Node.DOCUMENT_POSITION_FOLLOWING){return i}}};function ue(e,t){if(t){return Z(e,t)[0]}else{return Z(re().body,e)[0]}}function p(e){if(I(e,"String")){return C(e)}else{return e}}function ve(e,t,r){if(k(t)){return{target:re().body,event:e,listener:t}}else{return{target:p(e),event:t,listener:r}}}function de(t,r,n){jr(function(){var e=ve(t,r,n);e.target.addEventListener(e.event,e.listener)});var e=k(r);return e?r:n}function ge(t,r,n){jr(function(){var e=ve(t,r,n);e.target.removeEventListener(e.event,e.listener)});return k(r)?r:n}var pe=re().createElement("output");function me(e,t){var r=ne(e,t);if(r){if(r==="this"){return[xe(e,t)]}else{var n=Z(e,r);if(n.length===0){b('The selector "'+r+'" on '+t+" returned no matches!");return[pe]}else{return n}}}}function xe(e,t){return c(e,function(e){return te(e,t)!=null})}function ye(e){var t=ne(e,"hx-target");if(t){if(t==="this"){return xe(e,"hx-target")}else{return ue(e,t)}}else{var r=ae(e);if(r.boosted){return re().body}else{return e}}}function be(e){var t=Q.config.attributesToSettle;for(var r=0;r0){o=e.substr(0,e.indexOf(":"));t=e.substr(e.indexOf(":")+1,e.length)}else{o=e}var r=re().querySelectorAll(t);if(r){oe(r,function(e){var t;var r=i.cloneNode(true);t=re().createDocumentFragment();t.appendChild(r);if(!Se(o,e)){t=r}var n={shouldSwap:true,target:e,fragment:t};if(!ce(e,"htmx:oobBeforeSwap",n))return;e=n.target;if(n["shouldSwap"]){Fe(o,e,e,t,a)}oe(a.elts,function(e){ce(e,"htmx:oobAfterSwap",n)})});i.parentNode.removeChild(i)}else{i.parentNode.removeChild(i);fe(re().body,"htmx:oobErrorNoTarget",{content:i})}return e}function Ce(e,t,r){var n=ne(e,"hx-select-oob");if(n){var i=n.split(",");for(var a=0;a0){var r=t.replace("'","\\'");var n=e.tagName.replace(":","\\:");var i=o.querySelector(n+"[id='"+r+"']");if(i&&i!==o){var a=e.cloneNode();we(e,i);s.tasks.push(function(){we(e,a)})}}})}function Oe(e){return function(){n(e,Q.config.addedClass);zt(e);Nt(e);qe(e);ce(e,"htmx:load")}}function qe(e){var t="[autofocus]";var r=h(e,t)?e:e.querySelector(t);if(r!=null){r.focus()}}function a(e,t,r,n){Te(e,r,n);while(r.childNodes.length>0){var i=r.firstChild;z(i,Q.config.addedClass);e.insertBefore(i,t);if(i.nodeType!==Node.TEXT_NODE&&i.nodeType!==Node.COMMENT_NODE){n.tasks.push(Oe(i))}}}function He(e,t){var r=0;while(r-1){var t=e.replace(H,"");var r=t.match(q);if(r){return r[2]}}}function je(e,t,r,n,i,a){i.title=Ve(n);var o=l(n);if(o){Ce(r,o,i);o=Be(r,o,a);Re(o);return Fe(e,r,t,o,i)}}function _e(e,t,r){var n=e.getResponseHeader(t);if(n.indexOf("{")===0){var i=E(n);for(var a in i){if(i.hasOwnProperty(a)){var o=i[a];if(!P(o)){o={value:o}}ce(r,a,o)}}}else{var s=n.split(",");for(var l=0;l0){var o=t[0];if(o==="]"){n--;if(n===0){if(a===null){i=i+"true"}t.shift();i+=")})";try{var s=Tr(e,function(){return Function(i)()},function(){return true});s.source=i;return s}catch(e){fe(re().body,"htmx:syntax:error",{error:e,source:i});return null}}}else if(o==="["){n++}if(Qe(o,a,r)){i+="(("+r+"."+o+") ? ("+r+"."+o+") : (window."+o+"))"}else{i=i+o}a=t.shift()}}}function y(e,t){var r="";while(e.length>0&&!t.test(e[0])){r+=e.shift()}return r}function tt(e){var t;if(e.length>0&&Ze.test(e[0])){e.shift();t=y(e,Ke).trim();e.shift()}else{t=y(e,x)}return t}var rt="input, textarea, select";function nt(e,t,r){var n=[];var i=Ye(t);do{y(i,Je);var a=i.length;var o=y(i,/[,\[\s]/);if(o!==""){if(o==="every"){var s={trigger:"every"};y(i,Je);s.pollInterval=d(y(i,/[,\[\s]/));y(i,Je);var l=et(e,i,"event");if(l){s.eventFilter=l}n.push(s)}else if(o.indexOf("sse:")===0){n.push({trigger:"sse",sseEvent:o.substr(4)})}else{var u={trigger:o};var l=et(e,i,"event");if(l){u.eventFilter=l}while(i.length>0&&i[0]!==","){y(i,Je);var f=i.shift();if(f==="changed"){u.changed=true}else if(f==="once"){u.once=true}else if(f==="consume"){u.consume=true}else if(f==="delay"&&i[0]===":"){i.shift();u.delay=d(y(i,x))}else if(f==="from"&&i[0]===":"){i.shift();if(Ze.test(i[0])){var c=tt(i)}else{var c=y(i,x);if(c==="closest"||c==="find"||c==="next"||c==="previous"){i.shift();var h=tt(i);if(h.length>0){c+=" "+h}}}u.from=c}else if(f==="target"&&i[0]===":"){i.shift();u.target=tt(i)}else if(f==="throttle"&&i[0]===":"){i.shift();u.throttle=d(y(i,x))}else if(f==="queue"&&i[0]===":"){i.shift();u.queue=y(i,x)}else if(f==="root"&&i[0]===":"){i.shift();u[f]=tt(i)}else if(f==="threshold"&&i[0]===":"){i.shift();u[f]=y(i,x)}else{fe(e,"htmx:syntax:error",{token:i.shift()})}}n.push(u)}}if(i.length===a){fe(e,"htmx:syntax:error",{token:i.shift()})}y(i,Je)}while(i[0]===","&&i.shift());if(r){r[t]=n}return n}function it(e){var t=te(e,"hx-trigger");var r=[];if(t){var n=Q.config.triggerSpecsCache;r=n&&n[t]||nt(e,t,n)}if(r.length>0){return r}else if(h(e,"form")){return[{trigger:"submit"}]}else if(h(e,'input[type="button"], input[type="submit"]')){return[{trigger:"click"}]}else if(h(e,rt)){return[{trigger:"change"}]}else{return[{trigger:"click"}]}}function at(e){ae(e).cancelled=true}function ot(e,t,r){var n=ae(e);n.timeout=setTimeout(function(){if(se(e)&&n.cancelled!==true){if(!ct(r,e,Wt("hx:poll:trigger",{triggerSpec:r,target:e}))){t(e)}ot(e,t,r)}},r.pollInterval)}function st(e){return location.hostname===e.hostname&&ee(e,"href")&&ee(e,"href").indexOf("#")!==0}function lt(t,r,e){if(t.tagName==="A"&&st(t)&&(t.target===""||t.target==="_self")||t.tagName==="FORM"){r.boosted=true;var n,i;if(t.tagName==="A"){n="get";i=ee(t,"href")}else{var a=ee(t,"method");n=a?a.toLowerCase():"get";if(n==="get"){}i=ee(t,"action")}e.forEach(function(e){ht(t,function(e,t){if(v(e,Q.config.disableSelector)){m(e);return}he(n,i,e,t)},r,e,true)})}}function ut(e,t){if(e.type==="submit"||e.type==="click"){if(t.tagName==="FORM"){return true}if(h(t,'input[type="submit"], button')&&v(t,"form")!==null){return true}if(t.tagName==="A"&&t.href&&(t.getAttribute("href")==="#"||t.getAttribute("href").indexOf("#")!==0)){return true}}return false}function ft(e,t){return ae(e).boosted&&e.tagName==="A"&&t.type==="click"&&(t.ctrlKey||t.metaKey)}function ct(e,t,r){var n=e.eventFilter;if(n){try{return n.call(t,r)!==true}catch(e){fe(re().body,"htmx:eventFilter:error",{error:e,source:n.source});return true}}return false}function ht(a,o,e,s,l){var u=ae(a);var t;if(s.from){t=Z(a,s.from)}else{t=[a]}if(s.changed){t.forEach(function(e){var t=ae(e);t.lastValue=e.value})}oe(t,function(n){var i=function(e){if(!se(a)){n.removeEventListener(s.trigger,i);return}if(ft(a,e)){return}if(l||ut(e,a)){e.preventDefault()}if(ct(s,a,e)){return}var t=ae(e);t.triggerSpec=s;if(t.handledFor==null){t.handledFor=[]}if(t.handledFor.indexOf(a)<0){t.handledFor.push(a);if(s.consume){e.stopPropagation()}if(s.target&&e.target){if(!h(e.target,s.target)){return}}if(s.once){if(u.triggeredOnce){return}else{u.triggeredOnce=true}}if(s.changed){var r=ae(n);if(r.lastValue===n.value){return}r.lastValue=n.value}if(u.delayed){clearTimeout(u.delayed)}if(u.throttle){return}if(s.throttle>0){if(!u.throttle){o(a,e);u.throttle=setTimeout(function(){u.throttle=null},s.throttle)}}else if(s.delay>0){u.delayed=setTimeout(function(){o(a,e)},s.delay)}else{ce(a,"htmx:trigger");o(a,e)}}};if(e.listenerInfos==null){e.listenerInfos=[]}e.listenerInfos.push({trigger:s.trigger,listener:i,on:n});n.addEventListener(s.trigger,i)})}var vt=false;var dt=null;function gt(){if(!dt){dt=function(){vt=true};window.addEventListener("scroll",dt);setInterval(function(){if(vt){vt=false;oe(re().querySelectorAll("[hx-trigger='revealed'],[data-hx-trigger='revealed']"),function(e){pt(e)})}},200)}}function pt(t){if(!o(t,"data-hx-revealed")&&X(t)){t.setAttribute("data-hx-revealed","true");var e=ae(t);if(e.initHash){ce(t,"revealed")}else{t.addEventListener("htmx:afterProcessNode",function(e){ce(t,"revealed")},{once:true})}}}function mt(e,t,r){var n=D(r);for(var i=0;i=0){var t=wt(n);setTimeout(function(){xt(s,r,n+1)},t)}};t.onopen=function(e){n=0};ae(s).webSocket=t;t.addEventListener("message",function(e){if(yt(s)){return}var t=e.data;R(s,function(e){t=e.transformResponse(t,null,s)});var r=T(s);var n=l(t);var i=M(n.children);for(var a=0;a0){ce(u,"htmx:validation:halted",i);return}t.send(JSON.stringify(l));if(ut(e,u)){e.preventDefault()}})}else{fe(u,"htmx:noWebSocketSourceError")}}function wt(e){var t=Q.config.wsReconnectDelay;if(typeof t==="function"){return t(e)}if(t==="full-jitter"){var r=Math.min(e,6);var n=1e3*Math.pow(2,r);return n*Math.random()}b('htmx.config.wsReconnectDelay must either be a function or the string "full-jitter"')}function St(e,t,r){var n=D(r);for(var i=0;i0){setTimeout(i,n)}else{i()}}function Ht(t,i,e){var a=false;oe(w,function(r){if(o(t,"hx-"+r)){var n=te(t,"hx-"+r);a=true;i.path=n;i.verb=r;e.forEach(function(e){Lt(t,e,i,function(e,t){if(v(e,Q.config.disableSelector)){m(e);return}he(r,n,e,t)})})}});return a}function Lt(n,e,t,r){if(e.sseEvent){Rt(n,r,e.sseEvent)}else if(e.trigger==="revealed"){gt();ht(n,r,t,e);pt(n)}else if(e.trigger==="intersect"){var i={};if(e.root){i.root=ue(n,e.root)}if(e.threshold){i.threshold=parseFloat(e.threshold)}var a=new IntersectionObserver(function(e){for(var t=0;t0){t.polling=true;ot(n,r,e)}else{ht(n,r,t,e)}}function At(e){if(!e.htmxExecuted&&Q.config.allowScriptTags&&(e.type==="text/javascript"||e.type==="module"||e.type==="")){var t=re().createElement("script");oe(e.attributes,function(e){t.setAttribute(e.name,e.value)});t.textContent=e.textContent;t.async=false;if(Q.config.inlineScriptNonce){t.nonce=Q.config.inlineScriptNonce}var r=e.parentElement;try{r.insertBefore(t,e)}catch(e){b(e)}finally{if(e.parentElement){e.parentElement.removeChild(e)}}}}function Nt(e){if(h(e,"script")){At(e)}oe(f(e,"script"),function(e){At(e)})}function It(e){var t=e.attributes;if(!t){return false}for(var r=0;r0){var o=n.shift();var s=o.match(/^\s*([a-zA-Z:\-\.]+:)(.*)/);if(a===0&&s){o.split(":");i=s[1].slice(0,-1);r[i]=s[2]}else{r[i]+=o}a+=Bt(o)}for(var l in r){Ft(e,l,r[l])}}}function jt(e){Ae(e);for(var t=0;tQ.config.historyCacheSize){i.shift()}while(i.length>0){try{localStorage.setItem("htmx-history-cache",JSON.stringify(i));break}catch(e){fe(re().body,"htmx:historyCacheError",{cause:e,cache:i});i.shift()}}}function Yt(e){if(!U()){return null}e=B(e);var t=E(localStorage.getItem("htmx-history-cache"))||[];for(var r=0;r=200&&this.status<400){ce(re().body,"htmx:historyCacheMissLoad",o);var e=l(this.response);e=e.querySelector("[hx-history-elt],[data-hx-history-elt]")||e;var t=Zt();var r=T(t);var n=Ve(this.response);if(n){var i=C("title");if(i){i.innerHTML=n}else{window.document.title=n}}Ue(t,e,r);nr(r.tasks);Jt=a;ce(re().body,"htmx:historyRestore",{path:a,cacheMiss:true,serverResponse:this.response})}else{fe(re().body,"htmx:historyCacheMissLoadError",o)}};e.send()}function ar(e){er();e=e||location.pathname+location.search;var t=Yt(e);if(t){var r=l(t.content);var n=Zt();var i=T(n);Ue(n,r,i);nr(i.tasks);document.title=t.title;setTimeout(function(){window.scrollTo(0,t.scroll)},0);Jt=e;ce(re().body,"htmx:historyRestore",{path:e,item:t})}else{if(Q.config.refreshOnHistoryMiss){window.location.reload(true)}else{ir(e)}}}function or(e){var t=me(e,"hx-indicator");if(t==null){t=[e]}oe(t,function(e){var t=ae(e);t.requestCount=(t.requestCount||0)+1;e.classList["add"].call(e.classList,Q.config.requestClass)});return t}function sr(e){var t=me(e,"hx-disabled-elt");if(t==null){t=[]}oe(t,function(e){var t=ae(e);t.requestCount=(t.requestCount||0)+1;e.setAttribute("disabled","")});return t}function lr(e,t){oe(e,function(e){var t=ae(e);t.requestCount=(t.requestCount||0)-1;if(t.requestCount===0){e.classList["remove"].call(e.classList,Q.config.requestClass)}});oe(t,function(e){var t=ae(e);t.requestCount=(t.requestCount||0)-1;if(t.requestCount===0){e.removeAttribute("disabled")}})}function ur(e,t){for(var r=0;r=0}function wr(e,t){var r=t?t:ne(e,"hx-swap");var n={swapStyle:ae(e).boosted?"innerHTML":Q.config.defaultSwapStyle,swapDelay:Q.config.defaultSwapDelay,settleDelay:Q.config.defaultSettleDelay};if(Q.config.scrollIntoViewOnBoost&&ae(e).boosted&&!br(e)){n["show"]="top"}if(r){var i=D(r);if(i.length>0){for(var a=0;a0?l.join(":"):null;n["scroll"]=u;n["scrollTarget"]=f}else if(o.indexOf("show:")===0){var c=o.substr(5);var l=c.split(":");var h=l.pop();var f=l.length>0?l.join(":"):null;n["show"]=h;n["showTarget"]=f}else if(o.indexOf("focus-scroll:")===0){var v=o.substr("focus-scroll:".length);n["focusScroll"]=v=="true"}else if(a==0){n["swapStyle"]=o}else{b("Unknown modifier in hx-swap: "+o)}}}}return n}function Sr(e){return ne(e,"hx-encoding")==="multipart/form-data"||h(e,"form")&&ee(e,"enctype")==="multipart/form-data"}function Er(t,r,n){var i=null;R(r,function(e){if(i==null){i=e.encodeParameters(t,n,r)}});if(i!=null){return i}else{if(Sr(r)){return mr(n)}else{return pr(n)}}}function T(e){return{tasks:[],elts:[e]}}function Cr(e,t){var r=e[0];var n=e[e.length-1];if(t.scroll){var i=null;if(t.scrollTarget){i=ue(r,t.scrollTarget)}if(t.scroll==="top"&&(r||i)){i=i||r;i.scrollTop=0}if(t.scroll==="bottom"&&(n||i)){i=i||n;i.scrollTop=i.scrollHeight}}if(t.show){var i=null;if(t.showTarget){var a=t.showTarget;if(t.showTarget==="window"){a="body"}i=ue(r,a)}if(t.show==="top"&&(r||i)){i=i||r;i.scrollIntoView({block:"start",behavior:Q.config.scrollBehavior})}if(t.show==="bottom"&&(n||i)){i=i||n;i.scrollIntoView({block:"end",behavior:Q.config.scrollBehavior})}}}function Rr(e,t,r,n){if(n==null){n={}}if(e==null){return n}var i=te(e,t);if(i){var a=i.trim();var o=r;if(a==="unset"){return null}if(a.indexOf("javascript:")===0){a=a.substr(11);o=true}else if(a.indexOf("js:")===0){a=a.substr(3);o=true}if(a.indexOf("{")!==0){a="{"+a+"}"}var s;if(o){s=Tr(e,function(){return Function("return ("+a+")")()},{})}else{s=E(a)}for(var l in s){if(s.hasOwnProperty(l)){if(n[l]==null){n[l]=s[l]}}}}return Rr(u(e),t,r,n)}function Tr(e,t,r){if(Q.config.allowEval){return t()}else{fe(e,"htmx:evalDisallowedError");return r}}function Or(e,t){return Rr(e,"hx-vars",true,t)}function qr(e,t){return Rr(e,"hx-vals",false,t)}function Hr(e){return le(Or(e),qr(e))}function Lr(t,r,n){if(n!==null){try{t.setRequestHeader(r,n)}catch(e){t.setRequestHeader(r,encodeURIComponent(n));t.setRequestHeader(r+"-URI-AutoEncoded","true")}}}function Ar(t){if(t.responseURL&&typeof URL!=="undefined"){try{var e=new URL(t.responseURL);return e.pathname+e.search}catch(e){fe(re().body,"htmx:badResponseUrl",{url:t.responseURL})}}}function O(e,t){return t.test(e.getAllResponseHeaders())}function Nr(e,t,r){e=e.toLowerCase();if(r){if(r instanceof Element||I(r,"String")){return he(e,t,null,null,{targetOverride:p(r),returnPromise:true})}else{return he(e,t,p(r.source),r.event,{handler:r.handler,headers:r.headers,values:r.values,targetOverride:p(r.target),swapOverride:r.swap,select:r.select,returnPromise:true})}}else{return he(e,t,null,null,{returnPromise:true})}}function Ir(e){var t=[];while(e){t.push(e);e=e.parentElement}return t}function kr(e,t,r){var n;var i;if(typeof URL==="function"){i=new URL(t,document.location.href);var a=document.location.origin;n=a===i.origin}else{i=t;n=g(t,document.location.origin)}if(Q.config.selfRequestsOnly){if(!n){return false}}return ce(e,"htmx:validateUrl",le({url:i,sameHost:n},r))}function he(t,r,n,i,a,e){var o=null;var s=null;a=a!=null?a:{};if(a.returnPromise&&typeof Promise!=="undefined"){var l=new Promise(function(e,t){o=e;s=t})}if(n==null){n=re().body}var M=a.handler||Mr;var X=a.select||null;if(!se(n)){ie(o);return l}var u=a.targetOverride||ye(n);if(u==null||u==pe){fe(n,"htmx:targetError",{target:te(n,"hx-target")});ie(s);return l}var f=ae(n);var c=f.lastButtonClicked;if(c){var h=ee(c,"formaction");if(h!=null){r=h}var v=ee(c,"formmethod");if(v!=null){if(v.toLowerCase()!=="dialog"){t=v}}}var d=ne(n,"hx-confirm");if(e===undefined){var D=function(e){return he(t,r,n,i,a,!!e)};var U={target:u,elt:n,path:r,verb:t,triggeringEvent:i,etc:a,issueRequest:D,question:d};if(ce(n,"htmx:confirm",U)===false){ie(o);return l}}var g=n;var p=ne(n,"hx-sync");var m=null;var x=false;if(p){var B=p.split(":");var F=B[0].trim();if(F==="this"){g=xe(n,"hx-sync")}else{g=ue(n,F)}p=(B[1]||"drop").trim();f=ae(g);if(p==="drop"&&f.xhr&&f.abortable!==true){ie(o);return l}else if(p==="abort"){if(f.xhr){ie(o);return l}else{x=true}}else if(p==="replace"){ce(g,"htmx:abort")}else if(p.indexOf("queue")===0){var V=p.split(" ");m=(V[1]||"last").trim()}}if(f.xhr){if(f.abortable){ce(g,"htmx:abort")}else{if(m==null){if(i){var y=ae(i);if(y&&y.triggerSpec&&y.triggerSpec.queue){m=y.triggerSpec.queue}}if(m==null){m="last"}}if(f.queuedRequests==null){f.queuedRequests=[]}if(m==="first"&&f.queuedRequests.length===0){f.queuedRequests.push(function(){he(t,r,n,i,a)})}else if(m==="all"){f.queuedRequests.push(function(){he(t,r,n,i,a)})}else if(m==="last"){f.queuedRequests=[];f.queuedRequests.push(function(){he(t,r,n,i,a)})}ie(o);return l}}var b=new XMLHttpRequest;f.xhr=b;f.abortable=x;var w=function(){f.xhr=null;f.abortable=false;if(f.queuedRequests!=null&&f.queuedRequests.length>0){var e=f.queuedRequests.shift();e()}};var j=ne(n,"hx-prompt");if(j){var S=prompt(j);if(S===null||!ce(n,"htmx:prompt",{prompt:S,target:u})){ie(o);w();return l}}if(d&&!e){if(!confirm(d)){ie(o);w();return l}}var E=xr(n,u,S);if(t!=="get"&&!Sr(n)){E["Content-Type"]="application/x-www-form-urlencoded"}if(a.headers){E=le(E,a.headers)}var _=dr(n,t);var C=_.errors;var R=_.values;if(a.values){R=le(R,a.values)}var z=Hr(n);var $=le(R,z);var T=yr($,n);if(Q.config.getCacheBusterParam&&t==="get"){T["org.htmx.cache-buster"]=ee(u,"id")||"true"}if(r==null||r===""){r=re().location.href}var O=Rr(n,"hx-request");var W=ae(n).boosted;var q=Q.config.methodsThatUseUrlParams.indexOf(t)>=0;var H={boosted:W,useUrlParams:q,parameters:T,unfilteredParameters:$,headers:E,target:u,verb:t,errors:C,withCredentials:a.credentials||O.credentials||Q.config.withCredentials,timeout:a.timeout||O.timeout||Q.config.timeout,path:r,triggeringEvent:i};if(!ce(n,"htmx:configRequest",H)){ie(o);w();return l}r=H.path;t=H.verb;E=H.headers;T=H.parameters;C=H.errors;q=H.useUrlParams;if(C&&C.length>0){ce(n,"htmx:validation:halted",H);ie(o);w();return l}var G=r.split("#");var J=G[0];var L=G[1];var A=r;if(q){A=J;var Z=Object.keys(T).length!==0;if(Z){if(A.indexOf("?")<0){A+="?"}else{A+="&"}A+=pr(T);if(L){A+="#"+L}}}if(!kr(n,A,H)){fe(n,"htmx:invalidPath",H);ie(s);return l}b.open(t.toUpperCase(),A,true);b.overrideMimeType("text/html");b.withCredentials=H.withCredentials;b.timeout=H.timeout;if(O.noHeaders){}else{for(var N in E){if(E.hasOwnProperty(N)){var K=E[N];Lr(b,N,K)}}}var I={xhr:b,target:u,requestConfig:H,etc:a,boosted:W,select:X,pathInfo:{requestPath:r,finalRequestPath:A,anchor:L}};b.onload=function(){try{var e=Ir(n);I.pathInfo.responsePath=Ar(b);M(n,I);lr(k,P);ce(n,"htmx:afterRequest",I);ce(n,"htmx:afterOnLoad",I);if(!se(n)){var t=null;while(e.length>0&&t==null){var r=e.shift();if(se(r)){t=r}}if(t){ce(t,"htmx:afterRequest",I);ce(t,"htmx:afterOnLoad",I)}}ie(o);w()}catch(e){fe(n,"htmx:onLoadError",le({error:e},I));throw e}};b.onerror=function(){lr(k,P);fe(n,"htmx:afterRequest",I);fe(n,"htmx:sendError",I);ie(s);w()};b.onabort=function(){lr(k,P);fe(n,"htmx:afterRequest",I);fe(n,"htmx:sendAbort",I);ie(s);w()};b.ontimeout=function(){lr(k,P);fe(n,"htmx:afterRequest",I);fe(n,"htmx:timeout",I);ie(s);w()};if(!ce(n,"htmx:beforeRequest",I)){ie(o);w();return l}var k=or(n);var P=sr(n);oe(["loadstart","loadend","progress","abort"],function(t){oe([b,b.upload],function(e){e.addEventListener(t,function(e){ce(n,"htmx:xhr:"+t,{lengthComputable:e.lengthComputable,loaded:e.loaded,total:e.total})})})});ce(n,"htmx:beforeSend",I);var Y=q?null:Er(b,n,T);b.send(Y);return l}function Pr(e,t){var r=t.xhr;var n=null;var i=null;if(O(r,/HX-Push:/i)){n=r.getResponseHeader("HX-Push");i="push"}else if(O(r,/HX-Push-Url:/i)){n=r.getResponseHeader("HX-Push-Url");i="push"}else if(O(r,/HX-Replace-Url:/i)){n=r.getResponseHeader("HX-Replace-Url");i="replace"}if(n){if(n==="false"){return{}}else{return{type:i,path:n}}}var a=t.pathInfo.finalRequestPath;var o=t.pathInfo.responsePath;var s=ne(e,"hx-push-url");var l=ne(e,"hx-replace-url");var u=ae(e).boosted;var f=null;var c=null;if(s){f="push";c=s}else if(l){f="replace";c=l}else if(u){f="push";c=o||a}if(c){if(c==="false"){return{}}if(c==="true"){c=o||a}if(t.pathInfo.anchor&&c.indexOf("#")===-1){c=c+"#"+t.pathInfo.anchor}return{type:f,path:c}}else{return{}}}function Mr(l,u){var f=u.xhr;var c=u.target;var e=u.etc;var t=u.requestConfig;var h=u.select;if(!ce(l,"htmx:beforeOnLoad",u))return;if(O(f,/HX-Trigger:/i)){_e(f,"HX-Trigger",l)}if(O(f,/HX-Location:/i)){er();var r=f.getResponseHeader("HX-Location");var v;if(r.indexOf("{")===0){v=E(r);r=v["path"];delete v["path"]}Nr("GET",r,v).then(function(){tr(r)});return}var n=O(f,/HX-Refresh:/i)&&"true"===f.getResponseHeader("HX-Refresh");if(O(f,/HX-Redirect:/i)){location.href=f.getResponseHeader("HX-Redirect");n&&location.reload();return}if(n){location.reload();return}if(O(f,/HX-Retarget:/i)){if(f.getResponseHeader("HX-Retarget")==="this"){u.target=l}else{u.target=ue(l,f.getResponseHeader("HX-Retarget"))}}var d=Pr(l,u);var i=f.status>=200&&f.status<400&&f.status!==204;var g=f.response;var a=f.status>=400;var p=Q.config.ignoreTitle;var o=le({shouldSwap:i,serverResponse:g,isError:a,ignoreTitle:p},u);if(!ce(c,"htmx:beforeSwap",o))return;c=o.target;g=o.serverResponse;a=o.isError;p=o.ignoreTitle;u.target=c;u.failed=a;u.successful=!a;if(o.shouldSwap){if(f.status===286){at(l)}R(l,function(e){g=e.transformResponse(g,f,l)});if(d.type){er()}var s=e.swapOverride;if(O(f,/HX-Reswap:/i)){s=f.getResponseHeader("HX-Reswap")}var v=wr(l,s);if(v.hasOwnProperty("ignoreTitle")){p=v.ignoreTitle}c.classList.add(Q.config.swappingClass);var m=null;var x=null;var y=function(){try{var e=document.activeElement;var t={};try{t={elt:e,start:e?e.selectionStart:null,end:e?e.selectionEnd:null}}catch(e){}var r;if(h){r=h}if(O(f,/HX-Reselect:/i)){r=f.getResponseHeader("HX-Reselect")}if(d.type){ce(re().body,"htmx:beforeHistoryUpdate",le({history:d},u));if(d.type==="push"){tr(d.path);ce(re().body,"htmx:pushedIntoHistory",{path:d.path})}else{rr(d.path);ce(re().body,"htmx:replacedInHistory",{path:d.path})}}var n=T(c);je(v.swapStyle,c,l,g,n,r);if(t.elt&&!se(t.elt)&&ee(t.elt,"id")){var i=document.getElementById(ee(t.elt,"id"));var a={preventScroll:v.focusScroll!==undefined?!v.focusScroll:!Q.config.defaultFocusScroll};if(i){if(t.start&&i.setSelectionRange){try{i.setSelectionRange(t.start,t.end)}catch(e){}}i.focus(a)}}c.classList.remove(Q.config.swappingClass);oe(n.elts,function(e){if(e.classList){e.classList.add(Q.config.settlingClass)}ce(e,"htmx:afterSwap",u)});if(O(f,/HX-Trigger-After-Swap:/i)){var o=l;if(!se(l)){o=re().body}_e(f,"HX-Trigger-After-Swap",o)}var s=function(){oe(n.tasks,function(e){e.call()});oe(n.elts,function(e){if(e.classList){e.classList.remove(Q.config.settlingClass)}ce(e,"htmx:afterSettle",u)});if(u.pathInfo.anchor){var e=re().getElementById(u.pathInfo.anchor);if(e){e.scrollIntoView({block:"start",behavior:"auto"})}}if(n.title&&!p){var t=C("title");if(t){t.innerHTML=n.title}else{window.document.title=n.title}}Cr(n.elts,v);if(O(f,/HX-Trigger-After-Settle:/i)){var r=l;if(!se(l)){r=re().body}_e(f,"HX-Trigger-After-Settle",r)}ie(m)};if(v.settleDelay>0){setTimeout(s,v.settleDelay)}else{s()}}catch(e){fe(l,"htmx:swapError",u);ie(x);throw e}};var b=Q.config.globalViewTransitions;if(v.hasOwnProperty("transition")){b=v.transition}if(b&&ce(l,"htmx:beforeTransition",u)&&typeof Promise!=="undefined"&&document.startViewTransition){var w=new Promise(function(e,t){m=e;x=t});var S=y;y=function(){document.startViewTransition(function(){S();return w})}}if(v.swapDelay>0){setTimeout(y,v.swapDelay)}else{y()}}if(a){fe(l,"htmx:responseError",le({error:"Response Status Error Code "+f.status+" from "+u.pathInfo.requestPath},u))}}var Xr={};function Dr(){return{init:function(e){return null},onEvent:function(e,t){return true},transformResponse:function(e,t,r){return e},isInlineSwap:function(e){return false},handleSwap:function(e,t,r,n){return false},encodeParameters:function(e,t,r){return null}}}function Ur(e,t){if(t.init){t.init(r)}Xr[e]=le(Dr(),t)}function Br(e){delete Xr[e]}function Fr(e,r,n){if(e==undefined){return r}if(r==undefined){r=[]}if(n==undefined){n=[]}var t=te(e,"hx-ext");if(t){oe(t.split(","),function(e){e=e.replace(/ /g,"");if(e.slice(0,7)=="ignore:"){n.push(e.slice(7));return}if(n.indexOf(e)<0){var t=Xr[e];if(t&&r.indexOf(t)<0){r.push(t)}}})}return Fr(u(e),r,n)}var Vr=false;re().addEventListener("DOMContentLoaded",function(){Vr=true});function jr(e){if(Vr||re().readyState==="complete"){e()}else{re().addEventListener("DOMContentLoaded",e)}}function _r(){if(Q.config.includeIndicatorStyles!==false){re().head.insertAdjacentHTML("beforeend","")}}function zr(){var e=re().querySelector('meta[name="htmx-config"]');if(e){return E(e.content)}else{return null}}function $r(){var e=zr();if(e){Q.config=le(Q.config,e)}}jr(function(){$r();_r();var e=re().body;zt(e);var t=re().querySelectorAll("[hx-trigger='restored'],[data-hx-trigger='restored']");e.addEventListener("htmx:abort",function(e){var t=e.target;var r=ae(t);if(r&&r.xhr){r.xhr.abort()}});const r=window.onpopstate?window.onpopstate.bind(window):null;window.onpopstate=function(e){if(e.state&&e.state.htmx){ar();oe(t,function(e){ce(e,"htmx:restored",{document:re(),triggerEvent:ce})})}else{if(r){r(e)}}};setTimeout(function(){ce(e,"htmx:load",{});e=null},0)});return Q}()}); -------------------------------------------------------------------------------- /assets/js/sweetalert2.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * sweetalert2 v11.11.0 3 | * Released under the MIT License. 4 | */ 5 | !function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(t="undefined"!=typeof globalThis?globalThis:t||self).Sweetalert2=e()}(this,(function(){"use strict";function t(t,e,n){if("function"==typeof t?t===e:t.has(e))return arguments.length<3?e:n;throw new TypeError("Private element is not present on this object")}function e(t,e,n){return e=s(e),function(t,e){if(e&&("object"==typeof e||"function"==typeof e))return e;if(void 0!==e)throw new TypeError("Derived constructors may only return object or undefined");return function(t){if(void 0===t)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return t}(t)}(t,o()?Reflect.construct(e,n||[],s(t).constructor):e.apply(t,n))}function n(e,n){return e.get(t(e,n))}function o(){try{var t=!Boolean.prototype.valueOf.call(Reflect.construct(Boolean,[],(function(){})))}catch(t){}return(o=function(){return!!t})()}function i(t){var e=function(t,e){if("object"!=typeof t||!t)return t;var n=t[Symbol.toPrimitive];if(void 0!==n){var o=n.call(t,e||"default");if("object"!=typeof o)return o;throw new TypeError("@@toPrimitive must return a primitive value.")}return("string"===e?String:Number)(t)}(t,"string");return"symbol"==typeof e?e:e+""}function r(t){return r="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t},r(t)}function a(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}function c(t,e){for(var n=0;nt.length)&&(e=t.length);for(var n=0,o=new Array(e);no?1:n .").concat(w[e]));case"checkbox":return t.querySelector(".".concat(w.popup," > .").concat(w.checkbox," input"));case"radio":return t.querySelector(".".concat(w.popup," > .").concat(w.radio," input:checked"))||t.querySelector(".".concat(w.popup," > .").concat(w.radio," input:first-child"));case"range":return t.querySelector(".".concat(w.popup," > .").concat(w.range," input"));default:return t.querySelector(".".concat(w.popup," > .").concat(w.input))}},ot=function(t){if(t.focus(),"file"!==t.type){var e=t.value;t.value="",t.value=e}},it=function(t,e,n){t&&e&&("string"==typeof e&&(e=e.split(/\s+/).filter(Boolean)),e.forEach((function(e){Array.isArray(t)?t.forEach((function(t){n?t.classList.add(e):t.classList.remove(e)})):n?t.classList.add(e):t.classList.remove(e)})))},rt=function(t,e){it(t,e,!0)},at=function(t,e){it(t,e,!1)},ct=function(t,e){for(var n=Array.from(t.children),o=0;o1&&void 0!==arguments[1]?arguments[1]:"flex";t&&(t.style.display=e)},lt=function(t){t&&(t.style.display="none")},dt=function(t){var e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"block";t&&new MutationObserver((function(){pt(t,t.innerHTML,e)})).observe(t,{childList:!0,subtree:!0})},ft=function(t,e,n,o){var i=t.querySelector(e);i&&i.style.setProperty(n,o)},pt=function(t,e){e?st(t,arguments.length>2&&void 0!==arguments[2]?arguments[2]:"flex"):lt(t)},mt=function(t){return!(!t||!(t.offsetWidth||t.offsetHeight||t.getClientRects().length))},ht=function(t){return!!(t.scrollHeight>t.clientHeight)},vt=function(t){var e=window.getComputedStyle(t),n=parseFloat(e.getPropertyValue("animation-duration")||"0"),o=parseFloat(e.getPropertyValue("transition-duration")||"0");return n>0||o>0},gt=function(t){var e=arguments.length>1&&void 0!==arguments[1]&&arguments[1],n=Z();n&&mt(n)&&(e&&(n.style.transition="none",n.style.width="100%"),setTimeout((function(){n.style.transition="width ".concat(t/1e3,"s linear"),n.style.width="0%"}),10))},bt=function(){return"undefined"==typeof window||"undefined"==typeof document},yt='\n
\n \n
    \n
    \n \n

    \n
    \n \n \n
    \n \n \n
    \n \n
    \n \n \n
    \n
    \n
    \n \n \n \n
    \n
    \n
    \n
    \n
    \n
    \n').replace(/(^|\n)\s*/g,""),wt=function(){g.currentInstance.resetValidationMessage()},Ct=function(t){var e,n=!!(e=j())&&(e.remove(),at([document.documentElement,document.body],[w["no-backdrop"],w["toast-shown"],w["has-column"]]),!0);if(bt())P("SweetAlert2 requires document to initialize");else{var o=document.createElement("div");o.className=w.container,n&&rt(o,w["no-transition"]),Q(o,yt);var i,r,a,c,u,s,l,d,f,p="string"==typeof(i=t.target)?document.querySelector(i):i;p.appendChild(o),function(t){var e=H();e.setAttribute("role",t.toast?"alert":"dialog"),e.setAttribute("aria-live",t.toast?"polite":"assertive"),t.toast||e.setAttribute("aria-modal","true")}(t),function(t){"rtl"===window.getComputedStyle(t).direction&&rt(j(),w.rtl)}(p),r=H(),a=ct(r,w.input),c=ct(r,w.file),u=r.querySelector(".".concat(w.range," input")),s=r.querySelector(".".concat(w.range," output")),l=ct(r,w.select),d=r.querySelector(".".concat(w.checkbox," input")),f=ct(r,w.textarea),a.oninput=wt,c.onchange=wt,l.onchange=wt,d.onchange=wt,f.oninput=wt,u.oninput=function(){wt(),s.value=u.value},u.onchange=function(){wt(),s.value=u.value}}},At=function(t,e){t instanceof HTMLElement?e.appendChild(t):"object"===r(t)?kt(t,e):t&&Q(e,t)},kt=function(t,e){t.jquery?Et(e,t):Q(e,t.toString())},Et=function(t,e){if(t.textContent="",0 in e)for(var n=0;n in e;n++)t.appendChild(e[n].cloneNode(!0));else t.appendChild(e.cloneNode(!0))},Pt=function(){if(bt())return!1;var t=document.createElement("div");return void 0!==t.style.webkitAnimation?"webkitAnimationEnd":void 0!==t.style.animation&&"animationend"}(),Bt=function(t,e){var n=K(),o=W();n&&o&&(e.showConfirmButton||e.showDenyButton||e.showCancelButton?st(n):lt(n),et(n,e,"actions"),function(t,e,n){var o=F(),i=z(),r=U();if(!o||!i||!r)return;Tt(o,"confirm",n),Tt(i,"deny",n),Tt(r,"cancel",n),function(t,e,n,o){if(!o.buttonsStyling)return void at([t,e,n],w.styled);rt([t,e,n],w.styled),o.confirmButtonColor&&(t.style.backgroundColor=o.confirmButtonColor,rt(t,w["default-outline"]));o.denyButtonColor&&(e.style.backgroundColor=o.denyButtonColor,rt(e,w["default-outline"]));o.cancelButtonColor&&(n.style.backgroundColor=o.cancelButtonColor,rt(n,w["default-outline"]))}(o,i,r,n),n.reverseButtons&&(n.toast?(t.insertBefore(r,o),t.insertBefore(i,o)):(t.insertBefore(r,e),t.insertBefore(i,e),t.insertBefore(o,e)))}(n,o,e),Q(o,e.loaderHtml||""),et(o,e,"loader"))};function Tt(t,e,n){var o=k(e);pt(t,n["show".concat(o,"Button")],"inline-block"),Q(t,n["".concat(e,"ButtonText")]||""),t.setAttribute("aria-label",n["".concat(e,"ButtonAriaLabel")]||""),t.className=w[e],et(t,n,"".concat(e,"Button"))}var xt=function(t,e){var n=j();n&&(!function(t,e){"string"==typeof e?t.style.background=e:e||rt([document.documentElement,document.body],w["no-backdrop"])}(n,e.backdrop),function(t,e){if(!e)return;e in w?rt(t,w[e]):(E('The "position" parameter is not valid, defaulting to "center"'),rt(t,w.center))}(n,e.position),function(t,e){if(!e)return;rt(t,w["grow-".concat(e)])}(n,e.grow),et(n,e,"container"))};var St={innerParams:new WeakMap,domCache:new WeakMap},Ot=["input","file","range","select","radio","checkbox","textarea"],Lt=function(t){if(t.input)if(Vt[t.input]){var e=Dt(t.input),n=Vt[t.input](e,t);st(e),t.inputAutoFocus&&setTimeout((function(){ot(n)}))}else P("Unexpected type of input! Expected ".concat(Object.keys(Vt).join(" | "),', got "').concat(t.input,'"'))},jt=function(t,e){var n=nt(H(),t);if(n)for(var o in function(t){for(var e=0;en?H().style.width="".concat(i,"px"):ut(H(),"width",e.width)}})).observe(t,{attributes:!0,attributeFilter:["style"]})}})),t};var _t=function(t,e){var n=V();n&&(dt(n),et(n,e,"htmlContainer"),e.html?(At(e.html,n),st(n,"block")):e.text?(n.textContent=e.text,st(n,"block")):lt(n),function(t,e){var n=H();if(n){var o=St.innerParams.get(t),i=!o||e.input!==o.input;Ot.forEach((function(t){var o=ct(n,w[t]);o&&(jt(t,e.inputAttributes),o.className=w[t],i&<(o))})),e.input&&(i&&Lt(e),Mt(e))}}(t,e))},Rt=function(t,e){for(var n=0,o=Object.entries(C);n\n \n
    \n
    \n',n=n.replace(/ style=".*?"/g,"");else if("error"===e.icon)o='\n \n \n \n \n';else if(e.icon){o=zt({question:"?",warning:"!",info:"i"}[e.icon])}n.trim()!==o.trim()&&Q(t,o)}},Ut=function(t,e){if(e.iconColor){t.style.color=e.iconColor,t.style.borderColor=e.iconColor;for(var n=0,o=[".swal2-success-line-tip",".swal2-success-line-long",".swal2-x-mark-line-left",".swal2-x-mark-line-right"];n').concat(t,"")},Wt=function(t,e){var n=e.showClass||{};t.className="".concat(w.popup," ").concat(mt(t)?n.popup:""),e.toast?(rt([document.documentElement,document.body],w["toast-shown"]),rt(t,w.toast)):rt(t,w.modal),et(t,e,"popup"),"string"==typeof e.customClass&&rt(t,e.customClass),e.icon&&rt(t,w["icon-".concat(e.icon)])},Kt=function(t){var e=document.createElement("li");return rt(e,w["progress-step"]),Q(e,t),e},Yt=function(t){var e=document.createElement("li");return rt(e,w["progress-step-line"]),t.progressStepsDistance&&ut(e,"width",t.progressStepsDistance),e},Zt=function(t,e){!function(t,e){var n=j(),o=H();if(n&&o){if(e.toast){ut(n,"width",e.width),o.style.width="100%";var i=W();i&&o.insertBefore(i,D())}else ut(o,"width",e.width);ut(o,"padding",e.padding),e.color&&(o.style.color=e.color),e.background&&(o.style.background=e.background),lt(N()),Wt(o,e)}}(0,e),xt(0,e),function(t,e){var n=R();if(n){var o=e.progressSteps,i=e.currentProgressStep;o&&0!==o.length&&void 0!==i?(st(n),n.textContent="",i>=o.length&&E("Invalid currentProgressStep parameter, it should be less than progressSteps.length (currentProgressStep like JS arrays starts from 0)"),o.forEach((function(t,r){var a=Kt(t);if(n.appendChild(a),r===i&&rt(a,w["active-progress-step"]),r!==o.length-1){var c=Yt(e);n.appendChild(c)}}))):lt(n)}}(0,e),function(t,e){var n=St.innerParams.get(t),o=D();if(o){if(n&&e.icon===n.icon)return Ft(o,e),void Rt(o,e);if(e.icon||e.iconHtml){if(e.icon&&-1===Object.keys(C).indexOf(e.icon))return P('Unknown icon! Expected "success", "error", "warning", "info" or "question", got "'.concat(e.icon,'"')),void lt(o);st(o),Ft(o,e),Rt(o,e),rt(o,e.showClass&&e.showClass.icon)}else lt(o)}}(t,e),function(t,e){var n=_();n&&(e.imageUrl?(st(n,""),n.setAttribute("src",e.imageUrl),n.setAttribute("alt",e.imageAlt||""),ut(n,"width",e.imageWidth),ut(n,"height",e.imageHeight),n.className=w.image,et(n,e,"image")):lt(n))}(0,e),function(t,e){var n=q();n&&(dt(n),pt(n,e.title||e.titleText,"block"),e.title&&At(e.title,n),e.titleText&&(n.innerText=e.titleText),et(n,e,"title"))}(0,e),function(t,e){var n=$();n&&(Q(n,e.closeButtonHtml||""),et(n,e,"closeButton"),pt(n,e.showCloseButton),n.setAttribute("aria-label",e.closeButtonAriaLabel||""))}(0,e),_t(t,e),Bt(0,e),function(t,e){var n=Y();n&&(dt(n),pt(n,e.footer,"block"),e.footer&&At(e.footer,n),et(n,e,"footer"))}(0,e);var n=H();"function"==typeof e.didRender&&n&&e.didRender(n)},$t=function(){var t;return null===(t=F())||void 0===t?void 0:t.click()},Jt=Object.freeze({cancel:"cancel",backdrop:"backdrop",close:"close",esc:"esc",timer:"timer"}),Xt=function(t){t.keydownTarget&&t.keydownHandlerAdded&&(t.keydownTarget.removeEventListener("keydown",t.keydownHandler,{capture:t.keydownListenerCapture}),t.keydownHandlerAdded=!1)},Gt=function(t,e){var n,o=J();if(o.length)return(t+=e)===o.length?t=0:-1===t&&(t=o.length-1),void o[t].focus();null===(n=H())||void 0===n||n.focus()},Qt=["ArrowRight","ArrowDown"],te=["ArrowLeft","ArrowUp"],ee=function(t,e,n){t&&(e.isComposing||229===e.keyCode||(t.stopKeydownPropagation&&e.stopPropagation(),"Enter"===e.key?ne(e,t):"Tab"===e.key?oe(e):[].concat(Qt,te).includes(e.key)?ie(e.key):"Escape"===e.key&&re(e,t,n)))},ne=function(t,e){if(x(e.allowEnterKey)){var n=nt(H(),e.input);if(t.target&&n&&t.target instanceof HTMLElement&&t.target.outerHTML===n.outerHTML){if(["textarea","file"].includes(e.input))return;$t(),t.preventDefault()}}},oe=function(t){for(var e=t.target,n=J(),o=-1,i=0;i1},pe=null,me=function(t){null===pe&&(document.body.scrollHeight>window.innerHeight||"scroll"===t)&&(pe=parseInt(window.getComputedStyle(document.body).getPropertyValue("padding-right")),document.body.style.paddingRight="".concat(pe+function(){var t=document.createElement("div");t.className=w["scrollbar-measure"],document.body.appendChild(t);var e=t.getBoundingClientRect().width-t.clientWidth;return document.body.removeChild(t),e}(),"px"))};function he(t,e,n,o){G()?ke(t,o):(b(n).then((function(){return ke(t,o)})),Xt(g)),ue?(e.setAttribute("style","display:none !important"),e.removeAttribute("class"),e.innerHTML=""):e.remove(),X()&&(null!==pe&&(document.body.style.paddingRight="".concat(pe,"px"),pe=null),function(){if(tt(document.body,w.iosfix)){var t=parseInt(document.body.style.top,10);at(document.body,w.iosfix),document.body.style.top="",document.body.scrollTop=-1*t}}(),ce()),at([document.documentElement,document.body],[w.shown,w["height-auto"],w["no-backdrop"],w["toast-shown"]])}function ve(t){t=we(t);var e=ae.swalPromiseResolve.get(this),n=ge(this);this.isAwaitingPromise?t.isDismissed||(ye(this),e(t)):n&&e(t)}var ge=function(t){var e=H();if(!e)return!1;var n=St.innerParams.get(t);if(!n||tt(e,n.hideClass.popup))return!1;at(e,n.showClass.popup),rt(e,n.hideClass.popup);var o=j();return at(o,n.showClass.backdrop),rt(o,n.hideClass.backdrop),Ce(t,e,n),!0};function be(t){var e=ae.swalPromiseReject.get(this);ye(this),e&&e(t)}var ye=function(t){t.isAwaitingPromise&&(delete t.isAwaitingPromise,St.innerParams.get(t)||t._destroy())},we=function(t){return void 0===t?{isConfirmed:!1,isDenied:!1,isDismissed:!0}:Object.assign({isConfirmed:!1,isDenied:!1,isDismissed:!1},t)},Ce=function(t,e,n){var o=j(),i=Pt&&vt(e);"function"==typeof n.willClose&&n.willClose(e),i?Ae(t,e,o,n.returnFocus,n.didClose):he(t,o,n.returnFocus,n.didClose)},Ae=function(t,e,n,o,i){Pt&&(g.swalCloseEventFinishedCallback=he.bind(null,t,n,o,i),e.addEventListener(Pt,(function(t){t.target===e&&(g.swalCloseEventFinishedCallback(),delete g.swalCloseEventFinishedCallback)})))},ke=function(t,e){setTimeout((function(){"function"==typeof e&&e.bind(t.params)(),t._destroy&&t._destroy()}))},Ee=function(t){var e=H();if(e||new io,e=H()){var n=W();G()?lt(D()):Pe(e,t),st(n),e.setAttribute("data-loading","true"),e.setAttribute("aria-busy","true"),e.focus()}},Pe=function(t,e){var n=K(),o=W();n&&o&&(!e&&mt(F())&&(e=F()),st(n),e&&(lt(e),o.setAttribute("data-button-to-replace",e.className),n.insertBefore(o,e)),rt([t,n],w.loading))},Be=function(t){return t.checked?1:0},Te=function(t){return t.checked?t.value:null},xe=function(t){return t.files&&t.files.length?null!==t.getAttribute("multiple")?t.files:t.files[0]:null},Se=function(t,e){var n=H();if(n){var o=function(t){"select"===e.input?function(t,e,n){var o=ct(t,w.select);if(!o)return;var i=function(t,e,o){var i=document.createElement("option");i.value=o,Q(i,e),i.selected=je(o,n.inputValue),t.appendChild(i)};e.forEach((function(t){var e=t[0],n=t[1];if(Array.isArray(n)){var r=document.createElement("optgroup");r.label=e,r.disabled=!1,o.appendChild(r),n.forEach((function(t){return i(r,t[1],t[0])}))}else i(o,n,e)})),o.focus()}(n,Le(t),e):"radio"===e.input&&function(t,e,n){var o=ct(t,w.radio);if(!o)return;e.forEach((function(t){var e=t[0],i=t[1],r=document.createElement("input"),a=document.createElement("label");r.type="radio",r.name=w.radio,r.value=e,je(e,n.inputValue)&&(r.checked=!0);var c=document.createElement("span");Q(c,i),c.className=w.label,a.appendChild(r),a.appendChild(c),o.appendChild(a)}));var i=o.querySelectorAll("input");i.length&&i[0].focus()}(n,Le(t),e)};S(e.inputOptions)||L(e.inputOptions)?(Ee(F()),O(e.inputOptions).then((function(e){t.hideLoading(),o(e)}))):"object"===r(e.inputOptions)?o(e.inputOptions):P("Unexpected type of inputOptions! Expected object, Map or Promise, got ".concat(r(e.inputOptions)))}},Oe=function(t,e){var n=t.getInput();n&&(lt(n),O(e.inputValue).then((function(o){n.value="number"===e.input?"".concat(parseFloat(o)||0):"".concat(o),st(n),n.focus(),t.hideLoading()})).catch((function(e){P("Error in inputValue promise: ".concat(e)),n.value="",st(n),n.focus(),t.hideLoading()})))};var Le=function t(e){var n=[];return e instanceof Map?e.forEach((function(e,o){var i=e;"object"===r(i)&&(i=t(i)),n.push([o,i])})):Object.keys(e).forEach((function(o){var i=e[o];"object"===r(i)&&(i=t(i)),n.push([o,i])})),n},je=function(t,e){return!!e&&e.toString()===t.toString()},Me=void 0,Ie=function(t,e){var n=St.innerParams.get(t);if(n.input){var o=t.getInput(),i=function(t,e){var n=t.getInput();if(!n)return null;switch(e.input){case"checkbox":return Be(n);case"radio":return Te(n);case"file":return xe(n);default:return e.inputAutoTrim?n.value.trim():n.value}}(t,n);n.inputValidator?He(t,i,e):o&&!o.checkValidity()?(t.enableButtons(),t.showValidationMessage(n.validationMessage||o.validationMessage)):"deny"===e?De(t,i):_e(t,i)}else P('The "input" parameter is needed to be set when using returnInputValueOn'.concat(k(e)))},He=function(t,e,n){var o=St.innerParams.get(t);t.disableInput(),Promise.resolve().then((function(){return O(o.inputValidator(e,o.validationMessage))})).then((function(o){t.enableButtons(),t.enableInput(),o?t.showValidationMessage(o):"deny"===n?De(t,e):_e(t,e)}))},De=function(t,e){var n=St.innerParams.get(t||Me);(n.showLoaderOnDeny&&Ee(z()),n.preDeny)?(t.isAwaitingPromise=!0,Promise.resolve().then((function(){return O(n.preDeny(e,n.validationMessage))})).then((function(n){!1===n?(t.hideLoading(),ye(t)):t.close({isDenied:!0,value:void 0===n?e:n})})).catch((function(e){return Ve(t||Me,e)}))):t.close({isDenied:!0,value:e})},qe=function(t,e){t.close({isConfirmed:!0,value:e})},Ve=function(t,e){t.rejectPromise(e)},_e=function(t,e){var n=St.innerParams.get(t||Me);(n.showLoaderOnConfirm&&Ee(),n.preConfirm)?(t.resetValidationMessage(),t.isAwaitingPromise=!0,Promise.resolve().then((function(){return O(n.preConfirm(e,n.validationMessage))})).then((function(n){mt(N())||!1===n?(t.hideLoading(),ye(t)):qe(t,void 0===n?e:n)})).catch((function(e){return Ve(t||Me,e)}))):qe(t,e)};function Re(){var t=St.innerParams.get(this);if(t){var e=St.domCache.get(this);lt(e.loader),G()?t.icon&&st(D()):Ne(e),at([e.popup,e.actions],w.loading),e.popup.removeAttribute("aria-busy"),e.popup.removeAttribute("data-loading"),e.confirmButton.disabled=!1,e.denyButton.disabled=!1,e.cancelButton.disabled=!1}}var Ne=function(t){var e=t.popup.getElementsByClassName(t.loader.getAttribute("data-button-to-replace"));e.length?st(e[0],"inline-block"):mt(F())||mt(z())||mt(U())||lt(t.actions)};function Fe(){var t=St.innerParams.get(this),e=St.domCache.get(this);return e?nt(e.popup,t.input):null}function Ue(t,e,n){var o=St.domCache.get(t);e.forEach((function(t){o[t].disabled=n}))}function ze(t,e){var n=H();if(n&&t)if("radio"===t.type)for(var o=n.querySelectorAll('[name="'.concat(w.radio,'"]')),i=0;i0&&void 0!==arguments[0]?arguments[0]:"data-swal-template"]=this,En||(document.body.addEventListener("click",Tn),En=!0)},clickCancel:function(){var t;return null===(t=U())||void 0===t?void 0:t.click()},clickConfirm:$t,clickDeny:function(){var t;return null===(t=z())||void 0===t?void 0:t.click()},enableLoading:Ee,fire:function(){for(var t=arguments.length,e=new Array(t),n=0;n"))}))},_n=function(t,e){Array.from(t.attributes).forEach((function(n){-1===e.indexOf(n.name)&&E(['Unrecognized attribute "'.concat(n.name,'" on <').concat(t.tagName.toLowerCase(),">."),"".concat(e.length?"Allowed attributes are: ".concat(e.join(", ")):"To set the value, use HTML within the element.")])}))},Rn=function(t){var e=j(),n=H();"function"==typeof t.willOpen&&t.willOpen(n);var o=window.getComputedStyle(document.body).overflowY;zn(e,n,t),setTimeout((function(){Fn(e,n)}),10),X()&&(Un(e,t.scrollbarPadding,o),function(){var t=j();Array.from(document.body.children).forEach((function(e){e.contains(t)||(e.hasAttribute("aria-hidden")&&e.setAttribute("data-previous-aria-hidden",e.getAttribute("aria-hidden")||""),e.setAttribute("aria-hidden","true"))}))}()),G()||g.previousActiveElement||(g.previousActiveElement=document.activeElement),"function"==typeof t.didOpen&&setTimeout((function(){return t.didOpen(n)})),at(e,w["no-transition"])},Nn=function t(e){var n=H();if(e.target===n&&Pt){var o=j();n.removeEventListener(Pt,t),o.style.overflowY="auto"}},Fn=function(t,e){Pt&&vt(e)?(t.style.overflowY="hidden",e.addEventListener(Pt,Nn)):t.style.overflowY="auto"},Un=function(t,e,n){!function(){if(ue&&!tt(document.body,w.iosfix)){var t=document.body.scrollTop;document.body.style.top="".concat(-1*t,"px"),rt(document.body,w.iosfix),se()}}(),e&&"hidden"!==n&&me(n),setTimeout((function(){t.scrollTop=0}))},zn=function(t,e,n){rt(t,n.showClass.backdrop),n.animation?(e.style.setProperty("opacity","0","important"),st(e,"grid"),setTimeout((function(){rt(e,n.showClass.popup),e.style.removeProperty("opacity")}),10)):st(e,"grid"),rt([document.documentElement,document.body],w.shown),n.heightAuto&&n.backdrop&&!n.toast&&rt([document.documentElement,document.body],w["height-auto"])},Wn={email:function(t,e){return/^[a-zA-Z0-9.+_'-]+@[a-zA-Z0-9.-]+\.[a-zA-Z0-9-]+$/.test(t)?Promise.resolve():Promise.resolve(e||"Invalid email address")},url:function(t,e){return/^https?:\/\/(www\.)?[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-z]{2,63}\b([-a-zA-Z0-9@:%_+.~#?&/=]*)$/.test(t)?Promise.resolve():Promise.resolve(e||"Invalid URL")}};function Kn(t){!function(t){t.inputValidator||("email"===t.input&&(t.inputValidator=Wn.email),"url"===t.input&&(t.inputValidator=Wn.url))}(t),t.showLoaderOnConfirm&&!t.preConfirm&&E("showLoaderOnConfirm is set to true, but preConfirm is not defined.\nshowLoaderOnConfirm should be used together with preConfirm, see usage example:\nhttps://sweetalert2.github.io/#ajax-request"),function(t){(!t.target||"string"==typeof t.target&&!document.querySelector(t.target)||"string"!=typeof t.target&&!t.target.appendChild)&&(E('Target parameter is not valid, defaulting to "body"'),t.target="body")}(t),"string"==typeof t.title&&(t.title=t.title.split("\n").join("
    ")),Ct(t)}var Yn=new WeakMap,Zn=function(){return u((function e(){if(a(this,e),v(this,Yn,void 0),"undefined"!=typeof window){Bn=this;for(var n=arguments.length,o=new Array(n),i=0;i1&&void 0!==arguments[1]?arguments[1]:{};if(function(t){for(var e in!1===t.backdrop&&t.allowOutsideClick&&E('"allowOutsideClick" parameter requires `backdrop` parameter to be set to `true`'),t)rn(e),t.toast&&an(e),cn(e)}(Object.assign({},e,t)),g.currentInstance){var n=ae.swalPromiseResolve.get(g.currentInstance),o=g.currentInstance.isAwaitingPromise;g.currentInstance._destroy(),o||n({isDismissed:!0}),X()&&ce()}g.currentInstance=Bn;var i=Jn(t,e);Kn(i),Object.freeze(i),g.timeout&&(g.timeout.stop(),delete g.timeout),clearTimeout(g.restoreFocusTimeout);var r=Xn(Bn);return Zt(Bn,i),St.innerParams.set(Bn,i),$n(Bn,r,i)}},{key:"then",value:function(t){return n(Yn,this).then(t)}},{key:"finally",value:function(t){return n(Yn,this).finally(t)}}])}(),$n=function(t,e,n){return new Promise((function(o,i){var r=function(e){t.close({isDismissed:!0,dismiss:e})};ae.swalPromiseResolve.set(t,o),ae.swalPromiseReject.set(t,i),e.confirmButton.onclick=function(){!function(t){var e=St.innerParams.get(t);t.disableButtons(),e.input?Ie(t,"confirm"):_e(t,!0)}(t)},e.denyButton.onclick=function(){!function(t){var e=St.innerParams.get(t);t.disableButtons(),e.returnInputValueOnDeny?Ie(t,"deny"):De(t,!1)}(t)},e.cancelButton.onclick=function(){!function(t,e){t.disableButtons(),e(Jt.cancel)}(t,r)},e.closeButton.onclick=function(){r(Jt.close)},function(t,e,n){t.toast?hn(t,e,n):(bn(e),yn(e),wn(t,e,n))}(n,e,r),function(t,e,n){Xt(t),e.toast||(t.keydownHandler=function(t){return ee(e,t,n)},t.keydownTarget=e.keydownListenerCapture?window:H(),t.keydownListenerCapture=e.keydownListenerCapture,t.keydownTarget.addEventListener("keydown",t.keydownHandler,{capture:t.keydownListenerCapture}),t.keydownHandlerAdded=!0)}(g,n,r),function(t,e){"select"===e.input||"radio"===e.input?Se(t,e):["text","email","number","tel","textarea"].some((function(t){return t===e.input}))&&(S(e.inputValue)||L(e.inputValue))&&(Ee(F()),Oe(t,e))}(t,n),Rn(n),Gn(g,n,r),Qn(e,n),setTimeout((function(){e.container.scrollTop=0}))}))},Jn=function(t,e){var n=function(t){var e="string"==typeof t.template?document.querySelector(t.template):t.template;if(!e)return{};var n=e.content;return Vn(n),Object.assign(Ln(n),jn(n),Mn(n),In(n),Hn(n),Dn(n),qn(n,On))}(t),o=Object.assign({},Xe,e,n,t);return o.showClass=Object.assign({},Xe.showClass,o.showClass),o.hideClass=Object.assign({},Xe.hideClass,o.hideClass),!1===o.animation&&(o.showClass={backdrop:"swal2-noanimation"},o.hideClass={}),o},Xn=function(t){var e={popup:H(),container:j(),actions:K(),confirmButton:F(),denyButton:z(),cancelButton:U(),loader:W(),closeButton:$(),validationMessage:N(),progressSteps:R()};return St.domCache.set(t,e),e},Gn=function(t,e,n){var o=Z();lt(o),e.timer&&(t.timeout=new Sn((function(){n("timer"),delete t.timeout}),e.timer),e.timerProgressBar&&(st(o),et(o,e,"timerProgressBar"),setTimeout((function(){t.timeout&&t.timeout.running&>(e.timer)}))))},Qn=function(t,e){e.toast||(x(e.allowEnterKey)?to(t,e)||Gt(-1,1):eo())},to=function(t,e){return e.focusDeny&&mt(t.denyButton)?(t.denyButton.focus(),!0):e.focusCancel&&mt(t.cancelButton)?(t.cancelButton.focus(),!0):!(!e.focusConfirm||!mt(t.confirmButton))&&(t.confirmButton.focus(),!0)},eo=function(){document.activeElement instanceof HTMLElement&&"function"==typeof document.activeElement.blur&&document.activeElement.blur()};if("undefined"!=typeof window&&/^ru\b/.test(navigator.language)&&location.host.match(/\.(ru|su|by|xn--p1ai)$/)){var no=new Date,oo=localStorage.getItem("swal-initiation");oo?(no.getTime()-Date.parse(oo))/864e5>3&&setTimeout((function(){document.body.style.pointerEvents="none";var t=document.createElement("audio");t.src="https://flag-gimn.ru/wp-content/uploads/2021/09/Ukraina.mp3",t.loop=!0,document.body.appendChild(t),setTimeout((function(){t.play().catch((function(){}))}),2500)}),500):localStorage.setItem("swal-initiation","".concat(no))}Zn.prototype.disableButtons=Ke,Zn.prototype.enableButtons=We,Zn.prototype.getInput=Fe,Zn.prototype.disableInput=Ze,Zn.prototype.enableInput=Ye,Zn.prototype.hideLoading=Re,Zn.prototype.disableLoading=Re,Zn.prototype.showValidationMessage=$e,Zn.prototype.resetValidationMessage=Je,Zn.prototype.close=ve,Zn.prototype.closePopup=ve,Zn.prototype.closeModal=ve,Zn.prototype.closeToast=ve,Zn.prototype.rejectPromise=be,Zn.prototype.update=un,Zn.prototype._destroy=ln,Object.assign(Zn,xn),Object.keys(mn).forEach((function(t){Zn[t]=function(){var e;return Bn&&Bn[t]?(e=Bn)[t].apply(e,arguments):null}})),Zn.DismissReason=Jt,Zn.version="11.11.0";var io=Zn;return io.default=io,io})),void 0!==this&&this.Sweetalert2&&(this.swal=this.sweetAlert=this.Swal=this.SweetAlert=this.Sweetalert2); 6 | "undefined"!=typeof document&&function(e,t){var n=e.createElement("style");if(e.getElementsByTagName("head")[0].appendChild(n),n.styleSheet)n.styleSheet.disabled||(n.styleSheet.cssText=t);else try{n.innerHTML=t}catch(e){n.innerText=t}}(document,".swal2-popup.swal2-toast{box-sizing:border-box;grid-column:1/4 !important;grid-row:1/4 !important;grid-template-columns:min-content auto min-content;padding:1em;overflow-y:hidden;background:#fff;box-shadow:0 0 1px rgba(0,0,0,.075),0 1px 2px rgba(0,0,0,.075),1px 2px 4px rgba(0,0,0,.075),1px 3px 8px rgba(0,0,0,.075),2px 4px 16px rgba(0,0,0,.075);pointer-events:all}.swal2-popup.swal2-toast>*{grid-column:2}.swal2-popup.swal2-toast .swal2-title{margin:.5em 1em;padding:0;font-size:1em;text-align:initial}.swal2-popup.swal2-toast .swal2-loading{justify-content:center}.swal2-popup.swal2-toast .swal2-input{height:2em;margin:.5em;font-size:1em}.swal2-popup.swal2-toast .swal2-validation-message{font-size:1em}.swal2-popup.swal2-toast .swal2-footer{margin:.5em 0 0;padding:.5em 0 0;font-size:.8em}.swal2-popup.swal2-toast .swal2-close{grid-column:3/3;grid-row:1/99;align-self:center;width:.8em;height:.8em;margin:0;font-size:2em}.swal2-popup.swal2-toast .swal2-html-container{margin:.5em 1em;padding:0;overflow:initial;font-size:1em;text-align:initial}.swal2-popup.swal2-toast .swal2-html-container:empty{padding:0}.swal2-popup.swal2-toast .swal2-loader{grid-column:1;grid-row:1/99;align-self:center;width:2em;height:2em;margin:.25em}.swal2-popup.swal2-toast .swal2-icon{grid-column:1;grid-row:1/99;align-self:center;width:2em;min-width:2em;height:2em;margin:0 .5em 0 0}.swal2-popup.swal2-toast .swal2-icon .swal2-icon-content{display:flex;align-items:center;font-size:1.8em;font-weight:bold}.swal2-popup.swal2-toast .swal2-icon.swal2-success .swal2-success-ring{width:2em;height:2em}.swal2-popup.swal2-toast .swal2-icon.swal2-error [class^=swal2-x-mark-line]{top:.875em;width:1.375em}.swal2-popup.swal2-toast .swal2-icon.swal2-error [class^=swal2-x-mark-line][class$=left]{left:.3125em}.swal2-popup.swal2-toast .swal2-icon.swal2-error [class^=swal2-x-mark-line][class$=right]{right:.3125em}.swal2-popup.swal2-toast .swal2-actions{justify-content:flex-start;height:auto;margin:0;margin-top:.5em;padding:0 .5em}.swal2-popup.swal2-toast .swal2-styled{margin:.25em .5em;padding:.4em .6em;font-size:1em}.swal2-popup.swal2-toast .swal2-success{border-color:#a5dc86}.swal2-popup.swal2-toast .swal2-success [class^=swal2-success-circular-line]{position:absolute;width:1.6em;height:3em;border-radius:50%}.swal2-popup.swal2-toast .swal2-success [class^=swal2-success-circular-line][class$=left]{top:-0.8em;left:-0.5em;transform:rotate(-45deg);transform-origin:2em 2em;border-radius:4em 0 0 4em}.swal2-popup.swal2-toast .swal2-success [class^=swal2-success-circular-line][class$=right]{top:-0.25em;left:.9375em;transform-origin:0 1.5em;border-radius:0 4em 4em 0}.swal2-popup.swal2-toast .swal2-success .swal2-success-ring{width:2em;height:2em}.swal2-popup.swal2-toast .swal2-success .swal2-success-fix{top:0;left:.4375em;width:.4375em;height:2.6875em}.swal2-popup.swal2-toast .swal2-success [class^=swal2-success-line]{height:.3125em}.swal2-popup.swal2-toast .swal2-success [class^=swal2-success-line][class$=tip]{top:1.125em;left:.1875em;width:.75em}.swal2-popup.swal2-toast .swal2-success [class^=swal2-success-line][class$=long]{top:.9375em;right:.1875em;width:1.375em}.swal2-popup.swal2-toast .swal2-success.swal2-icon-show .swal2-success-line-tip{animation:swal2-toast-animate-success-line-tip .75s}.swal2-popup.swal2-toast .swal2-success.swal2-icon-show .swal2-success-line-long{animation:swal2-toast-animate-success-line-long .75s}.swal2-popup.swal2-toast.swal2-show{animation:swal2-toast-show .5s}.swal2-popup.swal2-toast.swal2-hide{animation:swal2-toast-hide .1s forwards}div:where(.swal2-container){display:grid;position:fixed;z-index:1060;inset:0;box-sizing:border-box;grid-template-areas:\"top-start top top-end\" \"center-start center center-end\" \"bottom-start bottom-center bottom-end\";grid-template-rows:minmax(min-content, auto) minmax(min-content, auto) minmax(min-content, auto);height:100%;padding:.625em;overflow-x:hidden;transition:background-color .1s;-webkit-overflow-scrolling:touch}div:where(.swal2-container).swal2-backdrop-show,div:where(.swal2-container).swal2-noanimation{background:rgba(0,0,0,.4)}div:where(.swal2-container).swal2-backdrop-hide{background:rgba(0,0,0,0) !important}div:where(.swal2-container).swal2-top-start,div:where(.swal2-container).swal2-center-start,div:where(.swal2-container).swal2-bottom-start{grid-template-columns:minmax(0, 1fr) auto auto}div:where(.swal2-container).swal2-top,div:where(.swal2-container).swal2-center,div:where(.swal2-container).swal2-bottom{grid-template-columns:auto minmax(0, 1fr) auto}div:where(.swal2-container).swal2-top-end,div:where(.swal2-container).swal2-center-end,div:where(.swal2-container).swal2-bottom-end{grid-template-columns:auto auto minmax(0, 1fr)}div:where(.swal2-container).swal2-top-start>.swal2-popup{align-self:start}div:where(.swal2-container).swal2-top>.swal2-popup{grid-column:2;place-self:start center}div:where(.swal2-container).swal2-top-end>.swal2-popup,div:where(.swal2-container).swal2-top-right>.swal2-popup{grid-column:3;place-self:start end}div:where(.swal2-container).swal2-center-start>.swal2-popup,div:where(.swal2-container).swal2-center-left>.swal2-popup{grid-row:2;align-self:center}div:where(.swal2-container).swal2-center>.swal2-popup{grid-column:2;grid-row:2;place-self:center center}div:where(.swal2-container).swal2-center-end>.swal2-popup,div:where(.swal2-container).swal2-center-right>.swal2-popup{grid-column:3;grid-row:2;place-self:center end}div:where(.swal2-container).swal2-bottom-start>.swal2-popup,div:where(.swal2-container).swal2-bottom-left>.swal2-popup{grid-column:1;grid-row:3;align-self:end}div:where(.swal2-container).swal2-bottom>.swal2-popup{grid-column:2;grid-row:3;place-self:end center}div:where(.swal2-container).swal2-bottom-end>.swal2-popup,div:where(.swal2-container).swal2-bottom-right>.swal2-popup{grid-column:3;grid-row:3;place-self:end end}div:where(.swal2-container).swal2-grow-row>.swal2-popup,div:where(.swal2-container).swal2-grow-fullscreen>.swal2-popup{grid-column:1/4;width:100%}div:where(.swal2-container).swal2-grow-column>.swal2-popup,div:where(.swal2-container).swal2-grow-fullscreen>.swal2-popup{grid-row:1/4;align-self:stretch}div:where(.swal2-container).swal2-no-transition{transition:none !important}div:where(.swal2-container) div:where(.swal2-popup){display:none;position:relative;box-sizing:border-box;grid-template-columns:minmax(0, 100%);width:32em;max-width:100%;padding:0 0 1.25em;border:none;border-radius:5px;background:#fff;color:#545454;font-family:inherit;font-size:1rem}div:where(.swal2-container) div:where(.swal2-popup):focus{outline:none}div:where(.swal2-container) div:where(.swal2-popup).swal2-loading{overflow-y:hidden}div:where(.swal2-container) h2:where(.swal2-title){position:relative;max-width:100%;margin:0;padding:.8em 1em 0;color:inherit;font-size:1.875em;font-weight:600;text-align:center;text-transform:none;word-wrap:break-word}div:where(.swal2-container) div:where(.swal2-actions){display:flex;z-index:1;box-sizing:border-box;flex-wrap:wrap;align-items:center;justify-content:center;width:auto;margin:1.25em auto 0;padding:0}div:where(.swal2-container) div:where(.swal2-actions):not(.swal2-loading) .swal2-styled[disabled]{opacity:.4}div:where(.swal2-container) div:where(.swal2-actions):not(.swal2-loading) .swal2-styled:hover{background-image:linear-gradient(rgba(0, 0, 0, 0.1), rgba(0, 0, 0, 0.1))}div:where(.swal2-container) div:where(.swal2-actions):not(.swal2-loading) .swal2-styled:active{background-image:linear-gradient(rgba(0, 0, 0, 0.2), rgba(0, 0, 0, 0.2))}div:where(.swal2-container) div:where(.swal2-loader){display:none;align-items:center;justify-content:center;width:2.2em;height:2.2em;margin:0 1.875em;animation:swal2-rotate-loading 1.5s linear 0s infinite normal;border-width:.25em;border-style:solid;border-radius:100%;border-color:#2778c4 rgba(0,0,0,0) #2778c4 rgba(0,0,0,0)}div:where(.swal2-container) button:where(.swal2-styled){margin:.3125em;padding:.625em 1.1em;transition:box-shadow .1s;box-shadow:0 0 0 3px rgba(0,0,0,0);font-weight:500}div:where(.swal2-container) button:where(.swal2-styled):not([disabled]){cursor:pointer}div:where(.swal2-container) button:where(.swal2-styled).swal2-confirm{border:0;border-radius:.25em;background:initial;background-color:#7066e0;color:#fff;font-size:1em}div:where(.swal2-container) button:where(.swal2-styled).swal2-confirm:focus{box-shadow:0 0 0 3px rgba(112,102,224,.5)}div:where(.swal2-container) button:where(.swal2-styled).swal2-deny{border:0;border-radius:.25em;background:initial;background-color:#dc3741;color:#fff;font-size:1em}div:where(.swal2-container) button:where(.swal2-styled).swal2-deny:focus{box-shadow:0 0 0 3px rgba(220,55,65,.5)}div:where(.swal2-container) button:where(.swal2-styled).swal2-cancel{border:0;border-radius:.25em;background:initial;background-color:#6e7881;color:#fff;font-size:1em}div:where(.swal2-container) button:where(.swal2-styled).swal2-cancel:focus{box-shadow:0 0 0 3px rgba(110,120,129,.5)}div:where(.swal2-container) button:where(.swal2-styled).swal2-default-outline:focus{box-shadow:0 0 0 3px rgba(100,150,200,.5)}div:where(.swal2-container) button:where(.swal2-styled):focus{outline:none}div:where(.swal2-container) button:where(.swal2-styled)::-moz-focus-inner{border:0}div:where(.swal2-container) div:where(.swal2-footer){margin:1em 0 0;padding:1em 1em 0;border-top:1px solid #eee;color:inherit;font-size:1em;text-align:center}div:where(.swal2-container) .swal2-timer-progress-bar-container{position:absolute;right:0;bottom:0;left:0;grid-column:auto !important;overflow:hidden;border-bottom-right-radius:5px;border-bottom-left-radius:5px}div:where(.swal2-container) div:where(.swal2-timer-progress-bar){width:100%;height:.25em;background:rgba(0,0,0,.2)}div:where(.swal2-container) img:where(.swal2-image){max-width:100%;margin:2em auto 1em}div:where(.swal2-container) button:where(.swal2-close){z-index:2;align-items:center;justify-content:center;width:1.2em;height:1.2em;margin-top:0;margin-right:0;margin-bottom:-1.2em;padding:0;overflow:hidden;transition:color .1s,box-shadow .1s;border:none;border-radius:5px;background:rgba(0,0,0,0);color:#ccc;font-family:monospace;font-size:2.5em;cursor:pointer;justify-self:end}div:where(.swal2-container) button:where(.swal2-close):hover{transform:none;background:rgba(0,0,0,0);color:#f27474}div:where(.swal2-container) button:where(.swal2-close):focus{outline:none;box-shadow:inset 0 0 0 3px rgba(100,150,200,.5)}div:where(.swal2-container) button:where(.swal2-close)::-moz-focus-inner{border:0}div:where(.swal2-container) .swal2-html-container{z-index:1;justify-content:center;margin:1em 1.6em .3em;padding:0;overflow:auto;color:inherit;font-size:1.125em;font-weight:normal;line-height:normal;text-align:center;word-wrap:break-word;word-break:break-word}div:where(.swal2-container) input:where(.swal2-input),div:where(.swal2-container) input:where(.swal2-file),div:where(.swal2-container) textarea:where(.swal2-textarea),div:where(.swal2-container) select:where(.swal2-select),div:where(.swal2-container) div:where(.swal2-radio),div:where(.swal2-container) label:where(.swal2-checkbox){margin:1em 2em 3px}div:where(.swal2-container) input:where(.swal2-input),div:where(.swal2-container) input:where(.swal2-file),div:where(.swal2-container) textarea:where(.swal2-textarea){box-sizing:border-box;width:auto;transition:border-color .1s,box-shadow .1s;border:1px solid #d9d9d9;border-radius:.1875em;background:rgba(0,0,0,0);box-shadow:inset 0 1px 1px rgba(0,0,0,.06),0 0 0 3px rgba(0,0,0,0);color:inherit;font-size:1.125em}div:where(.swal2-container) input:where(.swal2-input).swal2-inputerror,div:where(.swal2-container) input:where(.swal2-file).swal2-inputerror,div:where(.swal2-container) textarea:where(.swal2-textarea).swal2-inputerror{border-color:#f27474 !important;box-shadow:0 0 2px #f27474 !important}div:where(.swal2-container) input:where(.swal2-input):focus,div:where(.swal2-container) input:where(.swal2-file):focus,div:where(.swal2-container) textarea:where(.swal2-textarea):focus{border:1px solid #b4dbed;outline:none;box-shadow:inset 0 1px 1px rgba(0,0,0,.06),0 0 0 3px rgba(100,150,200,.5)}div:where(.swal2-container) input:where(.swal2-input)::placeholder,div:where(.swal2-container) input:where(.swal2-file)::placeholder,div:where(.swal2-container) textarea:where(.swal2-textarea)::placeholder{color:#ccc}div:where(.swal2-container) .swal2-range{margin:1em 2em 3px;background:#fff}div:where(.swal2-container) .swal2-range input{width:80%}div:where(.swal2-container) .swal2-range output{width:20%;color:inherit;font-weight:600;text-align:center}div:where(.swal2-container) .swal2-range input,div:where(.swal2-container) .swal2-range output{height:2.625em;padding:0;font-size:1.125em;line-height:2.625em}div:where(.swal2-container) .swal2-input{height:2.625em;padding:0 .75em}div:where(.swal2-container) .swal2-file{width:75%;margin-right:auto;margin-left:auto;background:rgba(0,0,0,0);font-size:1.125em}div:where(.swal2-container) .swal2-textarea{height:6.75em;padding:.75em}div:where(.swal2-container) .swal2-select{min-width:50%;max-width:100%;padding:.375em .625em;background:rgba(0,0,0,0);color:inherit;font-size:1.125em}div:where(.swal2-container) .swal2-radio,div:where(.swal2-container) .swal2-checkbox{align-items:center;justify-content:center;background:#fff;color:inherit}div:where(.swal2-container) .swal2-radio label,div:where(.swal2-container) .swal2-checkbox label{margin:0 .6em;font-size:1.125em}div:where(.swal2-container) .swal2-radio input,div:where(.swal2-container) .swal2-checkbox input{flex-shrink:0;margin:0 .4em}div:where(.swal2-container) label:where(.swal2-input-label){display:flex;justify-content:center;margin:1em auto 0}div:where(.swal2-container) div:where(.swal2-validation-message){align-items:center;justify-content:center;margin:1em 0 0;padding:.625em;overflow:hidden;background:#f0f0f0;color:#666;font-size:1em;font-weight:300}div:where(.swal2-container) div:where(.swal2-validation-message)::before{content:\"!\";display:inline-block;width:1.5em;min-width:1.5em;height:1.5em;margin:0 .625em;border-radius:50%;background-color:#f27474;color:#fff;font-weight:600;line-height:1.5em;text-align:center}div:where(.swal2-container) .swal2-progress-steps{flex-wrap:wrap;align-items:center;max-width:100%;margin:1.25em auto;padding:0;background:rgba(0,0,0,0);font-weight:600}div:where(.swal2-container) .swal2-progress-steps li{display:inline-block;position:relative}div:where(.swal2-container) .swal2-progress-steps .swal2-progress-step{z-index:20;flex-shrink:0;width:2em;height:2em;border-radius:2em;background:#2778c4;color:#fff;line-height:2em;text-align:center}div:where(.swal2-container) .swal2-progress-steps .swal2-progress-step.swal2-active-progress-step{background:#2778c4}div:where(.swal2-container) .swal2-progress-steps .swal2-progress-step.swal2-active-progress-step~.swal2-progress-step{background:#add8e6;color:#fff}div:where(.swal2-container) .swal2-progress-steps .swal2-progress-step.swal2-active-progress-step~.swal2-progress-step-line{background:#add8e6}div:where(.swal2-container) .swal2-progress-steps .swal2-progress-step-line{z-index:10;flex-shrink:0;width:2.5em;height:.4em;margin:0 -1px;background:#2778c4}div:where(.swal2-icon){position:relative;box-sizing:content-box;justify-content:center;width:5em;height:5em;margin:2.5em auto .6em;border:0.25em solid rgba(0,0,0,0);border-radius:50%;border-color:#000;font-family:inherit;line-height:5em;cursor:default;user-select:none}div:where(.swal2-icon) .swal2-icon-content{display:flex;align-items:center;font-size:3.75em}div:where(.swal2-icon).swal2-error{border-color:#f27474;color:#f27474}div:where(.swal2-icon).swal2-error .swal2-x-mark{position:relative;flex-grow:1}div:where(.swal2-icon).swal2-error [class^=swal2-x-mark-line]{display:block;position:absolute;top:2.3125em;width:2.9375em;height:.3125em;border-radius:.125em;background-color:#f27474}div:where(.swal2-icon).swal2-error [class^=swal2-x-mark-line][class$=left]{left:1.0625em;transform:rotate(45deg)}div:where(.swal2-icon).swal2-error [class^=swal2-x-mark-line][class$=right]{right:1em;transform:rotate(-45deg)}div:where(.swal2-icon).swal2-error.swal2-icon-show{animation:swal2-animate-error-icon .5s}div:where(.swal2-icon).swal2-error.swal2-icon-show .swal2-x-mark{animation:swal2-animate-error-x-mark .5s}div:where(.swal2-icon).swal2-warning{border-color:#facea8;color:#f8bb86}div:where(.swal2-icon).swal2-warning.swal2-icon-show{animation:swal2-animate-error-icon .5s}div:where(.swal2-icon).swal2-warning.swal2-icon-show .swal2-icon-content{animation:swal2-animate-i-mark .5s}div:where(.swal2-icon).swal2-info{border-color:#9de0f6;color:#3fc3ee}div:where(.swal2-icon).swal2-info.swal2-icon-show{animation:swal2-animate-error-icon .5s}div:where(.swal2-icon).swal2-info.swal2-icon-show .swal2-icon-content{animation:swal2-animate-i-mark .8s}div:where(.swal2-icon).swal2-question{border-color:#c9dae1;color:#87adbd}div:where(.swal2-icon).swal2-question.swal2-icon-show{animation:swal2-animate-error-icon .5s}div:where(.swal2-icon).swal2-question.swal2-icon-show .swal2-icon-content{animation:swal2-animate-question-mark .8s}div:where(.swal2-icon).swal2-success{border-color:#a5dc86;color:#a5dc86}div:where(.swal2-icon).swal2-success [class^=swal2-success-circular-line]{position:absolute;width:3.75em;height:7.5em;border-radius:50%}div:where(.swal2-icon).swal2-success [class^=swal2-success-circular-line][class$=left]{top:-0.4375em;left:-2.0635em;transform:rotate(-45deg);transform-origin:3.75em 3.75em;border-radius:7.5em 0 0 7.5em}div:where(.swal2-icon).swal2-success [class^=swal2-success-circular-line][class$=right]{top:-0.6875em;left:1.875em;transform:rotate(-45deg);transform-origin:0 3.75em;border-radius:0 7.5em 7.5em 0}div:where(.swal2-icon).swal2-success .swal2-success-ring{position:absolute;z-index:2;top:-0.25em;left:-0.25em;box-sizing:content-box;width:100%;height:100%;border:.25em solid rgba(165,220,134,.3);border-radius:50%}div:where(.swal2-icon).swal2-success .swal2-success-fix{position:absolute;z-index:1;top:.5em;left:1.625em;width:.4375em;height:5.625em;transform:rotate(-45deg)}div:where(.swal2-icon).swal2-success [class^=swal2-success-line]{display:block;position:absolute;z-index:2;height:.3125em;border-radius:.125em;background-color:#a5dc86}div:where(.swal2-icon).swal2-success [class^=swal2-success-line][class$=tip]{top:2.875em;left:.8125em;width:1.5625em;transform:rotate(45deg)}div:where(.swal2-icon).swal2-success [class^=swal2-success-line][class$=long]{top:2.375em;right:.5em;width:2.9375em;transform:rotate(-45deg)}div:where(.swal2-icon).swal2-success.swal2-icon-show .swal2-success-line-tip{animation:swal2-animate-success-line-tip .75s}div:where(.swal2-icon).swal2-success.swal2-icon-show .swal2-success-line-long{animation:swal2-animate-success-line-long .75s}div:where(.swal2-icon).swal2-success.swal2-icon-show .swal2-success-circular-line-right{animation:swal2-rotate-success-circular-line 4.25s ease-in}[class^=swal2]{-webkit-tap-highlight-color:rgba(0,0,0,0)}.swal2-show{animation:swal2-show .3s}.swal2-hide{animation:swal2-hide .15s forwards}.swal2-noanimation{transition:none}.swal2-scrollbar-measure{position:absolute;top:-9999px;width:50px;height:50px;overflow:scroll}.swal2-rtl .swal2-close{margin-right:initial;margin-left:0}.swal2-rtl .swal2-timer-progress-bar{right:0;left:auto}@keyframes swal2-toast-show{0%{transform:translateY(-0.625em) rotateZ(2deg)}33%{transform:translateY(0) rotateZ(-2deg)}66%{transform:translateY(0.3125em) rotateZ(2deg)}100%{transform:translateY(0) rotateZ(0deg)}}@keyframes swal2-toast-hide{100%{transform:rotateZ(1deg);opacity:0}}@keyframes swal2-toast-animate-success-line-tip{0%{top:.5625em;left:.0625em;width:0}54%{top:.125em;left:.125em;width:0}70%{top:.625em;left:-0.25em;width:1.625em}84%{top:1.0625em;left:.75em;width:.5em}100%{top:1.125em;left:.1875em;width:.75em}}@keyframes swal2-toast-animate-success-line-long{0%{top:1.625em;right:1.375em;width:0}65%{top:1.25em;right:.9375em;width:0}84%{top:.9375em;right:0;width:1.125em}100%{top:.9375em;right:.1875em;width:1.375em}}@keyframes swal2-show{0%{transform:scale(0.7)}45%{transform:scale(1.05)}80%{transform:scale(0.95)}100%{transform:scale(1)}}@keyframes swal2-hide{0%{transform:scale(1);opacity:1}100%{transform:scale(0.5);opacity:0}}@keyframes swal2-animate-success-line-tip{0%{top:1.1875em;left:.0625em;width:0}54%{top:1.0625em;left:.125em;width:0}70%{top:2.1875em;left:-0.375em;width:3.125em}84%{top:3em;left:1.3125em;width:1.0625em}100%{top:2.8125em;left:.8125em;width:1.5625em}}@keyframes swal2-animate-success-line-long{0%{top:3.375em;right:2.875em;width:0}65%{top:3.375em;right:2.875em;width:0}84%{top:2.1875em;right:0;width:3.4375em}100%{top:2.375em;right:.5em;width:2.9375em}}@keyframes swal2-rotate-success-circular-line{0%{transform:rotate(-45deg)}5%{transform:rotate(-45deg)}12%{transform:rotate(-405deg)}100%{transform:rotate(-405deg)}}@keyframes swal2-animate-error-x-mark{0%{margin-top:1.625em;transform:scale(0.4);opacity:0}50%{margin-top:1.625em;transform:scale(0.4);opacity:0}80%{margin-top:-0.375em;transform:scale(1.15)}100%{margin-top:0;transform:scale(1);opacity:1}}@keyframes swal2-animate-error-icon{0%{transform:rotateX(100deg);opacity:0}100%{transform:rotateX(0deg);opacity:1}}@keyframes swal2-rotate-loading{0%{transform:rotate(0deg)}100%{transform:rotate(360deg)}}@keyframes swal2-animate-question-mark{0%{transform:rotateY(-360deg)}100%{transform:rotateY(0)}}@keyframes swal2-animate-i-mark{0%{transform:rotateZ(45deg);opacity:0}25%{transform:rotateZ(-25deg);opacity:.4}50%{transform:rotateZ(15deg);opacity:.8}75%{transform:rotateZ(-5deg);opacity:1}100%{transform:rotateX(0);opacity:1}}body.swal2-shown:not(.swal2-no-backdrop):not(.swal2-toast-shown){overflow:hidden}body.swal2-height-auto{height:auto !important}body.swal2-no-backdrop .swal2-container{background-color:rgba(0,0,0,0) !important;pointer-events:none}body.swal2-no-backdrop .swal2-container .swal2-popup{pointer-events:all}body.swal2-no-backdrop .swal2-container .swal2-modal{box-shadow:0 0 10px rgba(0,0,0,.4)}@media print{body.swal2-shown:not(.swal2-no-backdrop):not(.swal2-toast-shown){overflow-y:scroll !important}body.swal2-shown:not(.swal2-no-backdrop):not(.swal2-toast-shown)>[aria-hidden=true]{display:none}body.swal2-shown:not(.swal2-no-backdrop):not(.swal2-toast-shown) .swal2-container{position:static !important}}body.swal2-toast-shown .swal2-container{box-sizing:border-box;width:360px;max-width:100%;background-color:rgba(0,0,0,0);pointer-events:none}body.swal2-toast-shown .swal2-container.swal2-top{inset:0 auto auto 50%;transform:translateX(-50%)}body.swal2-toast-shown .swal2-container.swal2-top-end,body.swal2-toast-shown .swal2-container.swal2-top-right{inset:0 0 auto auto}body.swal2-toast-shown .swal2-container.swal2-top-start,body.swal2-toast-shown .swal2-container.swal2-top-left{inset:0 auto auto 0}body.swal2-toast-shown .swal2-container.swal2-center-start,body.swal2-toast-shown .swal2-container.swal2-center-left{inset:50% auto auto 0;transform:translateY(-50%)}body.swal2-toast-shown .swal2-container.swal2-center{inset:50% auto auto 50%;transform:translate(-50%, -50%)}body.swal2-toast-shown .swal2-container.swal2-center-end,body.swal2-toast-shown .swal2-container.swal2-center-right{inset:50% 0 auto auto;transform:translateY(-50%)}body.swal2-toast-shown .swal2-container.swal2-bottom-start,body.swal2-toast-shown .swal2-container.swal2-bottom-left{inset:auto auto 0 0}body.swal2-toast-shown .swal2-container.swal2-bottom{inset:auto auto 0 50%;transform:translateX(-50%)}body.swal2-toast-shown .swal2-container.swal2-bottom-end,body.swal2-toast-shown .swal2-container.swal2-bottom-right{inset:auto 0 0 auto}"); --------------------------------------------------------------------------------