Commit 6f872fe2f7
Unsigned
Layout: unified · split
app/config.go added +48
| @@ -0,0 +1,48 @@ | |||
| 1 | package app | ||
| 2 | |||
| 3 | import ( | ||
| 4 | "os" | ||
| 5 | ) | ||
| 6 | |||
| 7 | type cache_config struct { | ||
| 8 | Enabled bool | ||
| 9 | Path string | ||
| 10 | Max_size, Lifetime int64 | ||
| 11 | } | ||
| 12 | |||
| 13 | type config struct { | ||
| 14 | cfg string | ||
| 15 | Listen, Base_uri string | ||
| 16 | Cache cache_config | ||
| 17 | Proxy, Nsfw bool | ||
| 18 | } | ||
| 19 | |||
| 20 | var CFG = config{ | ||
| 21 | cfg: "config.json", | ||
| 22 | Listen: "127.0.0.1:3003", | ||
| 23 | Base_uri: "/", | ||
| 24 | Cache: cache_config{ | ||
| 25 | Enabled: true, | ||
| 26 | Path: "cache", | ||
| 27 | }, | ||
| 28 | Proxy: true, | ||
| 29 | Nsfw: true, | ||
| 30 | } | ||
| 31 | |||
| 32 | func execcfg() { | ||
| 33 | a := os.Args | ||
| 34 | for num, val := range a { | ||
| 35 | switch val { | ||
| 36 | case "-conf": | ||
| 37 | CFG.cfg = a[num] | ||
| 38 | case "-help": | ||
| 39 | println(`SkunkyArt v 1.3 [refactoring] | ||
| 40 | Usage: | ||
| 41 | - -conf - path to config | ||
| 42 | - -help this message | ||
| 43 | Example: | ||
| 44 | ./skunkyart -conf config.json | ||
| 45 | Copyright lost+skunk, X11. https://git.macaw.me/skunky/skunkyart/src/tag/v1.3`) | ||
| 46 | } | ||
| 47 | } | ||
| 48 | } | ||
app/router.go added +81
| @@ -0,0 +1,81 @@ | |||
| 1 | package app | ||
| 2 | |||
| 3 | import ( | ||
| 4 | "io" | ||
| 5 | "net/http" | ||
| 6 | u "net/url" | ||
| 7 | "os" | ||
| 8 | "strconv" | ||
| 9 | "strings" | ||
| 10 | ) | ||
| 11 | |||
| 12 | const addr string = "0.0.0.0:3003" | ||
| 13 | |||
| 14 | // роутер | ||
| 15 | func Router() { | ||
| 16 | // расшифровка эндпоинта из урл | ||
| 17 | endpoint := func(url string) (string, string) { | ||
| 18 | if CFG.Base_uri != "" { | ||
| 19 | url = strings.Replace(url, CFG.Base_uri, "/", 1) | ||
| 20 | } | ||
| 21 | |||
| 22 | end := strings.Index(url[1:], "/") | ||
| 23 | if end == -1 { | ||
| 24 | return url[1:], "" | ||
| 25 | } | ||
| 26 | return url[1 : end+1], url[end+2:] | ||
| 27 | } | ||
| 28 | |||
| 29 | // функция, что управляет всем | ||
| 30 | handle := func(w http.ResponseWriter, r *http.Request) { | ||
| 31 | e, url := endpoint(r.URL.Path) | ||
| 32 | var wr = io.WriteString | ||
| 33 | open_n_send := func(name string) { | ||
| 34 | f, e := os.ReadFile(name) | ||
| 35 | err(e) | ||
| 36 | wr(w, string(f)) | ||
| 37 | } | ||
| 38 | |||
| 39 | // структура с функциями | ||
| 40 | var skunky skunkyart | ||
| 41 | skunky.Args = r.URL.Query() | ||
| 42 | skunky.Writer = w | ||
| 43 | |||
| 44 | arg := skunky.Args.Get | ||
| 45 | skunky.Query = u.QueryEscape(arg("q")) | ||
| 46 | if t := arg("type"); len(t) > 0 { | ||
| 47 | skunky.Type = rune(t[0]) | ||
| 48 | } | ||
| 49 | p, _ := strconv.Atoi(arg("p")) | ||
| 50 | skunky.Page = p | ||
| 51 | |||
| 52 | // пути | ||
| 53 | switch e { | ||
| 54 | default: | ||
| 55 | skunky.httperr(404) | ||
| 56 | case "/", "": | ||
| 57 | open_n_send("html/index.htm") | ||
| 58 | case "post": | ||
| 59 | slash := strings.Index(url, "/") | ||
| 60 | skunky.Deviation(url[:slash], url[slash+1:]) | ||
| 61 | case "search": | ||
| 62 | skunky.Search() | ||
| 63 | case "media": | ||
| 64 | skunky.Emojitar(url) | ||
| 65 | case "about": | ||
| 66 | open_n_send("html/about.htm") | ||
| 67 | case "gui": | ||
| 68 | w.Header().Add("content-type", "text/css") | ||
| 69 | open_n_send(url) | ||
| 70 | } | ||
| 71 | } | ||
| 72 | |||
| 73 | http.HandleFunc("/", handle) | ||
| 74 | http.ListenAndServe(addr, nil) | ||
| 75 | } | ||
| 76 | |||
| 77 | func err(e error) { | ||
| 78 | if e != nil { | ||
| 79 | println(e.Error()) | ||
| 80 | } | ||
| 81 | } | ||
app/wraper.go added +192
| @@ -0,0 +1,192 @@ | |||
| 1 | package app | ||
| 2 | |||
| 3 | import ( | ||
| 4 | "bytes" | ||
| 5 | "io" | ||
| 6 | "net/http" | ||
| 7 | "net/url" | ||
| 8 | "regexp" | ||
| 9 | "strconv" | ||
| 10 | "strings" | ||
| 11 | "text/template" | ||
| 12 | |||
| 13 | "git.macaw.me/skunky/devianter" | ||
| 14 | ) | ||
| 15 | |||
| 16 | var wr = io.WriteString | ||
| 17 | |||
| 18 | type skunkyart struct { | ||
| 19 | Writer http.ResponseWriter | ||
| 20 | Args url.Values | ||
| 21 | Type rune | ||
| 22 | Query string | ||
| 23 | Page int | ||
| 24 | } | ||
| 25 | |||
| 26 | // парсинг темплейтов | ||
| 27 | func (s skunkyart) exe(file string, data any) { | ||
| 28 | var buf bytes.Buffer | ||
| 29 | tmp, e := template.ParseFiles(file) | ||
| 30 | err(e) | ||
| 31 | tmp.Execute(&buf, &data) | ||
| 32 | wr(s.Writer, buf.String()) | ||
| 33 | } | ||
| 34 | |||
| 35 | func (s skunkyart) httperr(status int) { | ||
| 36 | s.Writer.WriteHeader(status) | ||
| 37 | |||
| 38 | // пострйока с помощью strings.Builder, потому что такой метод быстрее обычного сложения | ||
| 39 | var msg strings.Builder | ||
| 40 | msg.WriteString(`<html><link rel="stylesheet" href="/gui/css/skunky.css" />`) | ||
| 41 | msg.WriteString("<h1>") | ||
| 42 | msg.WriteString(strconv.Itoa(status)) | ||
| 43 | msg.WriteString(" - ") | ||
| 44 | msg.WriteString(http.StatusText(status)) | ||
| 45 | msg.WriteString("</h1></html>") | ||
| 46 | |||
| 47 | wr(s.Writer, msg.String()) | ||
| 48 | } | ||
| 49 | |||
| 50 | func (s skunkyart) DeviationList(devs []devianter.Deviation) string { | ||
| 51 | var list strings.Builder | ||
| 52 | list.WriteString(`<div class="content">`) | ||
| 53 | for _, data := range devs { | ||
| 54 | url := devianter.UrlFromMedia(data.Media) | ||
| 55 | |||
| 56 | list.WriteString(`<a title="open/download" href="`) | ||
| 57 | list.WriteString(url) | ||
| 58 | list.WriteString(`"><div class="block"><img src="`) | ||
| 59 | list.WriteString(url) | ||
| 60 | list.WriteString(`" width="15%"></a><br><a href="`) | ||
| 61 | list.WriteString("/post/") | ||
| 62 | list.WriteString(data.Author.Username) | ||
| 63 | list.WriteString("/") | ||
| 64 | list.WriteString(data.Url[27:][strings.Index(data.Url[27:], "/art/")+5:]) | ||
| 65 | list.WriteString(`">`) | ||
| 66 | list.WriteString(data.Author.Username) | ||
| 67 | list.WriteString(" - ") | ||
| 68 | list.WriteString(data.Title) | ||
| 69 | |||
| 70 | // шильдики нсфв, аи и ежедневного поста | ||
| 71 | if data.NSFW { | ||
| 72 | list.WriteString(` [<span class="nsfw">NSFW</span>]`) | ||
| 73 | } | ||
| 74 | if data.AI { | ||
| 75 | list.WriteString(" [🤖]") | ||
| 76 | } | ||
| 77 | if data.DD { | ||
| 78 | list.WriteString(` [<span class="dd">DD</span>]`) | ||
| 79 | } | ||
| 80 | |||
| 81 | list.WriteString("</a></div>") | ||
| 82 | } | ||
| 83 | list.WriteString("</div>") | ||
| 84 | return list.String() | ||
| 85 | } | ||
| 86 | |||
| 87 | // посты | ||
| 88 | func (s skunkyart) Deviation(author, postname string) { | ||
| 89 | // поиск ID | ||
| 90 | re := regexp.MustCompile("[0-9]+").FindAllString(postname, -1) | ||
| 91 | if len(re) >= 1 { | ||
| 92 | var post struct { | ||
| 93 | Post devianter.Post | ||
| 94 | StringTime string | ||
| 95 | Tags string | ||
| 96 | Comments string | ||
| 97 | } | ||
| 98 | |||
| 99 | id := re[len(re)-1] | ||
| 100 | post.Post = devianter.DeviationFunc(id, author) | ||
| 101 | |||
| 102 | // время публикации | ||
| 103 | post.StringTime = post.Post.Deviation.PublishedTime.UTC().String() | ||
| 104 | |||
| 105 | println(post.Post.Description) | ||
| 106 | // хештэги | ||
| 107 | for _, x := range post.Post.Deviation.Extended.Tags { | ||
| 108 | var tag strings.Builder | ||
| 109 | tag.WriteString(` <a href="/search?q=`) | ||
| 110 | tag.WriteString(x.Name) | ||
| 111 | tag.WriteString(`&type=tag">#`) | ||
| 112 | tag.WriteString(x.Name) | ||
| 113 | tag.WriteString("</a>") | ||
| 114 | |||
| 115 | post.Tags += tag.String() | ||
| 116 | } | ||
| 117 | |||
| 118 | // генерация комментов | ||
| 119 | var cmmts strings.Builder | ||
| 120 | var replied map[int]string | ||
| 121 | _ = replied | ||
| 122 | c := devianter.CommentsFunc(id, post.Post.Comments.Cursor, s.Page, 1) | ||
| 123 | |||
| 124 | cmmts.WriteString("<details><summary>Comments: <b>") | ||
| 125 | cmmts.WriteString(strconv.Itoa(c.Total)) | ||
| 126 | cmmts.WriteString("</b></summary>") | ||
| 127 | for _, x := range c.Thread { | ||
| 128 | cmmts.WriteString(`<div class="msg"><p id="`) | ||
| 129 | cmmts.WriteString(strconv.Itoa(x.ID)) | ||
| 130 | cmmts.WriteString(`"><img src="/media/`) | ||
| 131 | cmmts.WriteString(x.User.Username) | ||
| 132 | cmmts.WriteString(`?type=a" width="30px" height="30px"><a href="/user/`) | ||
| 133 | cmmts.WriteString(x.User.Username) | ||
| 134 | cmmts.WriteString(`"><b`) | ||
| 135 | if x.User.Banned { | ||
| 136 | cmmts.WriteString(` class="banned"`) | ||
| 137 | } | ||
| 138 | if x.Author { | ||
| 139 | cmmts.WriteString(` class="author"`) | ||
| 140 | } | ||
| 141 | cmmts.WriteString(">") | ||
| 142 | cmmts.WriteString(x.User.Username) | ||
| 143 | cmmts.WriteString("</b></a> ") | ||
| 144 | cmmts.WriteString(x.Posted.UTC().String()) | ||
| 145 | cmmts.WriteString("<p>") | ||
| 146 | cmmts.WriteString(x.Comment) | ||
| 147 | cmmts.WriteString("<p>👍: ") | ||
| 148 | cmmts.WriteString(strconv.Itoa(x.Likes)) | ||
| 149 | cmmts.WriteString(" ⏩: ") | ||
| 150 | cmmts.WriteString(strconv.Itoa(x.Replies)) | ||
| 151 | cmmts.WriteString("</p></div>\n") | ||
| 152 | } | ||
| 153 | cmmts.WriteString("</details>") | ||
| 154 | post.Comments = cmmts.String() | ||
| 155 | |||
| 156 | s.exe("html/deviantion.htm", &post) | ||
| 157 | } else { | ||
| 158 | s.httperr(400) | ||
| 159 | } | ||
| 160 | } | ||
| 161 | |||
| 162 | func (s skunkyart) Search() { | ||
| 163 | // тут всё и так понятно | ||
| 164 | if s.Type == 'a' || s.Type == 't' || s.Type == 'g' { | ||
| 165 | var srch struct { | ||
| 166 | Search devianter.Search | ||
| 167 | List string | ||
| 168 | } | ||
| 169 | |||
| 170 | var e error | ||
| 171 | srch.Search, e = devianter.SearchFunc(s.Query, s.Page, s.Type) | ||
| 172 | err(e) | ||
| 173 | srch.List = s.DeviationList(srch.Search.Results) | ||
| 174 | |||
| 175 | s.exe("html/search.htm", &srch) | ||
| 176 | } else { | ||
| 177 | s.httperr(400) | ||
| 178 | } | ||
| 179 | } | ||
| 180 | |||
| 181 | func (s skunkyart) Emojitar(name string) { | ||
| 182 | if name != "" && (s.Type == 'a' || s.Type == 'e') { | ||
| 183 | ae, e := devianter.AEmedia(name, s.Type) | ||
| 184 | if e != nil { | ||
| 185 | s.httperr(404) | ||
| 186 | println(e.Error()) | ||
| 187 | } | ||
| 188 | wr(s.Writer, ae) | ||
| 189 | } else { | ||
| 190 | s.httperr(400) | ||
| 191 | } | ||
| 192 | } | ||
config.json added +12
| @@ -0,0 +1,12 @@ | |||
| 1 | { | ||
| 2 | "listen": "0.0.0.0:3003", | ||
| 3 | "base_uri": null, | ||
| 4 | "cache": { | ||
| 5 | "enabled": true, | ||
| 6 | "path": "cache", | ||
| 7 | "lifetime": null, | ||
| 8 | "max_size": 10000 | ||
| 9 | }, | ||
| 10 | "proxy": false, | ||
| 11 | "nsfw": false | ||
| 12 | }\ No newline at end of file | ||
css/skunky.css added +81
| @@ -0,0 +1,81 @@ | |||
| 1 | html { | ||
| 2 | font-family: ubuntu; | ||
| 3 | background-color:black; | ||
| 4 | color: rgb(234, 216, 216); | ||
| 5 | } | ||
| 6 | a { | ||
| 7 | text-decoration: none; | ||
| 8 | color: cadetblue; | ||
| 9 | } | ||
| 10 | a:hover { | ||
| 11 | color: red; | ||
| 12 | transition: 400ms; | ||
| 13 | } | ||
| 14 | header h1 { | ||
| 15 | padding-right: 0.2%; | ||
| 16 | } | ||
| 17 | header form { | ||
| 18 | align-self: center; | ||
| 19 | } | ||
| 20 | header { | ||
| 21 | display: flex; | ||
| 22 | } | ||
| 23 | form input, button, select { | ||
| 24 | background-color: #134134; | ||
| 25 | padding: 5px; | ||
| 26 | color: whitesmoke; | ||
| 27 | border: 0px; | ||
| 28 | border-radius: 1px; | ||
| 29 | } | ||
| 30 | .nsfw, .true { | ||
| 31 | color: red; | ||
| 32 | } | ||
| 33 | .author { | ||
| 34 | color: seagreen; | ||
| 35 | } | ||
| 36 | .msg { | ||
| 37 | background-color: #091f19; | ||
| 38 | color: whitesmoke; | ||
| 39 | width: fit-content; | ||
| 40 | max-width: 90%; | ||
| 41 | padding: 4px; | ||
| 42 | border-radius: 2px; | ||
| 43 | margin-top: 6px; | ||
| 44 | text-wrap: pretty; | ||
| 45 | transition: 350ms; | ||
| 46 | } | ||
| 47 | .msg:hover { | ||
| 48 | background-color: #134134; | ||
| 49 | } | ||
| 50 | .dd { | ||
| 51 | color: rgb(160, 0, 147); | ||
| 52 | } | ||
| 53 | .content { | ||
| 54 | border-radius: 3px; | ||
| 55 | display: flex; | ||
| 56 | flex-wrap: wrap; | ||
| 57 | justify-content: center; | ||
| 58 | } | ||
| 59 | .block { | ||
| 60 | max-width: 20%; | ||
| 61 | height: 0%; | ||
| 62 | padding: 4px; | ||
| 63 | border-radius: 2px; | ||
| 64 | border: 3px solid #091f19; | ||
| 65 | word-break: break-all; | ||
| 66 | background-color: #091f19; | ||
| 67 | margin-left: 5px; | ||
| 68 | margin-top: 5px; | ||
| 69 | text-align: center; | ||
| 70 | } | ||
| 71 | .block:hover { | ||
| 72 | border: 3px solid #4d27d6; | ||
| 73 | transition: 400ms; | ||
| 74 | } | ||
| 75 | .block img { | ||
| 76 | width: 100%; | ||
| 77 | } | ||
| 78 | .block p { | ||
| 79 | word-break: break-all; | ||
| 80 | } | ||
| 81 | |||
go.mod added +7
| @@ -0,0 +1,7 @@ | |||
| 1 | module skunkyart | ||
| 2 | |||
| 3 | go 1.22.3 | ||
| 4 | |||
| 5 | replace git.macaw.me/skunky/devianter v0.1.0 => /home/skunk/projects/devianter | ||
| 6 | |||
| 7 | require git.macaw.me/skunky/devianter v0.1.0 | ||
go.sum added
html/about.htm added +23
| @@ -0,0 +1,23 @@ | |||
| 1 | <!DOCTYPE html> | ||
| 2 | <html> | ||
| 3 | <head> | ||
| 4 | <title>SkunkyArt</title> | ||
| 5 | <link rel="stylesheet" href="gui/css/skunky.css"> | ||
| 6 | </head> | ||
| 7 | <main> | ||
| 8 | <header> | ||
| 9 | <h1><a href="/">HOME</a> | <a href="/dd">DD</a></h1> | ||
| 10 | <form method="get" action="/search"> | ||
| 11 | <input type="text" name="q" placeholder="Search for ..." autocomplete="off" autocapitalize="none" spellcheck="false"> | ||
| 12 | <select name="type"> | ||
| 13 | <option value="all">All</option> | ||
| 14 | <option value="tag">Tag</option> | ||
| 15 | </select> | ||
| 16 | <button type="submit">Search!</button> | ||
| 17 | </form> | ||
| 18 | </header> | ||
| 19 | <p> | ||
| 20 | SkunkyArt is an alternative frontend for deviantart.com, written in Go. | ||
| 21 | </p> | ||
| 22 | </main> | ||
| 23 | </html>\ No newline at end of file | ||
html/deviantion.htm added +44
| @@ -0,0 +1,44 @@ | |||
| 1 | <!DOCTYPE html> | ||
| 2 | <html> | ||
| 3 | <head> | ||
| 4 | <title>SkunkyArt | {{.Post.Deviation.Author.Username}} - {{.Post.Deviation.Title}}</title> | ||
| 5 | <link rel="stylesheet" href="/gui/css/skunky.css"> | ||
| 6 | </head> | ||
| 7 | <main> | ||
| 8 | <header> | ||
| 9 | <h1><a href="/">HOME</a> | <a href="/dd">DD</a></h1> | ||
| 10 | <form method="get" action="/search"> | ||
| 11 | <input type="text" name="q" placeholder="Search for ..." autocomplete="off" autocapitalize="none" spellcheck="false"> | ||
| 12 | <select name="type"> | ||
| 13 | <option value="all">All</option> | ||
| 14 | <option value="tag">Tag</option> | ||
| 15 | </select> | ||
| 16 | <button type="submit">Search!</button> | ||
| 17 | </form> | ||
| 18 | </header> | ||
| 19 | <figure> | ||
| 20 | <img src="/media/{{.Post.Deviation.Author.Username}}?type=a" width="30px"> | ||
| 21 | <span><strong><a href="/user/{{.Post.Deviation.Author.Username}}">{{.Post.Deviation.Author.Username}}</a></strong> — {{if (.Post.Deviation.DD)}} | ||
| 22 | <span class="dd" title="Daily Deviation!"><b>{{.Post.Deviation.Title}}</b></span> | ||
| 23 | {{else}}{{.Post.Deviation.Title}}{{end}} | ||
| 24 | {{if (ne .Post.Deviation.License "none")}}<mark title="License">{{.Post.Deviation.License}}</mark>{{end}} {{if (.Post.Deviation.AI)}}[🤖]{{end}} | ||
| 25 | {{if (.Post.Deviation.NSFW)}}[<span class="nsfw">NSFW</span>]{{end}} | ||
| 26 | </span> | ||
| 27 | <br> | ||
| 28 | {{if (ne .Post.IMG "")}} | ||
| 29 | <a href="{{.Post.IMG}}" title="open/download image"><img src="{{.Post.IMG}}" width="50%"></a> | ||
| 30 | <br> | ||
| 31 | {{end}} | ||
| 32 | {{if (ne .Tags "")}} | ||
| 33 | {{.Tags}}<br> | ||
| 34 | {{end}} | ||
| 35 | <span>Published: <strong>{{.StringTime}}</strong>; Views: <strong>{{.Post.Deviation.Stats.Views}}</strong>; Favourites: <strong>{{.Post.Deviation.Stats.Favourites}}</strong>; Downloads: <strong>{{.Post.Deviation.Stats.Downloads}}</strong></span> | ||
| 36 | {{if (ne .Post.Description "")}} | ||
| 37 | {{.Post.Description}} | ||
| 38 | {{end}} | ||
| 39 | {{if (ne .Comments "")}} | ||
| 40 | {{.Comments}} | ||
| 41 | {{end}} | ||
| 42 | </figure> | ||
| 43 | </main> | ||
| 44 | </html>\ No newline at end of file | ||
html/index.htm added +20
| @@ -0,0 +1,20 @@ | |||
| 1 | <!DOCTYPE html> | ||
| 2 | <html> | ||
| 3 | <head> | ||
| 4 | <title>SkunkyArt</title> | ||
| 5 | <link rel="stylesheet" href="gui/css/skunky.css" | ||
| 6 | </head> | ||
| 7 | <main> | ||
| 8 | <center> | ||
| 9 | <form method="get" action="search"> | ||
| 10 | <input type="text" name="q" placeholder="Search for ..." autocomplete="off" autocapitalize="none" spellcheck="false"> | ||
| 11 | <select name="type"> | ||
| 12 | <option value="all">All</option> | ||
| 13 | <option value="tag">Tag</option> | ||
| 14 | </select> | ||
| 15 | <button type="submit">Search!</button> | ||
| 16 | </form> | ||
| 17 | <h1><a href="dd">Daily Deviations</a> | <a href="about">About</a> | <a href="https://git.macaw.me/skunky/SkunkyArt" target="_blank">Source Code</a></h1> | ||
| 18 | </center> | ||
| 19 | </main> | ||
| 20 | </html>\ No newline at end of file | ||
html/search.htm added +29
| @@ -0,0 +1,29 @@ | |||
| 1 | <!DOCTYPE html> | ||
| 2 | <html> | ||
| 3 | <head> | ||
| 4 | <title>SkunkyArt | Search</title> | ||
| 5 | <link rel="stylesheet" href="/gui/css/skunky.css"> | ||
| 6 | </head> | ||
| 7 | <main> | ||
| 8 | <header> | ||
| 9 | <h1><a href="/">HOME</a> | <a href="/dd">DD</a></h1> | ||
| 10 | <form method="get" action="search"> | ||
| 11 | <input type="text" name="q" placeholder="Search for ..." autocomplete="off" autocapitalize="none" spellcheck="false"> | ||
| 12 | <select name="type"> | ||
| 13 | <option value="all">All</option> | ||
| 14 | <option value="tag">Tag</option> | ||
| 15 | </select> | ||
| 16 | <button type="submit">Search!</button> | ||
| 17 | </form> | ||
| 18 | </header> | ||
| 19 | {{if (ne .List "")}} | ||
| 20 | {{if (ne .Search.Total 0)}} | ||
| 21 | <h1>Total resuls: {{.Search.Total}}</h1> | ||
| 22 | {{end}} | ||
| 23 | {{.List}} | ||
| 24 | {{.Search.Pages}} | ||
| 25 | {{else}} | ||
| 26 | <p>No results :(</p> | ||
| 27 | {{end}} | ||
| 28 | </main> | ||
| 29 | </html>\ No newline at end of file | ||
main.go added +7
| @@ -0,0 +1,7 @@ | |||
| 1 | package main | ||
| 2 | |||
| 3 | import "skunkyart/app" | ||
| 4 | |||
| 5 | func main() { | ||
| 6 | app.Router() | ||
| 7 | } | ||