├── 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 |
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 |
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 |
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 |
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 |
10 |
11 |
17 |
18 | for _, msg := range errMsgs {
19 |
{ msg }
20 | }
21 |
22 | ×
23 |
24 |
25 | }
26 | if len(sucMsgs) != 0 {
27 |
31 |
32 |
38 |
39 | for _, msg := range sucMsgs {
40 |
{ msg }
41 | }
42 |
43 | ×
44 |
45 |
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 |
5 |
10 |
53 |
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 |
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 |
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 | Tasks
28 | Status
29 | Options
30 |
31 |
32 | if len(todos) != 0 {
33 |
34 | for _, todo := range todos {
35 |
36 | { strconv.Itoa(int(todo.ID)) }
37 | { todo.Title }
38 |
39 | if todo.Status {
40 | ✅
41 | } else {
42 | ❌
43 | }
44 |
45 |
46 |
51 | Edit
52 |
53 |
76 | Delete
77 |
78 |
79 |
80 | }
81 |
82 | } else {
83 |
84 |
85 |
86 | You do not have anything to do
87 |
88 |
89 |
90 | }
91 |
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 |
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 |  
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/"+n+" ",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("",1);case"col":return s("",2);case"tr":return s("",2);case"td":case"th":return s("",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').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}");
--------------------------------------------------------------------------------