necessário

This commit is contained in:
silva guimaraes 2025-07-24 19:31:03 -03:00
parent 92b0a902ca
commit 7edb0a17ed
5 changed files with 199 additions and 162 deletions

64
main.go
View file

@ -111,7 +111,7 @@ func main() {
Port: ":2323",
Root: "/home/xi/seed/gallery-dl/",
Galleries: make([]gallery.Gallery, 0, 50),
UniqueTags: make(map[gallery.Tag]int),
UniqueTags: make(map[string]int),
UniqueArtists: make(map[string]int),
UniqueGroups: make(map[string]int),
UniqueParodies: make(map[string]int),
@ -276,35 +276,35 @@ func main() {
_ = png.Encode(io.MultiWriter(imageCache, w), destImage)
})
// http.DefaultServeMux.HandleFunc("POST /filter/{tag}/{sex}", func(w http.ResponseWriter, r *http.Request) {
// tagLookup := r.PathValue("tag")
// if tagLookup == "" {
// panic(fmt.Errorf("tag could not be found: %s", r.URL.Path))
// }
// if tagLookup == "clear" {
// state.Filtered = []gallery.Tag{}
// view.GalleriesListing(state.Galleries).Render(w)
// return
// }
// tagSexLookup := r.PathValue("sex")
// if tagSexLookup == "" {
// panic(fmt.Errorf("tag sex could not be found: %s", r.URL.Path))
// }
// sex := gallery.ToGender(tagSexLookup)
// i := slices.IndexFunc(state.TagKeys, func(a gallery.Tag) bool {
// return a.Name == tagLookup && a.Sex == sex
// })
// if i < 0 {
// panic(fmt.Errorf("tag could not be found: %s", r.URL.Path))
// }
// tag := state.TagKeys[i]
// i = slices.Index(state.Filtered, tag)
// if i < 0 {
// state.Filtered = append(state.Filtered, tag)
// } else {
// state.Filtered = slices.Delete(state.Filtered, i, i+1)
// }
// view.GalleriesListing(state.FilterGalleries()).Render(w)
// http.DefaultServeMux.HandleFunc("GET /filter/{tag}/{sex}", func(w http.ResponseWriter, r *http.Request) {
// tagLookup := r.PathValue("tag")
// if tagLookup == "" {
// panic(fmt.Errorf("tag could not be found: %s", r.URL.Path))
// }
// if tagLookup == "clear" {
// state.Filtered = []gallery.Tag{}
// view.SearchResults(state.Galleries).Render(w)
// return
// }
// tagSexLookup := r.PathValue("sex")
// if tagSexLookup == "" {
// panic(fmt.Errorf("tag sex could not be found: %s", r.URL.Path))
// }
// sex := gallery.Gender(tagSexLookup)
// i := slices.IndexFunc(state.TagKeys, func(a gallery.Tag) bool {
// return a.Name == tagLookup && a.Sex == sex
// })
// if i < 0 {
// panic(fmt.Errorf("tag could not be found: %s", r.URL.Path))
// }
// tag := state.TagKeys[i]
// i = slices.Index(state.Filtered, tag)
// if i < 0 {
// state.Filtered = append(state.Filtered, tag)
// } else {
// state.Filtered = slices.Delete(state.Filtered, i, i+1)
// }
// view.SearchResults(state.FilterGalleries()).Render(w)
// })
http.DefaultServeMux.HandleFunc("GET /stats", func(w http.ResponseWriter, r *http.Request) {
@ -328,7 +328,7 @@ func main() {
query := r.FormValue("search")
log.Println("search form result:", query)
if query == "" {
view.SearchResults(state.Galleries, 0).Render(w)
view.SearchResults(state.Galleries).Render(w)
return
}
// ranks := fuzzy.RankFindFold(search, state.GalleryNames)
@ -362,7 +362,7 @@ func main() {
}
galleries = append(galleries, state.Galleries[r.OriginalIndex])
}
view.SearchResults(galleries, 0).Render(w)
view.SearchResults(galleries).Render(w)
})
log.Fatal(server.Serve(ln))

View file

@ -3,19 +3,19 @@ package state
import (
"fmt"
"goreader/gallery"
"maps"
"slices"
_ "golang.org/x/image/webp"
)
type State struct {
Port string
Root string
CacheDir string
Galleries []gallery.Gallery
GalleryNames []string
UniqueTags map[gallery.Tag]int
Port string
Root string
CacheDir string
Galleries []gallery.Gallery
GalleryNames []string
// UniqueTags map[gallery.Tag]int
UniqueTags map[string]int
UniqueArtists map[string]int
UniqueGroups map[string]int
UniqueParodies map[string]int
@ -28,11 +28,11 @@ func (s *State) AddGallery(g gallery.Gallery) error {
s.Galleries = append(s.Galleries, g)
s.GalleryNames = append(s.GalleryNames, g.Name())
slices.SortFunc(s.Galleries, func(a, b gallery.Gallery) int {
return a.CTime.Compare(b.CTime)
return -a.CTime.Compare(b.CTime)
})
for _, tag := range g.Tags() {
s.UniqueTags[tag]++
s.UniqueTags[tag.Name]++
}
for _, artist := range g.Artists() {
s.UniqueArtists[artist]++
@ -43,7 +43,6 @@ func (s *State) AddGallery(g gallery.Gallery) error {
for _, parody := range g.Parodies() {
s.UniqueParodies[parody]++
}
s.TagKeys = slices.Collect(maps.Keys(s.UniqueTags))
return nil
}

View file

@ -6,6 +6,7 @@ import (
"goreader/gallery"
"goreader/state"
"strconv"
"strings"
"github.com/silva-guimaraes/gtag"
)
@ -37,15 +38,22 @@ func tag(t gallery.Tag, state *state.State) *gtag.Tag {
backgroundClass = "female"
}
d := gtag.New("a").
Href("javascript:void(0)").
Class("tag").
SetAttr("hx-post", fmt.Sprintf("/filter/%s/%s", t.Name, t.Sex)).
SetAttr("hx-target", "#listing").
SetAttr("hx-swap", "outerHTML")
Href(fmt.Sprintf("javascript:filter_tags('%s')", strings.TrimSpace(t.Name) /* FIXME */)).
Class("tag")
{
d.Tag("span").Text(gender).Class("name", backgroundClass)
d.Tag("span").Text(t.Name).Class("name")
d.Tag("span").Text(strconv.Itoa(state.UniqueTags[t])).Class("name", backgroundClass)
d.Tag("span").Text(strconv.Itoa(state.UniqueTags[t.Name])).Class("name", backgroundClass)
}
return d
}
func artistTag(artistName string) *gtag.Tag {
d := gtag.New("a").
Href(fmt.Sprintf("javascript:filter_artist('%s')", artistName /* FIXME */)).
Class("tag")
{
d.Tag("span").Text(artistName).Class("name")
}
return d
}
@ -55,7 +63,8 @@ func thumbnail(src string) *gtag.Tag {
}
func fullThumbnail(g gallery.Gallery) *gtag.Tag {
return thumbnail(fmt.Sprintf("/page/%s/1", g.Uuid()))
thumb := thumbnail(fmt.Sprintf("/page/%s/1", g.Uuid()))
return gtag.Div().Id("image-wrapper").Append(thumb)
}
func smallThumbnail(g gallery.Gallery) *gtag.Tag {
@ -71,9 +80,21 @@ func InspectorInfo(g gallery.Gallery, state *state.State) *gtag.Tag {
d.Tag("a").
SetAttr("href", fmt.Sprintf("/read/%s", g.Uuid())).
SetAttr("hx-boost", "false").
SetAttr("style", "color: white").
Tag("h1").Text(g.Name())
d.Tag("h2").Text(g.JpName())
Tag("h1").
SetAttr("style", "color: white; margin: 10px 0 10px 0;").
Text(g.Name())
d.Tag("h2").Style("margin: 0").Text(g.JpName())
d.Tag("h3").
Text(fmt.Sprintf("Pages: %d", len(g.Images()))).
Style("font-size: 14px; ; margin: 0;")
d.Tag("h2").Text("Artists")
artists := d.Div()
{
for _, a := range g.Artists() {
artists.Append(artistTag(a))
}
}
d.Tag("h2").Text("Tags")
tags := d.Div()
{
for _, t := range g.Tags() {
@ -85,10 +106,16 @@ func InspectorInfo(g gallery.Gallery, state *state.State) *gtag.Tag {
}
func cover(g gallery.Gallery) *gtag.Tag {
var s []string
for _, tag := range g.Tags() {
s = append(s, fmt.Sprintf("'%s'", strings.TrimSpace(tag.Name)) /* FIXME */)
}
dataset := fmt.Sprintf("[%s]", strings.Join(s, ", "))
a := gtag.New("a").
Class("cover").
Id(fmt.Sprintf("cover-%s", g.Uuid())).
SetAttr("href", fmt.Sprintf("/read/%s", g.Uuid())).
SetAttr("data-tags", dataset).
SetAttr("hx-get", fmt.Sprintf("/details/%s", g.Uuid())).
SetAttr("onmouseenter", "inspectSetTimeout(event)").
SetAttr("onmouseleave", "inspectClearTimeout(event)").
@ -101,22 +128,19 @@ func cover(g gallery.Gallery) *gtag.Tag {
return a
}
const galleriesPerPage = 50
const galleriesPerPage = 70
func SearchResults(galleries []gallery.Gallery, page int) *gtag.Tag {
glm := max(len(galleries)-1, 0)
recent := galleries[min(page*galleriesPerPage, glm):min((page+1)*galleriesPerPage, glm)]
m := gtag.New("main")
// HTMX faz com que isso receba [InspectorInfo] quando usuário paira o
// mouse sobre algum cover
m.Tag("section").Id("inspector")
results := m.Tag("section").Id("results")
func SearchResults(galleries []gallery.Gallery) *gtag.Tag {
s := gtag.New("section")
results := s.Tag("section").Id("results")
{
for _, g := range recent {
results.Append(cover(g))
results.Tag("header").Tag("h1").Text(fmt.Sprintf("Listing %d Galleries", len(galleries)))
covers := results.Tag("main")
for _, g := range galleries {
covers.Append(cover(g))
}
}
return m
return s
}
func Stats(state *state.State) *gtag.Tag {
@ -153,80 +177,18 @@ func Index(state *state.State, page int) *gtag.Tag {
{
nav := body.Tag("nav").Class("container")
{
nav.Tag("a").
Href("javascript:clear_filters()").
Text("clear filters")
nav.Div().Text("omakase v1")
}
center := body.Div().Id("center")
{
top := center.Tag("div").Class("top")
{
top.Div().Id("search-bar").VoidTag("input").
Class("ask-input").
SetAttr("type", "search").
SetAttr("name", "search").
SetAttr("autocomplete", "false").
SetAttr("placeholder", "Slash to search...").
SetAttr("hx-post", "/search").
SetAttr("hx-trigger", "input changed delay:50ms, search").
SetAttr("hx-target", "#search-results")
header := top.Tag("header").Class("container")
{
header.Div().Id("omakase").
Tag("a").
SetAttr("href", "/random").
Text("おまかせ").
SetAttr("target", "_blank")
header.Tag("hr").Style("opacity: 0.2;")
header.Append(Stats(state))
}
}
ret := center.Tag("main").Id("search-results")
{
controls := ret.Tag("section").AddClass("container").Id("controls")
{
paging := controls.Div()
{
maxPages := len(state.Galleries) / galleriesPerPage
previousPage := gtag.New("a").
AddClass("page-control").
Text("<")
if page > 1 {
previousPage.SetAttr("href",
fmt.Sprintf("?page=%d", page-1),
)
}
paging.Append(previousPage)
p := page
for ; p < min(page+3, maxPages); p++ {
paging.Tag("a").
AddClass("page-control").
SetAttr("href",
fmt.Sprintf("?page=%d", p),
).Text(fmt.Sprint(p))
}
if len(state.Galleries) > 0 && p != maxPages {
paging.Tag("span").Text("...")
}
paging.Tag("a").
AddClass("page-control").
SetAttr("href",
fmt.Sprintf("?page=%d", maxPages),
).Text(fmt.Sprint(maxPages))
nextPage := gtag.New("a").
AddClass("page-control").
SetAttr("href", "?page=1").
Text(">")
if page < maxPages {
nextPage.SetAttr("href",
fmt.Sprintf("?page=%d", page+1),
)
} else if page == maxPages {
nextPage.SetAttr("disabled", "true")
}
paging.Append(nextPage)
}
}
ret.Append(SearchResults(state.Galleries, page))
}
d := center.Div()
// HTMX faz com que isso receba [InspectorInfo] quando usuário paira o
// mouse sobre algum cover
d.Tag("section").Id("inspector")
d.Append(SearchResults(state.Galleries))
}
body.Tag("footer").P().Text(randomQuote())
body.Tag("script").Asis(indexJavascript)

View file

@ -10,13 +10,77 @@ hotkeys('/', {keyup: true}, function (event, _){
});
let filtered_tags = new Set();
/**
* @param {string} name
*/
function filter_tags(name) {
filtered_tags.add(name);
/**
* @type {NodeListOf<HTMLElement>}
*/
let covers = document.querySelectorAll('.cover');
for (let cover of covers) {
/**
* @type {Array<string>}
*/
let tags = eval(cover.dataset['tags']); // yeehaw!!! eu sou um cowboy yeehaw!!!
if (!(tags.includes(name))) {
cover.classList.add('hidden');
}
}
const url = new URL(window.location.href);
const new_params = new URLSearchParams([
['tags', Array.from(filtered_tags).join('&')]
// ...Array.from(url.searchParams.entries()),
]).toString();
window.history.replaceState(null, "", `${url.pathname}?${new_params}`);
}
/**
* @param {string} name
*/
function filter_artists(name) {
filtered_tags.add(name);
/**
* @type {NodeListOf<HTMLElement>}
*/
let covers = document.querySelectorAll('.cover');
for (let cover of covers) {
/**
* @type {Array<string>}
*/
let tags = eval(cover.dataset['tags']); // yeehaw!!! eu sou um cowboy yeehaw!!!
if (!(tags.includes(name))) {
cover.classList.add('hidden');
}
}
const url = new URL(window.location.href);
const new_params = new URLSearchParams([
['tags', Array.from(filtered_tags).join('&')]
// ...Array.from(url.searchParams.entries()),
]).toString();
window.history.replaceState(null, "", `${url.pathname}?${new_params}`);
}
// filtra tags na url da página
const url = new URL(window.location.href);
filtered_tags = new Set(url.searchParams.get('tags')?.split('&'));
filtered_tags?.forEach(filter_tags);
function clear_filters() {
window.history.replaceState(null, "", url.pathname);
document.querySelectorAll('.cover').forEach(x => x.classList.remove('hidden'));
filtered_tags = new Set();
}
/**
* @param {Event & {target: HTMLElement}} event
*/
function inspectSetTimeout(event) {
let target = event.target;
let id = setTimeout(
() => {
let targetId = '#' + target.id;
@ -33,7 +97,7 @@ function inspectSetTimeout(event) {
500
);
target.dataset['timeout'] = id
target.dataset['timeout'] = id;
}
/**
@ -41,6 +105,6 @@ function inspectSetTimeout(event) {
*/
function inspectClearTimeout(event) {
let target = event.target;
clearTimeout(target.dataset['timeout'])
clearTimeout(target.dataset['timeout']);
}

View file

@ -12,6 +12,12 @@
--main-margin: 62px;
}
* {
margin: 0;
font-size-adjust: ex-height 0.53;
box-sizing: border-box;
}
.page {
box-sizing: border-box;
position: absolute;
@ -20,7 +26,7 @@
}
.hidden {
display: none;
display: none !important;
}
#pages-container {
@ -94,12 +100,9 @@ body {
#index nav {
display: flex;
justify-content: flex-end;
/* margin-bottom: 11px; */
/* background-color: var(--haughty-gray); */
justify-content: space-between;
border-bottom: 1px solid var(--silver-lining-white);
padding: 5px;
/* box-shadow: black 1px 1px 1px 1px; */
}
#index #content {
@ -116,14 +119,7 @@ body {
padding: 20px;
}
#index #center {
margin: var(--main-margin) var(--main-margin) auto var(--main-margin);
}
.top {
/* width: 100%; */
/* display: flex; */
/* flex-direction: row; */
display: flex;
margin-bottom: 50px;
}
@ -172,7 +168,7 @@ div#search-bar {
}
}
#index header {
#index > header {
width: min-content;
padding: 20px;
font-family: sans-serif;
@ -233,10 +229,12 @@ footer {
.tag,
.caption {
font-family: Noto sans, sans-serif;
font-weight: 600;
font-family: sans-serif;
font-weight: 900;
color: white;
text-decoration-line: none;
margin: 2px;
border-radius: 3px 3px;
}
.tag {
@ -244,6 +242,7 @@ footer {
color: var(--almost-white);
text-decoration-line: none;
padding: 0.13em;
background-color: var(--main-shadow);
&>span:first-child {
font-family: monospace;
@ -251,9 +250,6 @@ footer {
border-radius: 3px 0 0 3px;
}
&>span:nth-child(2) {
background-color: var(--haughty-gray);
}
&>span:last-child {
border-radius: 0 3px 3px 0;
@ -283,6 +279,7 @@ footer {
.cover {
display: inline-block;
vertical-align: top;
width: 11%;
}
.caption {
@ -292,17 +289,21 @@ footer {
}
img {
filter: blur(15px);
filter: blur(30px);
}
#index img {
width: 100%;
}
.thumbnail,
.cover {
width: 148px;
/* width: 148px; */
height: auto;
opacity: 0.93;
}
#search-results {
#index #center {
& #controls {
width: calc(min-content / 2);
height: min-content;
@ -325,9 +326,9 @@ img {
}
& #results {
display: grid;
width: 65%;
/* display: grid; */
grid-template-columns: repeat(5, 1fr);
width: 65%;
@media (min-width: 1200px) {
grid-template-columns: repeat(6, 1fr);
@ -349,9 +350,20 @@ img {
Arial,
sans-serif;
& img {
& #image-wrapper {
position: relative;
width: 100%;
padding-top: 95vh;
overflow: hidden;
}
& #image-wrapper img {
position: absolute;
top: 0;
left: 0;
max-height: 95vh;
max-width: 100%;
object-fit: cover;
/* width: auto; */
/* height: auto; */
}
@ -374,7 +386,7 @@ img {
font-size: large;
}
& div {
& div:last-child {
margin-bottom: 100%;
}
}