diff options
author | Alexander NeonXP Kiryukhin <i@neonxp.ru> | 2024-07-21 19:26:56 +0300 |
---|---|---|
committer | Alexander NeonXP Kiryukhin <i@neonxp.ru> | 2024-07-21 19:28:56 +0300 |
commit | ce3111b0efe91e275ce070f9511b5b1b9801a46d (patch) | |
tree | 09fa4f10dfb1e17761339c798eefa73c6b18484f | |
parent | e9a64f3b41b5eae47dec7c0ecfd1caae83136abc (diff) |
Множество улучшенийv0.0.2
37 files changed, 1031 insertions, 386 deletions
diff --git a/.goreleaser.yaml b/.goreleaser.yaml index 0ec7c5e..525ddf8 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -12,6 +12,7 @@ before: hooks: - go mod tidy - go generate ./... + - templ generate builds: - env: diff --git a/app/cmd/migrate.go b/app/cmd/migrate.go index 3c4fd05..95a1dc3 100644 --- a/app/cmd/migrate.go +++ b/app/cmd/migrate.go @@ -17,35 +17,83 @@ var migrateCmd = &cobra.Command{ Long: `Up and down migrations`, } -var migrateUp = &cobra.Command{ - Use: "up", - Short: "Migrate up", - Long: `Up migrations`, - RunE: func(cmd *cobra.Command, args []string) error { - db, err := sql.Open("sqlite3", dbFile) - if err != nil { - return fmt.Errorf("open db failed: %w", err) - } - defer db.Close() - - driver, err := sqlite.WithInstance(db, &sqlite.Config{}) - if err != nil { - return fmt.Errorf("failed create migration driver: %w", err) - } - sourceDriver, err := iofs.New(migrations.FS, ".") - if err != nil { - return fmt.Errorf("failed open migrations: %w", err) - } - - m, err := migrate.NewWithInstance("fs", sourceDriver, "sqlite3", driver) - if err != nil { - return fmt.Errorf("open migration failed: %w", err) - } - - if err := m.Up(); err != nil && err != migrate.ErrNoChange { - return fmt.Errorf("do migration failed: %w", err) - } - - return nil - }, +func init() { + migrateCmd.AddCommand( + &cobra.Command{ + Use: "up", + Short: "Migrate up", + Long: `Up migrations`, + RunE: up, + }, + &cobra.Command{ + Use: "down", + Short: "Migrate down", + Long: `Down migrations`, + RunE: down, + }, + &cobra.Command{ + Use: "drop", + Short: "Drop db", + Long: `Deletes everything in the database`, + RunE: drop, + }, + ) +} + +func up(_ *cobra.Command, _ []string) error { + m, err := initMigrate() + if err != nil { + return fmt.Errorf("open migration failed: %w", err) + } + defer m.Close() + if err := m.Up(); err != nil && err != migrate.ErrNoChange { + return fmt.Errorf("do migration failed: %w", err) + } + + return nil +} + +func down(_ *cobra.Command, _ []string) error { + m, err := initMigrate() + if err != nil { + return fmt.Errorf("open migration failed: %w", err) + } + defer m.Close() + if err := m.Down(); err != nil && err != migrate.ErrNoChange { + return fmt.Errorf("do migration failed: %w", err) + } + + return nil +} + +func drop(_ *cobra.Command, _ []string) error { + m, err := initMigrate() + if err != nil { + return fmt.Errorf("open migration failed: %w", err) + } + defer m.Close() + if err := m.Drop(); err != nil && err != migrate.ErrNoChange { + return fmt.Errorf("do migration failed: %w", err) + } + + return nil +} + +func initMigrate() (*migrate.Migrate, error) { + db, err := sql.Open("sqlite3", dbFile) + if err != nil { + return nil, fmt.Errorf("open db failed: %w", err) + } + defer db.Close() + + driver, err := sqlite.WithInstance(db, &sqlite.Config{}) + if err != nil { + return nil, fmt.Errorf("failed create migration driver: %w", err) + } + sourceDriver, err := iofs.New(migrations.FS, ".") + if err != nil { + return nil, fmt.Errorf("failed open migrations: %w", err) + } + + return migrate.NewWithInstance("fs", sourceDriver, "sqlite3", driver) } diff --git a/app/cmd/root.go b/app/cmd/root.go index 33c3642..5803166 100644 --- a/app/cmd/root.go +++ b/app/cmd/root.go @@ -19,10 +19,14 @@ var ( func init() { cobra.OnInitialize(initLogger) cobra.OnInitialize(initConfig) - rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "gorum.yaml", "config file (default is 'gorum.yaml')") - rootCmd.PersistentFlags().StringVar(&dbFile, "db", "gorum.db", "database file (default is 'gorum.db')") + rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "gorum.yaml", "config file") + rootCmd.PersistentFlags().StringVar(&dbFile, "db", "gorum.db", "database file") rootCmd.PersistentFlags().BoolVar(&debug, "debug", false, "verbose debug output") viper.BindPFlag("db", serverCmd.Flags().Lookup("db")) + + rootCmd.AddCommand(serverCmd) + rootCmd.AddCommand(migrateCmd) + rootCmd.AddCommand(userCmd) } func initLogger() { @@ -43,8 +47,6 @@ func initConfig() { } func Execute() { - rootCmd.AddCommand(serverCmd) - if err := rootCmd.Execute(); err != nil { fmt.Println(err) os.Exit(1) diff --git a/app/cmd/serve.go b/app/cmd/serve.go index 5593c32..e2d2843 100644 --- a/app/cmd/serve.go +++ b/app/cmd/serve.go @@ -2,7 +2,6 @@ package cmd import ( "context" - "database/sql" "fmt" "log/slog" "net" @@ -15,10 +14,8 @@ import ( "github.com/michaeljs1990/sqlitestore" "github.com/spf13/cobra" "github.com/spf13/viper" - "github.com/uptrace/bun" - "github.com/uptrace/bun/dialect/sqlitedialect" - "github.com/uptrace/bun/extra/bundebug" "gitrepo.ru/neonxp/gorum/contextlib" + "gitrepo.ru/neonxp/gorum/db" "gitrepo.ru/neonxp/gorum/middleware" "gitrepo.ru/neonxp/gorum/repository" "gitrepo.ru/neonxp/gorum/routes" @@ -42,9 +39,9 @@ var ( ) func init() { - serverCmd.PersistentFlags().StringVar(&theme, "theme", "default", "theme to use (default is 'default')") - serverCmd.PersistentFlags().StringVar(&listen, "listen", ":8000", "bind address to listen (default is ':8000')") - serverCmd.PersistentFlags().StringVar(&sessionSecret, "session_secret", "s3cr3t", "sessions secret (default is 's3cr3t')") + serverCmd.PersistentFlags().StringVar(&theme, "theme", "default", "theme to use") + serverCmd.PersistentFlags().StringVar(&listen, "listen", ":8000", "bind address to listen") + serverCmd.PersistentFlags().StringVar(&sessionSecret, "session_secret", "s3cr3t", "sessions secret") viper.BindPFlag("theme", serverCmd.Flags().Lookup("theme")) viper.BindPFlag("listen", serverCmd.Flags().Lookup("listen")) viper.BindPFlag("session_secret", serverCmd.Flags().Lookup("session_secret")) @@ -60,14 +57,12 @@ func serve(ctx context.Context) error { ) ctx = context.WithValue(ctx, contextlib.ThemeKey, theme) - db, err := sql.Open("sqlite3", dbFile) + orm, err := db.GetDB(dbFile) if err != nil { - return fmt.Errorf("open db failed: %w", err) + return err } - defer db.Close() - orm := bun.NewDB(db, sqlitedialect.New()) - orm.AddQueryHook(bundebug.NewQueryHook(bundebug.WithVerbose(true))) + defer orm.Close() userRepo := repository.NewUser(orm) nodeRepo := repository.NewNode(orm) @@ -82,11 +77,7 @@ func serve(ctx context.Context) error { _ = utils.Render(c, views.ErrorPage(err)) } - e.Server.BaseContext = func(l net.Listener) context.Context { - return ctx - } - - sessionStore, err := sqlitestore.NewSqliteStoreFromConnection(db, "sessions", "", 0, []byte(sessionSecret)) + sessionStore, err := sqlitestore.NewSqliteStoreFromConnection(orm.DB, "sessions", "", 0, []byte(sessionSecret)) if err != nil { return fmt.Errorf("failed init session store: %w", err) } @@ -94,14 +85,14 @@ func serve(ctx context.Context) error { e.Use( echomiddleware.Recover(), echomiddleware.Gzip(), - echomiddleware.CSRFWithConfig(echomiddleware.CSRFConfig{ - Skipper: echomiddleware.DefaultSkipper, - TokenLength: 32, - TokenLookup: "form:" + echo.HeaderXCSRFToken, - ContextKey: "csrf", - CookieName: "_csrf", - CookieMaxAge: 86400, - }), + // echomiddleware.CSRFWithConfig(echomiddleware.CSRFConfig{ + // Skipper: echomiddleware.DefaultSkipper, + // TokenLength: 32, + // TokenLookup: "form:" + echo.HeaderXCSRFToken, + // ContextKey: "csrf", + // CookieName: "_csrf", + // CookieMaxAge: 86400, + // }), session.Middleware(sessionStore), middleware.UserMiddleware(), ) @@ -113,9 +104,12 @@ func serve(ctx context.Context) error { e.POST("/logout", r.Logout) e.GET("/", r.Node) - e.GET("/n/:id", r.Node) - e.GET("/n/:id/new", r.NewPost) - e.POST("/n/:id/new", r.NewPost) + e.GET("/p/:id", r.Node) + e.GET("/p/:id/new", r.NewPost) + e.POST("/p/:id/new", r.NewPost) + e.GET("/t/:id", r.Node) + e.GET("/t/:id/new", r.NewTopic) + e.POST("/t/:id/new", r.NewTopic) e.StaticFS("/assets", assets.FS) @@ -125,6 +119,9 @@ func serve(ctx context.Context) error { Addr: listen, Handler: e, ErrorLog: slog.NewLogLogger(slog.Default().Handler(), slog.LevelError), + ConnContext: func(cctx context.Context, c net.Conn) context.Context { + return ctx + }, } if err := server.ListenAndServe(); err != http.ErrServerClosed { return fmt.Errorf("server failed: %w", err) diff --git a/app/cmd/user.go b/app/cmd/user.go new file mode 100644 index 0000000..673c7b8 --- /dev/null +++ b/app/cmd/user.go @@ -0,0 +1,80 @@ +package cmd + +import ( + "bufio" + "fmt" + "os" + + "github.com/spf13/cobra" + "gitrepo.ru/neonxp/gorum/db" + "gitrepo.ru/neonxp/gorum/models" + "gitrepo.ru/neonxp/gorum/repository" +) + +var userCmd = &cobra.Command{ + Use: "user", + Short: "User managment", +} + +var createUserCmd = &cobra.Command{ + Use: "add", + Args: cobra.ExactArgs(3), + ArgAliases: []string{"username", "email", "role"}, + RunE: func(cmd *cobra.Command, args []string) error { + orm, err := db.GetDB(dbFile) + if err != nil { + return fmt.Errorf("failed init db: %w", err) + } + username := args[0] + email := args[1] + role := args[2] + iRole := models.RoleUser + switch role { + case "admin": + iRole = models.RoleAdmin + case "moderator": + iRole = models.RoleModerator + } + reader := bufio.NewReader(os.Stdin) + fmt.Printf("Enter password for user %s: ", username) + password, _ := reader.ReadString('\n') + + ur := repository.NewUser(orm) + id, err := ur.Create(cmd.Context(), email, password, username, iRole) + if err != nil { + return fmt.Errorf("failed create user: %w", err) + } + + fmt.Printf("Created user %s (id=%d, email=%s, role_id=%d)\n", username, id, email, iRole) + + return nil + }, +} + +var listUserCmd = &cobra.Command{ + Use: "list", + RunE: func(cmd *cobra.Command, args []string) error { + orm, err := db.GetDB(dbFile) + if err != nil { + return fmt.Errorf("failed init db: %w", err) + } + + ur := repository.NewUser(orm) + users, err := ur.List(cmd.Context()) + if err != nil { + return err + } + + fmt.Printf("ID\tUsername\tEmail\tRole\n") + for _, u := range users { + fmt.Printf("%d\t%s\t%s\t%d\n", u.ID, u.Username, u.Email, u.Role) + } + + return nil + }, +} + +func init() { + userCmd.AddCommand(createUserCmd) + userCmd.AddCommand(listUserCmd) +} diff --git a/db/db.go b/db/db.go new file mode 100644 index 0000000..5241cd1 --- /dev/null +++ b/db/db.go @@ -0,0 +1,34 @@ +package db + +import ( + "context" + "database/sql" + "fmt" + "log/slog" + + "github.com/uptrace/bun" + "github.com/uptrace/bun/dialect/sqlitedialect" +) + +func GetDB(dbFile string) (*bun.DB, error) { + db, err := sql.Open("sqlite3", dbFile) + if err != nil { + return nil, fmt.Errorf("open db failed: %w", err) + } + + orm := bun.NewDB(db, sqlitedialect.New()) + orm.AddQueryHook(&queryHook{}) + + return orm, nil +} + +type queryHook struct { +} + +func (*queryHook) BeforeQuery(ctx context.Context, qe *bun.QueryEvent) context.Context { + return ctx +} + +func (*queryHook) AfterQuery(ctx context.Context, qe *bun.QueryEvent) { + slog.Debug("query", slog.String("query", qe.Query)) +} @@ -11,7 +11,6 @@ require ( ) require ( - github.com/fatih/color v1.16.0 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/google/uuid v1.4.0 // indirect github.com/gorilla/context v1.1.2 // indirect @@ -67,7 +66,6 @@ require ( github.com/spf13/viper v1.19.0 github.com/uptrace/bun v1.2.1 github.com/uptrace/bun/dialect/sqlitedialect v1.2.1 - github.com/uptrace/bun/extra/bundebug v1.2.1 github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/fasttemplate v1.2.2 // indirect golang.org/x/crypto v0.22.0 @@ -2,12 +2,13 @@ github.com/a-h/templ v0.2.747 h1:D0dQ2lxC3W7Dxl6fxQ/1zZHBQslSkTSvl5FxP/CfdKg= github.com/a-h/templ v0.2.747/go.mod h1:69ObQIbrcuwPCU32ohNaWce3Cb7qM5GMiqN1K+2yop4= github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= -github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= -github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= +github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= +github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= @@ -43,6 +44,10 @@ github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/labstack/echo-contrib v0.17.1 h1:7I/he7ylVKsDUieaGRZ9XxxTYOjfQwVzHzUYrNykfCU= github.com/labstack/echo-contrib v0.17.1/go.mod h1:SnsCZtwHBAZm5uBSAtQtXQHI3wqEA73hvTn0bYMKnZA= github.com/labstack/echo/v4 v4.12.0 h1:IKpw49IMryVB2p1a4dzwlhP1O2Tf2E0Ir/450lH+kI0= @@ -65,12 +70,17 @@ github.com/michaeljs1990/sqlitestore v0.0.0-20210507162135-8585425bc864 h1:NkqeB github.com/michaeljs1990/sqlitestore v0.0.0-20210507162135-8585425bc864/go.mod h1:N6aiMetO+sSN0h4VC8RjkwiljKaZmgPsWzZG+mk6oec= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 h1:OdAsTTz6OkFY5QxjkYwrChwuRruF69c169dPK26NUlk= github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= +github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ= github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4= @@ -105,8 +115,6 @@ github.com/uptrace/bun v1.2.1 h1:2ENAcfeCfaY5+2e7z5pXrzFKy3vS8VXvkCag6N2Yzfk= github.com/uptrace/bun v1.2.1/go.mod h1:cNg+pWBUMmJ8rHnETgf65CEvn3aIKErrwOD6IA8e+Ec= github.com/uptrace/bun/dialect/sqlitedialect v1.2.1 h1:IprvkIKUjEjvt4VKpcmLpbMIucjrsmUPJOSlg19+a0Q= github.com/uptrace/bun/dialect/sqlitedialect v1.2.1/go.mod h1:mMQf4NUpgY8bnOanxGmxNiHCdALOggS4cZ3v63a9D/o= -github.com/uptrace/bun/extra/bundebug v1.2.1 h1:85MYpX3QESYI02YerKxUi1CD9mHuLrc2BXs1eOCtQus= -github.com/uptrace/bun/extra/bundebug v1.2.1/go.mod h1:sfGKIi0HSGxsTC/sgIHGwpnYduHHYhdMeOIwurgSY+Y= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo= @@ -137,8 +145,8 @@ golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w= golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE= -golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= +golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -164,6 +172,8 @@ golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/middleware/session/store.go b/middleware/session/store.go new file mode 100644 index 0000000..f22c64d --- /dev/null +++ b/middleware/session/store.go @@ -0,0 +1,30 @@ +package session + +import ( + "net/http" + + "github.com/gorilla/sessions" + "github.com/uptrace/bun" +) + +type SessionStore struct { + orm *bun.DB +} + +// Get should return a cached session. +func (s *SessionStore) Get(r *http.Request, name string) (*sessions.Session, error) { + panic("not implemented") // TODO: Implement +} + +// New should create and return a new session. +// +// Note that New should never return a nil session, even in the case of +// an error if using the Registry infrastructure to cache the session. +func (s *SessionStore) New(r *http.Request, name string) (*sessions.Session, error) { + panic("not implemented") // TODO: Implement +} + +// Save should persist session to the underlying store implementation. +func (s *SessionStore) Save(r *http.Request, w http.ResponseWriter, ss *sessions.Session) error { + panic("not implemented") // TODO: Implement +} diff --git a/models/node.go b/models/node.go index 16e426a..98a8d4e 100644 --- a/models/node.go +++ b/models/node.go @@ -21,6 +21,7 @@ type Node struct { CreatedAt int64 `bun:",nullzero,notnull,default:current_timestamp"` UpdatedAt int64 `bun:",nullzero,notnull,default:current_timestamp"` DeletedAt int64 + Children []*Node `bun:"rel:has-many,join:id=parent_id"` } var _ bun.BeforeAppendModelHook = (*Node)(nil) diff --git a/models/user.go b/models/user.go index 73afdce..73358fe 100644 --- a/models/user.go +++ b/models/user.go @@ -18,4 +18,13 @@ type User struct { Password string Username string Photo *string + Role UserRole } + +type UserRole int + +const ( + RoleUser UserRole = iota + RoleModerator + RoleAdmin +) diff --git a/repository/node.go b/repository/node.go index d5794b6..27282af 100644 --- a/repository/node.go +++ b/repository/node.go @@ -42,17 +42,17 @@ func (t *Node) Get(ctx context.Context, topicID int) (*models.Node, error) { Model(node). Where(`n.id = ?`, topicID). Relation("Author"). + Relation("Parent"). Scan(ctx) } -func (t *Node) List(ctx context.Context, topicID int) ([]*models.Node, int, error) { +func (t *Node) List(ctx context.Context, topicID int) ([]*models.Node, error) { posts := make([]*models.Node, 0) - count, err := t.db.NewSelect(). + return posts, t.db.NewSelect(). Model(&posts). Where(`parent_id = ?`, topicID). Relation("Author"). - ScanAndCount(ctx) - - return posts, count, err + Relation("Children"). + Scan(ctx) } diff --git a/repository/user.go b/repository/user.go index ec3b702..5c3cce6 100644 --- a/repository/user.go +++ b/repository/user.go @@ -19,7 +19,7 @@ func NewUser(db *bun.DB) *User { } } -func (u *User) Create(ctx context.Context, email, password, username string) (int, error) { +func (u *User) Create(ctx context.Context, email, password, username string, role models.UserRole) (int, error) { hpassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) if err != nil { @@ -30,6 +30,7 @@ func (u *User) Create(ctx context.Context, email, password, username string) (in Email: email, Password: string(hpassword), Username: username, + Role: role, } if _, err := u.db.NewInsert().Model(user).Returning("id").Exec(ctx); err != nil { @@ -52,3 +53,9 @@ func (u *User) Login(ctx context.Context, email, password string) (*models.User, return user, nil } + +func (u *User) List(ctx context.Context) ([]*models.User, error) { + ret := make([]*models.User, 0) + + return ret, u.db.NewSelect().Model(&ret).Scan(ctx) +} diff --git a/routes/node.go b/routes/node.go index c754b9a..2f5b7d3 100644 --- a/routes/node.go +++ b/routes/node.go @@ -34,12 +34,23 @@ func (r *Router) Node(c echo.Context) error { } } - nodes, count, err := r.nodeRepo.List(c.Request().Context(), parentID) + nodes, err := r.nodeRepo.List(c.Request().Context(), parentID) if err != nil { return err } - return utils.Render(c, views.Node(node, nodes, count)) + topics := make([]*models.Node, 0, len(nodes)) + posts := make([]*models.Node, 0, len(nodes)) + for _, n := range nodes { + switch n.Type { + case models.PostType: + posts = append(posts, n) + case models.TopicType: + topics = append(topics, n) + } + } + + return utils.Render(c, views.Node(node, topics, posts)) } func (r *Router) NewPost(c echo.Context) error { @@ -63,7 +74,38 @@ func (r *Router) NewPost(c echo.Context) error { return err } - return c.Redirect(302, fmt.Sprintf("/n/%d#post%d", parentID, postID)) + return c.Redirect(302, fmt.Sprintf("/t/%d#post%d", parentID, postID)) + } + + node, err := r.nodeRepo.Get(c.Request().Context(), parentID) + if err != nil { + return err + } + + return utils.Render(c, views.NewPost(node)) +} +func (r *Router) NewTopic(c echo.Context) error { + req := new(nodeRequest) + if err := c.Bind(req); err != nil { + return err + } + user := contextlib.GetUser(c.Request().Context()) + if user == nil { + return echo.ErrForbidden + } + sParentID := c.Param("id") + parentID, err := strconv.Atoi(sParentID) + if err != nil { + return err + } + + if c.Request().Method == http.MethodPost { + postID, err := r.nodeRepo.Create(c.Request().Context(), req.Type, req.Text, user.ID, parentID) + if err != nil { + return err + } + + return c.Redirect(302, fmt.Sprintf("/t/%d", postID)) } node, err := r.nodeRepo.Get(c.Request().Context(), parentID) @@ -71,5 +113,5 @@ func (r *Router) NewPost(c echo.Context) error { return err } - return utils.Render(c, views.NewNode(node)) + return utils.Render(c, views.NewTopic(node)) } diff --git a/routes/user.go b/routes/user.go index 95d5717..dd5bd61 100644 --- a/routes/user.go +++ b/routes/user.go @@ -7,6 +7,7 @@ import ( "github.com/gorilla/sessions" "github.com/labstack/echo-contrib/session" "github.com/labstack/echo/v4" + "gitrepo.ru/neonxp/gorum/models" "gitrepo.ru/neonxp/gorum/utils" "gitrepo.ru/neonxp/gorum/views" ) @@ -72,7 +73,7 @@ func (r *Router) Register(c echo.Context) error { return err } if c.Request().Method == http.MethodPost { - uid, err := r.userRepo.Create(c.Request().Context(), req.Email, req.Password, req.Username) + uid, err := r.userRepo.Create(c.Request().Context(), req.Email, req.Password, req.Username, models.RoleUser) if err != nil { return err } diff --git a/views/csrf.templ b/views/csrf.templ index 2bb3545..c019d4c 100644 --- a/views/csrf.templ +++ b/views/csrf.templ @@ -1,7 +1,6 @@ package views -import "github.com/labstack/echo/v4" templ CSRF() { - <input type="hidden" name={echo.HeaderXCSRFToken} value={ ctx.Value("csrf").(string) }/> + // <input type="hidden" name={echo.HeaderXCSRFToken} value={ ctx.Value("csrf").(string) } /> } diff --git a/views/csrf_templ.go b/views/csrf_templ.go new file mode 100644 index 0000000..53a7118 --- /dev/null +++ b/views/csrf_templ.go @@ -0,0 +1,31 @@ +// Code generated by templ - DO NOT EDIT. + +// templ: version: v0.2.747 +package views + +//lint:file-ignore SA4006 This context is only used if a nested component is present. + +import "github.com/a-h/templ" +import templruntime "github.com/a-h/templ/runtime" + +func CSRF() templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var1 := templ.GetChildren(ctx) + if templ_7745c5c3_Var1 == nil { + templ_7745c5c3_Var1 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + return templ_7745c5c3_Err + }) +} diff --git a/views/error.templ b/views/error.templ index d3a4a32..3e857ef 100644 --- a/views/error.templ +++ b/views/error.templ @@ -1,8 +1,8 @@ package views templ ErrorPage(err error) { - @Layout() { - <h1>Ошибка</h1> - {err.Error()} - } + @Layout(nil) { + <h1>Ошибка</h1> + { err.Error() } + } } diff --git a/views/error_templ.go b/views/error_templ.go index 66d40fd..8513d1d 100644 --- a/views/error_templ.go +++ b/views/error_templ.go @@ -38,14 +38,14 @@ func ErrorPage(err error) templ.Component { }() } ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 1) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<h1>Ошибка</h1>") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } var templ_7745c5c3_Var3 string templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(err.Error()) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/error.templ`, Line: 6, Col: 20} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/error.templ`, Line: 6, Col: 15} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3)) if templ_7745c5c3_Err != nil { @@ -53,7 +53,7 @@ func ErrorPage(err error) templ.Component { } return templ_7745c5c3_Err }) - templ_7745c5c3_Err = Layout().Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer) + templ_7745c5c3_Err = Layout(nil).Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } diff --git a/views/error_templ.txt b/views/error_templ.txt deleted file mode 100644 index d3a792d..0000000 --- a/views/error_templ.txt +++ /dev/null @@ -1 +0,0 @@ -<h1>Ошибка</h1> diff --git a/views/layouts.templ b/views/layouts.templ index 3db6f1b..f1c2deb 100644 --- a/views/layouts.templ +++ b/views/layouts.templ @@ -2,11 +2,12 @@ package views import ( "context" + "fmt" "gitrepo.ru/neonxp/gorum/contextlib" "gitrepo.ru/neonxp/gorum/models" ) -templ Layout() { +templ Layout(parent *models.Node) { <!DOCTYPE html> <html lang="ru"> <head> @@ -14,7 +15,7 @@ templ Layout() { <meta name="viewport" content="width=device-width, initial-scale=1"/> <meta name="color-scheme" content="light dark"/> <link rel="stylesheet" href={ "/assets/css/pico." + ctx.Value(contextlib.ThemeKey).(string) + ".min.css" }/> - <link rel="stylesheet" href="/assets/css/style.css" /> + <link rel="stylesheet" href="/assets/css/style.css"/> <title>Gorum</title> </head> <body> @@ -23,9 +24,23 @@ templ Layout() { <li> <strong>Gorum BBS</strong> </li> - <li> - <a href="/">Список тем</a> - </li> + if parent != nil { + <li> + <a href="/">Список тем</a> + </li> + <li> + switch parent.Type { + case models.PostType: + <a href={ templ.URL(fmt.Sprintf("/p/%d", parent.ID)) }>На уровень выше</a> + case models.TopicType: + <a href={ templ.URL(fmt.Sprintf("/t/%d", parent.ID)) }>К предыдущей теме</a> + } + </li> + } else { + <li> + <a href="/">Список тем</a> + </li> + } </ul> <ul> if isAuthorized(ctx) { @@ -42,7 +57,7 @@ templ Layout() { { children... } </main> <footer class="container-fluid"> - <small>Работает на <a href="https://gorum.tech/">Gorum</a>.</small> + <small>Работает на <a href="https://neonxp.ru/gorum">Gorum</a>.</small> </footer> </body> </html> diff --git a/views/layouts_templ.go b/views/layouts_templ.go index 4fdcb75..6191925 100644 --- a/views/layouts_templ.go +++ b/views/layouts_templ.go @@ -10,11 +10,12 @@ import templruntime "github.com/a-h/templ/runtime" import ( "context" + "fmt" "gitrepo.ru/neonxp/gorum/contextlib" "gitrepo.ru/neonxp/gorum/models" ) -func Layout() templ.Component { +func Layout(parent *models.Node) templ.Component { return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) @@ -32,48 +33,97 @@ func Layout() templ.Component { templ_7745c5c3_Var1 = templ.NopComponent } ctx = templ.ClearChildren(ctx) - templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 1) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<!doctype html><html lang=\"ru\"><head><meta charset=\"utf-8\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\"><meta name=\"color-scheme\" content=\"light dark\"><link rel=\"stylesheet\" href=\"") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } var templ_7745c5c3_Var2 string templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs("/assets/css/pico." + ctx.Value(contextlib.ThemeKey).(string) + ".min.css") if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/layouts.templ`, Line: 16, Col: 107} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/layouts.templ`, Line: 17, Col: 107} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 2) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\"><link rel=\"stylesheet\" href=\"/assets/css/style.css\"><title>Gorum</title></head><body><nav class=\"container-fluid\"><ul><li><strong>Gorum BBS</strong></li>") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if parent != nil { + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<li><a href=\"/\">Список тем</a></li><li>") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + switch parent.Type { + case models.PostType: + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<a href=\"") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var3 templ.SafeURL = templ.URL(fmt.Sprintf("/p/%d", parent.ID)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var3))) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">На уровень выше</a>") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + case models.TopicType: + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<a href=\"") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var4 templ.SafeURL = templ.URL(fmt.Sprintf("/t/%d", parent.ID)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var4))) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">К предыдущей теме</a>") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</li>") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } else { + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<li><a href=\"/\">Список тем</a></li>") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</ul><ul>") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } if isAuthorized(ctx) { - templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 3) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" <li>") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var3 string - templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(getUser(ctx).Username) + var templ_7745c5c3_Var5 string + templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(getUser(ctx).Username) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/layouts.templ`, Line: 33, Col: 33} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/layouts.templ`, Line: 48, Col: 33} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 4) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</li><li><form action=\"/logout\" method=\"POST\"><input type=\"submit\" value=\"Выход\"></form></li>") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } else { - templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 5) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<li><a href=\"/login\">Вход</a></li><li><a href=\"/register\">Регистрация</a></li>") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } - templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 6) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</ul></nav><main class=\"container\">") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -81,7 +131,7 @@ func Layout() templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 7) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</main><footer class=\"container-fluid\"><small>Работает на <a href=\"https://neonxp.ru/gorum\">Gorum</a>.</small></footer></body></html>") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } diff --git a/views/layouts_templ.txt b/views/layouts_templ.txt deleted file mode 100644 index ed4ab5e..0000000 --- a/views/layouts_templ.txt +++ /dev/null @@ -1,7 +0,0 @@ -<!doctype html><html lang=\"ru\"><head><meta charset=\"utf-8\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\"><meta name=\"color-scheme\" content=\"light dark\"><link rel=\"stylesheet\" href=\" -\"><link rel=\"stylesheet\" href=\"/assets/css/style.css\"><title>Gorum</title></head><body><nav class=\"container-fluid\"><ul><li><strong>Gorum BBS</strong></li><li><a href=\"/\">Список тем</a></li></ul><ul> - <li> -</li><li><form action=\"/logout\" method=\"POST\"><input type=\"submit\" value=\"Выход\"></form></li> -<li><a href=\"/login\">Вход</a></li><li><a href=\"/register\">Регистрация</a></li> -</ul></nav><main class=\"container\"> -</main><footer class=\"container-fluid\"><small>Работает на <a href=\"https://gorum.tech/\">Gorum</a>.</small></footer></body></html> diff --git a/views/login.templ b/views/login.templ index 7f3a385..6c897c6 100644 --- a/views/login.templ +++ b/views/login.templ @@ -1,7 +1,7 @@ package views templ Login(email string) { - @Layout() { + @Layout(nil) { <h1>Вход</h1> <form method="post"> <label for="email">Электропочта:</label> diff --git a/views/login_templ.go b/views/login_templ.go index d575513..034133f 100644 --- a/views/login_templ.go +++ b/views/login_templ.go @@ -38,7 +38,7 @@ func Login(email string) templ.Component { }() } ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 1) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<h1>Вход</h1><form method=\"post\"><label for=\"email\">Электропочта:</label> <input type=\"email\" id=\"email\" name=\"email\" required=\"true\" placeholder=\"имя@mail.ru\" value=\"") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -51,13 +51,13 @@ func Login(email string) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 2) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\"> <label for=\"password\">Пароль:</label> <input type=\"password\" id=\"password\" name=\"password\" required=\"true\" placeholder=\"пароль\"> <label for=\"remember\"><input type=\"checkbox\" id=\"remember\" name=\"remember\" checked=\"checked\"> Запомнить меня?</label> <input type=\"submit\" value=\"Войти\"></form>") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } return templ_7745c5c3_Err }) - templ_7745c5c3_Err = Layout().Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer) + templ_7745c5c3_Err = Layout(nil).Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } diff --git a/views/login_templ.txt b/views/login_templ.txt deleted file mode 100644 index 7b12aee..0000000 --- a/views/login_templ.txt +++ /dev/null @@ -1,2 +0,0 @@ -<h1>Вход</h1><form method=\"post\"><label for=\"email\">Электропочта:</label> <input type=\"email\" id=\"email\" name=\"email\" required=\"true\" placeholder=\"имя@mail.ru\" value=\" -\"> <label for=\"password\">Пароль:</label> <input type=\"password\" id=\"password\" name=\"password\" required=\"true\" placeholder=\"пароль\"> <label for=\"remember\"><input type=\"checkbox\" id=\"remember\" name=\"remember\" checked=\"checked\"> Запомнить меня?</label> <input type=\"submit\" value=\"Войти\"></form> diff --git a/views/new-node.templ b/views/new-node.templ deleted file mode 100644 index b92dbba..0000000 --- a/views/new-node.templ +++ /dev/null @@ -1,30 +0,0 @@ -package views - -import ( - "fmt" - "gitrepo.ru/neonxp/gorum/models" - "strconv" -) - -templ NewNode(parent *models.Node) { - <details> - <summary>Создать топик</summary> - <form method="post" action={ templ.URL(fmt.Sprintf("/n/%d/new", parent.ID)) }> - @CSRF() - <input type="hidden" name="type" value={ strconv.Itoa(int(models.TopicType)) }/> - <label for="text">Топик</label> - <input type="text" name="text" id="text" placeholder="имя темы..."/> - <input type="submit" value="Создать"/> - </form> - </details> - <details> - <summary>Создать пост</summary> - <form method="post" action={ templ.URL(fmt.Sprintf("/n/%d/new", parent.ID)) }> - @CSRF() - <input type="hidden" name="type" value={ strconv.Itoa(int(models.PostType)) }/> - <label for="text">Текст</label> - <textarea name="text" id="text" placeholder="текст..." rows="10"></textarea> - <input type="submit" value="Создать"/> - </form> - </details> -} diff --git a/views/new-node_templ.go b/views/new-node_templ.go deleted file mode 100644 index b838227..0000000 --- a/views/new-node_templ.go +++ /dev/null @@ -1,85 +0,0 @@ -// Code generated by templ - DO NOT EDIT. - -// templ: version: v0.2.747 -package views - -//lint:file-ignore SA4006 This context is only used if a nested component is present. - -import "github.com/a-h/templ" -import templruntime "github.com/a-h/templ/runtime" - -import ( - "fmt" - "gitrepo.ru/neonxp/gorum/models" - "strconv" -) - -func NewNode(parent *models.Node) templ.Component { - return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { - templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context - templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) - if !templ_7745c5c3_IsBuffer { - defer func() { - templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) - if templ_7745c5c3_Err == nil { - templ_7745c5c3_Err = templ_7745c5c3_BufErr - } - }() - } - ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Var1 := templ.GetChildren(ctx) - if templ_7745c5c3_Var1 == nil { - templ_7745c5c3_Var1 = templ.NopComponent - } - ctx = templ.ClearChildren(ctx) - templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 1) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - var templ_7745c5c3_Var2 templ.SafeURL = templ.URL(fmt.Sprintf("/n/%d/new", parent.ID)) - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var2))) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 2) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - var templ_7745c5c3_Var3 string - templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(strconv.Itoa(int(models.TopicType))) - if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/new-node.templ`, Line: 13, Col: 79} - } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3)) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 3) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - var templ_7745c5c3_Var4 templ.SafeURL = templ.URL(fmt.Sprintf("/n/%d/new", parent.ID)) - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var4))) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 4) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - var templ_7745c5c3_Var5 string - templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(strconv.Itoa(int(models.PostType))) - if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/new-node.templ`, Line: 22, Col: 78} - } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5)) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 5) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - return templ_7745c5c3_Err - }) -} diff --git a/views/new-node_templ.txt b/views/new-node_templ.txt deleted file mode 100644 index 4904f81..0000000 --- a/views/new-node_templ.txt +++ /dev/null @@ -1,5 +0,0 @@ -<details><summary>Создать топик</summary><form method=\"post\" action=\" -\"><input type=\"hidden\" name=\"type\" value=\" -\"> <label for=\"text\">Топик</label> <input type=\"text\" name=\"text\" id=\"text\" placeholder=\"имя темы...\"> <input type=\"submit\" value=\"Создать\"></form></details> <details><summary>Создать пост</summary><form method=\"post\" action=\" -\"><input type=\"hidden\" name=\"type\" value=\" -\"> <label for=\"text\">Текст</label> <textarea name=\"text\" id=\"text\" placeholder=\"текст...\" rows=\"10\"></textarea> <input type=\"submit\" value=\"Создать\"></form></details> diff --git a/views/new.templ b/views/new.templ new file mode 100644 index 0000000..c83a4a9 --- /dev/null +++ b/views/new.templ @@ -0,0 +1,51 @@ +package views + +import ( + "fmt" + "gitrepo.ru/neonxp/gorum/models" + "gitrepo.ru/neonxp/gorum/utils" + "strconv" +) + +templ NewPost(parent *models.Node) { + @Layout(parent.Parent) { + <article> + <header class="post-header"> + <span> + { parent.Author.Username } + </span> + <span> + { utils.FormatDate(parent.CreatedAt) } + </span> + </header> + @templ.Raw(utils.MarkdownToHTML(parent.Text)) + </article> + @NewPostForm(parent) + } +} +templ NewTopic(parent *models.Node) { + @Layout(parent.Parent) { + <h1>{parent.Text}</h1> + @NewTopicForm(parent) + } +} + +templ NewPostForm(parent *models.Node) { + <form method="post" action={ templ.URL(fmt.Sprintf("/p/%d/new", parent.ID)) }> + @CSRF() + <input type="hidden" name="type" value={ strconv.Itoa(int(models.PostType)) }/> + <label for="text"><strong>Ответ</strong></label> + <textarea name="text" id="text" placeholder="текст..." rows="5"></textarea> + <input type="submit" value="Создать"/> + </form> +} + +templ NewTopicForm(parent *models.Node) { + <form method="post" action={ templ.URL(fmt.Sprintf("/t/%d/new", parent.ID)) }> + @CSRF() + <input type="hidden" name="type" value={ strconv.Itoa(int(models.TopicType)) }/> + <label for="text"><strong>Новая тема</strong></label> + <input type="text" name="text" id="text" placeholder="название темы..." /> + <input type="submit" value="Создать"/> + </form> +} diff --git a/views/new_templ.go b/views/new_templ.go new file mode 100644 index 0000000..53f60ed --- /dev/null +++ b/views/new_templ.go @@ -0,0 +1,271 @@ +// Code generated by templ - DO NOT EDIT. + +// templ: version: v0.2.747 +package views + +//lint:file-ignore SA4006 This context is only used if a nested component is present. + +import "github.com/a-h/templ" +import templruntime "github.com/a-h/templ/runtime" + +import ( + "fmt" + "gitrepo.ru/neonxp/gorum/models" + "gitrepo.ru/neonxp/gorum/utils" + "strconv" +) + +func NewPost(parent *models.Node) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var1 := templ.GetChildren(ctx) + if templ_7745c5c3_Var1 == nil { + templ_7745c5c3_Var1 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Var2 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<article><header class=\"post-header\"><span>") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var3 string + templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(parent.Author.Username) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/new.templ`, Line: 15, Col: 29} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</span> <span>") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var4 string + templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(utils.FormatDate(parent.CreatedAt)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/new.templ`, Line: 18, Col: 41} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</span></header>") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templ.Raw(utils.MarkdownToHTML(parent.Text)).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</article>") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = NewPostForm(parent).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return templ_7745c5c3_Err + }) + templ_7745c5c3_Err = Layout(parent.Parent).Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return templ_7745c5c3_Err + }) +} + +func NewTopic(parent *models.Node) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var5 := templ.GetChildren(ctx) + if templ_7745c5c3_Var5 == nil { + templ_7745c5c3_Var5 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Var6 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<h1>") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var7 string + templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(parent.Text) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/new.templ`, Line: 28, Col: 18} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</h1>") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = NewTopicForm(parent).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return templ_7745c5c3_Err + }) + templ_7745c5c3_Err = Layout(parent.Parent).Render(templ.WithChildren(ctx, templ_7745c5c3_Var6), templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return templ_7745c5c3_Err + }) +} + +func NewPostForm(parent *models.Node) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var8 := templ.GetChildren(ctx) + if templ_7745c5c3_Var8 == nil { + templ_7745c5c3_Var8 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<form method=\"post\" action=\"") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var9 templ.SafeURL = templ.URL(fmt.Sprintf("/p/%d/new", parent.ID)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var9))) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = CSRF().Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<input type=\"hidden\" name=\"type\" value=\"") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var10 string + templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs(strconv.Itoa(int(models.PostType))) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/new.templ`, Line: 36, Col: 77} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var10)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\"> <label for=\"text\"><strong>Ответ</strong></label> <textarea name=\"text\" id=\"text\" placeholder=\"текст...\" rows=\"5\"></textarea> <input type=\"submit\" value=\"Создать\"></form>") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return templ_7745c5c3_Err + }) +} + +func NewTopicForm(parent *models.Node) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var11 := templ.GetChildren(ctx) + if templ_7745c5c3_Var11 == nil { + templ_7745c5c3_Var11 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<form method=\"post\" action=\"") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var12 templ.SafeURL = templ.URL(fmt.Sprintf("/t/%d/new", parent.ID)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var12))) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = CSRF().Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<input type=\"hidden\" name=\"type\" value=\"") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var13 string + templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinStringErrs(strconv.Itoa(int(models.TopicType))) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/new.templ`, Line: 46, Col: 78} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var13)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\"> <label for=\"text\"><strong>Новая тема</strong></label> <input type=\"text\" name=\"text\" id=\"text\" placeholder=\"название темы...\"> <input type=\"submit\" value=\"Создать\"></form>") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return templ_7745c5c3_Err + }) +} diff --git a/views/nodes.templ b/views/nodes.templ index c9baf2e..e6a120c 100644 --- a/views/nodes.templ +++ b/views/nodes.templ @@ -4,44 +4,46 @@ import ( "fmt" "gitrepo.ru/neonxp/gorum/models" "gitrepo.ru/neonxp/gorum/utils" + "strconv" ) -templ Node(node *models.Node, nodes []*models.Node, count int) { - @Layout() { - <h1>{ node.Text }</h1> - <table> - <thead> - <tr> - <th>Тема</th> - <th>Дата</th> - <th>Автор</th> - </tr> - </thead> - <tbody> - for _, n := range nodes { - if n.Type == models.TopicType { - @Topic(n) - } - } - if len(nodes) == 0 { +templ Node(node *models.Node, topics []*models.Node, nodes []*models.Node) { + @Layout(node.Parent) { + switch node.Type { + case models.TopicType: + <h1>{ node.Text }</h1> + <div> + <a href={ templ.URL(fmt.Sprintf("/t/%d/new", node.ID)) }>Новая подтема</a> + </div> + case models.PostType: + <h1>Пост</h1> + @Post(node, 0, false) + } + if len(topics) != 0 { + <table> + <thead> <tr> - <td colspan="3"> - <strong>Тем нет</strong> - </td> + <th>Тема</th> + <th>Тем/Ответов</th> + <th>Дата</th> + <th>Автор</th> </tr> - } - </tbody> - </table> + </thead> + <tbody> + for _, n := range topics { + @Topic(n) + } + </tbody> + </table> + } if len(nodes) == 0 { <strong>Постов нет</strong> } for _, n := range nodes { - if n.Type == models.PostType { - @Post(n) - } + @Post(n, level(node), true) } if isAuthorized(ctx) { - @NewNode(node) + @NewPostForm(node) } else { <a href="/login">Войдите</a> чтобы ответить в тему. } @@ -51,7 +53,10 @@ templ Node(node *models.Node, nodes []*models.Node, count int) { templ Topic(n *models.Node) { <tr> <td> - <a href={ templ.URL(fmt.Sprintf("/n/%d", n.ID)) }>{ n.Text }</a> + <a href={ templ.URL(fmt.Sprintf("/t/%d", n.ID)) }>{ n.Text }</a> + </td> + <td> + { strconv.Itoa(len(n.Children)) } </td> <td> { utils.FormatDate(n.CreatedAt) } @@ -62,21 +67,33 @@ templ Topic(n *models.Node) { </tr> } -templ Post(n *models.Node) { - <article id={ fmt.Sprintf("post%d", n.ID) }> +css levelStyle(level int) { + margin-left: { fmt.Sprintf("%dem", level) }; +} + +templ Post(n *models.Node, level int, withFooter bool) { + <article id={ fmt.Sprintf("post%d", n.ID) } class={ levelStyle(level) }> <header class="post-header"> - <span>Пост</span> <span> { n.Author.Username } - в + </span> + <span> { utils.FormatDate(n.CreatedAt) } - <a - href={ templ.URL(fmt.Sprintf("/n/%d#post%d", n.ParentID, n.ID)) } - > - # - </a> </span> </header> @templ.Raw(utils.MarkdownToHTML(n.Text)) + if withFooter { + <footer class="post-header"> + <a href={ templ.URL(fmt.Sprintf("/p/%d/new", n.ID)) }>Ответить</a> + <a href={ templ.URL(fmt.Sprintf("/p/%d", n.ID)) }>Ответов: { strconv.Itoa(len(n.Children)) }</a> + </footer> + } </article> } + +func level(node *models.Node) int { + if node.Type == models.PostType { + return 1 + } + return 0 +} diff --git a/views/nodes_templ.go b/views/nodes_templ.go index 0424f57..fc23258 100644 --- a/views/nodes_templ.go +++ b/views/nodes_templ.go @@ -12,9 +12,10 @@ import ( "fmt" "gitrepo.ru/neonxp/gorum/models" "gitrepo.ru/neonxp/gorum/utils" + "strconv" ) -func Node(node *models.Node, nodes []*models.Node, count int) templ.Component { +func Node(node *models.Node, topics []*models.Node, nodes []*models.Node) templ.Component { return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) @@ -44,73 +45,98 @@ func Node(node *models.Node, nodes []*models.Node, count int) templ.Component { }() } ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 1) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - var templ_7745c5c3_Var3 string - templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(node.Text) - if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/nodes.templ`, Line: 11, Col: 17} - } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3)) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err + switch node.Type { + case models.TopicType: + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<h1>") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var3 string + templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(node.Text) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/nodes.templ`, Line: 14, Col: 19} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</h1><div><a href=\"") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var4 templ.SafeURL = templ.URL(fmt.Sprintf("/t/%d/new", node.ID)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var4))) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">Новая подтема</a></div>") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + case models.PostType: + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<h1>Пост</h1>") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = Post(node, 0, false).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } } - templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 2) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - for _, n := range nodes { - if n.Type == models.TopicType { + if len(topics) != 0 { + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<table><thead><tr><th>Тема</th><th>Тем/Ответов</th><th>Дата</th><th>Автор</th></tr></thead> <tbody>") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + for _, n := range topics { templ_7745c5c3_Err = Topic(n).Render(ctx, templ_7745c5c3_Buffer) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } - } - if len(nodes) == 0 { - templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 3) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</tbody></table>") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } - templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 4) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } if len(nodes) == 0 { - templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 5) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<strong>Постов нет</strong> ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } for _, n := range nodes { - if n.Type == models.PostType { - templ_7745c5c3_Err = Post(n).Render(ctx, templ_7745c5c3_Buffer) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } + templ_7745c5c3_Err = Post(n, level(node), true).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err } } - templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 6) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } if isAuthorized(ctx) { - templ_7745c5c3_Err = NewNode(node).Render(ctx, templ_7745c5c3_Buffer) + templ_7745c5c3_Err = NewPostForm(node).Render(ctx, templ_7745c5c3_Buffer) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } else { - templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 7) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<a href=\"/login\">Войдите</a> чтобы ответить в тему.") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } return templ_7745c5c3_Err }) - templ_7745c5c3_Err = Layout().Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer) + templ_7745c5c3_Err = Layout(node.Parent).Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -131,60 +157,73 @@ func Topic(n *models.Node) templ.Component { }() } ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Var4 := templ.GetChildren(ctx) - if templ_7745c5c3_Var4 == nil { - templ_7745c5c3_Var4 = templ.NopComponent + templ_7745c5c3_Var5 := templ.GetChildren(ctx) + if templ_7745c5c3_Var5 == nil { + templ_7745c5c3_Var5 = templ.NopComponent } ctx = templ.ClearChildren(ctx) - templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 8) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<tr><td><a href=\"") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var5 templ.SafeURL = templ.URL(fmt.Sprintf("/n/%d", n.ID)) - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var5))) + var templ_7745c5c3_Var6 templ.SafeURL = templ.URL(fmt.Sprintf("/t/%d", n.ID)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var6))) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 9) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var6 string - templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(n.Text) + var templ_7745c5c3_Var7 string + templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(n.Text) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/nodes.templ`, Line: 54, Col: 61} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/nodes.templ`, Line: 56, Col: 61} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 10) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</a></td><td>") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var7 string - templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(utils.FormatDate(n.CreatedAt)) + var templ_7745c5c3_Var8 string + templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(strconv.Itoa(len(n.Children))) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/nodes.templ`, Line: 57, Col: 34} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/nodes.templ`, Line: 59, Col: 34} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 11) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</td><td>") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var8 string - templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(n.Author.Username) + var templ_7745c5c3_Var9 string + templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(utils.FormatDate(n.CreatedAt)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/nodes.templ`, Line: 60, Col: 22} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/nodes.templ`, Line: 62, Col: 34} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</td><td>") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var10 string + templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs(n.Author.Username) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/nodes.templ`, Line: 65, Col: 22} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var10)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 12) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</td></tr>") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -192,7 +231,17 @@ func Topic(n *models.Node) templ.Component { }) } -func Post(n *models.Node) templ.Component { +func levelStyle(level int) templ.CSSClass { + templ_7745c5c3_CSSBuilder := templruntime.GetBuilder() + templ_7745c5c3_CSSBuilder.WriteString(string(templ.SanitizeCSS(`margin-left`, fmt.Sprintf("%dem", level)))) + templ_7745c5c3_CSSID := templ.CSSID(`levelStyle`, templ_7745c5c3_CSSBuilder.String()) + return templ.ComponentCSSClass{ + ID: templ_7745c5c3_CSSID, + Class: templ.SafeCSS(`.` + templ_7745c5c3_CSSID + `{` + templ_7745c5c3_CSSBuilder.String() + `}`), + } +} + +func Post(n *models.Node, level int, withFooter bool) templ.Component { return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) @@ -205,60 +254,69 @@ func Post(n *models.Node) templ.Component { }() } ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Var9 := templ.GetChildren(ctx) - if templ_7745c5c3_Var9 == nil { - templ_7745c5c3_Var9 = templ.NopComponent + templ_7745c5c3_Var11 := templ.GetChildren(ctx) + if templ_7745c5c3_Var11 == nil { + templ_7745c5c3_Var11 = templ.NopComponent } ctx = templ.ClearChildren(ctx) - templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 13) + var templ_7745c5c3_Var12 = []any{levelStyle(level)} + templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var12...) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var10 string - templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("post%d", n.ID)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<article id=\"") if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/nodes.templ`, Line: 66, Col: 42} + return templ_7745c5c3_Err } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var10)) + var templ_7745c5c3_Var13 string + templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("post%d", n.ID)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/nodes.templ`, Line: 75, Col: 42} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var13)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 14) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\" class=\"") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var11 string - templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinStringErrs(n.Author.Username) + var templ_7745c5c3_Var14 string + templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var12).String()) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/nodes.templ`, Line: 70, Col: 23} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/nodes.templ`, Line: 1, Col: 0} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var11)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var14)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 15) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\"><header class=\"post-header\"><span>") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var12 string - templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinStringErrs(utils.FormatDate(n.CreatedAt)) + var templ_7745c5c3_Var15 string + templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.JoinStringErrs(n.Author.Username) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/nodes.templ`, Line: 72, Col: 35} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/nodes.templ`, Line: 78, Col: 23} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var12)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var15)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 16) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</span> <span>") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var13 templ.SafeURL = templ.URL(fmt.Sprintf("/n/%d#post%d", n.ParentID, n.ID)) - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var13))) + var templ_7745c5c3_Var16 string + templ_7745c5c3_Var16, templ_7745c5c3_Err = templ.JoinStringErrs(utils.FormatDate(n.CreatedAt)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/nodes.templ`, Line: 81, Col: 35} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var16)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 17) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</span></header>") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -266,10 +324,54 @@ func Post(n *models.Node) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 18) + if withFooter { + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<footer class=\"post-header\"><a href=\"") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var17 templ.SafeURL = templ.URL(fmt.Sprintf("/p/%d/new", n.ID)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var17))) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">Ответить</a> <a href=\"") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var18 templ.SafeURL = templ.URL(fmt.Sprintf("/p/%d", n.ID)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var18))) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">Ответов: ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var19 string + templ_7745c5c3_Var19, templ_7745c5c3_Err = templ.JoinStringErrs(strconv.Itoa(len(n.Children))) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/nodes.templ`, Line: 88, Col: 101} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var19)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</a></footer>") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</article>") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } return templ_7745c5c3_Err }) } + +func level(node *models.Node) int { + if node.Type == models.PostType { + return 1 + } + return 0 +} diff --git a/views/nodes_templ.txt b/views/nodes_templ.txt deleted file mode 100644 index 99b0698..0000000 --- a/views/nodes_templ.txt +++ /dev/null @@ -1,18 +0,0 @@ -<h1> -</h1><table><thead><tr><th>Тема</th><th>Дата</th><th>Автор</th></tr></thead> <tbody> -<tr><td colspan=\"3\"><strong>Тем нет</strong></td></tr> -</tbody></table> -<strong>Постов нет</strong> - -<a href=\"/login\">Войдите</a> чтобы ответить в тему. -<tr><td><a href=\" -\"> -</a></td><td> -</td><td> -</td></tr> -<article id=\" -\"><header class=\"post-header\"><span>Пост</span> <span> - в - <a href=\" -\">#</a></span></header> -</article> diff --git a/views/register.templ b/views/register.templ index 9974110..0a4384e 100644 --- a/views/register.templ +++ b/views/register.templ @@ -1,7 +1,7 @@ package views templ Register(username, email string) { - @Layout() { + @Layout(nil) { <h1>Регистрация</h1> <form method="post"> <label for="username">Имя пользователя:</label> diff --git a/views/register_templ.go b/views/register_templ.go index 184ef44..5008775 100644 --- a/views/register_templ.go +++ b/views/register_templ.go @@ -38,7 +38,7 @@ func Register(username, email string) templ.Component { }() } ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 1) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<h1>Регистрация</h1><form method=\"post\"><label for=\"username\">Имя пользователя:</label> <input type=\"text\" id=\"username\" name=\"username\" required=\"true\" placeholder=\"имя пользователя\" value=\"") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -51,7 +51,7 @@ func Register(username, email string) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 2) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\"> <label for=\"email\">Электропочта:</label> <input type=\"email\" id=\"email\" name=\"email\" required=\"true\" placeholder=\"имя@mail.ru\" value=\"") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -64,13 +64,13 @@ func Register(username, email string) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 3) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\"> <label for=\"password\">Пароль:</label> <input type=\"password\" id=\"password\" name=\"password\" required=\"true\" placeholder=\"пароль\"> <label for=\"password2\">Повторите пароль:</label> <input type=\"password\" id=\"password2\" name=\"password2\" required=\"true\" placeholder=\"повторите пароль\"> <input type=\"submit\" value=\"Регистрация\"></form>") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } return templ_7745c5c3_Err }) - templ_7745c5c3_Err = Layout().Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer) + templ_7745c5c3_Err = Layout(nil).Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } diff --git a/views/register_templ.txt b/views/register_templ.txt deleted file mode 100644 index a0d2d34..0000000 --- a/views/register_templ.txt +++ /dev/null @@ -1,3 +0,0 @@ -<h1>Регистрация</h1><form method=\"post\"><label for=\"username\">Имя пользователя:</label> <input type=\"text\" id=\"username\" name=\"username\" required=\"true\" placeholder=\"имя пользователя\" value=\" -\"> <label for=\"email\">Электропочта:</label> <input type=\"email\" id=\"email\" name=\"email\" required=\"true\" placeholder=\"имя@mail.ru\" value=\" -\"> <label for=\"password\">Пароль:</label> <input type=\"password\" id=\"password\" name=\"password\" required=\"true\" placeholder=\"пароль\"> <label for=\"password2\">Повторите пароль:</label> <input type=\"password\" id=\"password2\" name=\"password2\" required=\"true\" placeholder=\"повторите пароль\"> <input type=\"submit\" value=\"Регистрация\"></form> |