Compare commits

...

2 Commits

Author SHA1 Message Date
Seednode a87d9a26fd Update readme with --disable-buttons 2023-10-20 17:25:52 -05:00
Seednode d902368845 Add first/prev/next/last buttons to sorted content 2023-10-20 17:18:35 -05:00
8 changed files with 225 additions and 56 deletions

View File

@ -116,11 +116,12 @@ Flags:
--case-sensitive use case-sensitive matching for filters --case-sensitive use case-sensitive matching for filters
--code enable support for source code files --code enable support for source code files
--code-theme string theme for source code syntax highlighting (default "solarized-dark256") --code-theme string theme for source code syntax highlighting (default "solarized-dark256")
--disable-buttons disable first/prev/next/last buttons
--exit-on-error shut down webserver on error, instead of just printing the error --exit-on-error shut down webserver on error, instead of just printing the error
--fallback serve files as application/octet-stream if no matching format is registered --fallback serve files as application/octet-stream if no matching format is registered
-f, --filter enable filtering -f, --filter enable filtering
--flash enable support for shockwave flash files (via ruffle.rs) --flash enable support for shockwave flash files (via ruffle.rs)
--fun adds a bit of excitement to your day --fun add a bit of excitement to your day
--handlers display registered handlers (for debugging) --handlers display registered handlers (for debugging)
-h, --help help for roulette -h, --help help for roulette
--images enable support for image files --images enable support for image files

View File

@ -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
} }

View File

@ -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 {
return nil
}
}
_, err := fileList(args, &filters{}, "", index, formats) _, err := fileList(args, &filters{}, "", index, formats)
if err != nil { if err != nil {
return err return err
} }
}
}
return nil return nil
} }

View File

@ -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>`)

View File

@ -12,7 +12,7 @@ import (
) )
const ( const (
ReleaseVersion string = "3.0.2" ReleaseVersion string = "3.1.0"
) )
var ( var (
@ -22,6 +22,7 @@ var (
CaseSensitive bool CaseSensitive bool
Code bool Code bool
CodeTheme string CodeTheme string
DisableButtons bool
ExitOnError bool ExitOnError bool
Fallback bool Fallback bool
Filtering bool Filtering bool
@ -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")

View File

@ -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
}

View File

@ -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)

View File

@ -26,6 +26,7 @@ type dimensions struct {
} }
type Format struct { type Format struct {
DisableButtons bool
Fun bool Fun bool
} }
@ -33,7 +34,13 @@ 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%;}`)
if t.DisableButtons {
css.WriteString(`a{color:inherit;display:block;height:100%;width:100%;text-decoration:none;}`) 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 {