Add first/prev/next/last buttons to sorted content
This commit is contained in:
parent
9274162b4b
commit
d902368845
|
@ -87,7 +87,7 @@ func newFile(list []string, sortOrder string, regexes *regexes, formats types.Ty
|
||||||
}
|
}
|
||||||
case sortOrder == "desc":
|
case sortOrder == "desc":
|
||||||
for {
|
for {
|
||||||
splitPath.increment()
|
splitPath.number = splitPath.increment()
|
||||||
|
|
||||||
path, err = tryExtensions(splitPath, formats)
|
path, err = tryExtensions(splitPath, formats)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -95,7 +95,7 @@ func newFile(list []string, sortOrder string, regexes *regexes, formats types.Ty
|
||||||
}
|
}
|
||||||
|
|
||||||
if path == "" {
|
if path == "" {
|
||||||
splitPath.decrement()
|
splitPath.number = splitPath.decrement()
|
||||||
|
|
||||||
path, err = tryExtensions(splitPath, formats)
|
path, err = tryExtensions(splitPath, formats)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -119,9 +119,9 @@ func nextFile(filePath, sortOrder string, regexes *regexes, formats types.Types)
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
case sortOrder == "asc":
|
case sortOrder == "asc":
|
||||||
splitPath.increment()
|
splitPath.number = splitPath.increment()
|
||||||
case sortOrder == "desc":
|
case sortOrder == "desc":
|
||||||
splitPath.decrement()
|
splitPath.number = splitPath.decrement()
|
||||||
default:
|
default:
|
||||||
return "", nil
|
return "", nil
|
||||||
}
|
}
|
||||||
|
|
12
cmd/index.go
12
cmd/index.go
|
@ -186,13 +186,15 @@ func registerIndexHandlers(mux *httprouter.Router, args []string, index *fileInd
|
||||||
func importIndex(args []string, index *fileIndex, formats types.Types) error {
|
func importIndex(args []string, index *fileIndex, formats types.Types) error {
|
||||||
if IndexFile != "" {
|
if IndexFile != "" {
|
||||||
err := index.Import(IndexFile)
|
err := index.Import(IndexFile)
|
||||||
if err != nil {
|
if err == nil {
|
||||||
_, err := fileList(args, &filters{}, "", index, formats)
|
return nil
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_, err := fileList(args, &filters{}, "", index, formats)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
10
cmd/info.go
10
cmd/info.go
|
@ -19,7 +19,7 @@ import (
|
||||||
"seedno.de/seednode/roulette/types"
|
"seedno.de/seednode/roulette/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
func paginate(page int, fileCount int, ending bool) string {
|
func paginateIndex(page int, fileCount int, ending bool) string {
|
||||||
var html strings.Builder
|
var html strings.Builder
|
||||||
|
|
||||||
var firstPage int = 1
|
var firstPage int = 1
|
||||||
|
@ -120,8 +120,8 @@ func serveIndexHtml(args []string, index *fileIndex, shouldPaginate bool) httpro
|
||||||
htmlBody.WriteString(`table,td,tr{border:none;}td{border-bottom:1px solid black;}td{white-space:nowrap;padding:.5em}</style>`)
|
htmlBody.WriteString(`table,td,tr{border:none;}td{border-bottom:1px solid black;}td{white-space:nowrap;padding:.5em}</style>`)
|
||||||
htmlBody.WriteString(fmt.Sprintf("<title>Index contains %d files</title></head><body><table>", fileCount))
|
htmlBody.WriteString(fmt.Sprintf("<title>Index contains %d files</title></head><body><table>", fileCount))
|
||||||
|
|
||||||
if shouldPaginate {
|
if shouldPaginate && !DisableButtons {
|
||||||
htmlBody.WriteString(paginate(page, fileCount, false))
|
htmlBody.WriteString(paginateIndex(page, fileCount, false))
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(indexDump) > 0 {
|
if len(indexDump) > 0 {
|
||||||
|
@ -135,8 +135,8 @@ func serveIndexHtml(args []string, index *fileIndex, shouldPaginate bool) httpro
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if shouldPaginate {
|
if shouldPaginate && !DisableButtons {
|
||||||
htmlBody.WriteString(paginate(page, fileCount, true))
|
htmlBody.WriteString(paginateIndex(page, fileCount, true))
|
||||||
}
|
}
|
||||||
|
|
||||||
htmlBody.WriteString(`</table></body></html>`)
|
htmlBody.WriteString(`</table></body></html>`)
|
||||||
|
|
64
cmd/root.go
64
cmd/root.go
|
@ -12,40 +12,41 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
ReleaseVersion string = "3.0.2"
|
ReleaseVersion string = "3.1.0"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
All bool
|
All bool
|
||||||
Audio bool
|
Audio bool
|
||||||
Bind string
|
Bind string
|
||||||
CaseSensitive bool
|
CaseSensitive bool
|
||||||
Code bool
|
Code bool
|
||||||
CodeTheme string
|
CodeTheme string
|
||||||
ExitOnError bool
|
DisableButtons bool
|
||||||
Fallback bool
|
ExitOnError bool
|
||||||
Filtering bool
|
Fallback bool
|
||||||
Flash bool
|
Filtering bool
|
||||||
Fun bool
|
Flash bool
|
||||||
Handlers bool
|
Fun bool
|
||||||
Images bool
|
Handlers bool
|
||||||
Index bool
|
Images bool
|
||||||
IndexFile string
|
Index bool
|
||||||
Info bool
|
IndexFile string
|
||||||
MaxFileCount int
|
Info bool
|
||||||
MinFileCount int
|
MaxFileCount int
|
||||||
PageLength int
|
MinFileCount int
|
||||||
Port int
|
PageLength int
|
||||||
Prefix string
|
Port int
|
||||||
Profile bool
|
Prefix string
|
||||||
Recursive bool
|
Profile bool
|
||||||
Refresh bool
|
Recursive bool
|
||||||
Russian bool
|
Refresh bool
|
||||||
Sorting bool
|
Russian bool
|
||||||
Text bool
|
Sorting bool
|
||||||
Verbose bool
|
Text bool
|
||||||
Version bool
|
Verbose bool
|
||||||
Videos bool
|
Version bool
|
||||||
|
Videos bool
|
||||||
|
|
||||||
rootCmd = &cobra.Command{
|
rootCmd = &cobra.Command{
|
||||||
Use: "roulette <path> [path]...",
|
Use: "roulette <path> [path]...",
|
||||||
|
@ -88,6 +89,7 @@ func init() {
|
||||||
rootCmd.Flags().BoolVar(&CaseSensitive, "case-sensitive", false, "use case-sensitive matching for filters")
|
rootCmd.Flags().BoolVar(&CaseSensitive, "case-sensitive", false, "use case-sensitive matching for filters")
|
||||||
rootCmd.Flags().BoolVar(&Code, "code", false, "enable support for source code files")
|
rootCmd.Flags().BoolVar(&Code, "code", false, "enable support for source code files")
|
||||||
rootCmd.Flags().StringVar(&CodeTheme, "code-theme", "solarized-dark256", "theme for source code syntax highlighting")
|
rootCmd.Flags().StringVar(&CodeTheme, "code-theme", "solarized-dark256", "theme for source code syntax highlighting")
|
||||||
|
rootCmd.Flags().BoolVar(&DisableButtons, "disable-buttons", false, "disable first/prev/next/last buttons")
|
||||||
rootCmd.Flags().BoolVar(&ExitOnError, "exit-on-error", false, "shut down webserver on error, instead of just printing the error")
|
rootCmd.Flags().BoolVar(&ExitOnError, "exit-on-error", false, "shut down webserver on error, instead of just printing the error")
|
||||||
rootCmd.Flags().BoolVar(&Fallback, "fallback", false, "serve files as application/octet-stream if no matching format is registered")
|
rootCmd.Flags().BoolVar(&Fallback, "fallback", false, "serve files as application/octet-stream if no matching format is registered")
|
||||||
rootCmd.Flags().BoolVarP(&Filtering, "filter", "f", false, "enable filtering")
|
rootCmd.Flags().BoolVarP(&Filtering, "filter", "f", false, "enable filtering")
|
||||||
|
|
139
cmd/sort.go
139
cmd/sort.go
|
@ -6,8 +6,12 @@ package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
|
"seedno.de/seednode/roulette/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
type splitPath struct {
|
type splitPath struct {
|
||||||
|
@ -16,22 +20,22 @@ type splitPath struct {
|
||||||
extension string
|
extension string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (splitPath *splitPath) increment() {
|
func (splitPath *splitPath) increment() string {
|
||||||
asInt, err := strconv.Atoi(splitPath.number)
|
asInt, err := strconv.Atoi(splitPath.number)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
splitPath.number = fmt.Sprintf("%0*d", len(splitPath.number), asInt+1)
|
return fmt.Sprintf("%0*d", len(splitPath.number), asInt+1)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (splitPath *splitPath) decrement() {
|
func (splitPath *splitPath) decrement() string {
|
||||||
asInt, err := strconv.Atoi(splitPath.number)
|
asInt, err := strconv.Atoi(splitPath.number)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
splitPath.number = fmt.Sprintf("%0*d", len(splitPath.number), asInt-1)
|
return fmt.Sprintf("%0*d", len(splitPath.number), asInt-1)
|
||||||
}
|
}
|
||||||
|
|
||||||
func split(path string, regexes *regexes) (*splitPath, error) {
|
func split(path string, regexes *regexes) (*splitPath, error) {
|
||||||
|
@ -49,3 +53,126 @@ func split(path string, regexes *regexes) (*splitPath, error) {
|
||||||
|
|
||||||
return p, nil
|
return p, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getRange(path string, regexes *regexes, index *fileIndex) (string, string, error) {
|
||||||
|
splitPath, err := split(path, regexes)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
list := index.List()
|
||||||
|
|
||||||
|
sort.Slice(list, func(i, j int) bool {
|
||||||
|
return list[i] <= list[j]
|
||||||
|
})
|
||||||
|
|
||||||
|
var first, last, previous string
|
||||||
|
|
||||||
|
Loop:
|
||||||
|
for _, val := range list {
|
||||||
|
splitVal, err := split(val, regexes)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case splitVal.base == splitPath.base && first == "":
|
||||||
|
first = val
|
||||||
|
case splitVal.base != splitPath.base && first != "":
|
||||||
|
last = previous
|
||||||
|
|
||||||
|
break Loop
|
||||||
|
}
|
||||||
|
|
||||||
|
previous = val
|
||||||
|
}
|
||||||
|
|
||||||
|
return first, last, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func paginateSorted(path, first, last, queryParams string, regexes *regexes, formats types.Types) (string, error) {
|
||||||
|
split, err := split(path, regexes)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
var firstStatus, prevStatus, nextStatus, lastStatus string = "", "", "", ""
|
||||||
|
|
||||||
|
if path <= first {
|
||||||
|
firstStatus = " disabled"
|
||||||
|
prevStatus = " disabled"
|
||||||
|
}
|
||||||
|
|
||||||
|
if path >= last {
|
||||||
|
nextStatus = " disabled"
|
||||||
|
lastStatus = " disabled"
|
||||||
|
}
|
||||||
|
|
||||||
|
prevPath := &splitPath{
|
||||||
|
base: split.base,
|
||||||
|
number: split.decrement(),
|
||||||
|
extension: split.extension,
|
||||||
|
}
|
||||||
|
|
||||||
|
prevPage, err := tryExtensions(prevPath, formats)
|
||||||
|
switch {
|
||||||
|
case err != nil:
|
||||||
|
return "", err
|
||||||
|
case prevPage == "":
|
||||||
|
prevStatus = " disabled"
|
||||||
|
case prevPage < first:
|
||||||
|
prevPage = first
|
||||||
|
}
|
||||||
|
|
||||||
|
nextPath := &splitPath{
|
||||||
|
base: split.base,
|
||||||
|
number: split.increment(),
|
||||||
|
extension: split.extension,
|
||||||
|
}
|
||||||
|
|
||||||
|
nextPage, err := tryExtensions(nextPath, formats)
|
||||||
|
switch {
|
||||||
|
case err != nil:
|
||||||
|
return "", err
|
||||||
|
case nextPage == "":
|
||||||
|
nextStatus = " disabled"
|
||||||
|
case nextPage > last:
|
||||||
|
nextPage = last
|
||||||
|
}
|
||||||
|
|
||||||
|
var html strings.Builder
|
||||||
|
|
||||||
|
html.WriteString(`<table style="margin-left:auto;margin-right:auto;"><tr><td>`)
|
||||||
|
|
||||||
|
html.WriteString(fmt.Sprintf(`<button onclick="window.location.href = '%s%s%s%s';"%s>First</button>`,
|
||||||
|
Prefix,
|
||||||
|
mediaPrefix,
|
||||||
|
first,
|
||||||
|
queryParams,
|
||||||
|
firstStatus))
|
||||||
|
|
||||||
|
html.WriteString(fmt.Sprintf(`<button onclick="window.location.href = '%s%s%s%s';"%s>Prev</button>`,
|
||||||
|
Prefix,
|
||||||
|
mediaPrefix,
|
||||||
|
prevPage,
|
||||||
|
queryParams,
|
||||||
|
prevStatus))
|
||||||
|
|
||||||
|
html.WriteString(fmt.Sprintf(`<button onclick="window.location.href = '%s%s%s%s';"%s>Next</button>`,
|
||||||
|
Prefix,
|
||||||
|
mediaPrefix,
|
||||||
|
nextPage,
|
||||||
|
queryParams,
|
||||||
|
nextStatus))
|
||||||
|
|
||||||
|
html.WriteString(fmt.Sprintf(`<button onclick="window.location.href = '%s%s%s%s';"%s>Last</button>`,
|
||||||
|
Prefix,
|
||||||
|
mediaPrefix,
|
||||||
|
last,
|
||||||
|
queryParams,
|
||||||
|
lastStatus))
|
||||||
|
|
||||||
|
html.WriteString("</td></tr></table>")
|
||||||
|
|
||||||
|
return html.String(), nil
|
||||||
|
}
|
||||||
|
|
34
cmd/web.go
34
cmd/web.go
|
@ -307,7 +307,9 @@ func serveMedia(paths []string, regexes *regexes, index *fileIndex, formats type
|
||||||
|
|
||||||
refreshTimer, refreshInterval := refreshInterval(r)
|
refreshTimer, refreshInterval := refreshInterval(r)
|
||||||
|
|
||||||
rootUrl := Prefix + "/" + generateQueryParams(filters, sortOrder, refreshInterval)
|
queryParams := generateQueryParams(filters, sortOrder, refreshInterval)
|
||||||
|
|
||||||
|
rootUrl := Prefix + "/" + queryParams
|
||||||
|
|
||||||
var htmlBody strings.Builder
|
var htmlBody strings.Builder
|
||||||
htmlBody.WriteString(`<!DOCTYPE html><html class="bg" lang="en"><head>`)
|
htmlBody.WriteString(`<!DOCTYPE html><html class="bg" lang="en"><head>`)
|
||||||
|
@ -324,6 +326,33 @@ func serveMedia(paths []string, regexes *regexes, index *fileIndex, formats type
|
||||||
}
|
}
|
||||||
htmlBody.WriteString(title)
|
htmlBody.WriteString(title)
|
||||||
htmlBody.WriteString(`</head><body>`)
|
htmlBody.WriteString(`</head><body>`)
|
||||||
|
|
||||||
|
var first, last string
|
||||||
|
|
||||||
|
if Index && sortOrder != "" {
|
||||||
|
first, last, err = getRange(path, regexes, index)
|
||||||
|
if err != nil {
|
||||||
|
errorChannel <- err
|
||||||
|
|
||||||
|
serverError(w, r, nil)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if Index && !DisableButtons && sortOrder != "" {
|
||||||
|
paginate, err := paginateSorted(path, first, last, queryParams, regexes, formats)
|
||||||
|
if err != nil {
|
||||||
|
errorChannel <- err
|
||||||
|
|
||||||
|
serverError(w, r, nil)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
htmlBody.WriteString(paginate)
|
||||||
|
}
|
||||||
|
|
||||||
if refreshInterval != "0ms" {
|
if refreshInterval != "0ms" {
|
||||||
htmlBody.WriteString(refreshFunction(rootUrl, refreshTimer))
|
htmlBody.WriteString(refreshFunction(rootUrl, refreshTimer))
|
||||||
}
|
}
|
||||||
|
@ -337,6 +366,7 @@ func serveMedia(paths []string, regexes *regexes, index *fileIndex, formats type
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
htmlBody.WriteString(body)
|
htmlBody.WriteString(body)
|
||||||
|
|
||||||
htmlBody.WriteString(`</body></html>`)
|
htmlBody.WriteString(`</body></html>`)
|
||||||
|
|
||||||
startTime := time.Now()
|
startTime := time.Now()
|
||||||
|
@ -456,7 +486,7 @@ func ServePage(args []string) error {
|
||||||
// enable image support if no other flags are passed, to retain backwards compatibility
|
// enable image support if no other flags are passed, to retain backwards compatibility
|
||||||
// to be replaced with rootCmd.MarkFlagsOneRequired on next spf13/cobra update
|
// to be replaced with rootCmd.MarkFlagsOneRequired on next spf13/cobra update
|
||||||
if Images || All || len(formats) == 0 {
|
if Images || All || len(formats) == 0 {
|
||||||
formats.Add(images.Format{Fun: Fun})
|
formats.Add(images.Format{DisableButtons: DisableButtons, Fun: Fun})
|
||||||
}
|
}
|
||||||
|
|
||||||
paths, err := validatePaths(args, formats)
|
paths, err := validatePaths(args, formats)
|
||||||
|
|
|
@ -26,14 +26,21 @@ type dimensions struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type Format struct {
|
type Format struct {
|
||||||
Fun bool
|
DisableButtons bool
|
||||||
|
Fun bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t Format) Css() string {
|
func (t Format) Css() string {
|
||||||
var css strings.Builder
|
var css strings.Builder
|
||||||
|
|
||||||
css.WriteString(`html,body{margin:0;padding:0;height:100%;}`)
|
css.WriteString(`html,body{margin:0;padding:0;height:100%;}`)
|
||||||
css.WriteString(`a{color:inherit;display:block;height:100%;width:100%;text-decoration:none;}`)
|
|
||||||
|
if t.DisableButtons {
|
||||||
|
css.WriteString(`a{color:inherit;display:block;height:100%;width:100%;text-decoration:none;}`)
|
||||||
|
} else {
|
||||||
|
css.WriteString(`a{color:inherit;display:block;height:97%;width:100%;text-decoration:none;}`)
|
||||||
|
}
|
||||||
|
|
||||||
css.WriteString(`img{margin:auto;display:block;max-width:97%;max-height:97%;`)
|
css.WriteString(`img{margin:auto;display:block;max-width:97%;max-height:97%;`)
|
||||||
css.WriteString(`object-fit:scale-down;position:absolute;top:50%;left:50%;transform:translate(-50%,-50%)`)
|
css.WriteString(`object-fit:scale-down;position:absolute;top:50%;left:50%;transform:translate(-50%,-50%)`)
|
||||||
if t.Fun {
|
if t.Fun {
|
||||||
|
|
Loading…
Reference in New Issue