aboutsummaryrefslogtreecommitdiff
path: root/pkg
diff options
context:
space:
mode:
authorAlexander Neonxp Kiryukhin <i@neonxp.ru>2024-10-06 17:04:37 +0300
committerAlexander Neonxp Kiryukhin <i@neonxp.ru>2024-10-06 17:04:37 +0300
commit6160b4fdc5f37e4ceaa6b3c5acc855f466049d61 (patch)
treeb6e41f9cad22515a61cc50b0a82d98ee55e0c3e3 /pkg
parent81b13617c4d0ca68afb181d1105386f0c339864d (diff)
Первая версия
Diffstat (limited to 'pkg')
-rw-r--r--pkg/config/config.go25
-rw-r--r--pkg/db/config.go5
-rw-r--r--pkg/db/db.go18
-rw-r--r--pkg/handler/add.go60
-rw-r--r--pkg/handler/admin.go57
-rw-r--r--pkg/handler/handler.go7
-rw-r--r--pkg/handler/index.go32
-rw-r--r--pkg/handler/quote.go27
-rw-r--r--pkg/handler/random.go18
-rw-r--r--pkg/model/quote.go23
-rw-r--r--pkg/tpl/add.templ45
-rw-r--r--pkg/tpl/add_success.templ12
-rw-r--r--pkg/tpl/add_success_templ.go58
-rw-r--r--pkg/tpl/add_templ.go141
-rw-r--r--pkg/tpl/index.templ72
-rw-r--r--pkg/tpl/index_templ.go206
-rw-r--r--pkg/tpl/layout.templ51
-rw-r--r--pkg/tpl/layout_templ.go93
-rw-r--r--pkg/tpl/quote.templ30
-rw-r--r--pkg/tpl/quote_admin.templ36
-rw-r--r--pkg/tpl/quote_admin_templ.go150
-rw-r--r--pkg/tpl/quote_templ.go154
-rw-r--r--pkg/tpl/random.templ18
-rw-r--r--pkg/tpl/random_templ.go81
24 files changed, 1419 insertions, 0 deletions
diff --git a/pkg/config/config.go b/pkg/config/config.go
new file mode 100644
index 0000000..342012e
--- /dev/null
+++ b/pkg/config/config.go
@@ -0,0 +1,25 @@
+package config
+
+import (
+ "os"
+
+ "gopkg.in/yaml.v3"
+ "sh.org.ru/pkg/db"
+)
+
+type Config struct {
+ Debug bool `yaml:"debug"`
+ DB *db.Config `yaml:"db"`
+ Listen string `yaml:"listen"`
+ Admins map[string]string `yaml:"admins"`
+}
+
+func New(file string) (*Config, error) {
+ cfg := new(Config)
+ fp, err := os.Open(file)
+ if err != nil {
+ return nil, err
+ }
+
+ return cfg, yaml.NewDecoder(fp).Decode(cfg)
+}
diff --git a/pkg/db/config.go b/pkg/db/config.go
new file mode 100644
index 0000000..4df38f0
--- /dev/null
+++ b/pkg/db/config.go
@@ -0,0 +1,5 @@
+package db
+
+type Config struct {
+ DSN string `yaml:"dsn"`
+}
diff --git a/pkg/db/db.go b/pkg/db/db.go
new file mode 100644
index 0000000..8b7c47c
--- /dev/null
+++ b/pkg/db/db.go
@@ -0,0 +1,18 @@
+package db
+
+import (
+ "database/sql"
+
+ "github.com/uptrace/bun"
+ "github.com/uptrace/bun/dialect/pgdialect"
+ "github.com/uptrace/bun/driver/pgdriver"
+)
+
+// dsn := "postgres://postgres:@localhost:5432/test?sslmode=disable"
+// dsn := "unix://user:pass@dbname/var/run/postgresql/.s.PGSQL.5432"
+
+func New(config *Config) *bun.DB {
+ sqldb := sql.OpenDB(pgdriver.NewConnector(pgdriver.WithDSN(config.DSN)))
+
+ return bun.NewDB(sqldb, pgdialect.New())
+}
diff --git a/pkg/handler/add.go b/pkg/handler/add.go
new file mode 100644
index 0000000..a6a5ced
--- /dev/null
+++ b/pkg/handler/add.go
@@ -0,0 +1,60 @@
+package handler
+
+import (
+ "net/http"
+
+ "github.com/labstack/echo/v4"
+ "github.com/ssoda/captcha"
+ "sh.org.ru/pkg/model"
+ "sh.org.ru/pkg/tpl"
+)
+
+func (h *Handler) AddQuote(c echo.Context) error {
+ cid := captcha.New()
+ form := &tpl.AddQuoteForm{
+ CaptchaID: cid,
+ }
+ return tpl.
+ AddQuotePage(form, "").
+ Render(c.Request().Context(), c.Response())
+}
+
+func (h *Handler) AddQuotePost(c echo.Context) error {
+ form := &tpl.AddQuoteForm{}
+ if err := c.Bind(form); err != nil {
+ return err
+ }
+ if form.CaptchaValue == "" {
+ return formError(form, c, "Неверный код")
+ }
+ if !captcha.VerifyString(form.CaptchaID, form.CaptchaValue) {
+ return formError(form, c, "Неверный код")
+ }
+
+ if len(form.Quote) < 10 {
+ return formError(form, c, "Цитата слишком короткая")
+ }
+
+ q := &model.Quote{
+ Quote: form.Quote,
+ Approved: false,
+ Archive: false,
+ }
+ if _, err := h.DB.NewInsert().Model(q).Exec(c.Request().Context()); err != nil {
+ return err
+ }
+
+ return c.Redirect(http.StatusFound, "/add/success")
+}
+
+func (h *Handler) AddQuoteSuccess(c echo.Context) error {
+ return tpl.AddQuoteSuccessPage().Render(c.Request().Context(), c.Response())
+}
+
+func formError(form *tpl.AddQuoteForm, c echo.Context, err string) error {
+ form.CaptchaID = captcha.New()
+ form.CaptchaValue = ""
+ return tpl.
+ AddQuotePage(form, err).
+ Render(c.Request().Context(), c.Response())
+}
diff --git a/pkg/handler/admin.go b/pkg/handler/admin.go
new file mode 100644
index 0000000..8531011
--- /dev/null
+++ b/pkg/handler/admin.go
@@ -0,0 +1,57 @@
+package handler
+
+import (
+ "net/http"
+
+ "github.com/labstack/echo/v4"
+ "sh.org.ru/pkg/model"
+ "sh.org.ru/pkg/tpl"
+)
+
+func (h *Handler) Admin(c echo.Context) error {
+ quotes := make([]model.Quote, 0, 20)
+ count, err := h.DB.NewSelect().
+ Model((*model.Quote)(nil)).
+ Order("id ASC").
+ Where("approved = ?", false).
+ ScanAndCount(c.Request().Context(), &quotes)
+ if err != nil {
+ return err
+ }
+
+ return tpl.Admin(quotes, count).Render(c.Request().Context(), c.Response())
+}
+
+func (h *Handler) AdminAction(c echo.Context) error {
+ form := new(tpl.AdminForm)
+ if err := c.Bind(form); err != nil {
+ return err
+ }
+
+ switch form.Action {
+ case "approve":
+ _, err := h.DB.NewUpdate().
+ Model(&model.Quote{
+ ID: int64(form.ID),
+ Approved: true,
+ }).
+ Column("approved").
+ WherePK("id").
+ Exec(c.Request().Context())
+ if err != nil {
+ return err
+ }
+ case "decline":
+ _, err := h.DB.NewDelete().
+ Model(&model.Quote{
+ ID: int64(form.ID),
+ }).
+ WherePK("id").
+ Exec(c.Request().Context())
+ if err != nil {
+ return err
+ }
+ }
+
+ return c.Redirect(http.StatusFound, "/admin/")
+}
diff --git a/pkg/handler/handler.go b/pkg/handler/handler.go
new file mode 100644
index 0000000..5ba3966
--- /dev/null
+++ b/pkg/handler/handler.go
@@ -0,0 +1,7 @@
+package handler
+
+import "github.com/uptrace/bun"
+
+type Handler struct {
+ DB *bun.DB
+}
diff --git a/pkg/handler/index.go b/pkg/handler/index.go
new file mode 100644
index 0000000..611a544
--- /dev/null
+++ b/pkg/handler/index.go
@@ -0,0 +1,32 @@
+package handler
+
+import (
+ "github.com/labstack/echo/v4"
+ "sh.org.ru/pkg/model"
+ "sh.org.ru/pkg/tpl"
+)
+
+func (h *Handler) Index(c echo.Context) error {
+ p := &Pagination{}
+ if err := c.Bind(p); err != nil {
+ return err
+ }
+
+ quotes := make([]model.Quote, 0, 20)
+ count, err := h.DB.NewSelect().
+ Model((*model.Quote)(nil)).
+ Order("id DESC").
+ Where("approved = ?", true).
+ Limit(20).
+ Offset(p.Page*20).
+ ScanAndCount(c.Request().Context(), &quotes)
+ if err != nil {
+ return err
+ }
+
+ return tpl.Index(quotes, p.Page, count).Render(c.Request().Context(), c.Response())
+}
+
+type Pagination struct {
+ Page int `query:"page" default:"0"`
+}
diff --git a/pkg/handler/quote.go b/pkg/handler/quote.go
new file mode 100644
index 0000000..af9fd82
--- /dev/null
+++ b/pkg/handler/quote.go
@@ -0,0 +1,27 @@
+package handler
+
+import (
+ "strconv"
+
+ "github.com/labstack/echo/v4"
+ "sh.org.ru/pkg/model"
+ "sh.org.ru/pkg/tpl"
+)
+
+func (h *Handler) Quote(c echo.Context) error {
+ sid := c.Param("id")
+ id, err := strconv.Atoi(sid)
+ if err != nil {
+ return err
+ }
+
+ quote := new(model.Quote)
+ err = h.DB.NewSelect().
+ Model(quote).
+ Where("id = ?", id).Scan(c.Request().Context(), quote)
+ if err != nil {
+ return err
+ }
+
+ return tpl.QuotePage(quote).Render(c.Request().Context(), c.Response())
+}
diff --git a/pkg/handler/random.go b/pkg/handler/random.go
new file mode 100644
index 0000000..091058f
--- /dev/null
+++ b/pkg/handler/random.go
@@ -0,0 +1,18 @@
+package handler
+
+import (
+ "github.com/labstack/echo/v4"
+ "sh.org.ru/pkg/model"
+ "sh.org.ru/pkg/tpl"
+)
+
+func (h *Handler) Random(c echo.Context) error {
+ quotes := make([]model.Quote, 0, 20)
+ err := h.DB.NewRaw(`select q.* from quotes q where q.approved = true order by random() limit 20`).
+ Scan(c.Request().Context(), &quotes)
+ if err != nil {
+ return err
+ }
+
+ return tpl.Random(quotes).Render(c.Request().Context(), c.Response())
+}
diff --git a/pkg/model/quote.go b/pkg/model/quote.go
new file mode 100644
index 0000000..0ad89cf
--- /dev/null
+++ b/pkg/model/quote.go
@@ -0,0 +1,23 @@
+package model
+
+import (
+ "strings"
+ "time"
+
+ "github.com/uptrace/bun"
+)
+
+type Quote struct {
+ bun.BaseModel `bun:"table:quotes,alias:q"`
+
+ ID int64 `bun:",pk,autoincrement"`
+ Quote string `bun:",notnull"`
+ Approved bool
+ Archive bool
+ CreatedAt time.Time `bun:",nullzero,notnull,default:current_timestamp"`
+ DeletedAt time.Time `bun:",soft_delete,nullzero"`
+}
+
+func (q *Quote) Text() string {
+ return strings.ReplaceAll(q.Quote, "\n", "<br />")
+}
diff --git a/pkg/tpl/add.templ b/pkg/tpl/add.templ
new file mode 100644
index 0000000..0611d1b
--- /dev/null
+++ b/pkg/tpl/add.templ
@@ -0,0 +1,45 @@
+package tpl
+
+import "fmt"
+
+templ AddQuotePage(form *AddQuoteForm, err string) {
+ @Layout(HeaderParams{}) {
+ <h2>Добавление цитаты</h2>
+ if err != "" {
+ <article>
+ <header>Ошибка</header>
+ { err }
+ </article>
+ }
+ <form method="post">
+ <textarea rows="5" name="quote" placeholder="Текст цитаты">{ form.Quote }</textarea>
+ <input type="hidden" name="captcha_id" value={ form.CaptchaID }/>
+ <div role="group">
+ <img class="captcha" src={ fmt.Sprintf("/captcha/download/%s.png", form.CaptchaID) }/>
+ <audio id="audiocaptcha" src={ fmt.Sprintf("/captcha/download/%s.wav?lang=ru", form.CaptchaID) }></audio>
+ <a role="button" onclick="togglePlay()">Прослушать</a>
+ <input type="text" name="captcha_value" placeholder="Код с картинки"/>
+ </div>
+ <input type="submit" value="Отправить на модерацию"/>
+ </form>
+ <script>
+ var myAudio = document.getElementById("audiocaptcha");
+ var isPlaying = false;
+ function togglePlay() {
+ isPlaying ? myAudio.pause() : myAudio.play();
+ };
+ myAudio.onplaying = function() {
+ isPlaying = true;
+ };
+ myAudio.onpause = function() {
+ isPlaying = false;
+ };
+ </script>
+ }
+}
+
+type AddQuoteForm struct {
+ Quote string `form:"quote"`
+ CaptchaID string `form:"captcha_id"`
+ CaptchaValue string `form:"captcha_value"`
+}
diff --git a/pkg/tpl/add_success.templ b/pkg/tpl/add_success.templ
new file mode 100644
index 0000000..25caa7c
--- /dev/null
+++ b/pkg/tpl/add_success.templ
@@ -0,0 +1,12 @@
+package tpl
+
+templ AddQuoteSuccessPage() {
+ @Layout(HeaderParams{}) {
+ <h2>Добавление цитаты</h2>
+ <article>
+ <header>Успешно!</header>
+ Ваша цитата отправлена на модерацию. В случае успешной проверки, цитата скоро будет опубликована.
+ </article>
+ <a href="/" role="button">На главную страницу</a>
+ }
+}
diff --git a/pkg/tpl/add_success_templ.go b/pkg/tpl/add_success_templ.go
new file mode 100644
index 0000000..c036c68
--- /dev/null
+++ b/pkg/tpl/add_success_templ.go
@@ -0,0 +1,58 @@
+// Code generated by templ - DO NOT EDIT.
+
+// templ: version: v0.2.778
+package tpl
+
+//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 AddQuoteSuccessPage() 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
+ if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
+ return templ_7745c5c3_CtxErr
+ }
+ 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("<h2>Добавление цитаты</h2><article><header>Успешно!</header>Ваша цитата отправлена на модерацию. В случае успешной проверки, цитата скоро будет опубликована.</article><a href=\"/\" role=\"button\">На главную страницу</a>")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ return templ_7745c5c3_Err
+ })
+ templ_7745c5c3_Err = Layout(HeaderParams{}).Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ return templ_7745c5c3_Err
+ })
+}
+
+var _ = templruntime.GeneratedTemplate
diff --git a/pkg/tpl/add_templ.go b/pkg/tpl/add_templ.go
new file mode 100644
index 0000000..04fb615
--- /dev/null
+++ b/pkg/tpl/add_templ.go
@@ -0,0 +1,141 @@
+// Code generated by templ - DO NOT EDIT.
+
+// templ: version: v0.2.778
+package tpl
+
+//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"
+
+func AddQuotePage(form *AddQuoteForm, err string) 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
+ if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
+ return templ_7745c5c3_CtxErr
+ }
+ 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("<h2>Добавление цитаты</h2>")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ if err != "" {
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<article><header>Ошибка</header>")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var3 string
+ templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(err)
+ if templ_7745c5c3_Err != nil {
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/tpl/add.templ`, Line: 11, Col: 9}
+ }
+ _, 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("</article>")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" <form method=\"post\"><textarea rows=\"5\" name=\"quote\" placeholder=\"Текст цитаты\">")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var4 string
+ templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(form.Quote)
+ if templ_7745c5c3_Err != nil {
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/tpl/add.templ`, Line: 15, Col: 85}
+ }
+ _, 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("</textarea> <input type=\"hidden\" name=\"captcha_id\" value=\"")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var5 string
+ templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(form.CaptchaID)
+ if templ_7745c5c3_Err != nil {
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/tpl/add.templ`, Line: 16, Col: 64}
+ }
+ _, 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_7745c5c3_Buffer.WriteString("\"><div role=\"group\"><img class=\"captcha\" src=\"")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var6 string
+ templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("/captcha/download/%s.png", form.CaptchaID))
+ if templ_7745c5c3_Err != nil {
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/tpl/add.templ`, Line: 18, Col: 86}
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6))
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\"> <audio id=\"audiocaptcha\" src=\"")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var7 string
+ templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("/captcha/download/%s.wav?lang=ru", form.CaptchaID))
+ if templ_7745c5c3_Err != nil {
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/tpl/add.templ`, Line: 19, Col: 98}
+ }
+ _, 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("\"></audio> <a role=\"button\" onclick=\"togglePlay()\">Прослушать</a> <input type=\"text\" name=\"captcha_value\" placeholder=\"Код с картинки\"></div><input type=\"submit\" value=\"Отправить на модерацию\"></form><script>\n\t\t\tvar myAudio = document.getElementById(\"audiocaptcha\");\n\t\t\tvar isPlaying = false;\n\t\t\tfunction togglePlay() {\n\t\t\t\tisPlaying ? myAudio.pause() : myAudio.play();\n\t\t\t};\n\t\t\tmyAudio.onplaying = function() {\n\t\t\t\tisPlaying = true;\n\t\t\t};\n\t\t\tmyAudio.onpause = function() {\n\t\t\t\tisPlaying = false;\n\t\t\t};\n\t\t</script>")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ return templ_7745c5c3_Err
+ })
+ templ_7745c5c3_Err = Layout(HeaderParams{}).Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ return templ_7745c5c3_Err
+ })
+}
+
+type AddQuoteForm struct {
+ Quote string `form:"quote"`
+ CaptchaID string `form:"captcha_id"`
+ CaptchaValue string `form:"captcha_value"`
+}
+
+var _ = templruntime.GeneratedTemplate
diff --git a/pkg/tpl/index.templ b/pkg/tpl/index.templ
new file mode 100644
index 0000000..b405f3e
--- /dev/null
+++ b/pkg/tpl/index.templ
@@ -0,0 +1,72 @@
+package tpl
+
+import (
+ "fmt"
+ "sh.org.ru/pkg/model"
+ "strconv"
+)
+
+templ Index(quotes []model.Quote, page, count int) {
+ @Layout(HeaderParams{
+ Title: "Цитатник Рунета",
+ Description: "Новый цитатник Рунета",
+ URL: "https://sh.org.ru/",
+ }) {
+ for _, q := range quotes {
+ @Quote(&q)
+ }
+ <nav>
+ <ul>
+ if page > 0 {
+ <li><a href={ templ.URL(fmt.Sprintf("/?page=%d", page-1)) }>&larr;</a></li>
+ }
+ for _, p := range generatePagination(page, count/20) {
+ if p == "..." {
+ <li>...</li>
+ } else if p == strconv.Itoa(page) {
+ <li>[{ p }]</li>
+ } else {
+ <li><a href={ templ.URL(fmt.Sprintf("/?page=%s", p)) }>{ p }</a></li>
+ }
+ }
+
+ if page < count/20 {
+ <li><a href={ templ.URL(fmt.Sprintf("/?page=%d", page+1)) }>&rarr;</a></li>
+ }
+ </ul>
+ </nav>
+ Всего { strconv.Itoa(count) } цитат.
+ }
+}
+
+func generatePagination(currentPage, totalPages int) []string {
+ pagination := make([]string, 0, 11)
+
+ if currentPage <= 3 {
+ for i := 0; i <= currentPage+3; i++ {
+ pagination = append(pagination, strconv.Itoa(i))
+ }
+ pagination = append(pagination, "...")
+ pagination = append(pagination, strconv.Itoa(totalPages-2))
+ pagination = append(pagination, strconv.Itoa(totalPages-1))
+ pagination = append(pagination, strconv.Itoa(totalPages))
+ } else if currentPage >= totalPages-3 {
+ pagination = append(pagination, "0")
+ pagination = append(pagination, "1")
+ pagination = append(pagination, "2")
+ pagination = append(pagination, "...")
+ for i := currentPage - 3; i <= totalPages; i++ {
+ pagination = append(pagination, strconv.Itoa(i))
+ }
+ } else {
+ pagination = append(pagination, "0")
+ pagination = append(pagination, "...")
+ for i := currentPage - 2; i <= currentPage+2; i++ {
+ pagination = append(pagination, strconv.Itoa(i))
+ }
+ pagination = append(pagination, "...")
+ pagination = append(pagination, strconv.Itoa(totalPages))
+ }
+
+ return pagination
+}
diff --git a/pkg/tpl/index_templ.go b/pkg/tpl/index_templ.go
new file mode 100644
index 0000000..4d249ab
--- /dev/null
+++ b/pkg/tpl/index_templ.go
@@ -0,0 +1,206 @@
+// Code generated by templ - DO NOT EDIT.
+
+// templ: version: v0.2.778
+package tpl
+
+//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"
+ "sh.org.ru/pkg/model"
+ "strconv"
+)
+
+func Index(quotes []model.Quote, page, count int) 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
+ if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
+ return templ_7745c5c3_CtxErr
+ }
+ 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)
+ for _, q := range quotes {
+ templ_7745c5c3_Err = Quote(&q).Render(ctx, templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" <nav><ul>")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ if page > 0 {
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<li><a href=\"")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var3 templ.SafeURL = templ.URL(fmt.Sprintf("/?page=%d", page-1))
+ _, 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("\">&larr;</a></li>")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ }
+ for _, p := range generatePagination(page, count/20) {
+ if p == "..." {
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<li>...</li>")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ } else if p == strconv.Itoa(page) {
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<li>[")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var4 string
+ templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(p)
+ if templ_7745c5c3_Err != nil {
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/tpl/index.templ`, Line: 27, Col: 14}
+ }
+ _, 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("]</li>")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ } else {
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<li><a href=\"")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var5 templ.SafeURL = templ.URL(fmt.Sprintf("/?page=%s", p))
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var5)))
+ 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_Var6 string
+ templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(p)
+ if templ_7745c5c3_Err != nil {
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/tpl/index.templ`, Line: 29, Col: 64}
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6))
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</a></li>")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ }
+ }
+ if page < count/20 {
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<li><a href=\"")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var7 templ.SafeURL = templ.URL(fmt.Sprintf("/?page=%d", page+1))
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var7)))
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">&rarr;</a></li>")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</ul></nav>Всего ")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var8 string
+ templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(strconv.Itoa(count))
+ if templ_7745c5c3_Err != nil {
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/tpl/index.templ`, Line: 38, Col: 34}
+ }
+ _, 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_7745c5c3_Buffer.WriteString(" цитат.")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ return templ_7745c5c3_Err
+ })
+ templ_7745c5c3_Err = Layout(HeaderParams{
+ Title: "Цитатник Рунета",
+ Description: "Новый цитатник Рунета",
+ URL: "https://sh.org.ru/",
+ }).Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ return templ_7745c5c3_Err
+ })
+}
+
+func generatePagination(currentPage, totalPages int) []string {
+ pagination := make([]string, 0, 11)
+
+ if currentPage <= 3 {
+ for i := 0; i <= currentPage+3; i++ {
+ pagination = append(pagination, strconv.Itoa(i))
+ }
+ pagination = append(pagination, "...")
+ pagination = append(pagination, strconv.Itoa(totalPages-2))
+ pagination = append(pagination, strconv.Itoa(totalPages-1))
+ pagination = append(pagination, strconv.Itoa(totalPages))
+ } else if currentPage >= totalPages-3 {
+ pagination = append(pagination, "0")
+ pagination = append(pagination, "1")
+ pagination = append(pagination, "2")
+ pagination = append(pagination, "...")
+ for i := currentPage - 3; i <= totalPages; i++ {
+ pagination = append(pagination, strconv.Itoa(i))
+ }
+ } else {
+ pagination = append(pagination, "0")
+ pagination = append(pagination, "...")
+ for i := currentPage - 2; i <= currentPage+2; i++ {
+ pagination = append(pagination, strconv.Itoa(i))
+ }
+ pagination = append(pagination, "...")
+ pagination = append(pagination, strconv.Itoa(totalPages))
+ }
+
+ return pagination
+}
+
+var _ = templruntime.GeneratedTemplate
diff --git a/pkg/tpl/layout.templ b/pkg/tpl/layout.templ
new file mode 100644
index 0000000..fc9314e
--- /dev/null
+++ b/pkg/tpl/layout.templ
@@ -0,0 +1,51 @@
+package tpl
+
+templ Layout(params HeaderParams) {
+ <!DOCTYPE html>
+ <html lang="en">
+ <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="/css/pico.css"/>
+ <link rel="stylesheet" href="/css/style.css"/>
+ <link rel="stylesheet" href="/css/fork-awesome.min.css"/>
+ <meta property="og:title" content={ params.Title }/>
+ <meta property="og:url" content={ params.URL }/>
+ <meta property="og:description" content={ params.Description }/>
+ <title>ШОргРу</title>
+ </head>
+ <body>
+ <main class="container">
+ <nav>
+ <ul>
+ <li><a href="/"><strong>ШОргРу</strong></a></li>
+ </ul>
+ <ul>
+ <li><a href="/add">Добавить цитату</a></li>
+ <li><a href="/random">Случайные</a></li>
+ </ul>
+ </nav>
+ { children... }
+ </main>
+ </body>
+ <footer>
+ <main class="container">
+ <nav>
+ <ul>
+ <li>Сделал <a href="https://neonxp.ru/">NeonXP</a> в 2024 году.</li>
+ </ul>
+ <ul>
+ <a href="https://gitrepo.ru/NeonXP/ShOrgRu">Исходный код</a>
+ </ul>
+ </nav>
+ </main>
+ </footer>
+ </html>
+}
+
+type HeaderParams struct {
+ Title string
+ URL string
+ Description string
+}
diff --git a/pkg/tpl/layout_templ.go b/pkg/tpl/layout_templ.go
new file mode 100644
index 0000000..133beed
--- /dev/null
+++ b/pkg/tpl/layout_templ.go
@@ -0,0 +1,93 @@
+// Code generated by templ - DO NOT EDIT.
+
+// templ: version: v0.2.778
+package tpl
+
+//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 Layout(params HeaderParams) 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
+ if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
+ return templ_7745c5c3_CtxErr
+ }
+ 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_7745c5c3_Buffer.WriteString("<!doctype html><html lang=\"en\"><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=\"/css/pico.css\"><link rel=\"stylesheet\" href=\"/css/style.css\"><link rel=\"stylesheet\" href=\"/css/fork-awesome.min.css\"><meta property=\"og:title\" content=\"")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var2 string
+ templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(params.Title)
+ if templ_7745c5c3_Err != nil {
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/tpl/layout.templ`, Line: 13, Col: 51}
+ }
+ _, 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_7745c5c3_Buffer.WriteString("\"><meta property=\"og:url\" content=\"")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var3 string
+ templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(params.URL)
+ if templ_7745c5c3_Err != nil {
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/tpl/layout.templ`, Line: 14, Col: 47}
+ }
+ _, 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("\"><meta property=\"og:description\" content=\"")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var4 string
+ templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(params.Description)
+ if templ_7745c5c3_Err != nil {
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/tpl/layout.templ`, Line: 15, Col: 63}
+ }
+ _, 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("\"><title>ШОргРу</title></head><body><main class=\"container\"><nav><ul><li><a href=\"/\"><strong>ШОргРу</strong></a></li></ul><ul><li><a href=\"/add\">Добавить цитату</a></li><li><a href=\"/random\">Случайные</a></li></ul></nav>")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = templ_7745c5c3_Var1.Render(ctx, templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</main></body><footer><main class=\"container\"><nav><ul><li>Сделал <a href=\"https://neonxp.ru/\">NeonXP</a> в 2024 году.</li></ul><ul><a href=\"https://gitrepo.ru/NeonXP/ShOrgRu\">Исходный код</a></ul></nav></main></footer></html>")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ return templ_7745c5c3_Err
+ })
+}
+
+type HeaderParams struct {
+ Title string
+ URL string
+ Description string
+}
+
+var _ = templruntime.GeneratedTemplate
diff --git a/pkg/tpl/quote.templ b/pkg/tpl/quote.templ
new file mode 100644
index 0000000..459a873
--- /dev/null
+++ b/pkg/tpl/quote.templ
@@ -0,0 +1,30 @@
+package tpl
+
+import (
+ "fmt"
+ "sh.org.ru/pkg/model"
+ "strconv"
+)
+
+templ Quote(quote *model.Quote) {
+ <article>
+ <header><a href={ templ.URL(fmt.Sprintf("/quote/%d", quote.ID)) }>#{ strconv.Itoa(int(quote.ID)) }</a></header>
+ @templ.Raw(quote.Text())
+ <footer>
+ Поделиться:&nbsp;
+ <a target="_blank" href={ templ.URL(fmt.Sprintf("https://t.me/share/url?url=https://sh.org.ru/%d&text=%s", quote.ID, quote.Quote)) }><i class="fa fa-telegram" aria-hidden="true"></i></a>&nbsp;
+ <a target="_blank" href={ templ.URL(fmt.Sprintf("https://vk.com/share.php?url=https://sh.org.ru/%d", quote.ID)) }><i class="fa fa-vk" aria-hidden="true"></i></a>&nbsp;
+ <a target="_blank" href={ templ.URL(fmt.Sprintf("https://connect.ok.ru/offer?url=https://sh.org.ru/%d", quote.ID)) }><i class="fa fa-odnoklassniki-square" aria-hidden="true"></i></a>
+ </footer>
+ </article>
+}
+
+templ QuotePage(quote *model.Quote) {
+ @Layout(HeaderParams{
+ Title: "Цитата #" + strconv.Itoa(int(quote.ID)),
+ URL: fmt.Sprintf("https://sh.org.ru/quote/%d", quote.ID),
+ Description: templ.EscapeString(quote.Quote),
+ }) {
+ @Quote(quote)
+ }
+}
diff --git a/pkg/tpl/quote_admin.templ b/pkg/tpl/quote_admin.templ
new file mode 100644
index 0000000..80bafad
--- /dev/null
+++ b/pkg/tpl/quote_admin.templ
@@ -0,0 +1,36 @@
+package tpl
+
+import (
+ "sh.org.ru/pkg/model"
+ "strconv"
+)
+
+templ Admin(quotes []model.Quote, count int) {
+ @Layout(HeaderParams{}) {
+ for _, q := range quotes {
+ @QuoteAdmin(&q)
+ }
+ Всего { strconv.Itoa(count) } цитат.
+ }
+}
+
+templ QuoteAdmin(quote *model.Quote) {
+ <article>
+ <header>#{ strconv.Itoa(int(quote.ID)) }</header>
+ @templ.Raw(quote.Text())
+ <footer>
+ <form method="post" action="/admin/action">
+ <input type="hidden" name="id" value={ strconv.Itoa(int(quote.ID)) }/>
+ <div role="group">
+ <input class="primary" type="submit" name="action" value="approve"/>
+ <input class="secondary" type="submit" name="action" value="decline"/>
+ </div>
+ </form>
+ </footer>
+ </article>
+}
+
+type AdminForm struct {
+ ID int `form:"id"`
+ Action string `form:"action"`
+}
diff --git a/pkg/tpl/quote_admin_templ.go b/pkg/tpl/quote_admin_templ.go
new file mode 100644
index 0000000..ab8ceeb
--- /dev/null
+++ b/pkg/tpl/quote_admin_templ.go
@@ -0,0 +1,150 @@
+// Code generated by templ - DO NOT EDIT.
+
+// templ: version: v0.2.778
+package tpl
+
+//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 (
+ "sh.org.ru/pkg/model"
+ "strconv"
+)
+
+func Admin(quotes []model.Quote, count int) 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
+ if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
+ return templ_7745c5c3_CtxErr
+ }
+ 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)
+ for _, q := range quotes {
+ templ_7745c5c3_Err = QuoteAdmin(&q).Render(ctx, templ_7745c5c3_Buffer)
+ 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_Var3 string
+ templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(strconv.Itoa(count))
+ if templ_7745c5c3_Err != nil {
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/tpl/quote_admin.templ`, Line: 13, Col: 34}
+ }
+ _, 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(" цитат.")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ return templ_7745c5c3_Err
+ })
+ templ_7745c5c3_Err = Layout(HeaderParams{}).Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ return templ_7745c5c3_Err
+ })
+}
+
+func QuoteAdmin(quote *model.Quote) 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
+ if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
+ return templ_7745c5c3_CtxErr
+ }
+ 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_Var4 := templ.GetChildren(ctx)
+ if templ_7745c5c3_Var4 == nil {
+ templ_7745c5c3_Var4 = templ.NopComponent
+ }
+ ctx = templ.ClearChildren(ctx)
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<article><header>#")
+ 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(quote.ID)))
+ if templ_7745c5c3_Err != nil {
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/tpl/quote_admin.templ`, Line: 19, Col: 40}
+ }
+ _, 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_7745c5c3_Buffer.WriteString("</header>")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = templ.Raw(quote.Text()).Render(ctx, templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<footer><form method=\"post\" action=\"/admin/action\"><input type=\"hidden\" name=\"id\" value=\"")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var6 string
+ templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(strconv.Itoa(int(quote.ID)))
+ if templ_7745c5c3_Err != nil {
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/tpl/quote_admin.templ`, Line: 23, Col: 70}
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6))
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\"><div role=\"group\"><input class=\"primary\" type=\"submit\" name=\"action\" value=\"approve\"> <input class=\"secondary\" type=\"submit\" name=\"action\" value=\"decline\"></div></form></footer></article>")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ return templ_7745c5c3_Err
+ })
+}
+
+type AdminForm struct {
+ ID int `form:"id"`
+ Action string `form:"action"`
+}
+
+var _ = templruntime.GeneratedTemplate
diff --git a/pkg/tpl/quote_templ.go b/pkg/tpl/quote_templ.go
new file mode 100644
index 0000000..e374b88
--- /dev/null
+++ b/pkg/tpl/quote_templ.go
@@ -0,0 +1,154 @@
+// Code generated by templ - DO NOT EDIT.
+
+// templ: version: v0.2.778
+package tpl
+
+//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"
+ "sh.org.ru/pkg/model"
+ "strconv"
+)
+
+func Quote(quote *model.Quote) 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
+ if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
+ return templ_7745c5c3_CtxErr
+ }
+ 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_7745c5c3_Buffer.WriteString("<article><header><a href=\"")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var2 templ.SafeURL = templ.URL(fmt.Sprintf("/quote/%d", quote.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_7745c5c3_Buffer.WriteString("\">#")
+ 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(quote.ID)))
+ if templ_7745c5c3_Err != nil {
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/tpl/quote.templ`, Line: 11, Col: 98}
+ }
+ _, 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("</a></header>")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = templ.Raw(quote.Text()).Render(ctx, templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<footer>Поделиться:&nbsp; <a target=\"_blank\" href=\"")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var4 templ.SafeURL = templ.URL(fmt.Sprintf("https://t.me/share/url?url=https://sh.org.ru/%d&text=%s", quote.ID, quote.Quote))
+ _, 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("\"><i class=\"fa fa-telegram\" aria-hidden=\"true\"></i></a>&nbsp; <a target=\"_blank\" href=\"")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var5 templ.SafeURL = templ.URL(fmt.Sprintf("https://vk.com/share.php?url=https://sh.org.ru/%d", quote.ID))
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var5)))
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\"><i class=\"fa fa-vk\" aria-hidden=\"true\"></i></a>&nbsp; <a target=\"_blank\" href=\"")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var6 templ.SafeURL = templ.URL(fmt.Sprintf("https://connect.ok.ru/offer?url=https://sh.org.ru/%d", quote.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_7745c5c3_Buffer.WriteString("\"><i class=\"fa fa-odnoklassniki-square\" aria-hidden=\"true\"></i></a></footer></article>")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ return templ_7745c5c3_Err
+ })
+}
+
+func QuotePage(quote *model.Quote) 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
+ if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
+ return templ_7745c5c3_CtxErr
+ }
+ 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_Var7 := templ.GetChildren(ctx)
+ if templ_7745c5c3_Var7 == nil {
+ templ_7745c5c3_Var7 = templ.NopComponent
+ }
+ ctx = templ.ClearChildren(ctx)
+ templ_7745c5c3_Var8 := 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 = Quote(quote).Render(ctx, templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ return templ_7745c5c3_Err
+ })
+ templ_7745c5c3_Err = Layout(HeaderParams{
+ Title: "Цитата #" + strconv.Itoa(int(quote.ID)),
+ URL: fmt.Sprintf("https://sh.org.ru/quote/%d", quote.ID),
+ Description: templ.EscapeString(quote.Quote),
+ }).Render(templ.WithChildren(ctx, templ_7745c5c3_Var8), templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ return templ_7745c5c3_Err
+ })
+}
+
+var _ = templruntime.GeneratedTemplate
diff --git a/pkg/tpl/random.templ b/pkg/tpl/random.templ
new file mode 100644
index 0000000..63e7c03
--- /dev/null
+++ b/pkg/tpl/random.templ
@@ -0,0 +1,18 @@
+package tpl
+
+import "sh.org.ru/pkg/model"
+import "math/rand"
+import "fmt"
+
+templ Random(quotes []model.Quote) {
+ @Layout(HeaderParams{
+ Title: "Цитатник Рунета -- случайные",
+ Description: "Новый цитатник Рунета",
+ URL: "https://sh.org.ru/random",
+ }) {
+ for _, q := range quotes {
+ @Quote(&q)
+ }
+ <a role="button" href={templ.URL(fmt.Sprintf("/random?%d", rand.Int()))}>Обновить</a>
+ }
+}
diff --git a/pkg/tpl/random_templ.go b/pkg/tpl/random_templ.go
new file mode 100644
index 0000000..19a7035
--- /dev/null
+++ b/pkg/tpl/random_templ.go
@@ -0,0 +1,81 @@
+// Code generated by templ - DO NOT EDIT.
+
+// templ: version: v0.2.778
+package tpl
+
+//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 "sh.org.ru/pkg/model"
+import "math/rand"
+import "fmt"
+
+func Random(quotes []model.Quote) 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
+ if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
+ return templ_7745c5c3_CtxErr
+ }
+ 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)
+ for _, q := range quotes {
+ templ_7745c5c3_Err = Quote(&q).Render(ctx, templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" <a role=\"button\" href=\"")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var3 templ.SafeURL = templ.URL(fmt.Sprintf("/random?%d", rand.Int()))
+ _, 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
+ }
+ return templ_7745c5c3_Err
+ })
+ templ_7745c5c3_Err = Layout(HeaderParams{
+ Title: "Цитатник Рунета -- случайные",
+ Description: "Новый цитатник Рунета",
+ URL: "https://sh.org.ru/random",
+ }).Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ return templ_7745c5c3_Err
+ })
+}
+
+var _ = templruntime.GeneratedTemplate