Merge pull request #2 from gtsteffaniak/make-search-efficient
Better search and context. 15% reduced RAM, more keywords, context improvements.
This commit is contained in:
commit
4dd1d1c8e5
|
@ -6,6 +6,7 @@ rice-box.go
|
|||
/filebrowser
|
||||
/filebrowser.exe
|
||||
/dist
|
||||
/src/backend/vendor
|
||||
|
||||
.DS_Store
|
||||
node_modules
|
||||
|
|
13
Dockerfile
13
Dockerfile
|
@ -1,7 +1,8 @@
|
|||
FROM node:14.21-slim as nbuild
|
||||
FROM node:slim as nbuild
|
||||
WORKDIR /app
|
||||
COPY ./src/frontend ./
|
||||
COPY ./src/frontend/package*.json ./
|
||||
RUN npm i
|
||||
COPY ./src/frontend/ ./
|
||||
RUN npm run build
|
||||
|
||||
FROM golang:alpine as base
|
||||
|
@ -15,8 +16,8 @@ RUN apk --no-cache add \
|
|||
mailcap
|
||||
VOLUME /srv
|
||||
EXPOSE 80
|
||||
WORKDIR /app
|
||||
COPY --from=base /app/docker_config.json ./.filebrowser.json
|
||||
COPY --from=base /app/filebrowser ./filebrowser
|
||||
COPY --from=nbuild /app/dist/ ./frontend/dist/
|
||||
WORKDIR /
|
||||
COPY --from=base /app/.filebrowser.json /.filebrowser.json
|
||||
COPY --from=base /app/filebrowser /filebrowser
|
||||
COPY --from=nbuild /app/dist/ /frontend/dist/
|
||||
ENTRYPOINT [ "./filebrowser" ]
|
|
@ -3,6 +3,6 @@
|
|||
"baseURL": "",
|
||||
"address": "",
|
||||
"log": "stdout",
|
||||
"database": "/database.db",
|
||||
"database": "./database.db",
|
||||
"root": "/srv"
|
||||
}
|
|
@ -52,7 +52,6 @@ override the options.`,
|
|||
Port: mustGetString(flags, "port"),
|
||||
Log: mustGetString(flags, "log"),
|
||||
}
|
||||
|
||||
err := d.store.Settings.Save(s)
|
||||
checkErr(err)
|
||||
err = d.store.Settings.SaveServer(ser)
|
||||
|
|
|
@ -11,11 +11,10 @@ import (
|
|||
"os"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
"strconv"
|
||||
|
||||
homedir "github.com/mitchellh/go-homedir"
|
||||
"github.com/spf13/afero"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
|
@ -23,10 +22,10 @@ import (
|
|||
lumberjack "gopkg.in/natefinch/lumberjack.v2"
|
||||
|
||||
"github.com/gtsteffaniak/filebrowser/auth"
|
||||
"github.com/gtsteffaniak/filebrowser/search"
|
||||
"github.com/gtsteffaniak/filebrowser/diskcache"
|
||||
fbhttp "github.com/gtsteffaniak/filebrowser/http"
|
||||
"github.com/gtsteffaniak/filebrowser/img"
|
||||
"github.com/gtsteffaniak/filebrowser/search"
|
||||
"github.com/gtsteffaniak/filebrowser/settings"
|
||||
"github.com/gtsteffaniak/filebrowser/storage"
|
||||
"github.com/gtsteffaniak/filebrowser/users"
|
||||
|
@ -50,9 +49,6 @@ func init() {
|
|||
rootCmd.SetVersionTemplate("File Browser version {{printf \"%s\" .Version}}\n")
|
||||
|
||||
flags := rootCmd.Flags()
|
||||
// initialize indexing and schedule indexing ever n minutes (default 5)
|
||||
indexingInterval := getEnvVariableAsUint32("INDEXING_INTERVAL")
|
||||
go search.InitializeIndex(indexingInterval)
|
||||
|
||||
persistent := rootCmd.PersistentFlags()
|
||||
|
||||
|
@ -87,7 +83,7 @@ func addServerFlags(flags *pflag.FlagSet) {
|
|||
flags.String("cache-dir", "", "file cache directory (disabled if empty)")
|
||||
flags.Int("img-processors", 4, "image processors count") //nolint:gomnd
|
||||
flags.Bool("disable-thumbnails", false, "disable image thumbnails")
|
||||
flags.Bool("disable-preview-resize", false, "disable resize of image previews")
|
||||
flags.Bool("disable-preview-resize", true, "disable resize of image previews")
|
||||
flags.Bool("disable-exec", false, "disables Command Runner feature")
|
||||
flags.Bool("disable-type-detection-by-header", false, "disables type detection by reading file headers")
|
||||
}
|
||||
|
@ -154,6 +150,9 @@ user created with the credentials from options "username" and "password".`,
|
|||
}
|
||||
fileCache = diskcache.New(afero.NewOsFs(), cacheDir)
|
||||
}
|
||||
// initialize indexing and schedule indexing ever n minutes (default 5)
|
||||
indexingInterval := getEnvVariableAsUint32("INDEXING_INTERVAL")
|
||||
go search.InitializeIndex(indexingInterval)
|
||||
|
||||
server := getRunParams(cmd.Flags(), d.store)
|
||||
setupLog(server.Log)
|
||||
|
@ -365,7 +364,6 @@ func quickSetup(flags *pflag.FlagSet, d pythonData) {
|
|||
err = d.store.Auth.Save(&auth.JSONAuth{})
|
||||
}
|
||||
|
||||
checkErr(err)
|
||||
err = d.store.Settings.Save(set)
|
||||
checkErr(err)
|
||||
|
||||
|
@ -378,7 +376,6 @@ func quickSetup(flags *pflag.FlagSet, d pythonData) {
|
|||
Address: getParam(flags, "address"),
|
||||
Root: getParam(flags, "root"),
|
||||
}
|
||||
|
||||
err = d.store.Settings.SaveServer(ser)
|
||||
checkErr(err)
|
||||
|
||||
|
@ -409,10 +406,7 @@ func quickSetup(flags *pflag.FlagSet, d pythonData) {
|
|||
|
||||
func initConfig() {
|
||||
if cfgFile == "" {
|
||||
home, err := homedir.Dir()
|
||||
checkErr(err)
|
||||
v.AddConfigPath(".")
|
||||
v.AddConfigPath(home)
|
||||
v.AddConfigPath("/etc/filebrowser/")
|
||||
v.SetConfigName(".filebrowser")
|
||||
} else {
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
# Ignore everything in this directory
|
||||
*
|
||||
# Except this file
|
||||
!.gitignore
|
|
@ -13,7 +13,6 @@ require (
|
|||
github.com/maruel/natural v1.1.0
|
||||
github.com/marusama/semaphore/v2 v2.5.0
|
||||
github.com/mholt/archiver/v3 v3.5.1
|
||||
github.com/mitchellh/go-homedir v1.1.0
|
||||
github.com/pelletier/go-toml/v2 v2.0.8
|
||||
github.com/shirou/gopsutil/v3 v3.23.5
|
||||
github.com/spf13/afero v1.9.5
|
||||
|
|
|
@ -207,8 +207,6 @@ github.com/marusama/semaphore/v2 v2.5.0 h1:o/1QJD9DBYOWRnDhPwDVAXQn6mQYD0gZaS1Tp
|
|||
github.com/marusama/semaphore/v2 v2.5.0/go.mod h1:z9nMiNUekt/LTpTUQdpp+4sJeYqUGpwMHfW0Z8V8fnQ=
|
||||
github.com/mholt/archiver/v3 v3.5.1 h1:rDjOBX9JSF5BvoJGvjqK479aL70qh9DIpZCl+k7Clwo=
|
||||
github.com/mholt/archiver/v3 v3.5.1/go.mod h1:e3dqJ7H78uzsRSEACH1joayhuSyhnonssnDhppzS1L4=
|
||||
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
|
||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
|
||||
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/nwaples/rardecode v1.1.0 h1:vSxaY8vQhOcVr4mm5e8XllHWTiM4JF507A0Katqw7MQ=
|
||||
|
|
|
@ -8,31 +8,18 @@ import (
|
|||
var searchHandler = withUser(func(w http.ResponseWriter, r *http.Request, d *data) (int, error) {
|
||||
response := []map[string]interface{}{}
|
||||
query := r.URL.Query().Get("query")
|
||||
var files []string
|
||||
var dirs []string
|
||||
files, dirs = search.IndexedSearch(query,r.URL.Path,&files,&dirs)
|
||||
for _,v := range(files){
|
||||
response = append(response, map[string]interface{}{
|
||||
"dir": false,
|
||||
"path": v,
|
||||
})
|
||||
indexInfo, fileTypes := search.SearchAllIndexes(query, r.URL.Path)
|
||||
for _,path := range(indexInfo){
|
||||
f := fileTypes[path]
|
||||
responseObj := map[string]interface{}{
|
||||
"path" : path,
|
||||
}
|
||||
for _,v := range(dirs){
|
||||
response = append(response, map[string]interface{}{
|
||||
"dir": true,
|
||||
"path": v,
|
||||
})
|
||||
for filterType,_ := range(f) {
|
||||
if f[filterType] {
|
||||
responseObj[filterType] = f[filterType]
|
||||
}
|
||||
}
|
||||
response = append(response,responseObj)
|
||||
}
|
||||
files = files[:0]
|
||||
dirs = dirs[:0]
|
||||
// err := search.Search(d.user.Fs, r.URL.Path, query, d, func(path string, f os.FileInfo) error {
|
||||
// response = append(response, map[string]interface{}{
|
||||
// "dir": f.IsDir(),
|
||||
// "path": path,
|
||||
// })
|
||||
//
|
||||
// return nil
|
||||
// })
|
||||
|
||||
return renderJSON(w, r, response)
|
||||
})
|
|
@ -1,72 +1,67 @@
|
|||
package search
|
||||
|
||||
import (
|
||||
"mime"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
typeRegexp = regexp.MustCompile(`type:(\w+)`)
|
||||
)
|
||||
var typeRegexp = regexp.MustCompile(`type:(\w+)`)
|
||||
|
||||
type condition func(path string) bool
|
||||
|
||||
func extensionCondition(extension string) condition {
|
||||
return func(path string) bool {
|
||||
return filepath.Ext(path) == "."+extension
|
||||
}
|
||||
var documentTypes = []string{
|
||||
".word",
|
||||
".pdf",
|
||||
".txt",
|
||||
".doc",
|
||||
".docx",
|
||||
}
|
||||
|
||||
func imageCondition(path string) bool {
|
||||
extension := filepath.Ext(path)
|
||||
mimetype := mime.TypeByExtension(extension)
|
||||
|
||||
return strings.HasPrefix(mimetype, "image")
|
||||
var compressedFile = []string{
|
||||
".7z",
|
||||
".rar",
|
||||
".zip",
|
||||
".tar",
|
||||
".tar.gz",
|
||||
".tar.xz",
|
||||
}
|
||||
|
||||
func audioCondition(path string) bool {
|
||||
extension := filepath.Ext(path)
|
||||
mimetype := mime.TypeByExtension(extension)
|
||||
|
||||
return strings.HasPrefix(mimetype, "audio")
|
||||
type searchOptions struct {
|
||||
Conditions map[string]bool
|
||||
Terms []string
|
||||
}
|
||||
|
||||
func videoCondition(path string) bool {
|
||||
extension := filepath.Ext(path)
|
||||
mimetype := mime.TypeByExtension(extension)
|
||||
|
||||
return strings.HasPrefix(mimetype, "video")
|
||||
}
|
||||
|
||||
func parseSearch(value string) *searchOptions {
|
||||
func ParseSearch(value string) *searchOptions {
|
||||
opts := &searchOptions{
|
||||
CaseSensitive: strings.Contains(value, "case:sensitive"),
|
||||
Conditions: []condition{},
|
||||
Conditions: map[string]bool{
|
||||
"exact": strings.Contains(value, "case:exact"),
|
||||
},
|
||||
Terms: []string{},
|
||||
}
|
||||
|
||||
// removes the options from the value
|
||||
value = strings.Replace(value, "case:insensitive", "", -1)
|
||||
value = strings.Replace(value, "case:sensitive", "", -1)
|
||||
value = strings.Replace(value, "case:exact", "", -1)
|
||||
value = strings.Replace(value, "case:exact", "", -1)
|
||||
value = strings.TrimSpace(value)
|
||||
|
||||
types := typeRegexp.FindAllStringSubmatch(value, -1)
|
||||
for _, t := range types {
|
||||
if len(t) == 1 {
|
||||
for _, filterType := range types {
|
||||
if len(filterType) == 1 {
|
||||
continue
|
||||
}
|
||||
|
||||
switch t[1] {
|
||||
switch filterType[1] {
|
||||
case "image":
|
||||
opts.Conditions = append(opts.Conditions, imageCondition)
|
||||
opts.Conditions["image"] = true
|
||||
case "audio", "music":
|
||||
opts.Conditions = append(opts.Conditions, audioCondition)
|
||||
opts.Conditions["audio"] = true
|
||||
case "video":
|
||||
opts.Conditions = append(opts.Conditions, videoCondition)
|
||||
default:
|
||||
opts.Conditions = append(opts.Conditions, extensionCondition(t[1]))
|
||||
opts.Conditions["video"] = true
|
||||
case "doc":
|
||||
opts.Conditions["doc"] = true
|
||||
case "archive":
|
||||
opts.Conditions["archive"] = true
|
||||
case "folder":
|
||||
opts.Conditions["dir"] = true
|
||||
case "file":
|
||||
opts.Conditions["dir"] = false
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -75,14 +70,6 @@ func parseSearch(value string) *searchOptions {
|
|||
value = typeRegexp.ReplaceAllString(value, "")
|
||||
}
|
||||
|
||||
// If it's case insensitive, put everything in lowercase.
|
||||
if !opts.CaseSensitive {
|
||||
value = strings.ToLower(value)
|
||||
}
|
||||
|
||||
// Remove the spaces from the search value.
|
||||
value = strings.TrimSpace(value)
|
||||
|
||||
if value == "" {
|
||||
return opts
|
||||
}
|
||||
|
|
|
@ -4,15 +4,16 @@ import (
|
|||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"sort"
|
||||
"time"
|
||||
"mime"
|
||||
)
|
||||
|
||||
var (
|
||||
rootPath = "/srv" // DO NOT include trailing slash
|
||||
indexes map[string][]string
|
||||
rootPath string = "/srv"
|
||||
indexes = map[string][]string{}
|
||||
mutex sync.RWMutex
|
||||
lastIndexed time.Time
|
||||
)
|
||||
|
@ -20,28 +21,30 @@ var (
|
|||
func InitializeIndex(intervalMinutes uint32) {
|
||||
// Initialize the indexes map
|
||||
indexes = make(map[string][]string)
|
||||
indexes["dirs"] = []string{}
|
||||
indexes["files"] = []string{}
|
||||
var numFiles, numDirs int
|
||||
log.Println("Indexing files...")
|
||||
lastIndexedStart := time.Now()
|
||||
// Call the function to index files and directories
|
||||
totalNumFiles, totalNumDirs, err := indexFiles(rootPath,&numFiles,&numDirs)
|
||||
totalNumFiles, totalNumDirs, err := indexFiles(rootPath, &numFiles, &numDirs)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
lastIndexed = lastIndexedStart
|
||||
go indexingScheduler(intervalMinutes)
|
||||
log.Println("Successfully indexed files.")
|
||||
log.Println("Files found :",totalNumFiles)
|
||||
log.Println("Directories found :",totalNumDirs)
|
||||
log.Println("Files found :", totalNumFiles)
|
||||
log.Println("Directories found :", totalNumDirs)
|
||||
}
|
||||
|
||||
func indexingScheduler(intervalMinutes uint32) {
|
||||
log.Printf("Indexing scheduler will run every %v minutes",intervalMinutes)
|
||||
log.Printf("Indexing scheduler will run every %v minutes", intervalMinutes)
|
||||
for {
|
||||
time.Sleep(time.Duration(intervalMinutes) * time.Minute)
|
||||
var numFiles, numDirs int
|
||||
lastIndexedStart := time.Now()
|
||||
totalNumFiles, totalNumDirs, err := indexFiles(rootPath,&numFiles,&numDirs)
|
||||
totalNumFiles, totalNumDirs, err := indexFiles(rootPath, &numFiles, &numDirs)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
@ -53,7 +56,7 @@ func indexingScheduler(intervalMinutes uint32) {
|
|||
}
|
||||
|
||||
// Define a function to recursively index files and directories
|
||||
func indexFiles(path string, numFiles *int, numDirs *int) (int,int,error) {
|
||||
func indexFiles(path string, numFiles *int, numDirs *int) (int, int, error) {
|
||||
// Check if the current directory has been modified since last indexing
|
||||
dir, err := os.Open(path)
|
||||
if err != nil {
|
||||
|
@ -63,99 +66,103 @@ func indexFiles(path string, numFiles *int, numDirs *int) (int,int,error) {
|
|||
defer dir.Close()
|
||||
dirInfo, err := dir.Stat()
|
||||
if err != nil {
|
||||
return *numFiles,*numDirs,err
|
||||
return *numFiles, *numDirs, err
|
||||
}
|
||||
// Compare the last modified time of the directory with the last indexed time
|
||||
if dirInfo.ModTime().Before(lastIndexed) {
|
||||
return *numFiles,*numDirs,nil
|
||||
return *numFiles, *numDirs, nil
|
||||
}
|
||||
// Read the directory contents
|
||||
files, err := dir.Readdir(-1)
|
||||
if err != nil {
|
||||
return *numFiles,*numDirs,err
|
||||
return *numFiles, *numDirs, err
|
||||
}
|
||||
// Iterate over the files and directories
|
||||
for _, file := range files {
|
||||
if file.IsDir() {
|
||||
*numDirs++
|
||||
indexFiles(path+"/"+file.Name(),numFiles,numDirs)
|
||||
}
|
||||
addToIndex(path, file.Name(), true)
|
||||
indexFiles(path+"/"+file.Name(), numFiles, numDirs) // recursive
|
||||
} else {
|
||||
*numFiles++
|
||||
addToIndex(path, file.Name())
|
||||
addToIndex(path, file.Name(), false)
|
||||
}
|
||||
return *numFiles,*numDirs,nil
|
||||
}
|
||||
return *numFiles, *numDirs, nil
|
||||
}
|
||||
|
||||
func addToIndex(path string, fileName string) {
|
||||
func addToIndex(path string, fileName string, isDir bool) {
|
||||
mutex.Lock()
|
||||
defer mutex.Unlock()
|
||||
path = strings.TrimPrefix(path,rootPath+"/")
|
||||
path = strings.TrimSuffix(path,"/")
|
||||
path = strings.TrimPrefix(path, rootPath+"/")
|
||||
path = strings.TrimSuffix(path, "/")
|
||||
adjustedPath := path + "/" + fileName
|
||||
if path == rootPath {
|
||||
path = "/"
|
||||
adjustedPath = fileName
|
||||
}
|
||||
info, exists := indexes[path]
|
||||
if !exists {
|
||||
info = []string{}
|
||||
if isDir {
|
||||
indexes["dirs"] = append(indexes["dirs"], adjustedPath)
|
||||
}else{
|
||||
indexes["files"] = append(indexes["files"], adjustedPath)
|
||||
}
|
||||
info = append(info, fileName)
|
||||
indexes[path] = info
|
||||
}
|
||||
|
||||
func SearchAllIndexes(searchTerm string, scope string, files []string, dirs []string) ([]string, []string) {
|
||||
func SearchAllIndexes(search string, scope string) ([]string, map[string]map[string]bool) {
|
||||
searchOptions := ParseSearch(search)
|
||||
mutex.RLock()
|
||||
defer mutex.RUnlock()
|
||||
fileListTypes := make(map[string]map[string]bool)
|
||||
var matching []string
|
||||
maximum := 125
|
||||
|
||||
var matchingFiles []string
|
||||
var matchingDirs []string
|
||||
|
||||
for _, searchTerm := range searchOptions.Terms {
|
||||
if searchTerm == "" {
|
||||
continue
|
||||
}
|
||||
count := 0
|
||||
// Iterate over the indexes
|
||||
for dirName, v := range indexes {
|
||||
searchItems := v
|
||||
// Iterate over the path names
|
||||
for _, pathName := range searchItems {
|
||||
if dirName != "/" {
|
||||
pathName = dirName+"/"+pathName
|
||||
}
|
||||
// Check if the path name contains the search term
|
||||
if !containsSearchTerm(pathName, searchTerm) {
|
||||
continue
|
||||
}
|
||||
pathName = scopedPathNameFilter(pathName, scope)
|
||||
if pathName == "" {
|
||||
continue
|
||||
}
|
||||
matchingFiles = append(matchingFiles, pathName)
|
||||
}
|
||||
// Check if the path name contains the search term
|
||||
if !containsSearchTerm(dirName, searchTerm) {
|
||||
continue
|
||||
for _, dirName := range indexes["dirs"] {
|
||||
if count > maximum {
|
||||
break
|
||||
}
|
||||
pathName := scopedPathNameFilter(dirName, scope)
|
||||
if pathName == "" {
|
||||
continue
|
||||
}
|
||||
matchingDirs = append(matchingDirs, pathName)
|
||||
matches, fileType := containsSearchTerm(pathName, searchTerm, searchOptions.Conditions, true)
|
||||
if !matches {
|
||||
continue
|
||||
}
|
||||
count++
|
||||
matching = append(matching, pathName+"/")
|
||||
fileListTypes[pathName+"/"] = fileType
|
||||
}
|
||||
count = 0
|
||||
for _, fileName := range indexes["files"] {
|
||||
if count > maximum {
|
||||
break
|
||||
}
|
||||
pathName := scopedPathNameFilter(fileName, scope)
|
||||
if pathName == "" {
|
||||
continue
|
||||
}
|
||||
// Check if the path name contains the search term
|
||||
matches, fileType := containsSearchTerm(pathName, searchTerm, searchOptions.Conditions, false)
|
||||
if !matches {
|
||||
continue
|
||||
}
|
||||
matching = append(matching, pathName)
|
||||
fileListTypes[pathName] = fileType
|
||||
count++
|
||||
}
|
||||
}
|
||||
|
||||
// Sort the strings based on the number of elements after splitting by "/"
|
||||
sort.Slice(matchingFiles, func(i, j int) bool {
|
||||
parts1 := strings.Split(matchingFiles[i], "/")
|
||||
parts2 := strings.Split(matchingFiles[j], "/")
|
||||
sort.Slice(matching, func(i, j int) bool {
|
||||
parts1 := strings.Split(matching[i], "/")
|
||||
parts2 := strings.Split(matching[j], "/")
|
||||
return len(parts1) < len(parts2)
|
||||
})
|
||||
// Sort the strings based on the number of elements after splitting by "/"
|
||||
sort.Slice(matchingDirs, func(i, j int) bool {
|
||||
parts1 := strings.Split(matchingDirs[i], "/")
|
||||
parts2 := strings.Split(matchingDirs[j], "/")
|
||||
return len(parts1) < len(parts2)
|
||||
})
|
||||
|
||||
// Copy the matching files and dirs to the final slices
|
||||
files = append([]string{}, matchingFiles...)
|
||||
dirs = append([]string{}, matchingDirs...)
|
||||
|
||||
return files, dirs
|
||||
return matching, fileListTypes
|
||||
}
|
||||
|
||||
func scopedPathNameFilter(pathName string, scope string) string {
|
||||
|
@ -168,14 +175,54 @@ func scopedPathNameFilter(pathName string, scope string) string {
|
|||
return pathName
|
||||
}
|
||||
|
||||
func containsSearchTerm(pathName string, searchTerm string) bool {
|
||||
func containsSearchTerm(pathName string, searchTerm string, conditions map[string]bool, isDir bool) (bool, map[string]bool) {
|
||||
path := getLastPathComponent(pathName)
|
||||
// Perform case-insensitive search
|
||||
pathNameLower := strings.ToLower(path)
|
||||
searchTermLower := strings.ToLower(searchTerm)
|
||||
|
||||
return strings.Contains(pathNameLower, searchTermLower)
|
||||
fileTypes := map[string]bool{}
|
||||
matchesCondition := false
|
||||
extension := filepath.Ext(strings.ToLower(path))
|
||||
mimetype := mime.TypeByExtension(extension)
|
||||
fileTypes["audio"] = strings.HasPrefix(mimetype, "audio")
|
||||
fileTypes["image"] = strings.HasPrefix(mimetype, "image")
|
||||
fileTypes["video"] = strings.HasPrefix(mimetype, "video")
|
||||
fileTypes["doc"] = isDoc(extension)
|
||||
fileTypes["archive"] = isArchive(extension)
|
||||
fileTypes["dir"] = isDir
|
||||
anyFilter := false
|
||||
for t,v := range conditions {
|
||||
if t == "exact" {
|
||||
continue
|
||||
}
|
||||
matchesCondition = v == fileTypes[t]
|
||||
anyFilter = true
|
||||
}
|
||||
if !anyFilter {
|
||||
matchesCondition = true
|
||||
}
|
||||
if !conditions["exact"] {
|
||||
path = strings.ToLower(path)
|
||||
searchTerm = strings.ToLower(searchTerm)
|
||||
}
|
||||
return strings.Contains(path, searchTerm) && matchesCondition, fileTypes
|
||||
}
|
||||
|
||||
func isDoc(extension string) bool {
|
||||
for _, typefile := range documentTypes {
|
||||
if extension == typefile {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func isArchive(extension string) bool {
|
||||
for _, typefile := range compressedFile {
|
||||
if extension == typefile {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func getLastPathComponent(path string) string {
|
||||
// Use filepath.Base to extract the last component of the path
|
||||
return filepath.Base(path)
|
||||
|
|
|
@ -1,76 +0,0 @@
|
|||
package search
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/afero"
|
||||
"github.com/gtsteffaniak/filebrowser/rules"
|
||||
)
|
||||
|
||||
type searchOptions struct {
|
||||
CaseSensitive bool
|
||||
Conditions []condition
|
||||
Terms []string
|
||||
}
|
||||
|
||||
func IndexedSearch(query string, scope string, files *[]string, dirs *[]string) ([]string, []string) {
|
||||
*files, *dirs = SearchAllIndexes(query, scope, *files, *dirs)
|
||||
return *files, *dirs
|
||||
}
|
||||
|
||||
// Search searches for a query in a fs.
|
||||
func Search(fs afero.Fs, scope, query string, checker rules.Checker, found func(path string, f os.FileInfo) error) error {
|
||||
search := parseSearch(query)
|
||||
|
||||
scope = filepath.ToSlash(filepath.Clean(scope))
|
||||
scope = path.Join("/", scope)
|
||||
|
||||
return afero.Walk(fs, scope, func(fPath string, f os.FileInfo, err error) error {
|
||||
fPath = filepath.ToSlash(filepath.Clean(fPath))
|
||||
fPath = path.Join("/", fPath)
|
||||
relativePath := strings.TrimPrefix(fPath, scope)
|
||||
relativePath = strings.TrimPrefix(relativePath, "/")
|
||||
|
||||
if fPath == scope {
|
||||
return nil
|
||||
}
|
||||
|
||||
if !checker.Check(fPath) {
|
||||
return nil
|
||||
}
|
||||
|
||||
if len(search.Conditions) > 0 {
|
||||
match := false
|
||||
|
||||
for _, t := range search.Conditions {
|
||||
if t(fPath) {
|
||||
match = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !match {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
if len(search.Terms) > 0 {
|
||||
for _, term := range search.Terms {
|
||||
_, fileName := path.Split(fPath)
|
||||
if !search.CaseSensitive {
|
||||
fileName = strings.ToLower(fileName)
|
||||
term = strings.ToLower(term)
|
||||
}
|
||||
if strings.Contains(fileName, term) {
|
||||
return found(relativePath, f)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
return found(relativePath, f)
|
||||
})
|
||||
}
|
|
@ -101,7 +101,6 @@ require (
|
|||
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
|
||||
github.com/mbilski/exhaustivestruct v1.2.0 // indirect
|
||||
github.com/mgechev/revive v1.2.5 // indirect
|
||||
github.com/mitchellh/go-homedir v1.1.0 // indirect
|
||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||
github.com/moricho/tparallel v0.2.1 // indirect
|
||||
github.com/nakabonne/nestif v0.3.1 // indirect
|
||||
|
|
|
@ -360,8 +360,6 @@ github.com/mbilski/exhaustivestruct v1.2.0 h1:wCBmUnSYufAHO6J4AVWY6ff+oxWxsVFrwg
|
|||
github.com/mbilski/exhaustivestruct v1.2.0/go.mod h1:OeTBVxQWoEmB2J2JCHmXWPJ0aksxSUOUy+nvtVEfzXc=
|
||||
github.com/mgechev/revive v1.2.5 h1:UF9AR8pOAuwNmhXj2odp4mxv9Nx2qUIwVz8ZsU+Mbec=
|
||||
github.com/mgechev/revive v1.2.5/go.mod h1:nFOXent79jMTISAfOAasKfy0Z2Ejq0WX7Qn/KAdYopI=
|
||||
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
|
||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
|
||||
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
|
|
|
@ -2,7 +2,7 @@ package version
|
|||
|
||||
var (
|
||||
// Version is the current File Browser version.
|
||||
Version = "(0.1.0)"
|
||||
Version = "(0.1.2)"
|
||||
// CommitSHA is the commmit sha.
|
||||
CommitSHA = "(unknown)"
|
||||
)
|
||||
|
|
|
@ -1,13 +0,0 @@
|
|||
//go:build !dev
|
||||
// +build !dev
|
||||
|
||||
package frontend
|
||||
|
||||
import "embed"
|
||||
|
||||
//go:embed dist/*
|
||||
var assets embed.FS
|
||||
|
||||
func Assets() embed.FS {
|
||||
return assets
|
||||
}
|
|
@ -1,15 +0,0 @@
|
|||
//go:build dev
|
||||
// +build dev
|
||||
|
||||
package frontend
|
||||
|
||||
import (
|
||||
"io/fs"
|
||||
"os"
|
||||
)
|
||||
|
||||
var assets fs.FS = os.DirFS("frontend")
|
||||
|
||||
func Assets() fs.FS {
|
||||
return assets
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -5,21 +5,20 @@
|
|||
"scripts": {
|
||||
"serve": "vue-cli-service serve",
|
||||
"build": "find ./dist -maxdepth 1 -mindepth 1 ! -name '.gitignore' -exec rm -r {} + && vue-cli-service build --no-clean",
|
||||
"lint": "npx vue-cli-service lint --no-fix --max-warnings=0",
|
||||
"fix": "npx vue-cli-service lint",
|
||||
"watch": "find ./dist -maxdepth 1 -mindepth 1 ! -name '.gitignore' -exec rm -r {} + && vue-cli-service build --watch --no-clean"
|
||||
},
|
||||
"dependencies": {
|
||||
"normalize.css": "^8.0.1",
|
||||
"file-loader": "^6.2.0",
|
||||
"ace-builds": "^1.4.7",
|
||||
"clipboard": "^2.0.4",
|
||||
"core-js": "^3.9.1",
|
||||
"css-vars-ponyfill": "^2.4.3",
|
||||
"js-base64": "^2.5.1",
|
||||
"lodash.clonedeep": "^4.5.0",
|
||||
"lodash.throttle": "^4.1.1",
|
||||
"material-icons": "^1.10.5",
|
||||
"moment": "^2.29.4",
|
||||
"normalize.css": "^8.0.1",
|
||||
"noty": "^3.2.0-beta",
|
||||
"pretty-bytes": "^6.0.0",
|
||||
"qrcode.vue": "^1.7.0",
|
||||
|
@ -35,33 +34,10 @@
|
|||
"whatwg-fetch": "^3.6.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vue/cli-plugin-babel": "^4.1.2",
|
||||
"@vue/cli-plugin-eslint": "~4.5.0",
|
||||
"@vue/cli-service": "^4.1.2",
|
||||
"@vue/eslint-config-prettier": "^6.0.0",
|
||||
"babel-eslint": "^10.1.0",
|
||||
"compression-webpack-plugin": "^6.0.3",
|
||||
"eslint": "^6.7.2",
|
||||
"eslint-plugin-prettier": "^3.3.1",
|
||||
"eslint-plugin-vue": "^6.2.2",
|
||||
"prettier": "^2.2.1",
|
||||
"@vue/cli-service": "^5.0.8",
|
||||
"compression-webpack-plugin": "^10.0.0",
|
||||
"vue-template-compiler": "^2.6.10"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"root": true,
|
||||
"env": {
|
||||
"node": true
|
||||
},
|
||||
"extends": [
|
||||
"plugin:vue/essential",
|
||||
"eslint:recommended",
|
||||
"@vue/prettier"
|
||||
],
|
||||
"rules": {},
|
||||
"parserOptions": {
|
||||
"parser": "babel-eslint"
|
||||
}
|
||||
},
|
||||
"postcss": {
|
||||
"plugins": {
|
||||
"autoprefixer": {}
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
import vue from 'rollup-plugin-vue'
|
||||
import { nodeResolve } from '@rollup/plugin-node-resolve'
|
||||
import commonjs from '@rollup/plugin-commonjs'
|
||||
import { terser } from "rollup-plugin-terser"
|
||||
import postcss from 'rollup-plugin-postcss'
|
||||
import babel from '@rollup/plugin-babel'
|
||||
import replace from '@rollup/plugin-replace'
|
||||
import livereload from 'rollup-plugin-livereload'
|
||||
import css from 'rollup-plugin-css-only'
|
||||
import autoprefixer from 'autoprefixer'
|
||||
|
||||
export default {
|
||||
input: 'src/main.js', // Entry file
|
||||
output: {
|
||||
file: 'dist/build.js', // Output file
|
||||
format: 'iife', // Immediately Invoked Function Expression format suitable for <script> tag
|
||||
},
|
||||
plugins: [
|
||||
replace({
|
||||
'process.env.NODE_ENV': JSON.stringify('production'),
|
||||
'process.env.VUE_ENV': '"client"'
|
||||
}),
|
||||
nodeResolve({ browser: true, jsnext: true }), // Resolve modules from node_modules
|
||||
commonjs(), // Convert CommonJS modules to ES6
|
||||
vue({ css: false }), // Handle .vue files
|
||||
css({ output: 'bundle.css' }), // css to separate file
|
||||
postcss({ plugins: [autoprefixer()]}),
|
||||
babel({ babelHelpers: 'bundled' }), // Transpile to ES5
|
||||
terser(), // Minify the build
|
||||
livereload('dist') // Live reload for development
|
||||
],
|
||||
}
|
|
@ -1,65 +1,50 @@
|
|||
<template>
|
||||
<div id="search" @click="open" v-bind:class="{ active, ongoing }">
|
||||
<div id="input">
|
||||
<button
|
||||
v-if="active"
|
||||
class="action"
|
||||
@click="close"
|
||||
:aria-label="$t('buttons.close')"
|
||||
:title="$t('buttons.close')"
|
||||
>
|
||||
<button v-if="active" class="action" @click="close" :aria-label="$t('buttons.close')" :title="$t('buttons.close')">
|
||||
<i class="material-icons">arrow_back</i>
|
||||
</button>
|
||||
<i v-else class="material-icons">search</i>
|
||||
<input
|
||||
type="text"
|
||||
@keyup.exact="keyup"
|
||||
@input="submit"
|
||||
ref="input"
|
||||
:autofocus="active"
|
||||
v-model.trim="value"
|
||||
:aria-label="$t('search.search')"
|
||||
:placeholder="$t('search.search')"
|
||||
/>
|
||||
<input type="text" @keyup.exact="keyup" @input="submit" ref="input" :autofocus="active" v-model.trim="value"
|
||||
:aria-label="$t('search.search')" :placeholder="$t('search.search')" />
|
||||
</div>
|
||||
|
||||
<div id="result" ref="result">
|
||||
<div>
|
||||
<div id="result-list">
|
||||
<br>
|
||||
<br>
|
||||
<div class="button" style="width:100%">Search Context: {{ getContext(this.$route.path) }}</div>
|
||||
<template v-if="isEmpty">
|
||||
<p>{{ text }}</p>
|
||||
|
||||
<template v-if="value.length === 0">
|
||||
<div class="boxes">
|
||||
<h3>{{ $t("search.types") }}</h3>
|
||||
<div>
|
||||
<div
|
||||
tabindex="0"
|
||||
v-for="(v, k) in boxes"
|
||||
:key="k"
|
||||
role="button"
|
||||
@click="init('type:' + k)"
|
||||
:aria-label="$t('search.' + v.label)"
|
||||
>
|
||||
<div tabindex="0" v-for="(v, k) in boxes" :key="k" role="button" @click="init('type:' + k)"
|
||||
:aria-label="(v.label)">
|
||||
<i class="material-icons">{{ v.icon }}</i>
|
||||
<p>{{ $t("search." + v.label) }}</p>
|
||||
<p>{{ v.label }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
<ul v-show="filteredResults.length > 0">
|
||||
<li v-for="(s, k) in filteredResults" :key="k">
|
||||
<router-link @click.native="close" :to="s.url">
|
||||
<i v-if="s.dir" class="material-icons">folder</i>
|
||||
<i v-else class="material-icons">insert_drive_file</i>
|
||||
<span>./{{ s.path }}</span>
|
||||
<ul v-show="results.length > 0">
|
||||
<li v-for="(s, k) in results" :key="k" @click.stop.prevent="navigateTo(s.url)" style="cursor: pointer">
|
||||
<router-link to="#" event="">
|
||||
<i v-if="s.dir" class="material-icons folder-icons"> folder </i>
|
||||
<i v-else-if="s.audio" class="material-icons audio-icons"> volume_up </i>
|
||||
<i v-else-if="s.image" class="material-icons image-icons"> photo </i>
|
||||
<i v-else-if="s.video" class="material-icons video-icons"> movie </i>
|
||||
<i v-else-if="s.archive" class="material-icons archive-icons"> archive </i>
|
||||
<i v-else class="material-icons file-icons"> insert_drive_file </i>
|
||||
<span class="text-container">
|
||||
{{ basePath(s.path) }}<b>{{ baseName(s.path) }}</b>
|
||||
</span>
|
||||
</router-link>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<p id="renew">
|
||||
<i class="material-icons spin">autorenew</i>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -70,10 +55,13 @@ import url from "@/utils/url";
|
|||
import { search } from "@/api";
|
||||
|
||||
var boxes = {
|
||||
image: { label: "images", icon: "insert_photo" },
|
||||
audio: { label: "music", icon: "volume_up" },
|
||||
video: { label: "video", icon: "movie" },
|
||||
pdf: { label: "pdf", icon: "picture_as_pdf" },
|
||||
folder: { label: "folders", icon: "folder" },
|
||||
file: { label: "files", icon: "insert_drive_file" },
|
||||
archive: { label: "archives", icon: "archive" },
|
||||
image: { label: "images", icon: "photo" },
|
||||
audio: { label: "audio files", icon: "volume_up" },
|
||||
video: { label: "videos", icon: "movie" },
|
||||
doc: { label: "documents", icon: "picture_as_pdf" },
|
||||
};
|
||||
|
||||
export default {
|
||||
|
@ -92,7 +80,6 @@ export default {
|
|||
watch: {
|
||||
show(val, old) {
|
||||
this.active = val === "search";
|
||||
|
||||
if (old === "search" && !this.active) {
|
||||
if (this.reload) {
|
||||
this.setReload(true);
|
||||
|
@ -133,9 +120,6 @@ export default {
|
|||
? this.$t("search.typeToSearch")
|
||||
: this.$t("search.pressToSearch");
|
||||
},
|
||||
filteredResults() {
|
||||
return this.results.slice(0, this.resultsCount);
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.$refs.result.addEventListener("scroll", (event) => {
|
||||
|
@ -148,13 +132,31 @@ export default {
|
|||
});
|
||||
},
|
||||
methods: {
|
||||
async navigateTo(url) {
|
||||
this.closeHovers();
|
||||
await this.$nextTick();
|
||||
setTimeout(() => this.$router.push(url), 0);
|
||||
},
|
||||
getContext(url) {
|
||||
url = url.slice(1)
|
||||
let path = "./" + url.substring(url.indexOf('/') + 1);
|
||||
return path.replace(/\/+$/, '') + "/"
|
||||
},
|
||||
basePath(str) {
|
||||
let parts = str.replace(/\/$/, '').split("/")
|
||||
parts.pop()
|
||||
return parts.join("/") + "/"
|
||||
},
|
||||
baseName(str) {
|
||||
let parts = str.replace(/\/$/, '').split("/")
|
||||
return parts.pop();
|
||||
},
|
||||
...mapMutations(["showHover", "closeHovers", "setReload"]),
|
||||
open() {
|
||||
this.showHover("search");
|
||||
},
|
||||
close(event) {
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
this.closeHovers();
|
||||
},
|
||||
keyup(event) {
|
||||
|
@ -162,7 +164,6 @@ export default {
|
|||
this.close(event);
|
||||
return;
|
||||
}
|
||||
|
||||
this.results.length === 0;
|
||||
},
|
||||
init(string) {
|
||||
|
|
|
@ -37,7 +37,6 @@
|
|||
<script>
|
||||
import { enableThumbs } from "@/utils/constants";
|
||||
import { mapMutations, mapGetters, mapState } from "vuex";
|
||||
import filesize from "filesize";
|
||||
import moment from "moment";
|
||||
import { files as api } from "@/api";
|
||||
import * as upload from "@/utils/upload";
|
||||
|
@ -98,7 +97,7 @@ export default {
|
|||
methods: {
|
||||
...mapMutations(["addSelected", "removeSelected", "resetSelected"]),
|
||||
humanSize: function () {
|
||||
return this.type == "invalid_link" ? "invalid link" : filesize(this.size);
|
||||
return this.type == "invalid_link" ? "invalid link" : this.size;
|
||||
},
|
||||
humanTime: function () {
|
||||
if (this.readOnly == undefined && this.user.dateFormat) {
|
||||
|
|
|
@ -81,7 +81,6 @@
|
|||
|
||||
<script>
|
||||
import { mapState, mapGetters } from "vuex";
|
||||
import filesize from "filesize";
|
||||
import moment from "moment";
|
||||
import { files as api } from "@/api";
|
||||
|
||||
|
@ -92,7 +91,7 @@ export default {
|
|||
...mapGetters(["selectedCount", "isListing"]),
|
||||
humanSize: function () {
|
||||
if (this.selectedCount === 0 || !this.isListing) {
|
||||
return filesize(this.req.size);
|
||||
return this.req.size;
|
||||
}
|
||||
|
||||
let sum = 0;
|
||||
|
@ -101,7 +100,7 @@ export default {
|
|||
sum += this.req.items[selected].size;
|
||||
}
|
||||
|
||||
return filesize(sum);
|
||||
return sum;
|
||||
},
|
||||
humanTime: function () {
|
||||
if (this.selectedCount === 0) {
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
header {
|
||||
z-index: 1000;
|
||||
background-color: #fff;
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.075);
|
||||
box-shadow: 0 0 5px rgba(0, 0, 0, 0.1);
|
||||
position: fixed;
|
||||
|
@ -12,6 +11,7 @@ header {
|
|||
display: flex;
|
||||
padding: 0.5em 0.5em 0.5em 1em;
|
||||
align-items: center;
|
||||
backdrop-filter: blur(6px);
|
||||
}
|
||||
|
||||
header > * {
|
||||
|
@ -82,7 +82,7 @@ header .menu-button {
|
|||
}
|
||||
|
||||
#search #input {
|
||||
background-color: #f5f5f5;
|
||||
background-color: rgba(100, 100, 100, 0.2);
|
||||
display: flex;
|
||||
height: 100%;
|
||||
padding: 0em 0.75em;
|
||||
|
@ -93,9 +93,9 @@ header .menu-button {
|
|||
}
|
||||
|
||||
#search.active #input {
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.075);
|
||||
box-shadow: 0 0 5px rgba(0, 0, 0, 0.1);
|
||||
background-color: #fff;
|
||||
border-bottom: 3px solid rgba(0, 0, 0, 0.075);
|
||||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
|
||||
backdrop-filter: blur(6px);
|
||||
height: 4em;
|
||||
}
|
||||
|
||||
|
@ -103,11 +103,6 @@ header .menu-button {
|
|||
border-radius: 0 !important;
|
||||
}
|
||||
|
||||
#search.active i,
|
||||
#search.active input {
|
||||
color: #212121;
|
||||
}
|
||||
|
||||
#search #input>.action,
|
||||
#search #input>i {
|
||||
margin-right: 0.3em;
|
||||
|
@ -121,20 +116,44 @@ header .menu-button {
|
|||
padding: 0;
|
||||
}
|
||||
|
||||
#result-list {
|
||||
width: 60em;
|
||||
max-width: 100%;
|
||||
padding-top: 3em;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.text-container {
|
||||
white-space: nowrap; /* Prevents the text from wrapping */
|
||||
overflow: hidden; /* Hides the content that exceeds the div size */
|
||||
text-overflow: ellipsis; /* Adds "..." when the text overflows */
|
||||
width: 100%; /* Ensures the content takes the full width available */
|
||||
text-align: left;
|
||||
direction: rtl;
|
||||
}
|
||||
|
||||
#search #result {
|
||||
visibility: visible;
|
||||
max-height: none;
|
||||
background-color: #f8f8f8;
|
||||
overflow: hidden;
|
||||
background: white;
|
||||
display: flex;
|
||||
top: -4em;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
text-align: left;
|
||||
padding: 0;
|
||||
color: rgba(0, 0, 0, 0.6);
|
||||
height: 0;
|
||||
transition: .1s ease height, .1s ease padding;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
transition: .2s ease height, .2s ease padding;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
@media screen and (min-width: 800px) {
|
||||
#search #result {
|
||||
background: linear-gradient(to right, white 15%,lightgray 25%,lightgray 75%,white 85%);
|
||||
}
|
||||
}
|
||||
|
||||
body.rtl #search #result {
|
||||
direction: ltr;
|
||||
}
|
||||
|
@ -155,23 +174,17 @@ body.rtl #search #result ul>* {
|
|||
}
|
||||
|
||||
#search.active #result {
|
||||
padding: .5em;
|
||||
height: calc(100% - 4em);
|
||||
height: 100vh
|
||||
}
|
||||
|
||||
#search ul {
|
||||
margin-top: 1em;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
#search li {
|
||||
margin-bottom: .5em;
|
||||
}
|
||||
|
||||
#search #result>div {
|
||||
max-width: 45em;
|
||||
margin: 0 auto;
|
||||
margin: .5em;
|
||||
}
|
||||
|
||||
#search #result #renew {
|
||||
|
@ -186,8 +199,20 @@ body.rtl #search #result ul>* {
|
|||
display: block;
|
||||
}
|
||||
|
||||
#search.active #result i {
|
||||
color: #ccc;
|
||||
.folder-icons {
|
||||
color: var(--icon-blue);
|
||||
}
|
||||
.video-icons {
|
||||
color: lightskyblue;
|
||||
}
|
||||
.image-icons {
|
||||
color: lightcoral;
|
||||
}
|
||||
.archive-icons {
|
||||
color: tan;
|
||||
}
|
||||
.audio-icons {
|
||||
color: plum;
|
||||
}
|
||||
|
||||
#search.active #result>p>i {
|
||||
|
|
|
@ -182,7 +182,6 @@
|
|||
<script>
|
||||
import { mapState, mapMutations, mapGetters } from "vuex";
|
||||
import { pub as api } from "@/api";
|
||||
import filesize from "filesize";
|
||||
import moment from "moment";
|
||||
|
||||
import HeaderBar from "@/components/header/HeaderBar";
|
||||
|
@ -255,8 +254,7 @@ export default {
|
|||
if (this.req.isDir) {
|
||||
return this.req.items.length;
|
||||
}
|
||||
|
||||
return filesize(this.req.size);
|
||||
return this.req.size;
|
||||
},
|
||||
humanTime: function () {
|
||||
return moment(this.req.modified).fromNow();
|
||||
|
|
|
@ -805,9 +805,7 @@ export default {
|
|||
prompt: "download",
|
||||
confirm: (format) => {
|
||||
this.$store.commit("closeHovers");
|
||||
|
||||
let files = [];
|
||||
|
||||
if (this.selectedCount > 0) {
|
||||
for (let i of this.selected) {
|
||||
files.push(this.req.items[i].url);
|
||||
|
@ -822,7 +820,6 @@ export default {
|
|||
},
|
||||
switchView: async function () {
|
||||
this.$store.commit("closeHovers");
|
||||
|
||||
const modes = {
|
||||
list: "mosaic",
|
||||
mosaic: "mosaic gallery",
|
||||
|
|
Loading…
Reference in New Issue