Commit ed031b3530
Unsigned
Layout: unified · split
app/router.go +2 −2
| @@ -52,7 +52,7 @@ func Router() { | |||
| 52 | // пути | 52 | // пути |
| 53 | switch e { | 53 | switch e { |
| 54 | default: | 54 | default: |
| 55 | skunky.httperr(404) | 55 | skunky.ReturnHTTPError(404) |
| 56 | case "/", "": | 56 | case "/", "": |
| 57 | open_n_send("html/index.htm") | 57 | open_n_send("html/index.htm") |
| 58 | case "post": | 58 | case "post": |
| @@ -62,7 +62,7 @@ func Router() { | |||
| 62 | skunky.Search() | 62 | skunky.Search() |
| 63 | case "dd": | 63 | case "dd": |
| 64 | skunky.DD() | 64 | skunky.DD() |
| 65 | case "group": | 65 | case "group_user": |
| 66 | skunky.GRUser() | 66 | skunky.GRUser() |
| 67 | 67 | ||
| 68 | case "media": | 68 | case "media": |
app/util.go +198
| @@ -2,11 +2,38 @@ package app | |||
| 2 | 2 | ||
| 3 | import ( | 3 | import ( |
| 4 | "encoding/json" | 4 | "encoding/json" |
| 5 | "net/http" | ||
| 6 | "strconv" | ||
| 5 | "strings" | 7 | "strings" |
| 8 | "text/template" | ||
| 6 | 9 | ||
| 7 | "git.macaw.me/skunky/devianter" | 10 | "git.macaw.me/skunky/devianter" |
| 8 | ) | 11 | ) |
| 9 | 12 | ||
| 13 | // парсинг темплейтов | ||
| 14 | func (s skunkyart) ExecuteTemplate(file string, data any) { | ||
| 15 | var buf strings.Builder | ||
| 16 | tmp, e := template.ParseFiles(file) | ||
| 17 | err(e) | ||
| 18 | err(tmp.Execute(&buf, &data)) | ||
| 19 | wr(s.Writer, buf.String()) | ||
| 20 | } | ||
| 21 | |||
| 22 | func (s skunkyart) ReturnHTTPError(status int) { | ||
| 23 | s.Writer.WriteHeader(status) | ||
| 24 | |||
| 25 | // пострйока с помощью strings.Builder, потому что такой метод быстрее обычного сложения | ||
| 26 | var msg strings.Builder | ||
| 27 | msg.WriteString(`<html><link rel="stylesheet" href="/gui/css/skunky.css" />`) | ||
| 28 | msg.WriteString("<h1>") | ||
| 29 | msg.WriteString(strconv.Itoa(status)) | ||
| 30 | msg.WriteString(" - ") | ||
| 31 | msg.WriteString(http.StatusText(status)) | ||
| 32 | msg.WriteString("</h1></html>") | ||
| 33 | |||
| 34 | wr(s.Writer, msg.String()) | ||
| 35 | } | ||
| 36 | |||
| 10 | type text struct { | 37 | type text struct { |
| 11 | TXT string | 38 | TXT string |
| 12 | from int | 39 | from int |
| @@ -87,3 +114,174 @@ func ParseDescription(dscr devianter.Text) string { | |||
| 87 | 114 | ||
| 88 | return parseddescription.String() | 115 | return parseddescription.String() |
| 89 | } | 116 | } |
| 117 | |||
| 118 | // навигация по страницам | ||
| 119 | type dlist struct { | ||
| 120 | Pages int | ||
| 121 | More bool | ||
| 122 | } | ||
| 123 | |||
| 124 | // FIXME: на некоротрых артах первая страница может вызывать полное отсутствие панели навигации. | ||
| 125 | func (s skunkyart) NavBase(c dlist) string { | ||
| 126 | // TODO: сделать понятнее | ||
| 127 | // навигация по страницам | ||
| 128 | var list strings.Builder | ||
| 129 | list.WriteString("<br>") | ||
| 130 | p := s.Page | ||
| 131 | |||
| 132 | // функция для генерации ссылок | ||
| 133 | prevrev := func(msg string, page int, onpage bool) { | ||
| 134 | if !onpage { | ||
| 135 | list.WriteString(`<a href="?p=`) | ||
| 136 | list.WriteString(strconv.Itoa(page)) | ||
| 137 | if s.Type != 0 { | ||
| 138 | list.WriteString("&type=") | ||
| 139 | list.WriteRune(s.Type) | ||
| 140 | } | ||
| 141 | if s.Query != "" { | ||
| 142 | list.WriteString("&q=") | ||
| 143 | list.WriteString(s.Query) | ||
| 144 | } | ||
| 145 | list.WriteString(`">`) | ||
| 146 | list.WriteString(msg) | ||
| 147 | list.WriteString("</a> ") | ||
| 148 | } else { | ||
| 149 | list.WriteString(strconv.Itoa(page)) | ||
| 150 | list.WriteString(" ") | ||
| 151 | } | ||
| 152 | } | ||
| 153 | |||
| 154 | // вперёд-назад | ||
| 155 | if p > 1 { | ||
| 156 | prevrev("<= Prev |", p-1, false) | ||
| 157 | } else { | ||
| 158 | p = 1 | ||
| 159 | } | ||
| 160 | |||
| 161 | if c.Pages > 0 { | ||
| 162 | // назад | ||
| 163 | for x := p - 6; x < p && x > 0; x++ { | ||
| 164 | prevrev(strconv.Itoa(x), x, false) | ||
| 165 | } | ||
| 166 | |||
| 167 | // вперёд | ||
| 168 | for x := p; x <= p+6; x++ { | ||
| 169 | if x == p { | ||
| 170 | prevrev("", x, true) | ||
| 171 | x++ | ||
| 172 | } | ||
| 173 | |||
| 174 | if x > p { | ||
| 175 | prevrev(strconv.Itoa(x), x, false) | ||
| 176 | } | ||
| 177 | } | ||
| 178 | } | ||
| 179 | |||
| 180 | // вперёд-назад | ||
| 181 | if c.More { | ||
| 182 | prevrev("| Next =>", p+1, false) | ||
| 183 | } | ||
| 184 | |||
| 185 | return list.String() | ||
| 186 | } | ||
| 187 | |||
| 188 | func (s skunkyart) DeviationList(devs []devianter.Deviation, content ...dlist) string { | ||
| 189 | var list strings.Builder | ||
| 190 | list.WriteString(`<div class="content">`) | ||
| 191 | for _, data := range devs { | ||
| 192 | url := devianter.UrlFromMedia(data.Media) | ||
| 193 | |||
| 194 | list.WriteString(`<a title="open/download" href="`) | ||
| 195 | list.WriteString(url) | ||
| 196 | list.WriteString(`"><div class="block"><img src="`) | ||
| 197 | list.WriteString(url) | ||
| 198 | list.WriteString(`" width="15%"></a><br><a href="`) | ||
| 199 | list.WriteString("/post/") | ||
| 200 | list.WriteString(data.Author.Username) | ||
| 201 | list.WriteString("/") | ||
| 202 | list.WriteString(data.Url[27:][strings.Index(data.Url[27:], "/art/")+5:]) | ||
| 203 | list.WriteString(`">`) | ||
| 204 | list.WriteString(data.Author.Username) | ||
| 205 | list.WriteString(" - ") | ||
| 206 | list.WriteString(data.Title) | ||
| 207 | |||
| 208 | // шильдики нсфв, аи и ежедневного поста | ||
| 209 | if data.NSFW { | ||
| 210 | list.WriteString(` [<span class="nsfw">NSFW</span>]`) | ||
| 211 | } | ||
| 212 | if data.AI { | ||
| 213 | list.WriteString(" [🤖]") | ||
| 214 | } | ||
| 215 | if data.DD { | ||
| 216 | list.WriteString(` [<span class="dd">DD</span>]`) | ||
| 217 | } | ||
| 218 | |||
| 219 | list.WriteString("</a></div>") | ||
| 220 | } | ||
| 221 | list.WriteString("</div>") | ||
| 222 | list.WriteString(s.NavBase(content[0])) | ||
| 223 | |||
| 224 | return list.String() | ||
| 225 | } | ||
| 226 | |||
| 227 | // FIXME: первый комментарий не отображается. | ||
| 228 | func (s skunkyart) ParseComments(c devianter.Comments) string { | ||
| 229 | var cmmts strings.Builder | ||
| 230 | replied := make(map[int]string) | ||
| 231 | |||
| 232 | cmmts.WriteString("<details><summary>Comments: <b>") | ||
| 233 | cmmts.WriteString(strconv.Itoa(c.Total)) | ||
| 234 | cmmts.WriteString("</b></summary>") | ||
| 235 | for _, x := range c.Thread { | ||
| 236 | replied[x.ID] = x.User.Username | ||
| 237 | cmmts.WriteString(`<div class="msg`) | ||
| 238 | if x.Parent > 0 { | ||
| 239 | cmmts.WriteString(` reply`) | ||
| 240 | } | ||
| 241 | cmmts.WriteString(`"><p id="`) | ||
| 242 | cmmts.WriteString(strconv.Itoa(x.ID)) | ||
| 243 | cmmts.WriteString(`"><img src="/media/`) | ||
| 244 | cmmts.WriteString(x.User.Username) | ||
| 245 | cmmts.WriteString(`?type=a" width="30px" height="30px"><a href="/group_user?type=about&q=`) | ||
| 246 | cmmts.WriteString(x.User.Username) | ||
| 247 | cmmts.WriteString(`"><b`) | ||
| 248 | cmmts.WriteString(` class="`) | ||
| 249 | if x.User.Banned { | ||
| 250 | cmmts.WriteString(`banned`) | ||
| 251 | } | ||
| 252 | if x.Author { | ||
| 253 | cmmts.WriteString(`author`) | ||
| 254 | } | ||
| 255 | cmmts.WriteString(`">`) | ||
| 256 | cmmts.WriteString(x.User.Username) | ||
| 257 | cmmts.WriteString("</b></a> ") | ||
| 258 | |||
| 259 | if x.Parent > 0 { | ||
| 260 | cmmts.WriteString(` In reply to <a href="#`) | ||
| 261 | cmmts.WriteString(strconv.Itoa(x.Parent)) | ||
| 262 | cmmts.WriteString(`">`) | ||
| 263 | if replied[x.Parent] == "" { | ||
| 264 | cmmts.WriteString("???") | ||
| 265 | } else { | ||
| 266 | cmmts.WriteString(replied[x.Parent]) | ||
| 267 | } | ||
| 268 | cmmts.WriteString("</a>") | ||
| 269 | } | ||
| 270 | cmmts.WriteString(" [") | ||
| 271 | cmmts.WriteString(x.Posted.UTC().String()) | ||
| 272 | cmmts.WriteString("]<p>") | ||
| 273 | |||
| 274 | cmmts.WriteString(x.Comment) | ||
| 275 | cmmts.WriteString("<p>👍: ") | ||
| 276 | cmmts.WriteString(strconv.Itoa(x.Likes)) | ||
| 277 | cmmts.WriteString(" ⏩: ") | ||
| 278 | cmmts.WriteString(strconv.Itoa(x.Replies)) | ||
| 279 | cmmts.WriteString("</p></div>\n") | ||
| 280 | } | ||
| 281 | cmmts.WriteString(s.NavBase(dlist{ | ||
| 282 | Pages: 0, | ||
| 283 | More: c.HasMore, | ||
| 284 | })) | ||
| 285 | cmmts.WriteString("</details>") | ||
| 286 | return cmmts.String() | ||
| 287 | } | ||
app/wraper.go +103 −265
| @@ -1,14 +1,13 @@ | |||
| 1 | package app | 1 | package app |
| 2 | 2 | ||
| 3 | import ( | 3 | import ( |
| 4 | "bytes" | 4 | "fmt" |
| 5 | "io" | 5 | "io" |
| 6 | "net/http" | 6 | "net/http" |
| 7 | "net/url" | 7 | "net/url" |
| 8 | "regexp" | 8 | "regexp" |
| 9 | "strconv" | 9 | "strconv" |
| 10 | "strings" | 10 | "strings" |
| 11 | "text/template" | ||
| 12 | "time" | 11 | "time" |
| 13 | 12 | ||
| 14 | "git.macaw.me/skunky/devianter" | 13 | "git.macaw.me/skunky/devianter" |
| @@ -17,206 +16,108 @@ import ( | |||
| 17 | var wr = io.WriteString | 16 | var wr = io.WriteString |
| 18 | 17 | ||
| 19 | type skunkyart struct { | 18 | type skunkyart struct { |
| 20 | Writer http.ResponseWriter | 19 | Writer http.ResponseWriter |
| 21 | Args url.Values | 20 | Args url.Values |
| 22 | Type rune | 21 | Type rune |
| 23 | Query string | 22 | Query string |
| 24 | Page int | 23 | Page int |
| 25 | } | 24 | Templates struct { |
| 26 | 25 | GroupUser struct { | |
| 27 | // парсинг темплейтов | 26 | GR devianter.GRuser |
| 28 | func (s skunkyart) exe(file string, data any) { | 27 | CreationDate string |
| 29 | var buf bytes.Buffer | 28 | |
| 30 | tmp, e := template.ParseFiles(file) | 29 | About struct { |
| 31 | err(e) | 30 | A devianter.About |
| 32 | tmp.Execute(&buf, &data) | 31 | |
| 33 | wr(s.Writer, buf.String()) | 32 | DescriptionFormatted string |
| 34 | } | 33 | Interests, Social string |
| 35 | 34 | Comments string | |
| 36 | func (s skunkyart) httperr(status int) { | 35 | BG string |
| 37 | s.Writer.WriteHeader(status) | 36 | BGMeta devianter.Deviation |
| 38 | |||
| 39 | // пострйока с помощью strings.Builder, потому что такой метод быстрее обычного сложения | ||
| 40 | var msg strings.Builder | ||
| 41 | msg.WriteString(`<html><link rel="stylesheet" href="/gui/css/skunky.css" />`) | ||
| 42 | msg.WriteString("<h1>") | ||
| 43 | msg.WriteString(strconv.Itoa(status)) | ||
| 44 | msg.WriteString(" - ") | ||
| 45 | msg.WriteString(http.StatusText(status)) | ||
| 46 | msg.WriteString("</h1></html>") | ||
| 47 | |||
| 48 | wr(s.Writer, msg.String()) | ||
| 49 | } | ||
| 50 | |||
| 51 | // навигация по страницам | ||
| 52 | type dlist struct { | ||
| 53 | Pages int | ||
| 54 | More bool | ||
| 55 | } | ||
| 56 | |||
| 57 | // FIXME: на некоротрых артах первая страница может вызывать полное отсутствие панели навигации. | ||
| 58 | func (s skunkyart) NavBase(c dlist) string { | ||
| 59 | // TODO: сделать понятнее | ||
| 60 | // навигация по страницам | ||
| 61 | var list strings.Builder | ||
| 62 | list.WriteString("<br>") | ||
| 63 | p := s.Page | ||
| 64 | |||
| 65 | // функция для генерации ссылок | ||
| 66 | prevrev := func(msg string, page int, onpage bool) { | ||
| 67 | if !onpage { | ||
| 68 | list.WriteString(`<a href="?p=`) | ||
| 69 | list.WriteString(strconv.Itoa(page)) | ||
| 70 | if s.Type != 0 { | ||
| 71 | list.WriteString("&type=") | ||
| 72 | list.WriteRune(s.Type) | ||
| 73 | } | ||
| 74 | if s.Query != "" { | ||
| 75 | list.WriteString("&q=") | ||
| 76 | list.WriteString(s.Query) | ||
| 77 | } | ||
| 78 | list.WriteString(`">`) | ||
| 79 | list.WriteString(msg) | ||
| 80 | list.WriteString("</a> ") | ||
| 81 | } else { | ||
| 82 | list.WriteString(strconv.Itoa(page)) | ||
| 83 | list.WriteString(" ") | ||
| 84 | } | ||
| 85 | } | ||
| 86 | |||
| 87 | // вперёд-назад | ||
| 88 | if p > 1 { | ||
| 89 | prevrev("<= Prev |", p-1, false) | ||
| 90 | } else { | ||
| 91 | p = 1 | ||
| 92 | } | ||
| 93 | |||
| 94 | if c.Pages > 0 { | ||
| 95 | // назад | ||
| 96 | for x := p - 6; x < p && x > 0; x++ { | ||
| 97 | prevrev(strconv.Itoa(x), x, false) | ||
| 98 | } | ||
| 99 | |||
| 100 | // вперёд | ||
| 101 | for x := p; x <= p+6; x++ { | ||
| 102 | if x == p { | ||
| 103 | prevrev("", x, true) | ||
| 104 | x++ | ||
| 105 | } | 37 | } |
| 106 | 38 | ||
| 107 | if x > p { | 39 | Gallery struct { |
| 108 | prevrev(strconv.Itoa(x), x, false) | 40 | Pages int |
| 41 | List string | ||
| 109 | } | 42 | } |
| 110 | } | 43 | } |
| 44 | Search struct { | ||
| 45 | Content devianter.Search | ||
| 46 | List string | ||
| 47 | } | ||
| 111 | } | 48 | } |
| 112 | |||
| 113 | // вперёд-назад | ||
| 114 | if c.More { | ||
| 115 | prevrev("| Next =>", p+1, false) | ||
| 116 | } | ||
| 117 | |||
| 118 | return list.String() | ||
| 119 | } | 49 | } |
| 120 | 50 | ||
| 121 | func (s skunkyart) GRUser() { | 51 | func (s skunkyart) GRUser() { |
| 122 | var group struct { | ||
| 123 | GR devianter.GRuser | ||
| 124 | CreationDate string | ||
| 125 | |||
| 126 | About struct { | ||
| 127 | A devianter.About | ||
| 128 | |||
| 129 | DescriptionFormatted string | ||
| 130 | Interests string | ||
| 131 | Social string | ||
| 132 | BG devianter.Deviation | ||
| 133 | } | ||
| 134 | } | ||
| 135 | |||
| 136 | if len(s.Query) < 1 { | 52 | if len(s.Query) < 1 { |
| 137 | s.httperr(400) | 53 | s.ReturnHTTPError(400) |
| 138 | return | 54 | return |
| 139 | } | 55 | } |
| 140 | 56 | ||
| 141 | var g devianter.Group | 57 | var g devianter.Group |
| 142 | g.Name = s.Query | 58 | g.Name = s.Query |
| 143 | group.GR = g.GroupFunc() | 59 | s.Templates.GroupUser.GR = g.GroupFunc() |
| 144 | 60 | group := &s.Templates.GroupUser | |
| 145 | if g := group.GR; !g.Owner.Group { | ||
| 146 | for _, x := range g.Gruser.Page.Modules { | ||
| 147 | var about = group.About.A | ||
| 148 | if x.ModuleData.About.RegDate != 0 { | ||
| 149 | about = x.ModuleData.About | ||
| 150 | } | ||
| 151 | group.About.DescriptionFormatted = ParseDescription(about.Description) | ||
| 152 | 61 | ||
| 153 | for _, val := range x.ModuleData.About.Interests { | 62 | switch s.Type { |
| 154 | var interest strings.Builder | 63 | case 'a': |
| 155 | interest.WriteString(val.Label) | 64 | if g := group.GR; !g.Owner.Group { |
| 156 | interest.WriteString(": <b>") | 65 | for _, x := range g.Gruser.Page.Modules { |
| 157 | interest.WriteString(val.Value) | 66 | switch x.Name { |
| 158 | interest.WriteString("</b><br>") | 67 | case "about": |
| 159 | group.About.Interests += interest.String() | 68 | group.About.A = x.ModuleData.About |
| 160 | } | 69 | var about = group.About.A |
| 161 | 70 | group.About.DescriptionFormatted = ParseDescription(about.Description) | |
| 162 | for _, val := range x.ModuleData.About.SocialLinks { | 71 | group.About.Comments = s.ParseComments(devianter.CommentsFunc( |
| 163 | var social strings.Builder | 72 | strconv.Itoa(group.GR.Gruser.ID), |
| 164 | social.WriteString(`<a target="_blank" href="`) | 73 | "", |
| 165 | social.WriteString(val.Value) | 74 | s.Page, |
| 166 | social.WriteString(`">`) | 75 | 4, |
| 167 | social.WriteString(val.Value) | 76 | )) |
| 168 | social.WriteString("</a><br>") | 77 | |
| 169 | group.About.Social += social.String() | 78 | for _, val := range x.ModuleData.About.SocialLinks { |
| 170 | } | 79 | var social strings.Builder |
| 171 | 80 | social.WriteString(`<a target="_blank" href="`) | |
| 172 | if rd := x.ModuleData.About.RegDate; rd != 0 { | 81 | social.WriteString(val.Value) |
| 173 | group.CreationDate = time.Unix(time.Now().Unix()-rd, 0).UTC().String() | 82 | social.WriteString(`">`) |
| 83 | social.WriteString(val.Value) | ||
| 84 | social.WriteString("</a><br>") | ||
| 85 | group.About.Social += social.String() | ||
| 86 | } | ||
| 87 | |||
| 88 | for _, val := range x.ModuleData.About.Interests { | ||
| 89 | var interest strings.Builder | ||
| 90 | interest.WriteString(val.Label) | ||
| 91 | interest.WriteString(": <b>") | ||
| 92 | interest.WriteString(val.Value) | ||
| 93 | interest.WriteString("</b><br>") | ||
| 94 | group.About.Interests += interest.String() | ||
| 95 | } | ||
| 96 | |||
| 97 | if rd := x.ModuleData.About.RegDate; rd != 0 { | ||
| 98 | group.CreationDate = time.Unix(time.Now().Unix()-rd, 0).UTC().String() | ||
| 99 | } | ||
| 100 | case "cover_deviation": | ||
| 101 | group.About.BGMeta = x.ModuleData.CoverDeviation.Deviation | ||
| 102 | group.About.BG = devianter.UrlFromMedia(group.About.BGMeta.Media) | ||
| 103 | } | ||
| 174 | } | 104 | } |
| 175 | } | 105 | } else { |
| 176 | } else { | ||
| 177 | |||
| 178 | } | ||
| 179 | |||
| 180 | s.exe("html/gruser.htm", &group) | ||
| 181 | } | ||
| 182 | |||
| 183 | func (s skunkyart) DeviationList(devs []devianter.Deviation, content ...dlist) string { | ||
| 184 | var list strings.Builder | ||
| 185 | list.WriteString(`<div class="content">`) | ||
| 186 | for _, data := range devs { | ||
| 187 | url := devianter.UrlFromMedia(data.Media) | ||
| 188 | |||
| 189 | list.WriteString(`<a title="open/download" href="`) | ||
| 190 | list.WriteString(url) | ||
| 191 | list.WriteString(`"><div class="block"><img src="`) | ||
| 192 | list.WriteString(url) | ||
| 193 | list.WriteString(`" width="15%"></a><br><a href="`) | ||
| 194 | list.WriteString("/post/") | ||
| 195 | list.WriteString(data.Author.Username) | ||
| 196 | list.WriteString("/") | ||
| 197 | list.WriteString(data.Url[27:][strings.Index(data.Url[27:], "/art/")+5:]) | ||
| 198 | list.WriteString(`">`) | ||
| 199 | list.WriteString(data.Author.Username) | ||
| 200 | list.WriteString(" - ") | ||
| 201 | list.WriteString(data.Title) | ||
| 202 | 106 | ||
| 203 | // шильдики нсфв, аи и ежедневного поста | ||
| 204 | if data.NSFW { | ||
| 205 | list.WriteString(` [<span class="nsfw">NSFW</span>]`) | ||
| 206 | } | 107 | } |
| 207 | if data.AI { | 108 | case 'g': |
| 208 | list.WriteString(" [🤖]") | 109 | gallery := g.Gallery(s.Page) |
| 110 | fmt.Println(gallery) | ||
| 111 | for _, x := range gallery.Content.Gruser.Page.Modules { | ||
| 112 | group.Gallery.List = s.DeviationList(x.ModuleData.Folder.Deviations, dlist{ | ||
| 113 | Pages: x.ModuleData.Folder.Pages, | ||
| 114 | }) | ||
| 209 | } | 115 | } |
| 210 | if data.DD { | 116 | default: |
| 211 | list.WriteString(` [<span class="dd">DD</span>]`) | 117 | s.ReturnHTTPError(400) |
| 212 | } | ||
| 213 | |||
| 214 | list.WriteString("</a></div>") | ||
| 215 | } | 118 | } |
| 216 | list.WriteString("</div>") | ||
| 217 | list.WriteString(s.NavBase(content[0])) | ||
| 218 | 119 | ||
| 219 | return list.String() | 120 | s.ExecuteTemplate("html/gruser.htm", &s) |
| 220 | } | 121 | } |
| 221 | 122 | ||
| 222 | // посты | 123 | // посты |
| @@ -250,115 +151,52 @@ func (s skunkyart) Deviation(author, postname string) { | |||
| 250 | post.Tags += tag.String() | 151 | post.Tags += tag.String() |
| 251 | } | 152 | } |
| 252 | 153 | ||
| 253 | // FIXME: первый комментарий не отображается. | 154 | post.Comments = s.ParseComments(devianter.CommentsFunc(id, post.Post.Comments.Cursor, s.Page, 1)) |
| 254 | // генерация комментов | ||
| 255 | var cmmts strings.Builder | ||
| 256 | replied := make(map[int]string) | ||
| 257 | c := devianter.CommentsFunc(id, post.Post.Comments.Cursor, s.Page, 1) | ||
| 258 | |||
| 259 | cmmts.WriteString("<details><summary>Comments: <b>") | ||
| 260 | cmmts.WriteString(strconv.Itoa(c.Total)) | ||
| 261 | cmmts.WriteString("</b></summary>") | ||
| 262 | for _, x := range c.Thread { | ||
| 263 | replied[x.ID] = x.User.Username | ||
| 264 | cmmts.WriteString(`<div class="msg`) | ||
| 265 | if x.Parent > 0 { | ||
| 266 | cmmts.WriteString(` reply`) | ||
| 267 | } | ||
| 268 | cmmts.WriteString(`"><p id="`) | ||
| 269 | cmmts.WriteString(strconv.Itoa(x.ID)) | ||
| 270 | cmmts.WriteString(`"><img src="/media/`) | ||
| 271 | cmmts.WriteString(x.User.Username) | ||
| 272 | cmmts.WriteString(`?type=a" width="30px" height="30px"><a href="/user/`) | ||
| 273 | cmmts.WriteString(x.User.Username) | ||
| 274 | cmmts.WriteString(`"><b`) | ||
| 275 | cmmts.WriteString(` class="`) | ||
| 276 | if x.User.Banned { | ||
| 277 | cmmts.WriteString(`banned`) | ||
| 278 | } | ||
| 279 | if x.Author { | ||
| 280 | cmmts.WriteString(`author`) | ||
| 281 | } | ||
| 282 | cmmts.WriteString(`">`) | ||
| 283 | cmmts.WriteString(x.User.Username) | ||
| 284 | cmmts.WriteString("</b></a> ") | ||
| 285 | 155 | ||
| 286 | if x.Parent > 0 { | 156 | s.ExecuteTemplate("html/deviantion.htm", &post) |
| 287 | cmmts.WriteString(` In reply to <a href="#`) | ||
| 288 | cmmts.WriteString(strconv.Itoa(x.Parent)) | ||
| 289 | cmmts.WriteString(`">`) | ||
| 290 | if replied[x.Parent] == "" { | ||
| 291 | cmmts.WriteString("???") | ||
| 292 | } else { | ||
| 293 | cmmts.WriteString(replied[x.Parent]) | ||
| 294 | } | ||
| 295 | cmmts.WriteString("</a>") | ||
| 296 | } | ||
| 297 | cmmts.WriteString(" [") | ||
| 298 | cmmts.WriteString(x.Posted.UTC().String()) | ||
| 299 | cmmts.WriteString("]<p>") | ||
| 300 | |||
| 301 | cmmts.WriteString(x.Comment) | ||
| 302 | cmmts.WriteString("<p>👍: ") | ||
| 303 | cmmts.WriteString(strconv.Itoa(x.Likes)) | ||
| 304 | cmmts.WriteString(" ⏩: ") | ||
| 305 | cmmts.WriteString(strconv.Itoa(x.Replies)) | ||
| 306 | cmmts.WriteString("</p></div>\n") | ||
| 307 | } | ||
| 308 | cmmts.WriteString(s.NavBase(dlist{ | ||
| 309 | Pages: 0, | ||
| 310 | More: c.HasMore, | ||
| 311 | })) | ||
| 312 | cmmts.WriteString("</details>") | ||
| 313 | |||
| 314 | post.Comments = cmmts.String() | ||
| 315 | |||
| 316 | s.exe("html/deviantion.htm", &post) | ||
| 317 | } else { | 157 | } else { |
| 318 | s.httperr(400) | 158 | s.ReturnHTTPError(400) |
| 319 | } | 159 | } |
| 320 | } | 160 | } |
| 321 | 161 | ||
| 322 | func (s skunkyart) DD() { | 162 | func (s skunkyart) DD() { |
| 323 | dd := devianter.DailyDeviationsFunc(s.Page) | 163 | dd := devianter.DailyDeviationsFunc(s.Page) |
| 324 | s.exe("html/list.htm", s.DeviationList(dd.Deviations, dlist{ | 164 | s.ExecuteTemplate("html/list.htm", s.DeviationList(dd.Deviations, dlist{ |
| 325 | Pages: 0, | 165 | Pages: 0, |
| 326 | More: dd.HasMore, | 166 | More: dd.HasMore, |
| 327 | })) | 167 | })) |
| 328 | } | 168 | } |
| 329 | 169 | ||
| 330 | func (s skunkyart) Search() { | 170 | func (s skunkyart) Search() { |
| 331 | // тут всё и так понятно | 171 | var e error |
| 172 | ss := &s.Templates.Search | ||
| 332 | switch s.Type { | 173 | switch s.Type { |
| 333 | case 'a', 't', 'g': | 174 | case 'a', 't': |
| 334 | var srch struct { | 175 | ss.Content, e = devianter.SearchFunc(s.Query, s.Page, s.Type) |
| 335 | Search devianter.Search | 176 | case 'g': |
| 336 | List string | 177 | ss.Content, e = devianter.SearchFunc(s.Query, s.Page, s.Type, s.Args.Get("usr")) |
| 337 | } | ||
| 338 | |||
| 339 | var e error | ||
| 340 | srch.Search, e = devianter.SearchFunc(s.Query, s.Page, s.Type) | ||
| 341 | err(e) | ||
| 342 | srch.List = s.DeviationList(srch.Search.Results, dlist{ | ||
| 343 | Pages: srch.Search.Pages, | ||
| 344 | More: srch.Search.HasMore, | ||
| 345 | }) | ||
| 346 | |||
| 347 | s.exe("html/search.htm", &srch) | ||
| 348 | default: | 178 | default: |
| 349 | s.httperr(400) | 179 | s.ReturnHTTPError(400) |
| 350 | } | 180 | } |
| 181 | err(e) | ||
| 182 | |||
| 183 | ss.List = s.DeviationList(ss.Content.Results, dlist{ | ||
| 184 | Pages: ss.Content.Pages, | ||
| 185 | More: ss.Content.HasMore, | ||
| 186 | }) | ||
| 187 | |||
| 188 | s.ExecuteTemplate("html/search.htm", &s) | ||
| 351 | } | 189 | } |
| 352 | 190 | ||
| 353 | func (s skunkyart) Emojitar(name string) { | 191 | func (s skunkyart) Emojitar(name string) { |
| 354 | if name != "" && (s.Type == 'a' || s.Type == 'e') { | 192 | if name != "" && (s.Type == 'a' || s.Type == 'e') { |
| 355 | ae, e := devianter.AEmedia(name, s.Type) | 193 | ae, e := devianter.AEmedia(name, s.Type) |
| 356 | if e != nil { | 194 | if e != nil { |
| 357 | s.httperr(404) | 195 | s.ReturnHTTPError(404) |
| 358 | println(e.Error()) | 196 | println(e.Error()) |
| 359 | } | 197 | } |
| 360 | wr(s.Writer, ae) | 198 | wr(s.Writer, ae) |
| 361 | } else { | 199 | } else { |
| 362 | s.httperr(400) | 200 | s.ReturnHTTPError(400) |
| 363 | } | 201 | } |
| 364 | } | 202 | } |
css/skunky.css +8 −1
| @@ -83,4 +83,11 @@ form input, button, select { | |||
| 83 | .block p { | 83 | .block p { |
| 84 | word-break: break-all; | 84 | word-break: break-all; |
| 85 | } | 85 | } |
| 86 | 86 | .ubg { | |
| 87 | display: flex; | ||
| 88 | flex-wrap: wrap; | ||
| 89 | justify-content: center; | ||
| 90 | } | ||
| 91 | .ubg img { | ||
| 92 | width: 20%; | ||
| 93 | } | ||
html/deviantion.htm +1 −1
| @@ -18,7 +18,7 @@ | |||
| 18 | </header> | 18 | </header> |
| 19 | <figure> | 19 | <figure> |
| 20 | <img src="/media/{{.Post.Deviation.Author.Username}}?type=a" width="30px"> | 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)}} | 21 | <span><strong><a href="/group_user?type=about&q={{.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> | 22 | <span class="dd" title="Daily Deviation!"><b>{{.Post.Deviation.Title}}</b></span> |
| 23 | {{else}}{{.Post.Deviation.Title}}{{end}} | 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}} | 24 | {{if (ne .Post.Deviation.License "none")}}<mark title="License">{{.Post.Deviation.License}}</mark>{{end}} {{if (.Post.Deviation.AI)}}[🤖]{{end}} |
html/gruser.htm +56 −25
| @@ -1,43 +1,74 @@ | |||
| 1 | <!DOCTYPE html> | 1 | <!DOCTYPE html> |
| 2 | <html> | 2 | <html> |
| 3 | <head> | 3 | <head> |
| 4 | <title>SkunkyArt | {{.GR.Owner.Username}}</title> | 4 | <title>SkunkyArt | |
| 5 | {{if eq .Type 'a'}} | ||
| 6 | {{.Templates.GroupUser.GR.Owner.Username}} | ||
| 7 | {{else}} | ||
| 8 | gallery of {{.Templates.GroupUser.GR.Owner.Username}} | ||
| 9 | {{end}} | ||
| 10 | </title> | ||
| 5 | <link rel="stylesheet" href="/gui/css/skunky.css"> | 11 | <link rel="stylesheet" href="/gui/css/skunky.css"> |
| 6 | </head> | 12 | </head> |
| 7 | <main> | 13 | <main> |
| 8 | <header> | 14 | <header> |
| 9 | <h1><a href="/">HOME</a> | <a href="/dd">DD</a></h1> | 15 | <h1><a href="/">HOME</a> | <a href="/dd">DD</a> |
| 10 | <form method="get" action="/search"> | 16 | | <a href="?q={{.Templates.GroupUser.GR.Owner.Username}}&type={{if eq .Type 'a'}}gallery">Gallery</a></h1>{{else}}about">About</a></h1> |
| 11 | <input type="text" name="q" placeholder="Search for ..." autocomplete="off" autocapitalize="none" spellcheck="false"> | 17 | {{end}} |
| 18 | <form method="get" action="/search"> | ||
| 19 | <input type="gallery" name="q" placeholder="Search for ..." autocomplete="off" autocapitalize="none" spellcheck="false"> | ||
| 20 | <input type="hidden" name="usr" value="{{.Templates.GroupUser.GR.Owner.Username}}"> | ||
| 12 | <select name="type"> | 21 | <select name="type"> |
| 13 | <option value="all">All</option> | 22 | <option value="all">All</option> |
| 14 | <option value="tag">Tag</option> | 23 | <option value="tag">Tag</option> |
| 24 | <option value="gallery">Gallery</option> | ||
| 15 | </select> | 25 | </select> |
| 16 | <button type="submit">Search!</button> | 26 | <button type="submit">Search!</button> |
| 17 | </form> | 27 | </form> |
| 18 | </header> | 28 | </header> |
| 19 | <img src="/media/{{.GR.Owner.Username}}?type=a" width="30px" height="30px"> <b>{{.GR.Owner.Username}}</b> | 29 | {{if eq .Type 'a'}} |
| 20 | {{if (eq .About.A.Gender "male")}} | 30 | {{if ne .Templates.GroupUser.About.BG ""}} |
| 21 | ♂️ | 31 | <a href="{{.Templates.GroupUser.About.BGMeta.Url}}" class="ubg"><img title="{{if ne .Templates.GroupUser.GR.Owner.Username .Templates.GroupUser.About.BGMeta.Author.Username}} |
| 22 | {{end}} | 32 | {{.Templates.GroupUser.About.BGMeta.Author.Username}} - {{end}}{{.Templates.GroupUser.About.BGMeta.Title}}" src="{{.Templates.GroupUser.About.BG}}"></a> |
| 23 | {{if (eq .About.A.Gender "female")}} | 33 | {{end}} |
| 24 | ♀️ | ||
| 25 | {{end}} | ||
| 26 | [<span title="UID">{{.GR.Gruser.ID}}</span>] | ||
| 27 | [<span title="Registration date">{{.CreationDate}}</span>] | ||
| 28 | <i title="User's Tag">"{{.GR.Extra.Tag}}"</i> | ||
| 29 | 34 | ||
| 30 | <h3 id="stats"><a href="#stats">#</a> Statistics</h3> | 35 | <img src="/media/{{.Templates.GroupUser.GR.Owner.Username}}?type=a" width="30px" height="30px"> <b>{{.Templates.GroupUser.GR.Owner.Username}}</b> |
| 31 | <p>Favourites: <b>{{.GR.Extra.Stats.Favourites}}</b>; Deviations: <b>{{.GR.Extra.Stats.Deviations}}</b>; Watchers: <b>{{.GR.Extra.Stats.Watchers}}</b> | 36 | {{if (eq .Templates.GroupUser.About.A.Gender "male")}} |
| 32 | <p>Watching: <b>{{.GR.Extra.Stats.Watching}}</b>; Pageviews: <b>{{.GR.Extra.Stats.Pageviews}}</b>; Comments Made: <b>{{.GR.Extra.Stats.CommentsMade}}</b>; Friends: <b>{{.GR.Extra.Stats.Friends}}</b></p> | 37 | ♂️ |
| 33 | 38 | {{end}} | |
| 34 | <h3 id="interests"><a href="#interests">#</a> Interests</h3> | 39 | {{if (eq .Templates.GroupUser.About.A.Gender "female")}} |
| 35 | {{.About.Interests}} | 40 | ♀️ |
| 41 | {{end}} | ||
| 42 | [<span title="UID">{{.Templates.GroupUser.GR.Gruser.ID}}</span>] | ||
| 43 | [<span title="Registration date">{{.Templates.GroupUser.CreationDate}}</span>] | ||
| 44 | {{if ne .Templates.GroupUser.GR.Extra.Tag ""}} | ||
| 45 | <i title="User's Tag">"{{.Templates.GroupUser.GR.Extra.Tag}}"</i>{{end}} (<b>{{.Templates.GroupUser.About.A.Country}}</b>) | ||
| 36 | 46 | ||
| 37 | <h3 id="social"><a href="#social">#</a> Social Links</h3> | 47 | <h3 id="stats"><a href="#stats">#</a> Statistics</h3> |
| 38 | {{.About.Social}} | 48 | <p>Favourites: <b>{{.Templates.GroupUser.GR.Extra.Stats.Favourites}}</b>; Deviations: <b>{{.Templates.GroupUser.GR.Extra.Stats.Deviations}}</b>; Watchers: <b>{{.Templates.GroupUser.GR.Extra.Stats.Watchers}}</b> |
| 49 | <p>Watching: <b>{{.Templates.GroupUser.GR.Extra.Stats.Watching}}</b>; Pageviews: <b>{{.Templates.GroupUser.GR.Extra.Stats.Pageviews}}</b>; Comments Made: <b>{{.Templates.GroupUser.GR.Extra.Stats.CommentsMade}}</b>; Friends: <b>{{.Templates.GroupUser.GR.Extra.Stats.Friends}}</b></p> | ||
| 50 | |||
| 51 | {{if ne .Templates.GroupUser.About.Interests ""}} | ||
| 52 | <h3 id="interests"><a href="#interests">#</a> Interests</h3> | ||
| 53 | {{.Templates.GroupUser.About.Interests}} | ||
| 54 | {{end}} | ||
| 39 | 55 | ||
| 40 | <h3 id="about"><a href="#about">#</a> About me</h3> | 56 | {{if ne .Templates.GroupUser.About.Social ""}} |
| 41 | {{.About.DescriptionFormatted}} | 57 | <h3 id="social"><a href="#social">#</a> Social Links</h3> |
| 58 | {{.Templates.GroupUser.About.Social}} | ||
| 59 | {{end}} | ||
| 60 | |||
| 61 | {{if ne .Templates.GroupUser.About.DescriptionFormatted ""}} | ||
| 62 | <h3 id="about"><a href="#about">#</a> About me</h3> | ||
| 63 | {{.Templates.GroupUser.About.DescriptionFormatted}} | ||
| 64 | {{end}} | ||
| 65 | {{if ne .Templates.GroupUser.About.Comments ""}} | ||
| 66 | <br> | ||
| 67 | <h3 id="comments"><a href="#comments">#</a> Comments</h3> | ||
| 68 | {{.Templates.GroupUser.About.Comments}} | ||
| 69 | {{end}} | ||
| 70 | {{else}} | ||
| 71 | {{.Templates.GroupUser.Gallery.List}} | ||
| 72 | {{end}} | ||
| 42 | </main> | 73 | </main> |
| 43 | </html>\ No newline at end of file | 74 | </html> |
html/search.htm +5 −5
| @@ -1,7 +1,7 @@ | |||
| 1 | <!DOCTYPE html> | 1 | <!DOCTYPE html> |
| 2 | <html> | 2 | <html> |
| 3 | <head> | 3 | <head> |
| 4 | <title>SkunkyArt | Search</title> | 4 | <title>SkunkyArt | Search "{{.Query}}"</title> |
| 5 | <link rel="stylesheet" href="/gui/css/skunky.css"> | 5 | <link rel="stylesheet" href="/gui/css/skunky.css"> |
| 6 | </head> | 6 | </head> |
| 7 | <main> | 7 | <main> |
| @@ -16,11 +16,11 @@ | |||
| 16 | <button type="submit">Search!</button> | 16 | <button type="submit">Search!</button> |
| 17 | </form> | 17 | </form> |
| 18 | </header> | 18 | </header> |
| 19 | {{if (ne .List "")}} | 19 | {{if ne .Templates.Search.List ""}} |
| 20 | {{if (ne .Search.Total 0)}} | 20 | {{if ne .Templates.Search.Content.Total 0}} |
| 21 | <h1>Total resuls: {{.Search.Total}}</h1> | 21 | <h1>Total resuls: {{.Templates.Search.Content.Total}}</h1> |
| 22 | {{end}} | 22 | {{end}} |
| 23 | {{.List}} | 23 | {{.Templates.Search.List}} |
| 24 | {{else}} | 24 | {{else}} |
| 25 | <p>No results :(</p> | 25 | <p>No results :(</p> |
| 26 | {{end}} | 26 | {{end}} |