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 {
_, 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
} }

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,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")

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,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 {