diff --git a/pkg/models/pagination.go b/pkg/models/pagination.go index 51aa30e..737a5d9 100644 --- a/pkg/models/pagination.go +++ b/pkg/models/pagination.go @@ -1,11 +1,14 @@ package models import ( + "fmt" "math" + "math/rand" "net/http" "strconv" "code.nonshy.com/nonshy/website/pkg/config" + "code.nonshy.com/nonshy/website/pkg/encryption" ) // Pagination result object. @@ -19,12 +22,6 @@ type Pagination struct { lastPage bool } -// Page for Iter. -type Page struct { - Page int - IsCurrent bool -} - // Load the page from form or query parameters. func (p *Pagination) ParsePage(r *http.Request) { raw := r.FormValue("page") @@ -40,50 +37,88 @@ func (p *Pagination) ParsePage(r *http.Request) { } } +// Page for Iter. +type Page struct { + Page int // label to show in the button + IsCurrent bool // highlight the currently selected page button + + // this "button" is a drop-down menu of page numbers in the middle + IsOverflow bool + overflowFrom int + overflowTo int +} + // Iter the pages, for templates. +// +// If there are fewer than 7 pages (configurable) this returns a simple slice of buttons +// for each of the 7 pages to be drawn. If there are a LOT of pages, the middle button of +// the pager should be a drop-down menu to pick from the pages within. func (p *Pagination) Iter() []Page { var ( - pages = []Page{} - pageIdx int - total = p.Pages() + pages = []Page{} + total = p.Pages() ) for i := 1; i <= total; i++ { pages = append(pages, Page{ Page: i, IsCurrent: i == p.Page, }) - - if i == p.Page { - pageIdx = i - } } // Do we have A LOT of pages? - if len(pages) > config.PagerButtonLimit { - // We return a slide only N pages long. Where is our current page in the offset? - if pageIdx <= config.PagerButtonLimit/2 { - // We are near the front, return the first N pages. - return pages[:config.PagerButtonLimit+1] - } - - // Are we near the end? - if pageIdx > len(pages)-(config.PagerButtonLimit/2) { - // We are near the end, return the last N pages. - return pages[len(pages)-config.PagerButtonLimit-1:] - } - - // We are somewhere in the middle. - var result = []Page{} - for i := pageIdx - (config.PagerButtonLimit / 2) - 1; i < pageIdx+(config.PagerButtonLimit/2); i++ { - if i >= 0 && i < len(pages) { - result = append(result, pages[i]) + if len(pages) > config.PagerButtonLimit+1 { + // The left half of the buttons should be pages 1 thru N. + // The right half are the final pages M thru Last. + // In the middle will be the overflow drop-down of middle pages. + var ( + endLength = config.PagerButtonLimit / 2 + start = endLength + 1 + end = len(pages) - endLength + overflow = Page{ + Page: start, + IsCurrent: p.Page >= start && p.Page <= end, + IsOverflow: true, + overflowFrom: start, + overflowTo: end, } + result = []Page{} + ) + + // If we are currently selected on an overflow page, set the label to match. + if overflow.IsCurrent { + overflow.Page = p.Page } + + result = pages[:endLength] + result = append(result, overflow) + result = append(result, pages[end:]...) + return result } return pages } +// IterOverflow: if the Page represents an overflow drop-down menu, iterate the members of the menu +// for easy template integration. +func (p Page) IterOverflow() []Page { + var result = []Page{} + for i := p.overflowFrom; i <= p.overflowTo; i++ { + result = append(result, Page{ + Page: i, + IsCurrent: p.Page == i, + }) + } + return result +} + +// UniqueSerialID will return a unique JavaScript ID. +// +// It is used in front-end pages such as for Pagination drop-down menus, which may appear multiple times +// on a page and each use needs a unique ID attribute to connect the button to the dropdown. +func (p *Pagination) UniqueSerialID() string { + return encryption.Hash([]byte(fmt.Sprintf("%d", rand.Intn(9000000)))) +} + func (p Pagination) Pages() int { if p.PerPage == 0 { return 0 diff --git a/web/static/js/bulma.js b/web/static/js/bulma.js index 9296c08..5558eeb 100644 --- a/web/static/js/bulma.js +++ b/web/static/js/bulma.js @@ -82,6 +82,14 @@ document.addEventListener('DOMContentLoaded', () => { })); })(); + // Dropdown menus. + (document.querySelectorAll(".dropdown") || []).forEach(node => { + const button = node.querySelector("button"); + button.addEventListener("click", (e) => { + node.classList.toggle("is-active"); + }) + }); + // Common event handlers for bulma modals. (document.querySelectorAll(".modal-background, .modal-close, .photo-modal") || []).forEach(node => { const target = node.closest(".modal"); diff --git a/web/templates/partials/simple_pager.html b/web/templates/partials/simple_pager.html index 2d426a9..941dc28 100644 --- a/web/templates/partials/simple_pager.html +++ b/web/templates/partials/simple_pager.html @@ -13,18 +13,42 @@ See also: template_funcs.go for the SimplePager wrapper function. {{if .Pager.Pages}}