krz/skunky-art

Alternative privacy frontend for DeviantArt.

clone: git clone https://gitbay.org/krz/skunky-art.git

ed031b353078d0b58a67981de4d463c8563a20d7

unsigned

author: lost+skunk <skunky@ebloid.ru> · 2024-06-30T11:40:27Z

попытка реализовать галерею. так сыро потому что мне скоро уезжать
 app/router.go       |   4 +-
 app/util.go         | 198 ++++++++++++++++++++++++++++
 app/wraper.go       | 368 +++++++++++++++-------------------------------------
 css/skunky.css      |   9 +-
 html/deviantion.htm |   2 +-
 html/gruser.htm     |  81 ++++++++----
 html/search.htm     |  10 +-
 7 files changed, 373 insertions(+), 299 deletions(-)

diff --git a/app/router.go b/app/router.go
index 9e51fd9..d98e2a9 100644
--- a/app/router.go
+++ b/app/router.go
@@ -52,7 +52,7 @@ func Router() {
 		// пути
 		switch e {
 		default:
-			skunky.httperr(404)
+			skunky.ReturnHTTPError(404)
 		case "/", "":
 			open_n_send("html/index.htm")
 		case "post":
@@ -62,7 +62,7 @@ func Router() {
 			skunky.Search()
 		case "dd":
 			skunky.DD()
-		case "group":
+		case "group_user":
 			skunky.GRUser()
 
 		case "media":
diff --git a/app/util.go b/app/util.go
index 7c4d104..e4a1de2 100644
--- a/app/util.go
+++ b/app/util.go
@@ -2,11 +2,38 @@ package app
 
 import (
 	"encoding/json"
+	"net/http"
+	"strconv"
 	"strings"
+	"text/template"
 
 	"git.macaw.me/skunky/devianter"
 )
 
+// парсинг темплейтов
+func (s skunkyart) ExecuteTemplate(file string, data any) {
+	var buf strings.Builder
+	tmp, e := template.ParseFiles(file)
+	err(e)
+	err(tmp.Execute(&buf, &data))
+	wr(s.Writer, buf.String())
+}
+
+func (s skunkyart) ReturnHTTPError(status int) {
+	s.Writer.WriteHeader(status)
+
+	// пострйока с помощью strings.Builder, потому что такой метод быстрее обычного сложения
+	var msg strings.Builder
+	msg.WriteString(`<html><link rel="stylesheet" href="/gui/css/skunky.css" />`)
+	msg.WriteString("<h1>")
+	msg.WriteString(strconv.Itoa(status))
+	msg.WriteString(" - ")
+	msg.WriteString(http.StatusText(status))
+	msg.WriteString("</h1></html>")
+
+	wr(s.Writer, msg.String())
+}
+
 type text struct {
 	TXT  string
 	from int
@@ -87,3 +114,174 @@ func ParseDescription(dscr devianter.Text) string {
 
 	return parseddescription.String()
 }
+
+// навигация по страницам
+type dlist struct {
+	Pages int
+	More  bool
+}
+
+// FIXME: на некоротрых артах первая страница может вызывать полное отсутствие панели навигации.
+func (s skunkyart) NavBase(c dlist) string {
+	// TODO: сделать понятнее
+	// навигация по страницам
+	var list strings.Builder
+	list.WriteString("<br>")
+	p := s.Page
+
+	// функция для генерации ссылок
+	prevrev := func(msg string, page int, onpage bool) {
+		if !onpage {
+			list.WriteString(`<a href="?p=`)
+			list.WriteString(strconv.Itoa(page))
+			if s.Type != 0 {
+				list.WriteString("&type=")
+				list.WriteRune(s.Type)
+			}
+			if s.Query != "" {
+				list.WriteString("&q=")
+				list.WriteString(s.Query)
+			}
+			list.WriteString(`">`)
+			list.WriteString(msg)
+			list.WriteString("</a> ")
+		} else {
+			list.WriteString(strconv.Itoa(page))
+			list.WriteString(" ")
+		}
+	}
+
+	// вперёд-назад
+	if p > 1 {
+		prevrev("<= Prev |", p-1, false)
+	} else {
+		p = 1
+	}
+
+	if c.Pages > 0 {
+		// назад
+		for x := p - 6; x < p && x > 0; x++ {
+			prevrev(strconv.Itoa(x), x, false)
+		}
+
+		// вперёд
+		for x := p; x <= p+6; x++ {
+			if x == p {
+				prevrev("", x, true)
+				x++
+			}
+
+			if x > p {
+				prevrev(strconv.Itoa(x), x, false)
+			}
+		}
+	}
+
+	// вперёд-назад
+	if c.More {
+		prevrev("| Next =>", p+1, false)
+	}
+
+	return list.String()
+}
+
+func (s skunkyart) DeviationList(devs []devianter.Deviation, content ...dlist) string {
+	var list strings.Builder
+	list.WriteString(`<div class="content">`)
+	for _, data := range devs {
+		url := devianter.UrlFromMedia(data.Media)
+
+		list.WriteString(`<a title="open/download" href="`)
+		list.WriteString(url)
+		list.WriteString(`"><div class="block"><img src="`)
+		list.WriteString(url)
+		list.WriteString(`" width="15%"></a><br><a href="`)
+		list.WriteString("/post/")
+		list.WriteString(data.Author.Username)
+		list.WriteString("/")
+		list.WriteString(data.Url[27:][strings.Index(data.Url[27:], "/art/")+5:])
+		list.WriteString(`">`)
+		list.WriteString(data.Author.Username)
+		list.WriteString(" - ")
+		list.WriteString(data.Title)
+
+		// шильдики нсфв, аи и ежедневного поста
+		if data.NSFW {
+			list.WriteString(` [<span class="nsfw">NSFW</span>]`)
+		}
+		if data.AI {
+			list.WriteString(" [🤖]")
+		}
+		if data.DD {
+			list.WriteString(` [<span class="dd">DD</span>]`)
+		}
+
+		list.WriteString("</a></div>")
+	}
+	list.WriteString("</div>")
+	list.WriteString(s.NavBase(content[0]))
+
+	return list.String()
+}
+
+// FIXME: первый комментарий не отображается.
+func (s skunkyart) ParseComments(c devianter.Comments) string {
+	var cmmts strings.Builder
+	replied := make(map[int]string)
+
+	cmmts.WriteString("<details><summary>Comments: <b>")
+	cmmts.WriteString(strconv.Itoa(c.Total))
+	cmmts.WriteString("</b></summary>")
+	for _, x := range c.Thread {
+		replied[x.ID] = x.User.Username
+		cmmts.WriteString(`<div class="msg`)
+		if x.Parent > 0 {
+			cmmts.WriteString(` reply`)
+		}
+		cmmts.WriteString(`"><p id="`)
+		cmmts.WriteString(strconv.Itoa(x.ID))
+		cmmts.WriteString(`"><img src="/media/`)
+		cmmts.WriteString(x.User.Username)
+		cmmts.WriteString(`?type=a" width="30px" height="30px"><a href="/group_user?type=about&q=`)
+		cmmts.WriteString(x.User.Username)
+		cmmts.WriteString(`"><b`)
+		cmmts.WriteString(` class="`)
+		if x.User.Banned {
+			cmmts.WriteString(`banned`)
+		}
+		if x.Author {
+			cmmts.WriteString(`author`)
+		}
+		cmmts.WriteString(`">`)
+		cmmts.WriteString(x.User.Username)
+		cmmts.WriteString("</b></a> ")
+
+		if x.Parent > 0 {
+			cmmts.WriteString(` In reply to <a href="#`)
+			cmmts.WriteString(strconv.Itoa(x.Parent))
+			cmmts.WriteString(`">`)
+			if replied[x.Parent] == "" {
+				cmmts.WriteString("???")
+			} else {
+				cmmts.WriteString(replied[x.Parent])
+			}
+			cmmts.WriteString("</a>")
+		}
+		cmmts.WriteString(" [")
+		cmmts.WriteString(x.Posted.UTC().String())
+		cmmts.WriteString("]<p>")
+
+		cmmts.WriteString(x.Comment)
+		cmmts.WriteString("<p>👍: ")
+		cmmts.WriteString(strconv.Itoa(x.Likes))
+		cmmts.WriteString(" ⏩: ")
+		cmmts.WriteString(strconv.Itoa(x.Replies))
+		cmmts.WriteString("</p></div>\n")
+	}
+	cmmts.WriteString(s.NavBase(dlist{
+		Pages: 0,
+		More:  c.HasMore,
+	}))
+	cmmts.WriteString("</details>")
+	return cmmts.String()
+}
diff --git a/app/wraper.go b/app/wraper.go
index ccaccee..2b733fd 100644
--- a/app/wraper.go
+++ b/app/wraper.go
@@ -1,14 +1,13 @@
 package app
 
 import (
-	"bytes"
+	"fmt"
 	"io"
 	"net/http"
 	"net/url"
 	"regexp"
 	"strconv"
 	"strings"
-	"text/template"
 	"time"
 
 	"git.macaw.me/skunky/devianter"
@@ -17,206 +16,108 @@ import (
 var wr = io.WriteString
 
 type skunkyart struct {
-	Writer http.ResponseWriter
-	Args   url.Values
-	Type   rune
-	Query  string
-	Page   int
-}
-
-// парсинг темплейтов
-func (s skunkyart) exe(file string, data any) {
-	var buf bytes.Buffer
-	tmp, e := template.ParseFiles(file)
-	err(e)
-	tmp.Execute(&buf, &data)
-	wr(s.Writer, buf.String())
-}
-
-func (s skunkyart) httperr(status int) {
-	s.Writer.WriteHeader(status)
-
-	// пострйока с помощью strings.Builder, потому что такой метод быстрее обычного сложения
-	var msg strings.Builder
-	msg.WriteString(`<html><link rel="stylesheet" href="/gui/css/skunky.css" />`)
-	msg.WriteString("<h1>")
-	msg.WriteString(strconv.Itoa(status))
-	msg.WriteString(" - ")
-	msg.WriteString(http.StatusText(status))
-	msg.WriteString("</h1></html>")
-
-	wr(s.Writer, msg.String())
-}
-
-// навигация по страницам
-type dlist struct {
-	Pages int
-	More  bool
-}
-
-// FIXME: на некоротрых артах первая страница может вызывать полное отсутствие панели навигации.
-func (s skunkyart) NavBase(c dlist) string {
-	// TODO: сделать понятнее
-	// навигация по страницам
-	var list strings.Builder
-	list.WriteString("<br>")
-	p := s.Page
-
-	// функция для генерации ссылок
-	prevrev := func(msg string, page int, onpage bool) {
-		if !onpage {
-			list.WriteString(`<a href="?p=`)
-			list.WriteString(strconv.Itoa(page))
-			if s.Type != 0 {
-				list.WriteString("&type=")
-				list.WriteRune(s.Type)
-			}
-			if s.Query != "" {
-				list.WriteString("&q=")
-				list.WriteString(s.Query)
-			}
-			list.WriteString(`">`)
-			list.WriteString(msg)
-			list.WriteString("</a> ")
-		} else {
-			list.WriteString(strconv.Itoa(page))
-			list.WriteString(" ")
-		}
-	}
-
-	// вперёд-назад
-	if p > 1 {
-		prevrev("<= Prev |", p-1, false)
-	} else {
-		p = 1
-	}
-
-	if c.Pages > 0 {
-		// назад
-		for x := p - 6; x < p && x > 0; x++ {
-			prevrev(strconv.Itoa(x), x, false)
-		}
-
-		// вперёд
-		for x := p; x <= p+6; x++ {
-			if x == p {
-				prevrev("", x, true)
-				x++
+	Writer    http.ResponseWriter
+	Args      url.Values
+	Type      rune
+	Query     string
+	Page      int
+	Templates struct {
+		GroupUser struct {
+			GR           devianter.GRuser
+			CreationDate string
+
+			About struct {
+				A devianter.About
+
+				DescriptionFormatted string
+				Interests, Social    string
+				Comments             string
+				BG                   string
+				BGMeta               devianter.Deviation
 			}
 
-			if x > p {
-				prevrev(strconv.Itoa(x), x, false)
+			Gallery struct {
+				Pages int
+				List  string
 			}
 		}
+		Search struct {
+			Content devianter.Search
+			List    string
+		}
 	}
-
-	// вперёд-назад
-	if c.More {
-		prevrev("| Next =>", p+1, false)
-	}
-
-	return list.String()
 }
 
 func (s skunkyart) GRUser() {
-	var group struct {
-		GR           devianter.GRuser
-		CreationDate string
-
-		About struct {
-			A devianter.About
-
-			DescriptionFormatted string
-			Interests            string
-			Social               string
-			BG                   devianter.Deviation
-		}
-	}
-
 	if len(s.Query) < 1 {
-		s.httperr(400)
+		s.ReturnHTTPError(400)
 		return
 	}
 
 	var g devianter.Group
 	g.Name = s.Query
-	group.GR = g.GroupFunc()
-
-	if g := group.GR; !g.Owner.Group {
-		for _, x := range g.Gruser.Page.Modules {
-			var about = group.About.A
-			if x.ModuleData.About.RegDate != 0 {
-				about = x.ModuleData.About
-			}
-			group.About.DescriptionFormatted = ParseDescription(about.Description)
+	s.Templates.GroupUser.GR = g.GroupFunc()
+	group := &s.Templates.GroupUser
 
-			for _, val := range x.ModuleData.About.Interests {
-				var interest strings.Builder
-				interest.WriteString(val.Label)
-				interest.WriteString(": <b>")
-				interest.WriteString(val.Value)
-				interest.WriteString("</b><br>")
-				group.About.Interests += interest.String()
-			}
-
-			for _, val := range x.ModuleData.About.SocialLinks {
-				var social strings.Builder
-				social.WriteString(`<a target="_blank" href="`)
-				social.WriteString(val.Value)
-				social.WriteString(`">`)
-				social.WriteString(val.Value)
-				social.WriteString("</a><br>")
-				group.About.Social += social.String()
-			}
-
-			if rd := x.ModuleData.About.RegDate; rd != 0 {
-				group.CreationDate = time.Unix(time.Now().Unix()-rd, 0).UTC().String()
+	switch s.Type {
+	case 'a':
+		if g := group.GR; !g.Owner.Group {
+			for _, x := range g.Gruser.Page.Modules {
+				switch x.Name {
+				case "about":
+					group.About.A = x.ModuleData.About
+					var about = group.About.A
+					group.About.DescriptionFormatted = ParseDescription(about.Description)
+					group.About.Comments = s.ParseComments(devianter.CommentsFunc(
+						strconv.Itoa(group.GR.Gruser.ID),
+						"",
+						s.Page,
+						4,
+					))
+
+					for _, val := range x.ModuleData.About.SocialLinks {
+						var social strings.Builder
+						social.WriteString(`<a target="_blank" href="`)
+						social.WriteString(val.Value)
+						social.WriteString(`">`)
+						social.WriteString(val.Value)
+						social.WriteString("</a><br>")
+						group.About.Social += social.String()
+					}
+
+					for _, val := range x.ModuleData.About.Interests {
+						var interest strings.Builder
+						interest.WriteString(val.Label)
+						interest.WriteString(": <b>")
+						interest.WriteString(val.Value)
+						interest.WriteString("</b><br>")
+						group.About.Interests += interest.String()
+					}
+
+					if rd := x.ModuleData.About.RegDate; rd != 0 {
+						group.CreationDate = time.Unix(time.Now().Unix()-rd, 0).UTC().String()
+					}
+				case "cover_deviation":
+					group.About.BGMeta = x.ModuleData.CoverDeviation.Deviation
+					group.About.BG = devianter.UrlFromMedia(group.About.BGMeta.Media)
+				}
 			}
-		}
-	} else {
-
-	}
-
-	s.exe("html/gruser.htm", &group)
-}
-
-func (s skunkyart) DeviationList(devs []devianter.Deviation, content ...dlist) string {
-	var list strings.Builder
-	list.WriteString(`<div class="content">`)
-	for _, data := range devs {
-		url := devianter.UrlFromMedia(data.Media)
-
-		list.WriteString(`<a title="open/download" href="`)
-		list.WriteString(url)
-		list.WriteString(`"><div class="block"><img src="`)
-		list.WriteString(url)
-		list.WriteString(`" width="15%"></a><br><a href="`)
-		list.WriteString("/post/")
-		list.WriteString(data.Author.Username)
-		list.WriteString("/")
-		list.WriteString(data.Url[27:][strings.Index(data.Url[27:], "/art/")+5:])
-		list.WriteString(`">`)
-		list.WriteString(data.Author.Username)
-		list.WriteString(" - ")
-		list.WriteString(data.Title)
+		} else {
 
-		// шильдики нсфв, аи и ежедневного поста
-		if data.NSFW {
-			list.WriteString(` [<span class="nsfw">NSFW</span>]`)
 		}
-		if data.AI {
-			list.WriteString(" [🤖]")
+	case 'g':
+		gallery := g.Gallery(s.Page)
+		fmt.Println(gallery)
+		for _, x := range gallery.Content.Gruser.Page.Modules {
+			group.Gallery.List = s.DeviationList(x.ModuleData.Folder.Deviations, dlist{
+				Pages: x.ModuleData.Folder.Pages,
+			})
 		}
-		if data.DD {
-			list.WriteString(` [<span class="dd">DD</span>]`)
-		}
-
-		list.WriteString("</a></div>")
+	default:
+		s.ReturnHTTPError(400)
 	}
-	list.WriteString("</div>")
-	list.WriteString(s.NavBase(content[0]))
 
-	return list.String()
+	s.ExecuteTemplate("html/gruser.htm", &s)
 }
 
 // посты
@@ -250,115 +151,52 @@ func (s skunkyart) Deviation(author, postname string) {
 			post.Tags += tag.String()
 		}
 
-		// FIXME: первый комментарий не отображается.
-		// генерация комментов
-		var cmmts strings.Builder
-		replied := make(map[int]string)
-		c := devianter.CommentsFunc(id, post.Post.Comments.Cursor, s.Page, 1)
-
-		cmmts.WriteString("<details><summary>Comments: <b>")
-		cmmts.WriteString(strconv.Itoa(c.Total))
-		cmmts.WriteString("</b></summary>")
-		for _, x := range c.Thread {
-			replied[x.ID] = x.User.Username
-			cmmts.WriteString(`<div class="msg`)
-			if x.Parent > 0 {
-				cmmts.WriteString(` reply`)
-			}
-			cmmts.WriteString(`"><p id="`)
-			cmmts.WriteString(strconv.Itoa(x.ID))
-			cmmts.WriteString(`"><img src="/media/`)
-			cmmts.WriteString(x.User.Username)
-			cmmts.WriteString(`?type=a" width="30px" height="30px"><a href="/user/`)
-			cmmts.WriteString(x.User.Username)
-			cmmts.WriteString(`"><b`)
-			cmmts.WriteString(` class="`)
-			if x.User.Banned {
-				cmmts.WriteString(`banned`)
-			}
-			if x.Author {
-				cmmts.WriteString(`author`)
-			}
-			cmmts.WriteString(`">`)
-			cmmts.WriteString(x.User.Username)
-			cmmts.WriteString("</b></a> ")
+		post.Comments = s.ParseComments(devianter.CommentsFunc(id, post.Post.Comments.Cursor, s.Page, 1))
 
-			if x.Parent > 0 {
-				cmmts.WriteString(` In reply to <a href="#`)
-				cmmts.WriteString(strconv.Itoa(x.Parent))
-				cmmts.WriteString(`">`)
-				if replied[x.Parent] == "" {
-					cmmts.WriteString("???")
-				} else {
-					cmmts.WriteString(replied[x.Parent])
-				}
-				cmmts.WriteString("</a>")
-			}
-			cmmts.WriteString(" [")
-			cmmts.WriteString(x.Posted.UTC().String())
-			cmmts.WriteString("]<p>")
-
-			cmmts.WriteString(x.Comment)
-			cmmts.WriteString("<p>👍: ")
-			cmmts.WriteString(strconv.Itoa(x.Likes))
-			cmmts.WriteString(" ⏩: ")
-			cmmts.WriteString(strconv.Itoa(x.Replies))
-			cmmts.WriteString("</p></div>\n")
-		}
-		cmmts.WriteString(s.NavBase(dlist{
-			Pages: 0,
-			More:  c.HasMore,
-		}))
-		cmmts.WriteString("</details>")
-
-		post.Comments = cmmts.String()
-
-		s.exe("html/deviantion.htm", &post)
+		s.ExecuteTemplate("html/deviantion.htm", &post)
 	} else {
-		s.httperr(400)
+		s.ReturnHTTPError(400)
 	}
 }
 
 func (s skunkyart) DD() {
 	dd := devianter.DailyDeviationsFunc(s.Page)
-	s.exe("html/list.htm", s.DeviationList(dd.Deviations, dlist{
+	s.ExecuteTemplate("html/list.htm", s.DeviationList(dd.Deviations, dlist{
 		Pages: 0,
 		More:  dd.HasMore,
 	}))
 }
 
 func (s skunkyart) Search() {
-	// тут всё и так понятно
+	var e error
+	ss := &s.Templates.Search
 	switch s.Type {
-	case 'a', 't', 'g':
-		var srch struct {
-			Search devianter.Search
-			List   string
-		}
-
-		var e error
-		srch.Search, e = devianter.SearchFunc(s.Query, s.Page, s.Type)
-		err(e)
-		srch.List = s.DeviationList(srch.Search.Results, dlist{
-			Pages: srch.Search.Pages,
-			More:  srch.Search.HasMore,
-		})
-
-		s.exe("html/search.htm", &srch)
+	case 'a', 't':
+		ss.Content, e = devianter.SearchFunc(s.Query, s.Page, s.Type)
+	case 'g':
+		ss.Content, e = devianter.SearchFunc(s.Query, s.Page, s.Type, s.Args.Get("usr"))
 	default:
-		s.httperr(400)
+		s.ReturnHTTPError(400)
 	}
+	err(e)
+
+	ss.List = s.DeviationList(ss.Content.Results, dlist{
+		Pages: ss.Content.Pages,
+		More:  ss.Content.HasMore,
+	})
+
+	s.ExecuteTemplate("html/search.htm", &s)
 }
 
 func (s skunkyart) Emojitar(name string) {
 	if name != "" && (s.Type == 'a' || s.Type == 'e') {
 		ae, e := devianter.AEmedia(name, s.Type)
 		if e != nil {
-			s.httperr(404)
+			s.ReturnHTTPError(404)
 			println(e.Error())
 		}
 		wr(s.Writer, ae)
 	} else {
-		s.httperr(400)
+		s.ReturnHTTPError(400)
 	}
 }
diff --git a/css/skunky.css b/css/skunky.css
index 95e7984..9c00522 100644
--- a/css/skunky.css
+++ b/css/skunky.css
@@ -83,4 +83,11 @@ form input, button, select {
 .block p {
     word-break: break-all;
 }
-
+.ubg {
+    display: flex;
+    flex-wrap: wrap;
+    justify-content: center;
+}
+.ubg img {
+    width: 20%;
+}
diff --git a/html/deviantion.htm b/html/deviantion.htm
index 75484f6..6308c91 100644
--- a/html/deviantion.htm
+++ b/html/deviantion.htm
@@ -18,7 +18,7 @@
         </header>
         <figure>
             <img src="/media/{{.Post.Deviation.Author.Username}}?type=a" width="30px">
-            <span><strong><a href="/user/{{.Post.Deviation.Author.Username}}">{{.Post.Deviation.Author.Username}}</a></strong> — {{if (.Post.Deviation.DD)}}
+            <span><strong><a href="/group_user?type=about&q={{.Post.Deviation.Author.Username}}">{{.Post.Deviation.Author.Username}}</a></strong> — {{if (.Post.Deviation.DD)}}
                 <span class="dd" title="Daily Deviation!"><b>{{.Post.Deviation.Title}}</b></span>
                 {{else}}{{.Post.Deviation.Title}}{{end}}
                  {{if (ne .Post.Deviation.License "none")}}<mark title="License">{{.Post.Deviation.License}}</mark>{{end}} {{if (.Post.Deviation.AI)}}[🤖]{{end}} 
diff --git a/html/gruser.htm b/html/gruser.htm
index b81ceb1..5a5c801 100644
--- a/html/gruser.htm
+++ b/html/gruser.htm
@@ -1,43 +1,74 @@
 <!DOCTYPE html>
 <html>
     <head>
-        <title>SkunkyArt | {{.GR.Owner.Username}}</title>
+        <title>SkunkyArt | 
+        {{if eq .Type 'a'}}
+        {{.Templates.GroupUser.GR.Owner.Username}}
+        {{else}}
+        gallery of {{.Templates.GroupUser.GR.Owner.Username}}
+        {{end}}
+        </title>
         <link rel="stylesheet" href="/gui/css/skunky.css">
     </head>
     <main>
         <header>
-            <h1><a href="/">HOME</a> | <a href="/dd">DD</a></h1>
-            <form method="get" action="/search">
-                <input type="text" name="q" placeholder="Search for ..." autocomplete="off" autocapitalize="none" spellcheck="false">
+            <h1><a href="/">HOME</a> | <a href="/dd">DD</a>
+            | <a href="?q={{.Templates.GroupUser.GR.Owner.Username}}&type={{if eq .Type 'a'}}gallery">Gallery</a></h1>{{else}}about">About</a></h1>
+            {{end}}
+             <form method="get" action="/search">
+                <input type="gallery" name="q" placeholder="Search for ..." autocomplete="off" autocapitalize="none" spellcheck="false">
+                <input type="hidden" name="usr" value="{{.Templates.GroupUser.GR.Owner.Username}}">
                 <select name="type">
                     <option value="all">All</option>
                     <option value="tag">Tag</option>
+                    <option value="gallery">Gallery</option>
                 </select>
                 <button type="submit">Search!</button>
             </form>
         </header>
-        <img src="/media/{{.GR.Owner.Username}}?type=a" width="30px" height="30px"> <b>{{.GR.Owner.Username}}</b>
-        {{if (eq .About.A.Gender "male")}}
-        ♂️
-        {{end}}
-        {{if (eq .About.A.Gender "female")}}
-        ♀️
-        {{end}}
-        [<span title="UID">{{.GR.Gruser.ID}}</span>]
-        [<span title="Registration date">{{.CreationDate}}</span>]
-        <i title="User's Tag">"{{.GR.Extra.Tag}}"</i>
+        {{if eq .Type 'a'}}
+            {{if ne .Templates.GroupUser.About.BG ""}}
+            <a href="{{.Templates.GroupUser.About.BGMeta.Url}}" class="ubg"><img title="{{if ne .Templates.GroupUser.GR.Owner.Username .Templates.GroupUser.About.BGMeta.Author.Username}}
+            {{.Templates.GroupUser.About.BGMeta.Author.Username}} - {{end}}{{.Templates.GroupUser.About.BGMeta.Title}}" src="{{.Templates.GroupUser.About.BG}}"></a>
+            {{end}}
 
-        <h3 id="stats"><a href="#stats">#</a> Statistics</h3>
-        <p>Favourites: <b>{{.GR.Extra.Stats.Favourites}}</b>; Deviations: <b>{{.GR.Extra.Stats.Deviations}}</b>; Watchers: <b>{{.GR.Extra.Stats.Watchers}}</b>
-        <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>
-    
-        <h3 id="interests"><a href="#interests">#</a> Interests</h3>
-        {{.About.Interests}}
+            <img src="/media/{{.Templates.GroupUser.GR.Owner.Username}}?type=a" width="30px" height="30px"> <b>{{.Templates.GroupUser.GR.Owner.Username}}</b>
+            {{if (eq .Templates.GroupUser.About.A.Gender "male")}}
+            ♂️
+            {{end}}
+            {{if (eq .Templates.GroupUser.About.A.Gender "female")}}
+            ♀️
+            {{end}}
+            [<span title="UID">{{.Templates.GroupUser.GR.Gruser.ID}}</span>]
+            [<span title="Registration date">{{.Templates.GroupUser.CreationDate}}</span>]
+            {{if ne .Templates.GroupUser.GR.Extra.Tag ""}}
+            <i title="User's Tag">"{{.Templates.GroupUser.GR.Extra.Tag}}"</i>{{end}} (<b>{{.Templates.GroupUser.About.A.Country}}</b>)
 
-        <h3 id="social"><a href="#social">#</a> Social Links</h3>
-        {{.About.Social}}
+            <h3 id="stats"><a href="#stats">#</a> Statistics</h3>
+            <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>
+            <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>
+        
+            {{if ne .Templates.GroupUser.About.Interests ""}}
+            <h3 id="interests"><a href="#interests">#</a> Interests</h3>
+            {{.Templates.GroupUser.About.Interests}}
+            {{end}}
 
-        <h3 id="about"><a href="#about">#</a> About me</h3>
-        {{.About.DescriptionFormatted}}
+            {{if ne .Templates.GroupUser.About.Social ""}}
+            <h3 id="social"><a href="#social">#</a> Social Links</h3>
+            {{.Templates.GroupUser.About.Social}}
+            {{end}}
+
+            {{if ne .Templates.GroupUser.About.DescriptionFormatted ""}}
+            <h3 id="about"><a href="#about">#</a> About me</h3>
+            {{.Templates.GroupUser.About.DescriptionFormatted}}
+            {{end}}
+            {{if ne .Templates.GroupUser.About.Comments ""}}
+            <br>
+            <h3 id="comments"><a href="#comments">#</a> Comments</h3>
+            {{.Templates.GroupUser.About.Comments}}
+            {{end}}
+        {{else}}
+            {{.Templates.GroupUser.Gallery.List}}
+        {{end}}
     </main>
-</html>
\ No newline at end of file
+</html>
diff --git a/html/search.htm b/html/search.htm
index 8ab9438..a78feb6 100644
--- a/html/search.htm
+++ b/html/search.htm
@@ -1,7 +1,7 @@
 <!DOCTYPE html>
 <html>
     <head>
-        <title>SkunkyArt | Search</title>
+        <title>SkunkyArt | Search "{{.Query}}"</title>
         <link rel="stylesheet" href="/gui/css/skunky.css">
     </head>
     <main>
@@ -16,11 +16,11 @@
                 <button type="submit">Search!</button>
             </form>
         </header>
-        {{if (ne .List "")}}
-        {{if (ne .Search.Total 0)}}
-        <h1>Total resuls: {{.Search.Total}}</h1>
+        {{if ne .Templates.Search.List ""}}
+        {{if ne .Templates.Search.Content.Total 0}}
+        <h1>Total resuls: {{.Templates.Search.Content.Total}}</h1>
         {{end}}
-        {{.List}}
+        {{.Templates.Search.List}}
         {{else}}
         <p>No results :(</p>
         {{end}}