Beta/v0.5.3 (#374)
This commit is contained in:
parent
c84a3b0d41
commit
c4ccfd48f5
|
@ -24,7 +24,7 @@ jobs:
|
||||||
go-version: 'stable'
|
go-version: 'stable'
|
||||||
- uses: golangci/golangci-lint-action@v5
|
- uses: golangci/golangci-lint-action@v5
|
||||||
with:
|
with:
|
||||||
version: v1.60
|
version: 'v1.64'
|
||||||
working-directory: backend
|
working-directory: backend
|
||||||
format-backend:
|
format-backend:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
|
@ -56,7 +56,7 @@ jobs:
|
||||||
- name: Create Release
|
- name: Create Release
|
||||||
uses: softprops/action-gh-release@v2
|
uses: softprops/action-gh-release@v2
|
||||||
with:
|
with:
|
||||||
target_commitish: ${{ steps.extract_branch.outputs.branch_name }}
|
target_commitish: ${{ github.sha }}
|
||||||
token: ${{ secrets.PAT }}
|
token: ${{ secrets.PAT }}
|
||||||
tag_name: ${{ steps.extract_branch.outputs.tag_name }}
|
tag_name: ${{ steps.extract_branch.outputs.tag_name }}
|
||||||
prerelease: false # change this to false when stable gets released
|
prerelease: false # change this to false when stable gets released
|
||||||
|
|
|
@ -56,7 +56,7 @@ jobs:
|
||||||
- name: Create Release
|
- name: Create Release
|
||||||
uses: softprops/action-gh-release@v2
|
uses: softprops/action-gh-release@v2
|
||||||
with:
|
with:
|
||||||
target_commitish: ${{ steps.extract_branch.outputs.branch_name }}
|
target_commitish: ${{ github.sha }}
|
||||||
token: ${{ secrets.PAT }}
|
token: ${{ secrets.PAT }}
|
||||||
tag_name: ${{ steps.extract_branch.outputs.tag_name }}
|
tag_name: ${{ steps.extract_branch.outputs.tag_name }}
|
||||||
prerelease: false
|
prerelease: false
|
||||||
|
|
21
CHANGELOG.md
21
CHANGELOG.md
|
@ -2,6 +2,27 @@
|
||||||
|
|
||||||
All notable changes to this project will be documented in this file. For commit guidelines, please refer to [Standard Version](https://github.com/conventional-changelog/standard-version).
|
All notable changes to this project will be documented in this file. For commit guidelines, please refer to [Standard Version](https://github.com/conventional-changelog/standard-version).
|
||||||
|
|
||||||
|
## v0.5.3-beta
|
||||||
|
|
||||||
|
**New Features**
|
||||||
|
- onlyoffice disable filetypes for user specified file types. https://github.com/gtsteffaniak/filebrowser/issues/346
|
||||||
|
|
||||||
|
**Notes**:
|
||||||
|
- navbar/sidebar lightmode style tweaks.
|
||||||
|
- any item that has utf formatted text will get editor.
|
||||||
|
- tweaks to create options on context menu.
|
||||||
|
- removed small delay on preview before detecting the file.
|
||||||
|
|
||||||
|
**BugFixes**:
|
||||||
|
- fix `/files/` prefix loading issue https://github.com/gtsteffaniak/filebrowser/issues/362
|
||||||
|
- fix special characters in filename issue https://github.com/gtsteffaniak/filebrowser/issues/357
|
||||||
|
- fix drag and drop issue https://github.com/gtsteffaniak/filebrowser/issues/361
|
||||||
|
- fix conflict issue with creating same file after deletion.
|
||||||
|
- fix mimetype detection https://github.com/gtsteffaniak/filebrowser/issues/327
|
||||||
|
- subtitles for videos https://github.com/gtsteffaniak/filebrowser/issues/358
|
||||||
|
- supports caption sidecar files : ".vtt", ".srt", ".lrc", ".sbv", ".ass", ".ssa", ".sub", ".smi"
|
||||||
|
- embedded subtitles not yet supported.
|
||||||
|
|
||||||
## v0.5.2-beta
|
## v0.5.2-beta
|
||||||
|
|
||||||
**New Features**:
|
**New Features**:
|
||||||
|
|
|
@ -6,6 +6,9 @@
|
||||||
[](https://hub.docker.com/r/gtstef/filebrowser)
|
[](https://hub.docker.com/r/gtstef/filebrowser)
|
||||||
[](https://www.apache.org/licenses/LICENSE-2.0)
|
[](https://www.apache.org/licenses/LICENSE-2.0)
|
||||||
|
|
||||||
|
[](https://github.com/gtsteffaniak/filebrowser/discussions/368)
|
||||||
|
[](https://www.paypal.com/donate/?business=W5XKNXHJM2WPE&no_recurring=0¤cy_code=USD)
|
||||||
|
|
||||||
<img width="150" src="https://github.com/user-attachments/assets/59986a2a-f960-4536-aa35-4a9a7c98ad48" title="Logo">
|
<img width="150" src="https://github.com/user-attachments/assets/59986a2a-f960-4536-aa35-4a9a7c98ad48" title="Logo">
|
||||||
<h3>FileBrowser Quantum</h3>
|
<h3>FileBrowser Quantum</h3>
|
||||||
A modern web-based file manager
|
A modern web-based file manager
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
FROM golang:1.23-alpine AS base
|
FROM golang:1.24-alpine AS base
|
||||||
ARG VERSION
|
ARG VERSION
|
||||||
ARG REVISION
|
ARG REVISION
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
|
@ -24,6 +24,7 @@ import (
|
||||||
"github.com/gtsteffaniak/filebrowser/backend/cache"
|
"github.com/gtsteffaniak/filebrowser/backend/cache"
|
||||||
"github.com/gtsteffaniak/filebrowser/backend/errors"
|
"github.com/gtsteffaniak/filebrowser/backend/errors"
|
||||||
"github.com/gtsteffaniak/filebrowser/backend/fileutils"
|
"github.com/gtsteffaniak/filebrowser/backend/fileutils"
|
||||||
|
"github.com/gtsteffaniak/filebrowser/backend/logger"
|
||||||
"github.com/gtsteffaniak/filebrowser/backend/settings"
|
"github.com/gtsteffaniak/filebrowser/backend/settings"
|
||||||
"github.com/gtsteffaniak/filebrowser/backend/users"
|
"github.com/gtsteffaniak/filebrowser/backend/users"
|
||||||
"github.com/gtsteffaniak/filebrowser/backend/utils"
|
"github.com/gtsteffaniak/filebrowser/backend/utils"
|
||||||
|
@ -54,7 +55,7 @@ type FileInfo struct {
|
||||||
// for efficiency, a response will be a pointer to the data
|
// for efficiency, a response will be a pointer to the data
|
||||||
// extra calculated fields can be added here
|
// extra calculated fields can be added here
|
||||||
type ExtendedFileInfo struct {
|
type ExtendedFileInfo struct {
|
||||||
*FileInfo
|
FileInfo
|
||||||
Content string `json:"content,omitempty"` // text content of a file, if requested
|
Content string `json:"content,omitempty"` // text content of a file, if requested
|
||||||
Subtitles []string `json:"subtitles,omitempty"` // subtitles for video files
|
Subtitles []string `json:"subtitles,omitempty"` // subtitles for video files
|
||||||
Checksums map[string]string `json:"checksums,omitempty"` // checksums for the file
|
Checksums map[string]string `json:"checksums,omitempty"` // checksums for the file
|
||||||
|
@ -129,7 +130,7 @@ func FileInfoFaster(opts FileOptions) (ExtendedFileInfo, error) {
|
||||||
}
|
}
|
||||||
info, exists := index.GetReducedMetadata(opts.Path, opts.IsDir)
|
info, exists := index.GetReducedMetadata(opts.Path, opts.IsDir)
|
||||||
if !exists {
|
if !exists {
|
||||||
return response, err
|
return response, fmt.Errorf("could not get metadata for path: %v", opts.Path)
|
||||||
}
|
}
|
||||||
if opts.Content {
|
if opts.Content {
|
||||||
content, err := getContent("default", opts.Path)
|
content, err := getContent("default", opts.Path)
|
||||||
|
@ -138,11 +139,15 @@ func FileInfoFaster(opts FileOptions) (ExtendedFileInfo, error) {
|
||||||
}
|
}
|
||||||
response.Content = content
|
response.Content = content
|
||||||
}
|
}
|
||||||
response.FileInfo = info
|
response.FileInfo = *info
|
||||||
response.RealPath = realPath
|
response.RealPath = realPath
|
||||||
|
response.Source = opts.Source
|
||||||
if settings.Config.Integrations.OnlyOffice.Secret != "" && info.Type != "directory" && isOnlyOffice(info.Name) {
|
if settings.Config.Integrations.OnlyOffice.Secret != "" && info.Type != "directory" && isOnlyOffice(info.Name) {
|
||||||
response.OnlyOfficeId = generateOfficeId(realPath)
|
response.OnlyOfficeId = generateOfficeId(realPath)
|
||||||
}
|
}
|
||||||
|
if strings.HasPrefix(info.Type, "video") {
|
||||||
|
response.detectSubtitles(realPath)
|
||||||
|
}
|
||||||
return response, nil
|
return response, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -365,42 +370,32 @@ func (i *ItemInfo) DetectType(realPath string, saveContent bool) {
|
||||||
|
|
||||||
// TODO add subtitles back
|
// TODO add subtitles back
|
||||||
// detectSubtitles detects subtitles for video files.
|
// detectSubtitles detects subtitles for video files.
|
||||||
//func (i *FileInfo) detectSubtitles(path string) {
|
func (i *ExtendedFileInfo) detectSubtitles(path string) {
|
||||||
// if i.Type != "video" {
|
if !strings.HasPrefix(i.Type, "video") {
|
||||||
// return
|
logger.Debug("subtitles are not supported for this file : " + path)
|
||||||
// }
|
return
|
||||||
// parentDir := filepath.Dir(path)
|
}
|
||||||
// fileName := filepath.Base(path)
|
|
||||||
// i.Subtitles = []string{}
|
idx := GetIndex(i.Source)
|
||||||
// ext := filepath.Ext(fileName)
|
parentInfo, exists := idx.GetReducedMetadata(filepath.Dir(i.Path), true)
|
||||||
// dir, err := os.Open(parentDir)
|
if !exists {
|
||||||
// if err != nil {
|
return
|
||||||
// // Directory must have been deleted, remove it from the index
|
}
|
||||||
// return
|
base := strings.Split(i.Name, ".")[0]
|
||||||
// }
|
for _, f := range parentInfo.Files {
|
||||||
// defer dir.Close() // Ensure directory handle is closed
|
baseName := strings.Split(f.Name, ".")[0]
|
||||||
//
|
if baseName != base {
|
||||||
// files, err := dir.Readdir(-1)
|
continue
|
||||||
// if err != nil {
|
}
|
||||||
// return
|
|
||||||
// }
|
for _, subtitleExt := range []string{".vtt", ".srt", ".lrc", ".sbv", ".ass", ".ssa", ".sub", ".smi"} {
|
||||||
//
|
if strings.HasSuffix(f.Name, subtitleExt) {
|
||||||
// base := strings.TrimSuffix(fileName, ext)
|
fullPathBase := strings.Split(i.Path, ".")[0]
|
||||||
// subtitleExts := []string{".vtt", ".txt", ".srt", ".lrc"}
|
i.Subtitles = append(i.Subtitles, fullPathBase+subtitleExt)
|
||||||
//
|
}
|
||||||
// for _, f := range files {
|
}
|
||||||
// if f.IsDir() || !strings.HasPrefix(f.Name(), base) {
|
}
|
||||||
// continue
|
}
|
||||||
// }
|
|
||||||
//
|
|
||||||
// for _, subtitleExt := range subtitleExts {
|
|
||||||
// if strings.HasSuffix(f.Name(), subtitleExt) {
|
|
||||||
// i.Subtitles = append(i.Subtitles, filepath.Join(parentDir, f.Name()))
|
|
||||||
// break
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//}
|
|
||||||
|
|
||||||
func IsNamedPipe(mode os.FileMode) bool {
|
func IsNamedPipe(mode os.FileMode) bool {
|
||||||
return mode&os.ModeNamedPipe != 0
|
return mode&os.ModeNamedPipe != 0
|
||||||
|
|
|
@ -54,11 +54,11 @@ func Initialize(source settings.Source) {
|
||||||
|
|
||||||
if !newIndex.Source.Config.Disabled {
|
if !newIndex.Source.Config.Disabled {
|
||||||
time.Sleep(time.Second)
|
time.Sleep(time.Second)
|
||||||
logger.Info("Initializing index and assessing file system complexity")
|
logger.Info(fmt.Sprintf("initializing index: [%v]", newIndex.Source.Name))
|
||||||
newIndex.RunIndexing("/", false)
|
newIndex.RunIndexing("/", false)
|
||||||
go newIndex.setupIndexingScanners()
|
go newIndex.setupIndexingScanners()
|
||||||
} else {
|
} else {
|
||||||
logger.Debug("Indexing disabled for source: " + newIndex.Source.Name)
|
logger.Debug("indexing disabled for source: " + newIndex.Source.Name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -122,7 +122,7 @@ func (idx *Index) indexDirectory(adjustedPath string, quick, recursive bool) err
|
||||||
|
|
||||||
// Process each file and directory in the current directory
|
// Process each file and directory in the current directory
|
||||||
for _, file := range files {
|
for _, file := range files {
|
||||||
isHidden := isHidden(file, idx.Path+combinedPath)
|
isHidden := isHidden(file, idx.Source.Path+combinedPath)
|
||||||
isDir := file.IsDir()
|
isDir := file.IsDir()
|
||||||
fullCombined := combinedPath + file.Name()
|
fullCombined := combinedPath + file.Name()
|
||||||
if idx.shouldSkip(isDir, isHidden, fullCombined) {
|
if idx.shouldSkip(isDir, isHidden, fullCombined) {
|
||||||
|
|
|
@ -76,9 +76,9 @@ func (idx *Index) RunIndexing(origin string, quick bool) {
|
||||||
prevNumDirs := idx.NumDirs
|
prevNumDirs := idx.NumDirs
|
||||||
prevNumFiles := idx.NumFiles
|
prevNumFiles := idx.NumFiles
|
||||||
if quick {
|
if quick {
|
||||||
logger.Debug("Starting quick scan")
|
logger.Debug(fmt.Sprintf("Starting quick scan for [%v]", idx.Source.Name))
|
||||||
} else {
|
} else {
|
||||||
logger.Debug("Starting full scan")
|
logger.Debug(fmt.Sprintf("Starting full scan for [%v]", idx.Source.Name))
|
||||||
idx.NumDirs = 0
|
idx.NumDirs = 0
|
||||||
idx.NumFiles = 0
|
idx.NumFiles = 0
|
||||||
}
|
}
|
||||||
|
@ -106,18 +106,18 @@ func (idx *Index) RunIndexing(origin string, quick bool) {
|
||||||
idx.assessment = "normal"
|
idx.assessment = "normal"
|
||||||
}
|
}
|
||||||
if firstRun {
|
if firstRun {
|
||||||
logger.Info(fmt.Sprintf("Index assessment : complexity=%v directories=%v files=%v", idx.assessment, idx.NumDirs, idx.NumFiles))
|
logger.Info(fmt.Sprintf("Index assessment : index=%v complexity=%v directories=%v files=%v", idx.Source.Name, idx.assessment, idx.NumDirs, idx.NumFiles))
|
||||||
} else {
|
} else {
|
||||||
logger.Debug(fmt.Sprintf("Index assessment : complexity=%v directories=%v files=%v", idx.assessment, idx.NumDirs, idx.NumFiles))
|
logger.Debug(fmt.Sprintf("Index assessment : iindex=%v complexity=%v directories=%v files=%v", idx.Source.Name, idx.assessment, idx.NumDirs, idx.NumFiles))
|
||||||
}
|
}
|
||||||
if idx.NumDirs != prevNumDirs || idx.NumFiles != prevNumFiles {
|
if idx.NumDirs != prevNumDirs || idx.NumFiles != prevNumFiles {
|
||||||
idx.FilesChangedDuringIndexing = true
|
idx.FilesChangedDuringIndexing = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if firstRun {
|
if firstRun {
|
||||||
logger.Info(fmt.Sprintf("Time spent indexing : %v seconds", idx.indexingTime))
|
logger.Info(fmt.Sprintf("Time spent indexing [%v]: %v seconds", idx.Source.Name, idx.indexingTime))
|
||||||
} else {
|
} else {
|
||||||
logger.Debug(fmt.Sprintf("Time spent indexing : %v seconds", idx.indexingTime))
|
logger.Debug(fmt.Sprintf("Time spent indexing [%v]: %v seconds", idx.Source.Name, idx.indexingTime))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,612 @@
|
||||||
|
package files
|
||||||
|
|
||||||
|
// This file contains code primarily sourced from::
|
||||||
|
// github.com/kataras/iris
|
||||||
|
|
||||||
|
import (
|
||||||
|
"mime"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// ContentBinaryHeaderValue header value for binary data.
|
||||||
|
ContentBinaryHeaderValue = "application/octet-stream"
|
||||||
|
// ContentWebassemblyHeaderValue header value for web assembly files.
|
||||||
|
ContentWebassemblyHeaderValue = "application/wasm"
|
||||||
|
// ContentHTMLHeaderValue is the string of text/html response header's content type value.
|
||||||
|
ContentHTMLHeaderValue = "text/html"
|
||||||
|
// ContentJSONHeaderValue header value for JSON data.
|
||||||
|
ContentJSONHeaderValue = "application/json"
|
||||||
|
// ContentJSONProblemHeaderValue header value for JSON API problem error.
|
||||||
|
// Read more at: https://tools.ietf.org/html/rfc7807
|
||||||
|
ContentJSONProblemHeaderValue = "application/problem+json"
|
||||||
|
// ContentXMLProblemHeaderValue header value for XML API problem error.
|
||||||
|
// Read more at: https://tools.ietf.org/html/rfc7807
|
||||||
|
ContentXMLProblemHeaderValue = "application/problem+xml"
|
||||||
|
// ContentJavascriptHeaderValue header value for JSONP & Javascript data.
|
||||||
|
ContentJavascriptHeaderValue = "text/javascript"
|
||||||
|
// ContentTextHeaderValue header value for Text data.
|
||||||
|
ContentTextHeaderValue = "text/plain"
|
||||||
|
// ContentXMLHeaderValue header value for XML data.
|
||||||
|
ContentXMLHeaderValue = "text/xml"
|
||||||
|
// ContentXMLUnreadableHeaderValue obsolete header value for XML.
|
||||||
|
ContentXMLUnreadableHeaderValue = "application/xml"
|
||||||
|
// ContentMarkdownHeaderValue custom key/content type, the real is the text/html.
|
||||||
|
ContentMarkdownHeaderValue = "text/markdown"
|
||||||
|
// ContentYAMLHeaderValue header value for YAML data.
|
||||||
|
ContentYAMLHeaderValue = "application/x-yaml"
|
||||||
|
// ContentYAMLTextHeaderValue header value for YAML plain text.
|
||||||
|
ContentYAMLTextHeaderValue = "text/yaml"
|
||||||
|
// ContentProtobufHeaderValue header value for Protobuf messages data.
|
||||||
|
ContentProtobufHeaderValue = "application/x-protobuf"
|
||||||
|
// ContentMsgPackHeaderValue header value for MsgPack data.
|
||||||
|
ContentMsgPackHeaderValue = "application/msgpack"
|
||||||
|
// ContentMsgPack2HeaderValue alternative header value for MsgPack data.
|
||||||
|
ContentMsgPack2HeaderValue = "application/x-msgpack"
|
||||||
|
// ContentFormHeaderValue header value for post form data.
|
||||||
|
ContentFormHeaderValue = "application/x-www-form-urlencoded"
|
||||||
|
// ContentFormMultipartHeaderValue header value for post multipart form data.
|
||||||
|
ContentFormMultipartHeaderValue = "multipart/form-data"
|
||||||
|
// ContentMultipartRelatedHeaderValue header value for multipart related data.
|
||||||
|
ContentMultipartRelatedHeaderValue = "multipart/related"
|
||||||
|
// ContentGRPCHeaderValue Content-Type header value for gRPC.
|
||||||
|
ContentGRPCHeaderValue = "application/grpc"
|
||||||
|
)
|
||||||
|
|
||||||
|
var types = map[string]string{
|
||||||
|
".3dm": "x-world/x-3dmf",
|
||||||
|
".3dmf": "x-world/x-3dmf",
|
||||||
|
".7z": "application/x-7z-compressed",
|
||||||
|
".a": "application/octet-stream",
|
||||||
|
".aab": "application/x-authorware-bin",
|
||||||
|
".aam": "application/x-authorware-map",
|
||||||
|
".aas": "application/x-authorware-seg",
|
||||||
|
".abc": "text/vndabc",
|
||||||
|
".ace": "application/x-ace-compressed",
|
||||||
|
".acgi": "text/html",
|
||||||
|
".afl": "video/animaflex",
|
||||||
|
".ai": "application/postscript",
|
||||||
|
".aif": "audio/aiff",
|
||||||
|
".aifc": "audio/aiff",
|
||||||
|
".aiff": "audio/aiff",
|
||||||
|
".aim": "application/x-aim",
|
||||||
|
".aip": "text/x-audiosoft-intra",
|
||||||
|
".alz": "application/x-alz-compressed",
|
||||||
|
".ani": "application/x-navi-animation",
|
||||||
|
".aos": "application/x-nokia-9000-communicator-add-on-software",
|
||||||
|
".aps": "application/mime",
|
||||||
|
".apk": "application/vnd.android.package-archive",
|
||||||
|
".arc": "application/x-arc-compressed",
|
||||||
|
".arj": "application/arj",
|
||||||
|
".art": "image/x-jg",
|
||||||
|
".asf": "video/x-ms-asf",
|
||||||
|
".asm": "text/x-asm",
|
||||||
|
".asp": "text/asp",
|
||||||
|
".asx": "application/x-mplayer2",
|
||||||
|
".au": "audio/basic",
|
||||||
|
".avi": "video/x-msvideo",
|
||||||
|
".avs": "video/avs-video",
|
||||||
|
".bcpio": "application/x-bcpio",
|
||||||
|
".bin": "application/mac-binary",
|
||||||
|
".bmp": "image/bmp",
|
||||||
|
".boo": "application/book",
|
||||||
|
".book": "application/book",
|
||||||
|
".boz": "application/x-bzip2",
|
||||||
|
".bsh": "application/x-bsh",
|
||||||
|
".bz2": "application/x-bzip2",
|
||||||
|
".bz": "application/x-bzip",
|
||||||
|
".c++": ContentTextHeaderValue,
|
||||||
|
".c": "text/x-c",
|
||||||
|
".cab": "application/vnd.ms-cab-compressed",
|
||||||
|
".cat": "application/vndms-pkiseccat",
|
||||||
|
".cc": "text/x-c",
|
||||||
|
".ccad": "application/clariscad",
|
||||||
|
".cco": "application/x-cocoa",
|
||||||
|
".cdf": "application/cdf",
|
||||||
|
".cer": "application/pkix-cert",
|
||||||
|
".cha": "application/x-chat",
|
||||||
|
".chat": "application/x-chat",
|
||||||
|
".chrt": "application/vnd.kde.kchart",
|
||||||
|
".class": "application/java",
|
||||||
|
".com": ContentTextHeaderValue,
|
||||||
|
".conf": ContentTextHeaderValue,
|
||||||
|
".cpio": "application/x-cpio",
|
||||||
|
".cpp": "text/x-c",
|
||||||
|
".cpt": "application/mac-compactpro",
|
||||||
|
".crl": "application/pkcs-crl",
|
||||||
|
".crt": "application/pkix-cert",
|
||||||
|
".crx": "application/x-chrome-extension",
|
||||||
|
".csh": "text/x-scriptcsh",
|
||||||
|
".css": "text/css",
|
||||||
|
".csv": "text/csv",
|
||||||
|
".cxx": ContentTextHeaderValue,
|
||||||
|
".dar": "application/x-dar",
|
||||||
|
".dcr": "application/x-director",
|
||||||
|
".deb": "application/x-debian-package",
|
||||||
|
".deepv": "application/x-deepv",
|
||||||
|
".def": ContentTextHeaderValue,
|
||||||
|
".der": "application/x-x509-ca-cert",
|
||||||
|
".dif": "video/x-dv",
|
||||||
|
".dir": "application/x-director",
|
||||||
|
".divx": "video/divx",
|
||||||
|
".dl": "video/dl",
|
||||||
|
".dmg": "application/x-apple-diskimage",
|
||||||
|
".doc": "application/msword",
|
||||||
|
".dot": "application/msword",
|
||||||
|
".dp": "application/commonground",
|
||||||
|
".drw": "application/drafting",
|
||||||
|
".dump": "application/octet-stream",
|
||||||
|
".dv": "video/x-dv",
|
||||||
|
".dvi": "application/x-dvi",
|
||||||
|
".dwf": "drawing/x-dwf=(old)",
|
||||||
|
".dwg": "application/acad",
|
||||||
|
".dxf": "application/dxf",
|
||||||
|
".dxr": "application/x-director",
|
||||||
|
".el": "text/x-scriptelisp",
|
||||||
|
".elc": "application/x-bytecodeelisp=(compiled=elisp)",
|
||||||
|
".eml": "message/rfc822",
|
||||||
|
".env": "application/x-envoy",
|
||||||
|
".eps": "application/postscript",
|
||||||
|
".es": "application/x-esrehber",
|
||||||
|
".etx": "text/x-setext",
|
||||||
|
".evy": "application/envoy",
|
||||||
|
".exe": "application/octet-stream",
|
||||||
|
".f77": "text/x-fortran",
|
||||||
|
".f90": "text/x-fortran",
|
||||||
|
".f": "text/x-fortran",
|
||||||
|
".fdf": "application/vndfdf",
|
||||||
|
".fif": "application/fractals",
|
||||||
|
".fli": "video/fli",
|
||||||
|
".flo": "image/florian",
|
||||||
|
".flv": "video/x-flv",
|
||||||
|
".flx": "text/vndfmiflexstor",
|
||||||
|
".fmf": "video/x-atomic3d-feature",
|
||||||
|
".for": "text/x-fortran",
|
||||||
|
".fpx": "image/vndfpx",
|
||||||
|
".frl": "application/freeloader",
|
||||||
|
".funk": "audio/make",
|
||||||
|
".g3": "image/g3fax",
|
||||||
|
".g": ContentTextHeaderValue,
|
||||||
|
".gif": "image/gif",
|
||||||
|
".gl": "video/gl",
|
||||||
|
".gsd": "audio/x-gsm",
|
||||||
|
".gsm": "audio/x-gsm",
|
||||||
|
".gsp": "application/x-gsp",
|
||||||
|
".gss": "application/x-gss",
|
||||||
|
".gtar": "application/x-gtar",
|
||||||
|
".gz": "application/x-compressed",
|
||||||
|
".gzip": "application/x-gzip",
|
||||||
|
".h": "text/x-h",
|
||||||
|
".hdf": "application/x-hdf",
|
||||||
|
".help": "application/x-helpfile",
|
||||||
|
".hgl": "application/vndhp-hpgl",
|
||||||
|
".hh": "text/x-h",
|
||||||
|
".hlb": "text/x-script",
|
||||||
|
".hlp": "application/hlp",
|
||||||
|
".hpg": "application/vndhp-hpgl",
|
||||||
|
".hpgl": "application/vndhp-hpgl",
|
||||||
|
".hqx": "application/binhex",
|
||||||
|
".hta": "application/hta",
|
||||||
|
".htc": "text/x-component",
|
||||||
|
".htm": "text/html",
|
||||||
|
".html": "text/html",
|
||||||
|
".htmls": "text/html",
|
||||||
|
".htt": "text/webviewhtml",
|
||||||
|
".htx": "text/html",
|
||||||
|
".ice": "x-conference/x-cooltalk",
|
||||||
|
".ico": "image/x-icon",
|
||||||
|
".ics": "text/calendar",
|
||||||
|
".icz": "text/calendar",
|
||||||
|
".idc": ContentTextHeaderValue,
|
||||||
|
".ief": "image/ief",
|
||||||
|
".iefs": "image/ief",
|
||||||
|
".iges": "application/iges",
|
||||||
|
".igs": "application/iges",
|
||||||
|
".ima": "application/x-ima",
|
||||||
|
".imap": "application/x-httpd-imap",
|
||||||
|
".inf": "application/inf",
|
||||||
|
".ins": "application/x-internett-signup",
|
||||||
|
".ip": "application/x-ip2",
|
||||||
|
".isu": "video/x-isvideo",
|
||||||
|
".it": "audio/it",
|
||||||
|
".iv": "application/x-inventor",
|
||||||
|
".ivr": "i-world/i-vrml",
|
||||||
|
".ivy": "application/x-livescreen",
|
||||||
|
".jam": "audio/x-jam",
|
||||||
|
".jav": "text/x-java-source",
|
||||||
|
".java": "text/x-java-source",
|
||||||
|
".jcm": "application/x-java-commerce",
|
||||||
|
".jfif-tbnl": "image/jpeg",
|
||||||
|
".jfif": "image/jpeg",
|
||||||
|
".jnlp": "application/x-java-jnlp-file",
|
||||||
|
".jpe": "image/jpeg",
|
||||||
|
".jpeg": "image/jpeg",
|
||||||
|
".jpg": "image/jpeg",
|
||||||
|
".jps": "image/x-jps",
|
||||||
|
".js": ContentJavascriptHeaderValue,
|
||||||
|
".mjs": ContentJavascriptHeaderValue,
|
||||||
|
".json": ContentJSONHeaderValue,
|
||||||
|
".vue": ContentJavascriptHeaderValue,
|
||||||
|
".jut": "image/jutvision",
|
||||||
|
".kar": "audio/midi",
|
||||||
|
".karbon": "application/vnd.kde.karbon",
|
||||||
|
".kfo": "application/vnd.kde.kformula",
|
||||||
|
".flw": "application/vnd.kde.kivio",
|
||||||
|
".kml": "application/vnd.google-earth.kml+xml",
|
||||||
|
".kmz": "application/vnd.google-earth.kmz",
|
||||||
|
".kon": "application/vnd.kde.kontour",
|
||||||
|
".kpr": "application/vnd.kde.kpresenter",
|
||||||
|
".kpt": "application/vnd.kde.kpresenter",
|
||||||
|
".ksp": "application/vnd.kde.kspread",
|
||||||
|
".kwd": "application/vnd.kde.kword",
|
||||||
|
".kwt": "application/vnd.kde.kword",
|
||||||
|
".ksh": "text/x-scriptksh",
|
||||||
|
".la": "audio/nspaudio",
|
||||||
|
".lam": "audio/x-liveaudio",
|
||||||
|
".latex": "application/x-latex",
|
||||||
|
".lha": "application/lha",
|
||||||
|
".lhx": "application/octet-stream",
|
||||||
|
".list": ContentTextHeaderValue,
|
||||||
|
".lma": "audio/nspaudio",
|
||||||
|
".log": ContentTextHeaderValue,
|
||||||
|
".lsp": "text/x-scriptlisp",
|
||||||
|
".lst": ContentTextHeaderValue,
|
||||||
|
".lsx": "text/x-la-asf",
|
||||||
|
".ltx": "application/x-latex",
|
||||||
|
".lzh": "application/octet-stream",
|
||||||
|
".lzx": "application/lzx",
|
||||||
|
".m1v": "video/mpeg",
|
||||||
|
".m2a": "audio/mpeg",
|
||||||
|
".m2v": "video/mpeg",
|
||||||
|
".m3u": "audio/x-mpegurl",
|
||||||
|
".m": "text/x-m",
|
||||||
|
".man": "application/x-troff-man",
|
||||||
|
".manifest": "text/cache-manifest",
|
||||||
|
".map": "application/x-navimap",
|
||||||
|
".mar": ContentTextHeaderValue,
|
||||||
|
".mbd": "application/mbedlet",
|
||||||
|
".mc$": "application/x-magic-cap-package-10",
|
||||||
|
".mcd": "application/mcad",
|
||||||
|
".mcf": "text/mcf",
|
||||||
|
".mcp": "application/netmc",
|
||||||
|
".me": "application/x-troff-me",
|
||||||
|
".mht": "message/rfc822",
|
||||||
|
".mhtml": "message/rfc822",
|
||||||
|
".mid": "application/x-midi",
|
||||||
|
".midi": "application/x-midi",
|
||||||
|
".mif": "application/x-frame",
|
||||||
|
".mime": "message/rfc822",
|
||||||
|
".mjf": "audio/x-vndaudioexplosionmjuicemediafile",
|
||||||
|
".mjpg": "video/x-motion-jpeg",
|
||||||
|
".mm": "application/base64",
|
||||||
|
".mme": "application/base64",
|
||||||
|
".mod": "audio/mod",
|
||||||
|
".moov": "video/quicktime",
|
||||||
|
".mov": "video/quicktime",
|
||||||
|
".movie": "video/x-sgi-movie",
|
||||||
|
".mp2": "audio/mpeg",
|
||||||
|
".mp3": "audio/mpeg",
|
||||||
|
".mp4": "video/mp4",
|
||||||
|
".mpa": "audio/mpeg",
|
||||||
|
".mpc": "application/x-project",
|
||||||
|
".mpe": "video/mpeg",
|
||||||
|
".mpeg": "video/mpeg",
|
||||||
|
".mpg": "video/mpeg",
|
||||||
|
".mpga": "audio/mpeg",
|
||||||
|
".mpp": "application/vndms-project",
|
||||||
|
".mpt": "application/x-project",
|
||||||
|
".mpv": "application/x-project",
|
||||||
|
".mpx": "application/x-project",
|
||||||
|
".mrc": "application/marc",
|
||||||
|
".ms": "application/x-troff-ms",
|
||||||
|
".mv": "video/x-sgi-movie",
|
||||||
|
".my": "audio/make",
|
||||||
|
".mzz": "application/x-vndaudioexplosionmzz",
|
||||||
|
".nap": "image/naplps",
|
||||||
|
".naplps": "image/naplps",
|
||||||
|
".nc": "application/x-netcdf",
|
||||||
|
".ncm": "application/vndnokiaconfiguration-message",
|
||||||
|
".nif": "image/x-niff",
|
||||||
|
".niff": "image/x-niff",
|
||||||
|
".nix": "application/x-mix-transfer",
|
||||||
|
".nsc": "application/x-conference",
|
||||||
|
".nvd": "application/x-navidoc",
|
||||||
|
".o": "application/octet-stream",
|
||||||
|
".oda": "application/oda",
|
||||||
|
".odb": "application/vnd.oasis.opendocument.database",
|
||||||
|
".odc": "application/vnd.oasis.opendocument.chart",
|
||||||
|
".odf": "application/vnd.oasis.opendocument.formula",
|
||||||
|
".odg": "application/vnd.oasis.opendocument.graphics",
|
||||||
|
".odi": "application/vnd.oasis.opendocument.image",
|
||||||
|
".odm": "application/vnd.oasis.opendocument.text-master",
|
||||||
|
".odp": "application/vnd.oasis.opendocument.presentation",
|
||||||
|
".ods": "application/vnd.oasis.opendocument.spreadsheet",
|
||||||
|
".odt": "application/vnd.oasis.opendocument.text",
|
||||||
|
".oga": "audio/ogg",
|
||||||
|
".ogg": "audio/ogg",
|
||||||
|
".ogv": "video/ogg",
|
||||||
|
".omc": "application/x-omc",
|
||||||
|
".omcd": "application/x-omcdatamaker",
|
||||||
|
".omcr": "application/x-omcregerator",
|
||||||
|
".otc": "application/vnd.oasis.opendocument.chart-template",
|
||||||
|
".otf": "application/vnd.oasis.opendocument.formula-template",
|
||||||
|
".otg": "application/vnd.oasis.opendocument.graphics-template",
|
||||||
|
".oth": "application/vnd.oasis.opendocument.text-web",
|
||||||
|
".oti": "application/vnd.oasis.opendocument.image-template",
|
||||||
|
".otm": "application/vnd.oasis.opendocument.text-master",
|
||||||
|
".otp": "application/vnd.oasis.opendocument.presentation-template",
|
||||||
|
".ots": "application/vnd.oasis.opendocument.spreadsheet-template",
|
||||||
|
".ott": "application/vnd.oasis.opendocument.text-template",
|
||||||
|
".p10": "application/pkcs10",
|
||||||
|
".p12": "application/pkcs-12",
|
||||||
|
".p7a": "application/x-pkcs7-signature",
|
||||||
|
".p7c": "application/pkcs7-mime",
|
||||||
|
".p7m": "application/pkcs7-mime",
|
||||||
|
".p7r": "application/x-pkcs7-certreqresp",
|
||||||
|
".p7s": "application/pkcs7-signature",
|
||||||
|
".p": "text/x-pascal",
|
||||||
|
".part": "application/pro_eng",
|
||||||
|
".pas": "text/pascal",
|
||||||
|
".pbm": "image/x-portable-bitmap",
|
||||||
|
".pcl": "application/vndhp-pcl",
|
||||||
|
".pct": "image/x-pict",
|
||||||
|
".pcx": "image/x-pcx",
|
||||||
|
".pdb": "chemical/x-pdb",
|
||||||
|
".pdf": "application/pdf",
|
||||||
|
".pfunk": "audio/make",
|
||||||
|
".pgm": "image/x-portable-graymap",
|
||||||
|
".pic": "image/pict",
|
||||||
|
".pict": "image/pict",
|
||||||
|
".pkg": "application/x-newton-compatible-pkg",
|
||||||
|
".pko": "application/vndms-pkipko",
|
||||||
|
".pl": "text/x-scriptperl",
|
||||||
|
".plx": "application/x-pixclscript",
|
||||||
|
".pm4": "application/x-pagemaker",
|
||||||
|
".pm5": "application/x-pagemaker",
|
||||||
|
".pm": "text/x-scriptperl-module",
|
||||||
|
".png": "image/png",
|
||||||
|
".pnm": "application/x-portable-anymap",
|
||||||
|
".pot": "application/mspowerpoint",
|
||||||
|
".pov": "model/x-pov",
|
||||||
|
".ppa": "application/vndms-powerpoint",
|
||||||
|
".ppm": "image/x-portable-pixmap",
|
||||||
|
".pps": "application/mspowerpoint",
|
||||||
|
".ppt": "application/mspowerpoint",
|
||||||
|
".ppz": "application/mspowerpoint",
|
||||||
|
".pre": "application/x-freelance",
|
||||||
|
".prt": "application/pro_eng",
|
||||||
|
".ps": "application/postscript",
|
||||||
|
".psd": "application/octet-stream",
|
||||||
|
".pvu": "paleovu/x-pv",
|
||||||
|
".pwz": "application/vndms-powerpoint",
|
||||||
|
".py": "text/x-scriptphyton",
|
||||||
|
".pyc": "application/x-bytecodepython",
|
||||||
|
".qcp": "audio/vndqcelp",
|
||||||
|
".qd3": "x-world/x-3dmf",
|
||||||
|
".qd3d": "x-world/x-3dmf",
|
||||||
|
".qif": "image/x-quicktime",
|
||||||
|
".qt": "video/quicktime",
|
||||||
|
".qtc": "video/x-qtc",
|
||||||
|
".qti": "image/x-quicktime",
|
||||||
|
".qtif": "image/x-quicktime",
|
||||||
|
".ra": "audio/x-pn-realaudio",
|
||||||
|
".ram": "audio/x-pn-realaudio",
|
||||||
|
".rar": "application/x-rar-compressed",
|
||||||
|
".ras": "application/x-cmu-raster",
|
||||||
|
".rast": "image/cmu-raster",
|
||||||
|
".rexx": "text/x-scriptrexx",
|
||||||
|
".rf": "image/vndrn-realflash",
|
||||||
|
".rgb": "image/x-rgb",
|
||||||
|
".rm": "application/vndrn-realmedia",
|
||||||
|
".rmi": "audio/mid",
|
||||||
|
".rmm": "audio/x-pn-realaudio",
|
||||||
|
".rmp": "audio/x-pn-realaudio",
|
||||||
|
".rng": "application/ringing-tones",
|
||||||
|
".rnx": "application/vndrn-realplayer",
|
||||||
|
".roff": "application/x-troff",
|
||||||
|
".rp": "image/vndrn-realpix",
|
||||||
|
".rpm": "audio/x-pn-realaudio-plugin",
|
||||||
|
".rt": "text/vndrn-realtext",
|
||||||
|
".rtf": "text/richtext",
|
||||||
|
".rtx": "text/richtext",
|
||||||
|
".rv": "video/vndrn-realvideo",
|
||||||
|
".s": "text/x-asm",
|
||||||
|
".s3m": "audio/s3m",
|
||||||
|
".s7z": "application/x-7z-compressed",
|
||||||
|
".saveme": "application/octet-stream",
|
||||||
|
".sbk": "application/x-tbook",
|
||||||
|
".scm": "text/x-scriptscheme",
|
||||||
|
".sdml": ContentTextHeaderValue,
|
||||||
|
".sdp": "application/sdp",
|
||||||
|
".sdr": "application/sounder",
|
||||||
|
".sea": "application/sea",
|
||||||
|
".set": "application/set",
|
||||||
|
".sgm": "text/x-sgml",
|
||||||
|
".sgml": "text/x-sgml",
|
||||||
|
".sh": "text/x-scriptsh",
|
||||||
|
".shar": "application/x-bsh",
|
||||||
|
".shtml": "text/x-server-parsed-html",
|
||||||
|
".sid": "audio/x-psid",
|
||||||
|
".skd": "application/x-koan",
|
||||||
|
".skm": "application/x-koan",
|
||||||
|
".skp": "application/x-koan",
|
||||||
|
".skt": "application/x-koan",
|
||||||
|
".sit": "application/x-stuffit",
|
||||||
|
".sitx": "application/x-stuffitx",
|
||||||
|
".sl": "application/x-seelogo",
|
||||||
|
".smi": "application/smil",
|
||||||
|
".smil": "application/smil",
|
||||||
|
".snd": "audio/basic",
|
||||||
|
".sol": "application/solids",
|
||||||
|
".spc": "text/x-speech",
|
||||||
|
".spl": "application/futuresplash",
|
||||||
|
".spr": "application/x-sprite",
|
||||||
|
".sprite": "application/x-sprite",
|
||||||
|
".spx": "audio/ogg",
|
||||||
|
".src": "application/x-wais-source",
|
||||||
|
".srt": "text/plain",
|
||||||
|
".sbv": "text/plain",
|
||||||
|
".ssa": "text/plain",
|
||||||
|
".ssi": "text/x-server-parsed-html",
|
||||||
|
".ssm": "application/streamingmedia",
|
||||||
|
".sst": "application/vndms-pkicertstore",
|
||||||
|
".step": "application/step",
|
||||||
|
".stl": "application/sla",
|
||||||
|
".stp": "application/step",
|
||||||
|
".sv4cpio": "application/x-sv4cpio",
|
||||||
|
".sv4crc": "application/x-sv4crc",
|
||||||
|
".svf": "image/vnddwg",
|
||||||
|
".svg": "image/svg+xml",
|
||||||
|
".svr": "application/x-world",
|
||||||
|
".swf": "application/x-shockwave-flash",
|
||||||
|
".t": "application/x-troff",
|
||||||
|
".talk": "text/x-speech",
|
||||||
|
".tar": "application/x-tar",
|
||||||
|
".tbk": "application/toolbook",
|
||||||
|
".tcl": "text/x-scripttcl",
|
||||||
|
".tcsh": "text/x-scripttcsh",
|
||||||
|
".tex": "application/x-tex",
|
||||||
|
".texi": "application/x-texinfo",
|
||||||
|
".texinfo": "application/x-texinfo",
|
||||||
|
".text": ContentTextHeaderValue,
|
||||||
|
".tgz": "application/gnutar",
|
||||||
|
".tif": "image/tiff",
|
||||||
|
".tiff": "image/tiff",
|
||||||
|
".tr": "application/x-troff",
|
||||||
|
".tsi": "audio/tsp-audio",
|
||||||
|
".tsp": "application/dsptype",
|
||||||
|
".tsv": "text/tab-separated-values",
|
||||||
|
".turbot": "image/florian",
|
||||||
|
".txt": ContentTextHeaderValue,
|
||||||
|
".uil": "text/x-uil",
|
||||||
|
".uni": "text/uri-list",
|
||||||
|
".unis": "text/uri-list",
|
||||||
|
".unv": "application/i-deas",
|
||||||
|
".uri": "text/uri-list",
|
||||||
|
".uris": "text/uri-list",
|
||||||
|
".ustar": "application/x-ustar",
|
||||||
|
".uu": "text/x-uuencode",
|
||||||
|
".uue": "text/x-uuencode",
|
||||||
|
".vcd": "application/x-cdlink",
|
||||||
|
".vcf": "text/x-vcard",
|
||||||
|
".vcard": "text/x-vcard",
|
||||||
|
".vcs": "text/x-vcalendar",
|
||||||
|
".vda": "application/vda",
|
||||||
|
".vdo": "video/vdo",
|
||||||
|
".vew": "application/groupwise",
|
||||||
|
".viv": "video/vivo",
|
||||||
|
".vivo": "video/vivo",
|
||||||
|
".vmd": "application/vocaltec-media-desc",
|
||||||
|
".vmf": "application/vocaltec-media-file",
|
||||||
|
".voc": "audio/voc",
|
||||||
|
".vos": "video/vosaic",
|
||||||
|
".vox": "audio/voxware",
|
||||||
|
".vqe": "audio/x-twinvq-plugin",
|
||||||
|
".vqf": "audio/x-twinvq",
|
||||||
|
".vql": "audio/x-twinvq-plugin",
|
||||||
|
".vrml": "application/x-vrml",
|
||||||
|
".vrt": "x-world/x-vrt",
|
||||||
|
".vsd": "application/x-visio",
|
||||||
|
".vst": "application/x-visio",
|
||||||
|
".vsw": "application/x-visio",
|
||||||
|
".w60": "application/wordperfect60",
|
||||||
|
".w61": "application/wordperfect61",
|
||||||
|
".w6w": "application/msword",
|
||||||
|
".wav": "audio/wav",
|
||||||
|
".wb1": "application/x-qpro",
|
||||||
|
".wbmp": "image/vnd.wap.wbmp",
|
||||||
|
".web": "application/vndxara",
|
||||||
|
".wiz": "application/msword",
|
||||||
|
".wk1": "application/x-123",
|
||||||
|
".wmf": "windows/metafile",
|
||||||
|
".wml": "text/vnd.wap.wml",
|
||||||
|
".wmlc": "application/vnd.wap.wmlc",
|
||||||
|
".wmls": "text/vnd.wap.wmlscript",
|
||||||
|
".wmlsc": "application/vnd.wap.wmlscriptc",
|
||||||
|
".word": "application/msword",
|
||||||
|
".wp5": "application/wordperfect",
|
||||||
|
".wp6": "application/wordperfect",
|
||||||
|
".wp": "application/wordperfect",
|
||||||
|
".wpd": "application/wordperfect",
|
||||||
|
".wq1": "application/x-lotus",
|
||||||
|
".wri": "application/mswrite",
|
||||||
|
".wrl": "application/x-world",
|
||||||
|
".wrz": "model/vrml",
|
||||||
|
".wsc": "text/scriplet",
|
||||||
|
".wsrc": "application/x-wais-source",
|
||||||
|
".wtk": "application/x-wintalk",
|
||||||
|
".x-png": "image/png",
|
||||||
|
".xbm": "image/x-xbitmap",
|
||||||
|
".xdr": "video/x-amt-demorun",
|
||||||
|
".xgz": "xgl/drawing",
|
||||||
|
".xif": "image/vndxiff",
|
||||||
|
".xl": "application/excel",
|
||||||
|
".xla": "application/excel",
|
||||||
|
".xlb": "application/excel",
|
||||||
|
".xlc": "application/excel",
|
||||||
|
".xld": "application/excel",
|
||||||
|
".xlk": "application/excel",
|
||||||
|
".xll": "application/excel",
|
||||||
|
".xlm": "application/excel",
|
||||||
|
".xls": "application/excel",
|
||||||
|
".xlt": "application/excel",
|
||||||
|
".xlv": "application/excel",
|
||||||
|
".xlw": "application/excel",
|
||||||
|
".xm": "audio/xm",
|
||||||
|
".xml": ContentXMLHeaderValue,
|
||||||
|
".xmz": "xgl/movie",
|
||||||
|
".xpix": "application/x-vndls-xpix",
|
||||||
|
".xpm": "image/x-xpixmap",
|
||||||
|
".xsr": "video/x-amt-showrun",
|
||||||
|
".xwd": "image/x-xwd",
|
||||||
|
".xyz": "chemical/x-pdb",
|
||||||
|
".z": "application/x-compress",
|
||||||
|
".zip": "application/zip",
|
||||||
|
".zoo": "application/octet-stream",
|
||||||
|
".zsh": "text/x-scriptzsh",
|
||||||
|
".docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
||||||
|
".docm": "application/vnd.ms-word.document.macroEnabled.12",
|
||||||
|
".dotx": "application/vnd.openxmlformats-officedocument.wordprocessingml.template",
|
||||||
|
".dotm": "application/vnd.ms-word.template.macroEnabled.12",
|
||||||
|
".xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
||||||
|
".xlsm": "application/vnd.ms-excel.sheet.macroEnabled.12",
|
||||||
|
".xltx": "application/vnd.openxmlformats-officedocument.spreadsheetml.template",
|
||||||
|
".xltm": "application/vnd.ms-excel.template.macroEnabled.12",
|
||||||
|
".xlsb": "application/vnd.ms-excel.sheet.binary.macroEnabled.12",
|
||||||
|
".xlam": "application/vnd.ms-excel.addin.macroEnabled.12",
|
||||||
|
".pptx": "application/vnd.openxmlformats-officedocument.presentationml.presentation",
|
||||||
|
".pptm": "application/vnd.ms-powerpoint.presentation.macroEnabled.12",
|
||||||
|
".ppsx": "application/vnd.openxmlformats-officedocument.presentationml.slideshow",
|
||||||
|
".ppsm": "application/vnd.ms-powerpoint.slideshow.macroEnabled.12",
|
||||||
|
".potx": "application/vnd.openxmlformats-officedocument.presentationml.template",
|
||||||
|
".potm": "application/vnd.ms-powerpoint.template.macroEnabled.12",
|
||||||
|
".ppam": "application/vnd.ms-powerpoint.addin.macroEnabled.12",
|
||||||
|
".sldx": "application/vnd.openxmlformats-officedocument.presentationml.slide",
|
||||||
|
".sldm": "application/vnd.ms-powerpoint.slide.macroEnabled.12",
|
||||||
|
".thmx": "application/vnd.ms-officetheme",
|
||||||
|
".onetoc": "application/onenote",
|
||||||
|
".onetoc2": "application/onenote",
|
||||||
|
".onetmp": "application/onenote",
|
||||||
|
".onepkg": "application/onenote",
|
||||||
|
".xpi": "application/x-xpinstall",
|
||||||
|
".wasm": "application/wasm",
|
||||||
|
".m4a": "audio/mp4",
|
||||||
|
".flac": "audio/x-flac",
|
||||||
|
".amr": "audio/amr",
|
||||||
|
".aac": "audio/aac",
|
||||||
|
".opus": "video/ogg",
|
||||||
|
".m4v": "video/mp4",
|
||||||
|
".mkv": "video/x-matroska",
|
||||||
|
".caf": "audio/x-caf",
|
||||||
|
".m3u8": "application/x-mpegURL",
|
||||||
|
".mpd": "application/dash+xml",
|
||||||
|
".webp": "image/webp",
|
||||||
|
".epub": "application/epub+zip",
|
||||||
|
}
|
||||||
|
|
||||||
|
//nolint:gochecknoinits
|
||||||
|
func init() {
|
||||||
|
for ext, typ := range types {
|
||||||
|
// skip errors
|
||||||
|
_ = mime.AddExtensionType(ext, typ)
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,7 +1,6 @@
|
||||||
package fileutils
|
package fileutils
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
|
@ -12,21 +11,15 @@ import (
|
||||||
// By default, the rename system call is used. If src and dst point to different volumes,
|
// By default, the rename system call is used. If src and dst point to different volumes,
|
||||||
// the file copy is used as a fallback.
|
// the file copy is used as a fallback.
|
||||||
func MoveFile(src, dst string) error {
|
func MoveFile(src, dst string) error {
|
||||||
fmt.Println("moving", src, dst)
|
|
||||||
if os.Rename(src, dst) == nil {
|
if os.Rename(src, dst) == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
fmt.Println("copyfile instead", src, dst)
|
|
||||||
|
|
||||||
// fallback
|
// fallback
|
||||||
err := CopyFile(src, dst)
|
err := CopyFile(src, dst)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("ok it errored too", err)
|
|
||||||
|
|
||||||
_ = os.Remove(dst)
|
_ = os.Remove(dst)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
fmt.Println("removing", src)
|
|
||||||
if err := os.Remove(src); err != nil {
|
if err := os.Remove(src); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,7 @@ require (
|
||||||
github.com/disintegration/imaging v1.6.2
|
github.com/disintegration/imaging v1.6.2
|
||||||
github.com/dsoprea/go-exif/v3 v3.0.1
|
github.com/dsoprea/go-exif/v3 v3.0.1
|
||||||
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568
|
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568
|
||||||
github.com/goccy/go-yaml v1.15.17
|
github.com/goccy/go-yaml v1.15.23
|
||||||
github.com/golang-jwt/jwt/v4 v4.5.1
|
github.com/golang-jwt/jwt/v4 v4.5.1
|
||||||
github.com/google/go-cmp v0.6.0
|
github.com/google/go-cmp v0.6.0
|
||||||
github.com/shirou/gopsutil/v3 v3.24.5
|
github.com/shirou/gopsutil/v3 v3.24.5
|
||||||
|
@ -43,8 +43,8 @@ require (
|
||||||
github.com/swaggo/files v1.0.1 // indirect
|
github.com/swaggo/files v1.0.1 // indirect
|
||||||
github.com/yusufpapurcu/wmi v1.2.4 // indirect
|
github.com/yusufpapurcu/wmi v1.2.4 // indirect
|
||||||
go.etcd.io/bbolt v1.4.0 // indirect
|
go.etcd.io/bbolt v1.4.0 // indirect
|
||||||
golang.org/x/net v0.34.0 // indirect
|
golang.org/x/net v0.35.0 // indirect
|
||||||
golang.org/x/tools v0.29.0 // indirect
|
golang.org/x/tools v0.30.0 // indirect
|
||||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
)
|
)
|
||||||
|
|
|
@ -46,8 +46,8 @@ github.com/go-openapi/spec v0.21.0 h1:LTVzPc3p/RzRnkQqLRndbAzjY0d0BCL72A6j3CdL9Z
|
||||||
github.com/go-openapi/spec v0.21.0/go.mod h1:78u6VdPw81XU44qEWGhtr982gJ5BWg2c0I5XwVMotYk=
|
github.com/go-openapi/spec v0.21.0/go.mod h1:78u6VdPw81XU44qEWGhtr982gJ5BWg2c0I5XwVMotYk=
|
||||||
github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE=
|
github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE=
|
||||||
github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ=
|
github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ=
|
||||||
github.com/goccy/go-yaml v1.15.17 h1:dK4FbbTTEOZTLH/NW3/xBqg0JdC14YKVmYwS9GT3H60=
|
github.com/goccy/go-yaml v1.15.23 h1:WS0GAX1uNPDLUvLkNU2vXq6oTnsmfVFocjQ/4qA48qo=
|
||||||
github.com/goccy/go-yaml v1.15.17/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
|
github.com/goccy/go-yaml v1.15.23/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
|
||||||
github.com/golang-jwt/jwt/v4 v4.5.1 h1:JdqV9zKUdtaa9gdPlywC3aeoEsR681PlKC+4F5gQgeo=
|
github.com/golang-jwt/jwt/v4 v4.5.1 h1:JdqV9zKUdtaa9gdPlywC3aeoEsR681PlKC+4F5gQgeo=
|
||||||
github.com/golang-jwt/jwt/v4 v4.5.1/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
|
github.com/golang-jwt/jwt/v4 v4.5.1/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
|
||||||
github.com/golang/geo v0.0.0-20190916061304-5b978397cfec/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI=
|
github.com/golang/geo v0.0.0-20190916061304-5b978397cfec/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI=
|
||||||
|
@ -112,8 +112,8 @@ golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+o
|
||||||
golang.org/x/image v0.24.0 h1:AN7zRgVsbvmTfNyqIbbOraYL8mSwcKncEj8ofjgzcMQ=
|
golang.org/x/image v0.24.0 h1:AN7zRgVsbvmTfNyqIbbOraYL8mSwcKncEj8ofjgzcMQ=
|
||||||
golang.org/x/image v0.24.0/go.mod h1:4b/ITuLfqYq1hqZcjofwctIhi7sZh2WaCjvsBNjjya8=
|
golang.org/x/image v0.24.0/go.mod h1:4b/ITuLfqYq1hqZcjofwctIhi7sZh2WaCjvsBNjjya8=
|
||||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||||
golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4=
|
golang.org/x/mod v0.23.0 h1:Zb7khfcRGKk+kqfxFaP5tZqCnDZMjC5VtUBs87Hr6QM=
|
||||||
golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
|
golang.org/x/mod v0.23.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
|
||||||
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20191105084925-a882066a44e0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20191105084925-a882066a44e0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
@ -125,8 +125,8 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v
|
||||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||||
golang.org/x/net v0.0.0-20221002022538-bcab6841153b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
|
golang.org/x/net v0.0.0-20221002022538-bcab6841153b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
|
||||||
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||||
golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0=
|
golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8=
|
||||||
golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k=
|
golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk=
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w=
|
golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w=
|
||||||
|
@ -160,8 +160,8 @@ golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||||
golang.org/x/tools v0.29.0 h1:Xx0h3TtM9rzQpQuR4dKLrdglAmCEN5Oi+P74JdhdzXE=
|
golang.org/x/tools v0.30.0 h1:BgcpHewrV5AUp2G9MebG4XPFI1E2W41zU1SaqVA9vJY=
|
||||||
golang.org/x/tools v0.29.0/go.mod h1:KMQVMRsVxU6nHCFXrBPhDB8XncLNLM0lIy/F14RP588=
|
golang.org/x/tools v0.30.0/go.mod h1:c347cR/OJfw5TI+GfX7RUPNMdDRRbjvYTS0jPyvsVtY=
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||||
google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
|
google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
|
||||||
|
|
|
@ -11,7 +11,7 @@ import (
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/golang-jwt/jwt/v4"
|
jwt "github.com/golang-jwt/jwt/v4"
|
||||||
"github.com/golang-jwt/jwt/v4/request"
|
"github.com/golang-jwt/jwt/v4/request"
|
||||||
"golang.org/x/crypto/bcrypt"
|
"golang.org/x/crypto/bcrypt"
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/golang-jwt/jwt/v4"
|
jwt "github.com/golang-jwt/jwt/v4"
|
||||||
"github.com/gtsteffaniak/filebrowser/backend/files"
|
"github.com/gtsteffaniak/filebrowser/backend/files"
|
||||||
"github.com/gtsteffaniak/filebrowser/backend/logger"
|
"github.com/gtsteffaniak/filebrowser/backend/logger"
|
||||||
"github.com/gtsteffaniak/filebrowser/backend/runner"
|
"github.com/gtsteffaniak/filebrowser/backend/runner"
|
||||||
|
|
|
@ -7,7 +7,7 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/asdine/storm/v3"
|
storm "github.com/asdine/storm/v3"
|
||||||
"github.com/gtsteffaniak/filebrowser/backend/diskcache"
|
"github.com/gtsteffaniak/filebrowser/backend/diskcache"
|
||||||
"github.com/gtsteffaniak/filebrowser/backend/files"
|
"github.com/gtsteffaniak/filebrowser/backend/files"
|
||||||
"github.com/gtsteffaniak/filebrowser/backend/img"
|
"github.com/gtsteffaniak/filebrowser/backend/img"
|
||||||
|
@ -50,7 +50,7 @@ func mockFileInfoFaster(t *testing.T) {
|
||||||
// Mock the function to skip execution
|
// Mock the function to skip execution
|
||||||
FileInfoFasterFunc = func(opts files.FileOptions) (files.ExtendedFileInfo, error) {
|
FileInfoFasterFunc = func(opts files.FileOptions) (files.ExtendedFileInfo, error) {
|
||||||
return files.ExtendedFileInfo{
|
return files.ExtendedFileInfo{
|
||||||
FileInfo: &files.FileInfo{
|
FileInfo: files.FileInfo{
|
||||||
Path: opts.Path,
|
Path: opts.Path,
|
||||||
ItemInfo: files.ItemInfo{
|
ItemInfo: files.ItemInfo{
|
||||||
Name: "mocked_file",
|
Name: "mocked_file",
|
||||||
|
|
|
@ -11,7 +11,7 @@ import (
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/golang-jwt/jwt/v4"
|
jwt "github.com/golang-jwt/jwt/v4"
|
||||||
"github.com/gtsteffaniak/filebrowser/backend/cache"
|
"github.com/gtsteffaniak/filebrowser/backend/cache"
|
||||||
"github.com/gtsteffaniak/filebrowser/backend/files"
|
"github.com/gtsteffaniak/filebrowser/backend/files"
|
||||||
"github.com/gtsteffaniak/filebrowser/backend/settings"
|
"github.com/gtsteffaniak/filebrowser/backend/settings"
|
||||||
|
@ -56,7 +56,7 @@ func onlyofficeClientConfigGetHandler(w http.ResponseWriter, r *http.Request, d
|
||||||
path := pathParts[len(pathParts)-1]
|
path := pathParts[len(pathParts)-1]
|
||||||
urlFirst := pathParts[0]
|
urlFirst := pathParts[0]
|
||||||
if settings.Config.Server.InternalUrl != "" {
|
if settings.Config.Server.InternalUrl != "" {
|
||||||
urlFirst = settings.Config.Server.InternalUrl
|
urlFirst = strings.TrimSuffix(settings.Config.Server.InternalUrl, "/")
|
||||||
replacement := strings.Split(url, "/api/raw")[0]
|
replacement := strings.Split(url, "/api/raw")[0]
|
||||||
url = strings.Replace(url, replacement, settings.Config.Server.InternalUrl, 1)
|
url = strings.Replace(url, replacement, settings.Config.Server.InternalUrl, 1)
|
||||||
}
|
}
|
||||||
|
|
|
@ -153,7 +153,7 @@ func previewCacheKey(realPath, previewSize string, modTime time.Time) string {
|
||||||
return fmt.Sprintf("%x%x%x", realPath, modTime.Unix(), previewSize)
|
return fmt.Sprintf("%x%x%x", realPath, modTime.Unix(), previewSize)
|
||||||
}
|
}
|
||||||
|
|
||||||
func rawFileHandler(w http.ResponseWriter, r *http.Request, file *files.FileInfo) (int, error) {
|
func rawFileHandler(w http.ResponseWriter, r *http.Request, file files.FileInfo) (int, error) {
|
||||||
idx := files.GetIndex("default")
|
idx := files.GetIndex("default")
|
||||||
realPath, _, _ := idx.GetRealPath(file.Path)
|
realPath, _, _ := idx.GetRealPath(file.Path)
|
||||||
fd, err := os.Open(realPath)
|
fd, err := os.Open(realPath)
|
||||||
|
|
|
@ -57,7 +57,7 @@ func rawHandler(w http.ResponseWriter, r *http.Request, d *requestContext) (int,
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return http.StatusBadRequest, fmt.Errorf("invalid path encoding: %v", err)
|
return http.StatusBadRequest, fmt.Errorf("invalid path encoding: %v", err)
|
||||||
}
|
}
|
||||||
fileList := strings.Split(files, ",")
|
fileList := strings.Split(files, ",|")
|
||||||
for i, f := range fileList {
|
for i, f := range fileList {
|
||||||
fileList[i] = filepath.Join(filePrefix, f)
|
fileList[i] = filepath.Join(filePrefix, f)
|
||||||
}
|
}
|
||||||
|
@ -196,6 +196,7 @@ func rawFilesHandler(w http.ResponseWriter, r *http.Request, d *requestContext,
|
||||||
// Set headers and serve the file
|
// Set headers and serve the file
|
||||||
setContentDisposition(w, r, fileName)
|
setContentDisposition(w, r, fileName)
|
||||||
w.Header().Set("Cache-Control", "private")
|
w.Header().Set("Cache-Control", "private")
|
||||||
|
w.Header().Set("X-Content-Type-Options", "nosniff")
|
||||||
|
|
||||||
// Serve the content
|
// Serve the content
|
||||||
http.ServeContent(w, r, fileName, fileInfo.ModTime(), fd)
|
http.ServeContent(w, r, fileName, fileInfo.ModTime(), fd)
|
||||||
|
|
|
@ -10,7 +10,7 @@ import (
|
||||||
"io"
|
"io"
|
||||||
|
|
||||||
"github.com/disintegration/imaging"
|
"github.com/disintegration/imaging"
|
||||||
"github.com/dsoprea/go-exif/v3"
|
exif "github.com/dsoprea/go-exif/v3"
|
||||||
|
|
||||||
exifcommon "github.com/dsoprea/go-exif/v3/common"
|
exifcommon "github.com/dsoprea/go-exif/v3/common"
|
||||||
)
|
)
|
||||||
|
|
|
@ -19,7 +19,7 @@ import (
|
||||||
"runtime"
|
"runtime"
|
||||||
"unicode"
|
"unicode"
|
||||||
|
|
||||||
"github.com/flynn/go-shlex"
|
shlex "github.com/flynn/go-shlex"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|
|
@ -25,7 +25,7 @@ func (r *Runner) RunHook(fn func() error, evt, path, dst string, user *users.Use
|
||||||
dst, _, _ = idx.GetRealPath(user.Scope, dst)
|
dst, _, _ = idx.GetRealPath(user.Scope, dst)
|
||||||
|
|
||||||
if r.Enabled {
|
if r.Enabled {
|
||||||
if val, ok := r.Commands["before_"+evt]; ok {
|
if val, ok := r.Settings.Commands["before_"+evt]; ok {
|
||||||
for _, command := range val {
|
for _, command := range val {
|
||||||
err := r.exec(command, "before_"+evt, path, dst, user)
|
err := r.exec(command, "before_"+evt, path, dst, user)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -41,7 +41,7 @@ func (r *Runner) RunHook(fn func() error, evt, path, dst string, user *users.Use
|
||||||
}
|
}
|
||||||
|
|
||||||
if r.Enabled {
|
if r.Enabled {
|
||||||
if val, ok := r.Commands["after_"+evt]; ok {
|
if val, ok := r.Settings.Commands["after_"+evt]; ok {
|
||||||
for _, command := range val {
|
for _, command := range val {
|
||||||
err := r.exec(command, "after_"+evt, path, dst, user)
|
err := r.exec(command, "after_"+evt, path, dst, user)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -7,7 +7,7 @@ import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/goccy/go-yaml"
|
yaml "github.com/goccy/go-yaml"
|
||||||
"github.com/gtsteffaniak/filebrowser/backend/logger"
|
"github.com/gtsteffaniak/filebrowser/backend/logger"
|
||||||
"github.com/gtsteffaniak/filebrowser/backend/users"
|
"github.com/gtsteffaniak/filebrowser/backend/users"
|
||||||
"github.com/gtsteffaniak/filebrowser/backend/version"
|
"github.com/gtsteffaniak/filebrowser/backend/version"
|
||||||
|
@ -155,6 +155,7 @@ func setDefaults() Settings {
|
||||||
Name: "FileBrowser Quantum",
|
Name: "FileBrowser Quantum",
|
||||||
},
|
},
|
||||||
UserDefaults: UserDefaults{
|
UserDefaults: UserDefaults{
|
||||||
|
DisableOnlyOfficeExt: ".txt .csv .html",
|
||||||
StickySidebar: true,
|
StickySidebar: true,
|
||||||
Scope: ".",
|
Scope: ".",
|
||||||
LockPassword: false,
|
LockPassword: false,
|
||||||
|
|
|
@ -4,7 +4,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/goccy/go-yaml"
|
yaml "github.com/goccy/go-yaml"
|
||||||
"github.com/google/go-cmp/cmp"
|
"github.com/google/go-cmp/cmp"
|
||||||
"github.com/gtsteffaniak/filebrowser/backend/logger"
|
"github.com/gtsteffaniak/filebrowser/backend/logger"
|
||||||
)
|
)
|
||||||
|
|
|
@ -163,4 +163,5 @@ type UserDefaults struct {
|
||||||
DateFormat bool `json:"dateFormat"`
|
DateFormat bool `json:"dateFormat"`
|
||||||
ThemeColor string `json:"themeColor"`
|
ThemeColor string `json:"themeColor"`
|
||||||
QuickDownload bool `json:"quickDownload"`
|
QuickDownload bool `json:"quickDownload"`
|
||||||
|
DisableOnlyOfficeExt string `json:"disableOnlyOfficeExt"`
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
package bolt
|
package bolt
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/asdine/storm/v3"
|
storm "github.com/asdine/storm/v3"
|
||||||
"github.com/gtsteffaniak/filebrowser/backend/auth"
|
"github.com/gtsteffaniak/filebrowser/backend/auth"
|
||||||
"github.com/gtsteffaniak/filebrowser/backend/errors"
|
"github.com/gtsteffaniak/filebrowser/backend/errors"
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
package bolt
|
package bolt
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/asdine/storm/v3"
|
storm "github.com/asdine/storm/v3"
|
||||||
|
|
||||||
"github.com/gtsteffaniak/filebrowser/backend/auth"
|
"github.com/gtsteffaniak/filebrowser/backend/auth"
|
||||||
"github.com/gtsteffaniak/filebrowser/backend/settings"
|
"github.com/gtsteffaniak/filebrowser/backend/settings"
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
package bolt
|
package bolt
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/asdine/storm/v3"
|
storm "github.com/asdine/storm/v3"
|
||||||
"github.com/gtsteffaniak/filebrowser/backend/settings"
|
"github.com/gtsteffaniak/filebrowser/backend/settings"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/asdine/storm/v3"
|
storm "github.com/asdine/storm/v3"
|
||||||
"github.com/asdine/storm/v3/q"
|
"github.com/asdine/storm/v3/q"
|
||||||
|
|
||||||
"github.com/gtsteffaniak/filebrowser/backend/errors"
|
"github.com/gtsteffaniak/filebrowser/backend/errors"
|
||||||
|
|
|
@ -4,7 +4,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
|
||||||
"github.com/asdine/storm/v3"
|
storm "github.com/asdine/storm/v3"
|
||||||
|
|
||||||
"github.com/gtsteffaniak/filebrowser/backend/errors"
|
"github.com/gtsteffaniak/filebrowser/backend/errors"
|
||||||
"github.com/gtsteffaniak/filebrowser/backend/settings"
|
"github.com/gtsteffaniak/filebrowser/backend/settings"
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
package bolt
|
package bolt
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/asdine/storm/v3"
|
storm "github.com/asdine/storm/v3"
|
||||||
|
|
||||||
"github.com/gtsteffaniak/filebrowser/backend/errors"
|
"github.com/gtsteffaniak/filebrowser/backend/errors"
|
||||||
)
|
)
|
||||||
|
|
|
@ -5,7 +5,7 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
"github.com/asdine/storm/v3"
|
storm "github.com/asdine/storm/v3"
|
||||||
"github.com/gtsteffaniak/filebrowser/backend/auth"
|
"github.com/gtsteffaniak/filebrowser/backend/auth"
|
||||||
"github.com/gtsteffaniak/filebrowser/backend/errors"
|
"github.com/gtsteffaniak/filebrowser/backend/errors"
|
||||||
"github.com/gtsteffaniak/filebrowser/backend/files"
|
"github.com/gtsteffaniak/filebrowser/backend/files"
|
||||||
|
|
|
@ -1364,6 +1364,9 @@ const docTemplate = `{
|
||||||
"dateFormat": {
|
"dateFormat": {
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
},
|
},
|
||||||
|
"disableOnlyOfficeExt": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
"disableSettings": {
|
"disableSettings": {
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
},
|
},
|
||||||
|
@ -1575,6 +1578,9 @@ const docTemplate = `{
|
||||||
"dateFormat": {
|
"dateFormat": {
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
},
|
},
|
||||||
|
"disableOnlyOfficeExt": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
"disableSettings": {
|
"disableSettings": {
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
},
|
},
|
||||||
|
|
|
@ -1353,6 +1353,9 @@
|
||||||
"dateFormat": {
|
"dateFormat": {
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
},
|
},
|
||||||
|
"disableOnlyOfficeExt": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
"disableSettings": {
|
"disableSettings": {
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
},
|
},
|
||||||
|
@ -1564,6 +1567,9 @@
|
||||||
"dateFormat": {
|
"dateFormat": {
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
},
|
},
|
||||||
|
"disableOnlyOfficeExt": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
"disableSettings": {
|
"disableSettings": {
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
},
|
},
|
||||||
|
|
|
@ -132,6 +132,8 @@ definitions:
|
||||||
type: boolean
|
type: boolean
|
||||||
dateFormat:
|
dateFormat:
|
||||||
type: boolean
|
type: boolean
|
||||||
|
disableOnlyOfficeExt:
|
||||||
|
type: string
|
||||||
disableSettings:
|
disableSettings:
|
||||||
type: boolean
|
type: boolean
|
||||||
gallerySize:
|
gallerySize:
|
||||||
|
@ -273,6 +275,8 @@ definitions:
|
||||||
type: boolean
|
type: boolean
|
||||||
dateFormat:
|
dateFormat:
|
||||||
type: boolean
|
type: boolean
|
||||||
|
disableOnlyOfficeExt:
|
||||||
|
type: string
|
||||||
disableSettings:
|
disableSettings:
|
||||||
type: boolean
|
type: boolean
|
||||||
gallerySize:
|
gallerySize:
|
||||||
|
|
|
@ -3,7 +3,7 @@ package users
|
||||||
import (
|
import (
|
||||||
"regexp"
|
"regexp"
|
||||||
|
|
||||||
"github.com/golang-jwt/jwt/v4"
|
jwt "github.com/golang-jwt/jwt/v4"
|
||||||
)
|
)
|
||||||
|
|
||||||
type AuthToken struct {
|
type AuthToken struct {
|
||||||
|
@ -57,12 +57,13 @@ type User struct {
|
||||||
GallerySize int `json:"gallerySize"`
|
GallerySize int `json:"gallerySize"`
|
||||||
ThemeColor string `json:"themeColor"`
|
ThemeColor string `json:"themeColor"`
|
||||||
QuickDownload bool `json:"quickDownload"`
|
QuickDownload bool `json:"quickDownload"`
|
||||||
|
DisableOnlyOfficeExt string `json:"disableOnlyOfficeExt"`
|
||||||
}
|
}
|
||||||
|
|
||||||
var PublicUser = User{
|
var PublicUser = User{
|
||||||
Username: "publicUser", // temp user not registered
|
Username: "publicUser", // temp user not registered
|
||||||
Password: "publicUser", // temp user not registered
|
Password: "publicUser", // temp user not registered
|
||||||
Scope: "./",
|
Scope: "/does/not/exist",
|
||||||
ViewMode: "normal",
|
ViewMode: "normal",
|
||||||
LockPassword: true,
|
LockPassword: true,
|
||||||
Perm: Permissions{
|
Perm: Permissions{
|
||||||
|
|
|
@ -22,6 +22,7 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@onlyoffice/document-editor-vue": "^1.4.0",
|
"@onlyoffice/document-editor-vue": "^1.4.0",
|
||||||
"ace-builds": "^1.24.2",
|
"ace-builds": "^1.24.2",
|
||||||
|
"axios": "^1.7.9",
|
||||||
"clipboard": "^2.0.4",
|
"clipboard": "^2.0.4",
|
||||||
"css-vars-ponyfill": "^2.4.3",
|
"css-vars-ponyfill": "^2.4.3",
|
||||||
"dompurify": "^3.2.4",
|
"dompurify": "^3.2.4",
|
||||||
|
@ -29,6 +30,7 @@
|
||||||
"marked": "^15.0.6",
|
"marked": "^15.0.6",
|
||||||
"normalize.css": "^8.0.1",
|
"normalize.css": "^8.0.1",
|
||||||
"qrcode.vue": "^3.4.1",
|
"qrcode.vue": "^3.4.1",
|
||||||
|
"srt-support-for-html5-videos": "^2.6.11",
|
||||||
"vue": "^3.4.21",
|
"vue": "^3.4.21",
|
||||||
"vue-i18n": "^9.10.2",
|
"vue-i18n": "^9.10.2",
|
||||||
"vue-lazyload": "^3.0.0",
|
"vue-lazyload": "^3.0.0",
|
||||||
|
|
|
@ -1,243 +1,244 @@
|
||||||
import { fetchURL, adjustedData } from "./utils";
|
import { fetchURL, adjustedData } from './utils'
|
||||||
import { removePrefix, getApiPath } from "@/utils/url.js";
|
import { removePrefix, getApiPath } from '@/utils/url.js'
|
||||||
import { state } from "@/store";
|
import { state } from '@/store'
|
||||||
import { notify } from "@/notify";
|
import { notify } from '@/notify'
|
||||||
import { externalUrl } from "@/utils/constants";
|
import { externalUrl } from '@/utils/constants'
|
||||||
|
|
||||||
// Notify if errors occur
|
// Notify if errors occur
|
||||||
export async function fetchFiles(url, content = false) {
|
export async function fetchFiles (url, content = false) {
|
||||||
try {
|
try {
|
||||||
let path = encodeURIComponent(removePrefix(url, "files"));
|
let path = encodeURIComponent(removePrefix(url, 'files'))
|
||||||
const apiPath = getApiPath("api/resources",{path: path, content: content});
|
const apiPath = getApiPath('api/resources', {
|
||||||
const res = await fetchURL(apiPath);
|
path: path,
|
||||||
const data = await res.json();
|
content: content
|
||||||
const adjusted = adjustedData(data, url);
|
})
|
||||||
return adjusted;
|
const res = await fetchURL(apiPath)
|
||||||
|
const data = await res.json()
|
||||||
|
const adjusted = adjustedData(data, url)
|
||||||
|
return adjusted
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
notify.showError(err.message || "Error fetching data");
|
notify.showError(err.message || 'Error fetching data')
|
||||||
throw err;
|
throw err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function resourceAction(url, method, content) {
|
async function resourceAction (url, method, content) {
|
||||||
try {
|
try {
|
||||||
let opts = { method };
|
let opts = { method }
|
||||||
if (content) {
|
if (content) {
|
||||||
opts.body = content;
|
opts.body = content
|
||||||
}
|
}
|
||||||
let path = encodeURIComponent(removePrefix(url, "files"));
|
let path = encodeURIComponent(removePrefix(url, 'files'))
|
||||||
const apiPath = getApiPath("api/resources", { path: path });
|
const apiPath = getApiPath('api/resources', { path: path })
|
||||||
const res = await fetchURL(apiPath, opts);
|
const res = await fetchURL(apiPath, opts)
|
||||||
return res;
|
return res
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
notify.showError(err.message || "Error performing resource action");
|
notify.showError(err.message || 'Error performing resource action')
|
||||||
throw err;
|
throw err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function remove(url) {
|
export async function remove (url) {
|
||||||
try {
|
try {
|
||||||
let path = encodeURIComponent(removePrefix(url, "files"));
|
let path = encodeURIComponent(removePrefix(url, 'files'))
|
||||||
return await resourceAction(path, "DELETE");
|
return await resourceAction(path, 'DELETE')
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
notify.showError(err.message || "Error deleting resource");
|
notify.showError(err.message || 'Error deleting resource')
|
||||||
throw err;
|
throw err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function put(url, content = "") {
|
export async function put (url, content = '') {
|
||||||
try {
|
try {
|
||||||
let path = encodeURIComponent(removePrefix(url, "files"));
|
let path = encodeURIComponent(removePrefix(url, 'files'))
|
||||||
return await resourceAction(path, "PUT", content);
|
return await resourceAction(path, 'PUT', content)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
notify.showError(err.message || "Error putting resource");
|
notify.showError(err.message || 'Error putting resource')
|
||||||
throw err;
|
throw err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function download(format, files) {
|
export function download (format, files) {
|
||||||
if (format != "zip") {
|
if (format != 'zip') {
|
||||||
format = "tar.gz"
|
format = 'tar.gz'
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
let fileargs = "";
|
let fileargs = ''
|
||||||
if (files.length === 1) {
|
if (files.length === 1) {
|
||||||
fileargs = decodeURI(removePrefix(files[0], "files"))
|
fileargs = decodeURI(removePrefix(files[0], 'files'))
|
||||||
} else {
|
} else {
|
||||||
for (let file of files) {
|
for (let file of files) {
|
||||||
fileargs += decodeURI(removePrefix(file,"files")) + ",";
|
fileargs += decodeURI(removePrefix(file, 'files')) + ',|'
|
||||||
}
|
}
|
||||||
fileargs = fileargs.substring(0, fileargs.length - 1);
|
fileargs = fileargs.substring(0, fileargs.length - 1)
|
||||||
}
|
}
|
||||||
const apiPath = getApiPath("api/raw", { files: encodeURIComponent(fileargs), algo: format });
|
const apiPath = getApiPath('api/raw', {
|
||||||
const url = window.origin+apiPath
|
files: encodeURIComponent(fileargs),
|
||||||
window.open(url);
|
algo: format
|
||||||
|
})
|
||||||
|
const url = window.origin + apiPath
|
||||||
|
window.open(url)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
notify.showError(err.message || "Error downloading files");
|
notify.showError(err.message || 'Error downloading files')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function post(url, content = "", overwrite = false, onupload) {
|
export async function post (url, content = '', overwrite = false, onupload) {
|
||||||
try {
|
try {
|
||||||
url = removePrefix(url, "files");
|
url = removePrefix(url, 'files')
|
||||||
|
|
||||||
let bufferContent;
|
let bufferContent
|
||||||
if (
|
if (
|
||||||
content instanceof Blob &&
|
content instanceof Blob &&
|
||||||
!["http:", "https:"].includes(window.location.protocol)
|
!['http:', 'https:'].includes(window.location.protocol)
|
||||||
) {
|
) {
|
||||||
bufferContent = await new Response(content).arrayBuffer();
|
bufferContent = await new Response(content).arrayBuffer()
|
||||||
}
|
}
|
||||||
|
|
||||||
const apiPath = getApiPath("api/resources", { path: url, override: overwrite });
|
const apiPath = getApiPath('api/resources', {
|
||||||
|
path: url,
|
||||||
|
override: overwrite
|
||||||
|
})
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
let request = new XMLHttpRequest();
|
let request = new XMLHttpRequest()
|
||||||
request.open(
|
request.open('POST', apiPath, true)
|
||||||
"POST",
|
request.setRequestHeader('X-Auth', state.jwt)
|
||||||
apiPath,
|
|
||||||
true
|
|
||||||
);
|
|
||||||
request.setRequestHeader("X-Auth", state.jwt);
|
|
||||||
|
|
||||||
if (typeof onupload === "function") {
|
if (typeof onupload === 'function') {
|
||||||
request.upload.onprogress = (event) => {
|
request.upload.onprogress = event => {
|
||||||
if (event.lengthComputable) {
|
if (event.lengthComputable) {
|
||||||
const percentComplete = Math.round((event.loaded / event.total) * 100);
|
const percentComplete = Math.round(
|
||||||
onupload(percentComplete); // Pass the percentage to the callback
|
(event.loaded / event.total) * 100
|
||||||
|
)
|
||||||
|
onupload(percentComplete) // Pass the percentage to the callback
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
request.onload = () => {
|
request.onload = () => {
|
||||||
if (request.status === 200) {
|
if (request.status === 200) {
|
||||||
resolve(request.responseText);
|
resolve(request.responseText)
|
||||||
} else if (request.status === 409) {
|
} else if (request.status === 409) {
|
||||||
reject(request.status);
|
reject(request.status)
|
||||||
} else {
|
} else {
|
||||||
reject(request.responseText);
|
reject(request.responseText)
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
request.send(bufferContent || content)
|
||||||
request.onerror = () => {
|
})
|
||||||
reject(new Error("001 Connection aborted"));
|
|
||||||
};
|
|
||||||
|
|
||||||
request.send(bufferContent || content);
|
|
||||||
});
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
notify.showError(err.message || "Error posting resource");
|
notify.showError(err.message || 'Error posting resource')
|
||||||
throw err;
|
throw err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function moveCopy(items, action = "copy", overwrite = false, rename = false) {
|
export async function moveCopy (
|
||||||
|
items,
|
||||||
|
action = 'copy',
|
||||||
|
overwrite = false,
|
||||||
|
rename = false
|
||||||
|
) {
|
||||||
let params = {
|
let params = {
|
||||||
overwrite: overwrite,
|
overwrite: overwrite,
|
||||||
action: action,
|
action: action,
|
||||||
rename: rename,
|
rename: rename
|
||||||
};
|
}
|
||||||
try {
|
try {
|
||||||
// Create an array of fetch calls
|
// Create an array of fetch calls
|
||||||
let promises = items.map((item) => {
|
let promises = items.map(item => {
|
||||||
let toPath = encodeURIComponent(removePrefix(decodeURI(item.to), "files"));
|
let toPath = encodeURIComponent(removePrefix(decodeURI(item.to), 'files'))
|
||||||
let fromPath = encodeURIComponent(removePrefix(decodeURI(item.from), "files"));
|
let fromPath = encodeURIComponent(
|
||||||
let localParams = { ...params, destination: toPath, from: fromPath };
|
removePrefix(decodeURI(item.from), 'files')
|
||||||
const apiPath = getApiPath("api/resources", localParams);
|
)
|
||||||
return fetch(apiPath, { method: "PATCH" }).then((response) => {
|
let localParams = { ...params, destination: toPath, from: fromPath }
|
||||||
|
const apiPath = getApiPath('api/resources', localParams)
|
||||||
|
return fetch(apiPath, { method: 'PATCH' }).then(response => {
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
// Throw an error if the fetch fails
|
// Throw an error if the fetch fails
|
||||||
return response.text().then((text) => {
|
return response.text().then(text => {
|
||||||
throw new Error(`Failed to move/copy: ${text || response.statusText}`);
|
throw new Error(
|
||||||
});
|
`Failed to move/copy: ${text || response.statusText}`
|
||||||
|
)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
return response;
|
return response
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
|
|
||||||
// Await all promises and ensure errors propagate
|
// Await all promises and ensure errors propagate
|
||||||
await Promise.all(promises);
|
await Promise.all(promises)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
notify.showError(err.message || "Error moving/copying resources");
|
notify.showError(err.message || 'Error moving/copying resources')
|
||||||
throw err; // Re-throw the error to propagate it back to the caller
|
throw err // Re-throw the error to propagate it back to the caller
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function checksum (path, algo) {
|
||||||
export async function checksum(path, algo) {
|
|
||||||
try {
|
try {
|
||||||
const params = {
|
const params = {
|
||||||
path: encodeURIComponent(removePrefix(path, "files")),
|
path: encodeURIComponent(removePrefix(path, 'files')),
|
||||||
checksum: algo,
|
checksum: algo
|
||||||
};
|
}
|
||||||
const apiPath = getApiPath("api/resources", params);
|
const apiPath = getApiPath('api/resources', params)
|
||||||
const res = await fetchURL(apiPath);
|
const res = await fetchURL(apiPath)
|
||||||
const data = await res.json();
|
const data = await res.json()
|
||||||
return data.checksums[algo];
|
return data.checksums[algo]
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
notify.showError(err.message || "Error fetching checksum");
|
notify.showError(err.message || 'Error fetching checksum')
|
||||||
throw err;
|
throw err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getDownloadURL(path, inline, useExternal) {
|
export function getDownloadURL (path, inline, useExternal) {
|
||||||
try {
|
try {
|
||||||
const params = {
|
const params = {
|
||||||
files: encodeURIComponent(removePrefix(decodeURI(path),"files")),
|
files: encodeURIComponent(removePrefix(decodeURI(path), 'files')),
|
||||||
...(inline && { inline: "true" }),
|
...(inline && { inline: 'true' })
|
||||||
};
|
}
|
||||||
const apiPath = getApiPath("api/raw", params);
|
const apiPath = getApiPath('api/raw', params)
|
||||||
if (externalUrl && useExternal) {
|
if (externalUrl && useExternal) {
|
||||||
return externalUrl+apiPath
|
return externalUrl + apiPath
|
||||||
}
|
}
|
||||||
return window.origin+apiPath
|
return window.origin + apiPath
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
notify.showError(err.message || "Error getting download URL");
|
notify.showError(err.message || 'Error getting download URL')
|
||||||
throw err;
|
throw err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getPreviewURL(path, size, modified) {
|
export function getPreviewURL (path, size, modified) {
|
||||||
try {
|
try {
|
||||||
const params = {
|
const params = {
|
||||||
path: encodeURIComponent(removePrefix(decodeURI(path),"files")),
|
path: encodeURIComponent(removePrefix(decodeURI(path), 'files')),
|
||||||
size: size,
|
size: size,
|
||||||
key: Date.parse(modified),
|
key: Date.parse(modified),
|
||||||
inline: "true",
|
inline: 'true'
|
||||||
};
|
}
|
||||||
const apiPath = getApiPath("api/preview", params);
|
const apiPath = getApiPath('api/preview', params)
|
||||||
return window.origin+apiPath
|
return window.origin + apiPath
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
notify.showError(err.message || "Error getting preview URL");
|
notify.showError(err.message || 'Error getting preview URL')
|
||||||
throw err;
|
throw err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getSubtitlesURL(file) {
|
export function getSubtitlesURL (path) {
|
||||||
try {
|
|
||||||
const subtitles = [];
|
|
||||||
for (const sub of file.subtitles) {
|
|
||||||
const params = {
|
const params = {
|
||||||
inline: "true",
|
inline: true,
|
||||||
path: encodeURIComponent(removePrefix(sub,"files"))
|
files: path,
|
||||||
};
|
source: 'default'
|
||||||
const apiPath = getApiPath("api/raw", params);
|
|
||||||
return window.origin+apiPath
|
|
||||||
}
|
|
||||||
|
|
||||||
return subtitles;
|
|
||||||
} catch (err) {
|
|
||||||
notify.showError(err.message || "Error fetching subtitles URL");
|
|
||||||
throw err;
|
|
||||||
}
|
}
|
||||||
|
const apiPath = getApiPath('api/raw', params)
|
||||||
|
return window.origin + apiPath
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function usage(source) {
|
export async function usage (source) {
|
||||||
try {
|
try {
|
||||||
const apiPath = getApiPath("api/usage", { source: source });
|
const apiPath = getApiPath('api/usage', { source: source })
|
||||||
const res = await fetchURL(apiPath);
|
const res = await fetchURL(apiPath)
|
||||||
return await res.json();
|
return await res.json()
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
notify.showError(err.message || "Error fetching usage data");
|
notify.showError(err.message || 'Error fetching usage data')
|
||||||
throw err;
|
throw err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -68,7 +68,12 @@ export function adjustedData(data, url) {
|
||||||
data.items = [...(data.folders || []), ...(data.files || [])];
|
data.items = [...(data.folders || []), ...(data.files || [])];
|
||||||
|
|
||||||
data.items = data.items.map((item) => {
|
data.items = data.items.map((item) => {
|
||||||
item.url = `${data.url}${item.name}`;
|
item.url = `${data.url}${encodeURIComponent(item.name)}`;
|
||||||
|
if (data.path == "/") {
|
||||||
|
item.path = `/${item.name}`
|
||||||
|
} else {
|
||||||
|
item.path = `${data.path}/${item.name}`
|
||||||
|
}
|
||||||
if (item.type === "directory") {
|
if (item.type === "directory") {
|
||||||
item.url += "/";
|
item.url += "/";
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,21 +13,23 @@ describe('adjustedData', () => {
|
||||||
{ name: "file1.txt", type: "file" },
|
{ name: "file1.txt", type: "file" },
|
||||||
{ name: "file2.txt", type: "file" },
|
{ name: "file2.txt", type: "file" },
|
||||||
],
|
],
|
||||||
|
path: "/root"
|
||||||
};
|
};
|
||||||
|
|
||||||
const url = "http://example.com/unit-testing/files/path/to/directory";
|
const url = "http://example.com/root/";
|
||||||
|
|
||||||
const expected = {
|
const expected = {
|
||||||
type: "directory",
|
type: "directory",
|
||||||
url: "http://example.com/unit-testing/files/path/to/directory/",
|
url: "http://example.com/root/",
|
||||||
folders: [],
|
folders: [],
|
||||||
files: [],
|
files: [],
|
||||||
items: [
|
items: [
|
||||||
{ name: "folder1", type: "directory", url: "http://example.com/unit-testing/files/path/to/directory/folder1/" },
|
{ name: "folder1", path: "/root/folder1", type: "directory", url: "http://example.com/root/folder1/" },
|
||||||
{ name: "folder2", type: "directory", url: "http://example.com/unit-testing/files/path/to/directory/folder2/" },
|
{ name: "folder2", path: "/root/folder2", type: "directory", url: "http://example.com/root/folder2/" },
|
||||||
{ name: "file1.txt", type: "file", url: "http://example.com/unit-testing/files/path/to/directory/file1.txt" },
|
{ name: "file1.txt", path: "/root/file1.txt", type: "file", url: "http://example.com/root/file1.txt" },
|
||||||
{ name: "file2.txt", type: "file", url: "http://example.com/unit-testing/files/path/to/directory/file2.txt" },
|
{ name: "file2.txt", path: "/root/file2.txt", type: "file", url: "http://example.com/root/file2.txt" },
|
||||||
],
|
],
|
||||||
|
path: "/root",
|
||||||
};
|
};
|
||||||
|
|
||||||
expect(adjustedData(input, url)).toEqual(expected);
|
expect(adjustedData(input, url)).toEqual(expected);
|
||||||
|
|
|
@ -13,27 +13,36 @@
|
||||||
<div v-if="selectedCount > 0" class="button selected-count-header">
|
<div v-if="selectedCount > 0" class="button selected-count-header">
|
||||||
<span>{{ selectedCount }} selected</span>
|
<span>{{ selectedCount }} selected</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<action
|
<action
|
||||||
v-if="!isSearchActive"
|
v-if="!showCreate"
|
||||||
|
icon="add"
|
||||||
|
label="New"
|
||||||
|
@action="startShowCreate"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<action
|
||||||
|
v-if="showCreate && !isSearchActive"
|
||||||
icon="create_new_folder"
|
icon="create_new_folder"
|
||||||
:label="$t('sidebar.newFolder')"
|
:label="$t('sidebar.newFolder')"
|
||||||
@action="showHover('newDir')"
|
@action="showHover('newDir')"
|
||||||
/>
|
/>
|
||||||
<action
|
<action
|
||||||
v-if="!headerButtons.select && !isSearchActive"
|
v-if="showCreate && !isSearchActive"
|
||||||
icon="note_add"
|
icon="note_add"
|
||||||
:label="$t('sidebar.newFile')"
|
:label="$t('sidebar.newFile')"
|
||||||
@action="showHover('newFile')"
|
@action="showHover('newFile')"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<action
|
<action
|
||||||
v-if="!headerButtons.select && !isSearchActive"
|
v-if="showCreate && !isSearchActive"
|
||||||
icon="file_upload"
|
icon="file_upload"
|
||||||
:label="$t('buttons.upload')"
|
:label="$t('buttons.upload')"
|
||||||
@action="uploadFunc"
|
@action="uploadFunc"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<action
|
<action
|
||||||
v-if="headerButtons.select"
|
v-if="!showCreate && headerButtons.select"
|
||||||
icon="info"
|
icon="info"
|
||||||
:label="$t('buttons.info')"
|
:label="$t('buttons.info')"
|
||||||
show="info"
|
show="info"
|
||||||
|
@ -45,38 +54,38 @@
|
||||||
@action="toggleMultipleSelection"
|
@action="toggleMultipleSelection"
|
||||||
/>
|
/>
|
||||||
<action
|
<action
|
||||||
v-if="headerButtons.download"
|
v-if="!showCreate && headerButtons.download"
|
||||||
icon="file_download"
|
icon="file_download"
|
||||||
:label="$t('buttons.download')"
|
:label="$t('buttons.download')"
|
||||||
@action="startDownload"
|
@action="startDownload"
|
||||||
:counter="selectedCount"
|
:counter="selectedCount"
|
||||||
/>
|
/>
|
||||||
<action
|
<action
|
||||||
v-if="headerButtons.share"
|
v-if="!showCreate && headerButtons.share"
|
||||||
icon="share"
|
icon="share"
|
||||||
:label="$t('buttons.share')"
|
:label="$t('buttons.share')"
|
||||||
show="share"
|
show="share"
|
||||||
/>
|
/>
|
||||||
<action
|
<action
|
||||||
v-if="headerButtons.rename && !isSearchActive"
|
v-if="!showCreate && headerButtons.rename && !isSearchActive"
|
||||||
icon="mode_edit"
|
icon="mode_edit"
|
||||||
:label="$t('buttons.rename')"
|
:label="$t('buttons.rename')"
|
||||||
show="rename"
|
show="rename"
|
||||||
/>
|
/>
|
||||||
<action
|
<action
|
||||||
v-if="headerButtons.copy"
|
v-if="!showCreate && headerButtons.copy"
|
||||||
icon="content_copy"
|
icon="content_copy"
|
||||||
:label="$t('buttons.copyFile')"
|
:label="$t('buttons.copyFile')"
|
||||||
show="copy"
|
show="copy"
|
||||||
/>
|
/>
|
||||||
<action
|
<action
|
||||||
v-if="headerButtons.move"
|
v-if="!showCreate && headerButtons.move"
|
||||||
icon="forward"
|
icon="forward"
|
||||||
:label="$t('buttons.moveFile')"
|
:label="$t('buttons.moveFile')"
|
||||||
show="move"
|
show="move"
|
||||||
/>
|
/>
|
||||||
<action
|
<action
|
||||||
v-if="headerButtons.delete"
|
v-if="!showCreate && headerButtons.delete"
|
||||||
icon="delete"
|
icon="delete"
|
||||||
:label="$t('buttons.delete')"
|
:label="$t('buttons.delete')"
|
||||||
show="delete"
|
show="delete"
|
||||||
|
@ -88,6 +97,8 @@
|
||||||
import downloadFiles from "@/utils/download";
|
import downloadFiles from "@/utils/download";
|
||||||
import { state, getters, mutations } from "@/store"; // Import your custom store
|
import { state, getters, mutations } from "@/store"; // Import your custom store
|
||||||
import Action from "@/components/Action.vue";
|
import Action from "@/components/Action.vue";
|
||||||
|
import { onlyOfficeUrl } from "@/utils/constants.js";
|
||||||
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "ContextMenu",
|
name: "ContextMenu",
|
||||||
|
@ -98,9 +109,20 @@ export default {
|
||||||
return {
|
return {
|
||||||
posX: 0,
|
posX: 0,
|
||||||
posY: 0,
|
posY: 0,
|
||||||
|
showCreate: false,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
showContext() {
|
||||||
|
if (getters.currentPromptName() == "ContextMenu" && state.prompts != []) {
|
||||||
|
this.setPositions();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
onlyofficeEnabled() {
|
||||||
|
return onlyOfficeUrl !== "";
|
||||||
|
},
|
||||||
isSearchActive() {
|
isSearchActive() {
|
||||||
return state.isSearchActive;
|
return state.isSearchActive;
|
||||||
},
|
},
|
||||||
|
@ -113,13 +135,6 @@ export default {
|
||||||
centered() {
|
centered() {
|
||||||
return getters.isMobile() || !this.posX || !this.posY;
|
return getters.isMobile() || !this.posX || !this.posY;
|
||||||
},
|
},
|
||||||
showContext() {
|
|
||||||
if (getters.currentPromptName() == "ContextMenu" && state.prompts != []) {
|
|
||||||
this.setPositions();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
},
|
|
||||||
top() {
|
top() {
|
||||||
// Ensure the context menu stays within the viewport
|
// Ensure the context menu stays within the viewport
|
||||||
return Math.min(
|
return Math.min(
|
||||||
|
@ -153,6 +168,9 @@ export default {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
startShowCreate() {
|
||||||
|
this.showCreate = true;
|
||||||
|
},
|
||||||
uploadFunc() {
|
uploadFunc() {
|
||||||
mutations.showHover("upload");
|
mutations.showHover("upload");
|
||||||
},
|
},
|
||||||
|
@ -160,6 +178,11 @@ export default {
|
||||||
return mutations.showHover(value);
|
return mutations.showHover(value);
|
||||||
},
|
},
|
||||||
setPositions() {
|
setPositions() {
|
||||||
|
if (state.selected.length > 0) {
|
||||||
|
this.showCreate = false;
|
||||||
|
} else {
|
||||||
|
this.showCreate = true;
|
||||||
|
}
|
||||||
const contextProps = getters.currentPrompt().props;
|
const contextProps = getters.currentPrompt().props;
|
||||||
let tempX = contextProps.posX;
|
let tempX = contextProps.posX;
|
||||||
let tempY = contextProps.posY;
|
let tempY = contextProps.posY;
|
||||||
|
|
|
@ -35,7 +35,7 @@
|
||||||
|
|
||||||
<!-- Search results for desktop -->
|
<!-- Search results for desktop -->
|
||||||
<div v-show="active" id="results" ref="result">
|
<div v-show="active" id="results" ref="result">
|
||||||
<div class="searchContext">Search Context: {{ getContext }}</div>
|
<div aria-label="search-path" class="searchContext">Search Context: {{ getContext }}</div>
|
||||||
<div id="result-list">
|
<div id="result-list">
|
||||||
<div>
|
<div>
|
||||||
<div v-if="active">
|
<div v-if="active">
|
||||||
|
|
|
@ -76,14 +76,14 @@ export default {
|
||||||
if (state.isSearchActive) {
|
if (state.isSearchActive) {
|
||||||
this.items = [
|
this.items = [
|
||||||
{
|
{
|
||||||
from: "/files" + state.selected[0].url,
|
from: "/files" + state.selected[0].path,
|
||||||
name: state.selected[0].name,
|
name: state.selected[0].name,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
} else {
|
} else {
|
||||||
for (let item of state.selected) {
|
for (let item of state.selected) {
|
||||||
this.items.push({
|
this.items.push({
|
||||||
from: state.req.items[item].url,
|
from: state.req.items[item].path,
|
||||||
// add to: dest
|
// add to: dest
|
||||||
name: state.req.items[item].name,
|
name: state.req.items[item].name,
|
||||||
});
|
});
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
<p v-else>
|
<p v-else>
|
||||||
{{ $t("prompts.deleteMessageMultiple", { count: selectedCount }) }}
|
{{ $t("prompts.deleteMessageMultiple", { count: selectedCount }) }}
|
||||||
</p>
|
</p>
|
||||||
<div style="display: grid" class="searchContext">
|
<div style="display: grid" aria-label="delete-path" class="searchContext">
|
||||||
<span v-for="(item, index) in nav" :key="index"> {{ item }} </span>
|
<span v-for="(item, index) in nav" :key="index"> {{ item }} </span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -23,7 +23,7 @@
|
||||||
<button
|
<button
|
||||||
@click="submit"
|
@click="submit"
|
||||||
class="button button--flat button--red"
|
class="button button--flat button--red"
|
||||||
:aria-label="$t('buttons.delete')"
|
aria-label="Confirm-Delete"
|
||||||
:title="$t('buttons.delete')"
|
:title="$t('buttons.delete')"
|
||||||
>
|
>
|
||||||
{{ $t("buttons.delete") }}
|
{{ $t("buttons.delete") }}
|
||||||
|
@ -37,7 +37,6 @@ import { filesApi } from "@/api";
|
||||||
import buttons from "@/utils/buttons";
|
import buttons from "@/utils/buttons";
|
||||||
import { state, getters, mutations } from "@/store";
|
import { state, getters, mutations } from "@/store";
|
||||||
import { notify } from "@/notify";
|
import { notify } from "@/notify";
|
||||||
import { removePrefix } from "@/utils/url";
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "delete",
|
name: "delete",
|
||||||
|
@ -57,7 +56,7 @@ export default {
|
||||||
}
|
}
|
||||||
let paths = [];
|
let paths = [];
|
||||||
for (let index of state.selected) {
|
for (let index of state.selected) {
|
||||||
paths.push(removePrefix(state.req.items[index].url, "files"));
|
paths.push(state.req.items[index].path);
|
||||||
}
|
}
|
||||||
return paths;
|
return paths;
|
||||||
},
|
},
|
||||||
|
@ -71,7 +70,7 @@ export default {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (state.isSearchActive) {
|
if (state.isSearchActive) {
|
||||||
await filesApi.remove(state.selected[0].url);
|
await filesApi.remove(state.selected[0].path);
|
||||||
buttons.success("delete");
|
buttons.success("delete");
|
||||||
notify.showSuccess("Deleted item successfully");
|
notify.showSuccess("Deleted item successfully");
|
||||||
mutations.closeHovers();
|
mutations.closeHovers();
|
||||||
|
@ -95,14 +94,14 @@ export default {
|
||||||
|
|
||||||
let promises = [];
|
let promises = [];
|
||||||
for (let index of state.selected) {
|
for (let index of state.selected) {
|
||||||
promises.push(filesApi.remove(state.req.items[index].url));
|
promises.push(filesApi.remove(state.req.items[index].path));
|
||||||
}
|
}
|
||||||
|
|
||||||
await Promise.all(promises);
|
await Promise.all(promises);
|
||||||
buttons.success("delete");
|
buttons.success("delete");
|
||||||
notify.showSuccess("Deleted item successfully");
|
notify.showSuccess("Deleted item successfully! reloading...");
|
||||||
window.location.reload();
|
mutations.setReload(true); // Handle reload as neededs
|
||||||
mutations.setReload(true); // Handle reload as needed
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
buttons.done("delete");
|
buttons.done("delete");
|
||||||
notify.showError(e);
|
notify.showError(e);
|
||||||
|
|
|
@ -72,7 +72,7 @@ export default {
|
||||||
|
|
||||||
let promises = [];
|
let promises = [];
|
||||||
for (let index of this.selected) {
|
for (let index of this.selected) {
|
||||||
promises.push(usersApi.remove(state.req.items[index].url));
|
promises.push(usersApi.remove(state.req.items[index].path));
|
||||||
}
|
}
|
||||||
|
|
||||||
await Promise.all(promises);
|
await Promise.all(promises);
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<div class="searchContext">Path: {{ nav }}</div>
|
<div aria-label="filelist-path" class="searchContext">Path: {{ nav }}</div>
|
||||||
<ul class="file-list">
|
<ul class="file-list">
|
||||||
<li
|
<li
|
||||||
@click="itemClick"
|
@click="itemClick"
|
||||||
|
|
|
@ -165,12 +165,12 @@ export default {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
let link;
|
let link;
|
||||||
if (state.isSearchActive) {
|
if (state.isSearchActive) {
|
||||||
const hash = await filesApi.checksum(state.selected[0].url, algo);
|
const hash = await filesApi.checksum(state.selected[0].path, algo);
|
||||||
event.target.innerHTML = hash;
|
event.target.innerHTML = hash;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (getters.selectedCount()) {
|
if (getters.selectedCount()) {
|
||||||
link = state.req.items[this.selected[0]].url;
|
link = state.req.items[this.selected[0]].path;
|
||||||
} else {
|
} else {
|
||||||
link = state.route.path;
|
link = state.route.path;
|
||||||
}
|
}
|
||||||
|
|
|
@ -75,14 +75,14 @@ export default {
|
||||||
if (state.isSearchActive) {
|
if (state.isSearchActive) {
|
||||||
this.items = [
|
this.items = [
|
||||||
{
|
{
|
||||||
from: "/files" + state.selected[0].url,
|
from: "/files" + state.selected[0].path,
|
||||||
name: state.selected[0].name,
|
name: state.selected[0].name,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
} else {
|
} else {
|
||||||
for (let item of state.selected) {
|
for (let item of state.selected) {
|
||||||
this.items.push({
|
this.items.push({
|
||||||
from: state.req.items[item].url,
|
from: state.req.items[item].path,
|
||||||
// add to: dest
|
// add to: dest
|
||||||
name: state.req.items[item].name,
|
name: state.req.items[item].name,
|
||||||
});
|
});
|
||||||
|
|
|
@ -106,12 +106,10 @@ export default {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
window.location.reload();
|
mutations.setReload(true);
|
||||||
mutations.closeHovers();
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
notify.showError(error);
|
notify.showError(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
<div class="card-title">
|
<div class="card-title">
|
||||||
<h2>{{ $t("buttons.share") }}</h2>
|
<h2>{{ $t("buttons.share") }}</h2>
|
||||||
</div>
|
</div>
|
||||||
<div class="searchContext">Path: {{ subpath }}</div>
|
<div aria-label="share-paths" class="searchContext">Path: {{ subpath }}</div>
|
||||||
<p>Note: anyone who has access to the link (and optional password) can access the shared files. There is no need to be logged in.</p>
|
<p>Note: anyone who has access to the link (and optional password) can access the shared files. There is no need to be logged in.</p>
|
||||||
<template v-if="listing">
|
<template v-if="listing">
|
||||||
<div class="card-content">
|
<div class="card-content">
|
||||||
|
|
|
@ -1,63 +0,0 @@
|
||||||
<template>
|
|
||||||
<form class="rules small">
|
|
||||||
<div v-for="(rule, index) in rules" :key="index">
|
|
||||||
<input type="checkbox" v-model="rule.regex" /><label>Regex</label>
|
|
||||||
<input type="checkbox" v-model="rule.allow" /><label>Allow</label>
|
|
||||||
|
|
||||||
<input
|
|
||||||
@keypress.enter.prevent
|
|
||||||
type="text"
|
|
||||||
v-if="rule.regex"
|
|
||||||
v-model="rule.regexp.raw"
|
|
||||||
:placeholder="$t('settings.insertRegex')"
|
|
||||||
/>
|
|
||||||
<input
|
|
||||||
@keypress.enter.prevent
|
|
||||||
type="text"
|
|
||||||
v-else
|
|
||||||
v-model="rule.path"
|
|
||||||
:placeholder="$t('settings.insertPath')"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<button class="button button--red" @click="remove($event, index)">
|
|
||||||
-
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<button class="button" @click="create" default="false">
|
|
||||||
{{ $t("buttons.new") }}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
name: "rules-textarea",
|
|
||||||
props: ["rules"],
|
|
||||||
methods: {
|
|
||||||
remove(event, index) {
|
|
||||||
event.preventDefault();
|
|
||||||
let rules = [...this.rules];
|
|
||||||
rules.splice(index, 1);
|
|
||||||
this.$emit("update:rules", [...rules]);
|
|
||||||
},
|
|
||||||
create(event) {
|
|
||||||
event.preventDefault();
|
|
||||||
|
|
||||||
this.$emit("update:rules", [
|
|
||||||
...this.rules,
|
|
||||||
{
|
|
||||||
allow: true,
|
|
||||||
path: "",
|
|
||||||
regex: false,
|
|
||||||
regexp: {
|
|
||||||
raw: "",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
</script>
|
|
|
@ -190,6 +190,7 @@ export default {
|
||||||
.user-card {
|
.user-card {
|
||||||
flex-direction: row !important;
|
flex-direction: row !important;
|
||||||
justify-content: space-between !important;
|
justify-content: space-between !important;
|
||||||
|
color: var(--textPrimary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.quick-toggles {
|
.quick-toggles {
|
||||||
|
@ -197,6 +198,7 @@ export default {
|
||||||
justify-content: space-evenly;
|
justify-content: space-evenly;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
margin-top: 0.5em !important;
|
margin-top: 0.5em !important;
|
||||||
|
color: var(--textPrimary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.quick-toggles button {
|
.quick-toggles button {
|
||||||
|
|
|
@ -83,7 +83,8 @@ export default {
|
||||||
}
|
}
|
||||||
|
|
||||||
@supports (backdrop-filter: none) {
|
@supports (backdrop-filter: none) {
|
||||||
nav {
|
#sidebar {
|
||||||
|
background-color: rgba(237, 237, 237, 0.33) !important;
|
||||||
backdrop-filter: blur(16px) invert(0.1);
|
backdrop-filter: blur(16px) invert(0.1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -126,7 +127,7 @@ body.rtl .action {
|
||||||
|
|
||||||
.credits {
|
.credits {
|
||||||
font-size: 1em;
|
font-size: 1em;
|
||||||
color: var(--textSecondary);
|
color: var(--textPrimary);
|
||||||
padding-left: 1em;
|
padding-left: 1em;
|
||||||
padding-bottom: 1em;
|
padding-bottom: 1em;
|
||||||
}
|
}
|
||||||
|
@ -139,7 +140,6 @@ body.rtl .action {
|
||||||
|
|
||||||
.credits a,
|
.credits a,
|
||||||
.credits a:hover {
|
.credits a:hover {
|
||||||
color: inherit;
|
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
--alt-background: #ddd;
|
--alt-background: #ddd;
|
||||||
--surfacePrimary: gray;
|
--surfacePrimary: gray;
|
||||||
--surfaceSecondary: lightgray;
|
--surfaceSecondary: lightgray;
|
||||||
--textPrimary: white;
|
--textPrimary: #546e7a;
|
||||||
--textSecondary: gray;
|
--textSecondary: gray;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -15,7 +15,8 @@ header {
|
||||||
|
|
||||||
@supports (backdrop-filter: none) {
|
@supports (backdrop-filter: none) {
|
||||||
header {
|
header {
|
||||||
backdrop-filter: blur(16px);
|
background-color: rgba(237, 237, 237, 0.33) !important;
|
||||||
|
backdrop-filter: blur(16px) invert(0.1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -49,3 +50,11 @@ header img {
|
||||||
header .action span {
|
header .action span {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Header with backdrop-filter support */
|
||||||
|
@supports (backdrop-filter: none) {
|
||||||
|
header {
|
||||||
|
background-color: rgba(237, 237, 237, 0.33) !important;
|
||||||
|
backdrop-filter: blur(16px) invert(0.1);
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,7 +3,6 @@ export function startLoading (from, to) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('startLoading', from, to)
|
|
||||||
// Get the spinner canvas element
|
// Get the spinner canvas element
|
||||||
let spinner = document.querySelector('.notification-spinner')
|
let spinner = document.querySelector('.notification-spinner')
|
||||||
if (!spinner) {
|
if (!spinner) {
|
||||||
|
@ -27,8 +26,8 @@ export function startLoading (from, to) {
|
||||||
let degrees = from * 3.6 // Convert percentage to degrees
|
let degrees = from * 3.6 // Convert percentage to degrees
|
||||||
let new_degrees = to * 3.6 // Convert percentage to degrees
|
let new_degrees = to * 3.6 // Convert percentage to degrees
|
||||||
let difference = new_degrees - degrees
|
let difference = new_degrees - degrees
|
||||||
let color = spinner.style.color || '#666'
|
let color = spinner.style.color || '#fff'
|
||||||
let bgcolor = '#fff'
|
let bgcolor = '#666'
|
||||||
let animation_loop
|
let animation_loop
|
||||||
|
|
||||||
// Clear any existing animation loop
|
// Clear any existing animation loop
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import { removePrefix } from "@/utils/url.js";
|
import { removePrefix } from "@/utils/url.js";
|
||||||
|
import { getFileExtension } from "@/utils/files.js";
|
||||||
import { state } from "./state.js";
|
import { state } from "./state.js";
|
||||||
import { mutations } from "./mutations.js";
|
import { mutations } from "./mutations.js";
|
||||||
import { noAuth } from "@/utils/constants.js";
|
import { noAuth } from "@/utils/constants.js";
|
||||||
|
@ -146,7 +147,7 @@ export const getters = {
|
||||||
if (state.req.type !== undefined) {
|
if (state.req.type !== undefined) {
|
||||||
if (state.req.type == "directory") {
|
if (state.req.type == "directory") {
|
||||||
return "listingView";
|
return "listingView";
|
||||||
} else if (state.req?.onlyOfficeId) {
|
} else if (getters.onlyOfficeEnabled(state.req.name)) {
|
||||||
return "onlyOfficeEditor";
|
return "onlyOfficeEditor";
|
||||||
} else if ("content" in state.req && state.req.type == "text/markdown" && window.location.hash != "#edit") {
|
} else if ("content" in state.req && state.req.type == "text/markdown" && window.location.hash != "#edit") {
|
||||||
return "markdownViewer";
|
return "markdownViewer";
|
||||||
|
@ -240,4 +241,19 @@ export const getters = {
|
||||||
|
|
||||||
return files.sort((a, b) => a.progress - b.progress);
|
return files.sort((a, b) => a.progress - b.progress);
|
||||||
},
|
},
|
||||||
|
onlyOfficeEnabled: (filename) => {
|
||||||
|
if (!state.req?.onlyOfficeId) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
const ext = getFileExtension(filename);
|
||||||
|
if (state.user.disableOnlyOfficeExt) {
|
||||||
|
const disabledList = state.user.disableOnlyOfficeExt.split(" ");
|
||||||
|
for (const e of disabledList) {
|
||||||
|
if (e.trim().toLowerCase() === ext.toLowerCase()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -2,6 +2,7 @@ import { reactive } from 'vue';
|
||||||
import { detectLocale } from "@/i18n";
|
import { detectLocale } from "@/i18n";
|
||||||
|
|
||||||
export const state = reactive({
|
export const state = reactive({
|
||||||
|
disableOnlyOfficeExt: "",
|
||||||
isSafari: /^((?!chrome|android).)*safari/i.test(navigator.userAgent),
|
isSafari: /^((?!chrome|android).)*safari/i.test(navigator.userAgent),
|
||||||
activeSettingsView: "",
|
activeSettingsView: "",
|
||||||
isMobile: window.innerWidth <= 800,
|
isMobile: window.innerWidth <= 800,
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
export function getFileExtension(filename) {
|
||||||
|
const firstDotIndex = filename.indexOf('.');
|
||||||
|
if (firstDotIndex === -1) {
|
||||||
|
return filename; // No dot found, return the original filename
|
||||||
|
}
|
||||||
|
return filename.substring(firstDotIndex);
|
||||||
|
}
|
|
@ -0,0 +1,190 @@
|
||||||
|
export function convertToVTT (ext, text) {
|
||||||
|
if (ext == '.vtt') {
|
||||||
|
return text
|
||||||
|
}
|
||||||
|
let vttContent = 'WEBVTT\n\n'
|
||||||
|
let lrcLines = text.split('\n').filter(line => line.trim() !== '') // Filter out empty lines
|
||||||
|
|
||||||
|
switch (ext.toLowerCase()) {
|
||||||
|
case '.srt':
|
||||||
|
// Convert SRT to VTT
|
||||||
|
vttContent += text
|
||||||
|
.replace(/\r\n|\r/g, '\n') // Normalize newlines
|
||||||
|
.replace(
|
||||||
|
/\d+\n(\d{2}):(\d{2}):(\d{2}),(\d{3}) --> (\d{2}):(\d{2}):(\d{2}),(\d{3})/g,
|
||||||
|
(match, h1, m1, s1, ms1, h2, m2, s2, ms2) => {
|
||||||
|
return `${parseInt(h1)}:${m1}:${s1}.${ms1} --> ${parseInt(
|
||||||
|
h2
|
||||||
|
)}:${m2}:${s2}.${ms2}`
|
||||||
|
}
|
||||||
|
) // Fix timestamps (remove leading zeros)
|
||||||
|
.replace(/\n\n+/g, '\n\n') // Ensure proper spacing
|
||||||
|
.replace(/(\d+:\d{2}:\d{2}\.\d{3})\n([^\n])/g, '$1\n$2') // Prevent extra blank lines
|
||||||
|
.trim()
|
||||||
|
break
|
||||||
|
|
||||||
|
case '.sbv':
|
||||||
|
// Convert SBV to VTT
|
||||||
|
vttContent += text
|
||||||
|
.replace(
|
||||||
|
/(\d{1,2}:\d{2}:\d{2}\.\d{3}),(\d{1,2}:\d{2}:\d{2}\.\d{3})/g,
|
||||||
|
'$1 --> $2'
|
||||||
|
) // Convert comma to -->
|
||||||
|
.replace(/\n\n+/g, '\n\n')
|
||||||
|
.trim()
|
||||||
|
break
|
||||||
|
|
||||||
|
case '.lrc':
|
||||||
|
// Convert LRC to VTT
|
||||||
|
lrcLines = lrcLines
|
||||||
|
.map((line, index) => {
|
||||||
|
if (line.startsWith('[')) {
|
||||||
|
let [time, dialogue] = line.split(']') // Remove square brackets
|
||||||
|
if (!time || !dialogue) return '' // Skip invalid lines
|
||||||
|
|
||||||
|
time = time.slice(1) // Remove opening square bracket
|
||||||
|
|
||||||
|
// Format time to HH:MM:SS.MMM
|
||||||
|
let startTime = formatLrcTime(time)
|
||||||
|
let endTime =
|
||||||
|
index + 1 < lrcLines.length
|
||||||
|
? formatLrcTime(lrcLines[index + 1].split(']')[0].slice(1))
|
||||||
|
: startTime // Use next line's timestamp for end time
|
||||||
|
|
||||||
|
return `${startTime} --> ${endTime}\n ${dialogue.trim()}` // Add leading space before dialogue
|
||||||
|
} else {
|
||||||
|
return '' // Skip lines that don't start with '['
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.filter(line => line !== '')
|
||||||
|
.join('\n\n')
|
||||||
|
.trim() // Remove empty lines
|
||||||
|
|
||||||
|
vttContent += lrcLines
|
||||||
|
break
|
||||||
|
|
||||||
|
case '.ass':
|
||||||
|
case '.ssa':
|
||||||
|
// Convert ASS/SSA to VTT
|
||||||
|
vttContent += text
|
||||||
|
.split('\n')
|
||||||
|
.filter(line => line.startsWith('Dialogue:')) // Keep only dialogue lines
|
||||||
|
.map(line => {
|
||||||
|
let parts = line.split(',')
|
||||||
|
let startTime = formatAssTime(parts[1].trim())
|
||||||
|
let endTime = formatAssTime(parts[2].trim())
|
||||||
|
let dialogue = parts.slice(9).join(',').trim()
|
||||||
|
return `${startTime} --> ${endTime}\n ${dialogue}` // Add leading space before dialogue
|
||||||
|
})
|
||||||
|
.join('\n\n')
|
||||||
|
.trim()
|
||||||
|
break
|
||||||
|
case '.smi':
|
||||||
|
// Convert SAMI to VTT
|
||||||
|
vttContent += text
|
||||||
|
.replace(/\r\n|\r/g, '\n') // Normalize newlines
|
||||||
|
.match(/<SYNC Start=\d+>.*?<\/SYNC>/gs) // Match all <SYNC> blocks
|
||||||
|
?.map(syncBlock => {
|
||||||
|
let startTime = syncBlock.match(/<SYNC Start=(\d+)>/)[1] // Extract start time in ms
|
||||||
|
let dialogueMatch = syncBlock.match(/<P[^>]*>(.*?)<\/P>/) // Extract text inside <P>
|
||||||
|
let dialogue = dialogueMatch
|
||||||
|
? dialogueMatch[1].replace(/<[^>]+>/g, '').trim()
|
||||||
|
: '' // Remove HTML tags
|
||||||
|
|
||||||
|
if (!dialogue) return '' // Skip empty captions
|
||||||
|
let startFormatted = formatMillisecondsToVTT(startTime)
|
||||||
|
|
||||||
|
// Use next start time as the end time if available
|
||||||
|
let nextSyncMatch = text.match(
|
||||||
|
new RegExp(`<SYNC Start=${parseInt(startTime) + 1}>`)
|
||||||
|
)
|
||||||
|
let endTime = nextSyncMatch
|
||||||
|
? nextSyncMatch[1]
|
||||||
|
: parseInt(startTime) + 3000 // Default to +3s
|
||||||
|
|
||||||
|
let endFormatted = formatMillisecondsToVTT(endTime)
|
||||||
|
return `${startFormatted} --> ${endFormatted}\n${dialogue}`
|
||||||
|
})
|
||||||
|
.filter(syncBlock => syncBlock !== '')
|
||||||
|
.join('\n\n')
|
||||||
|
.trim()
|
||||||
|
break
|
||||||
|
case '.sub':
|
||||||
|
// Convert SUB to VTT
|
||||||
|
vttContent += text
|
||||||
|
.replace(/\r\n|\r/g, '\n') // Normalize newlines
|
||||||
|
.split('\n\n') // Split by empty lines (each caption block)
|
||||||
|
.map(block => {
|
||||||
|
let lines = block.split('\n').filter(line => line.trim() !== ''); // Remove empty lines
|
||||||
|
if (lines.length < 2) return ''; // Ensure each block has a timestamp and text
|
||||||
|
|
||||||
|
let [time, ...dialogue] = lines; // First line is time, rest is dialogue
|
||||||
|
let [start, end] = time.split(','); // Split start and end time
|
||||||
|
|
||||||
|
if (!start || !end || dialogue.length === 0) return ''; // Skip invalid blocks
|
||||||
|
|
||||||
|
start = formatSubTime(start.trim());
|
||||||
|
end = formatSubTime(end.trim());
|
||||||
|
|
||||||
|
return `${start} --> ${end}\n${dialogue.join(' ')}`; // Join dialogue lines into one block
|
||||||
|
})
|
||||||
|
.filter(block => block !== '') // Remove empty blocks
|
||||||
|
.join('\n\n')
|
||||||
|
.trim();
|
||||||
|
break;
|
||||||
|
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new Error('Unsupported subtitle format.')
|
||||||
|
}
|
||||||
|
|
||||||
|
return vttContent
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper function to format LRC time (e.g., 00:00.000 -> 00:00:00.000)
|
||||||
|
function formatLrcTime (time) {
|
||||||
|
let [minutes, seconds] = time.split(':')
|
||||||
|
let [sec, ms] = seconds.split('.')
|
||||||
|
ms = ms ? ms.padEnd(3, '0') : '000' // Ensure milliseconds are 3 digits
|
||||||
|
|
||||||
|
// Return in the correct format
|
||||||
|
return `00:${minutes}:${sec}.${ms}`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper function to fix ASS/SSA timestamps
|
||||||
|
function formatAssTime (time) {
|
||||||
|
let parts = time.split(':')
|
||||||
|
let hours = parts.length === 3 ? parseInt(parts[0]) : 0
|
||||||
|
let minutes = parts.length === 3 ? parts[1] : parts[0]
|
||||||
|
let seconds = parts[parts.length - 1].replace('.', ':')
|
||||||
|
|
||||||
|
// Ensure proper milliseconds formatting (000)
|
||||||
|
const [sec, ms = '000'] = seconds.split(':')
|
||||||
|
return `${hours}:${minutes}:${sec}.${ms.padEnd(3, '0')}`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper function to ensure proper SUB timestamp format (with 3-digit milliseconds)
|
||||||
|
function formatSubTime(time) {
|
||||||
|
let parts = time.split(':');
|
||||||
|
if (parts.length < 3) return '00:00:00.000'; // Fallback for invalid input
|
||||||
|
|
||||||
|
let [hours, minutes, secMs] = parts;
|
||||||
|
let [seconds, ms = '000'] = secMs.split('.');
|
||||||
|
|
||||||
|
ms = ms.padEnd(3, '0'); // Ensure milliseconds are 3 digits
|
||||||
|
|
||||||
|
return `${hours}:${minutes}:${seconds}.${ms}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Helper function to convert milliseconds to HH:MM:SS.MMM format
|
||||||
|
function formatMillisecondsToVTT (ms) {
|
||||||
|
let totalSeconds = Math.floor(ms / 1000)
|
||||||
|
let milliseconds = (ms % 1000).toString().padStart(3, '0')
|
||||||
|
let hours = Math.floor(totalSeconds / 3600)
|
||||||
|
let minutes = Math.floor((totalSeconds % 3600) / 60)
|
||||||
|
let seconds = totalSeconds % 60
|
||||||
|
return `${hours}:${minutes.toString().padStart(2, '0')}:${seconds
|
||||||
|
.toString()
|
||||||
|
.padStart(2, '0')}.${milliseconds}`
|
||||||
|
}
|
|
@ -128,7 +128,7 @@ export async function handleFiles(files, base, overwrite = false) {
|
||||||
overwrite,
|
overwrite,
|
||||||
};
|
};
|
||||||
let last = 0;
|
let last = 0;
|
||||||
notify.showPopup("success",`(${c} of ${count}) Uploading ${file.name}`,false);
|
notify.showPopup("success", `(${c} of ${count}) Uploading ${file.name}`, false);
|
||||||
await filesApi.post(
|
await filesApi.post(
|
||||||
item.path,
|
item.path,
|
||||||
item.file,
|
item.file,
|
||||||
|
@ -143,23 +143,22 @@ export async function handleFiles(files, base, overwrite = false) {
|
||||||
last = percentComplete;
|
last = percentComplete;
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
blockUpdates = false;
|
blockUpdates = false;
|
||||||
}, 200);
|
}, 250);
|
||||||
}
|
}
|
||||||
)
|
).then(response => {
|
||||||
.then(response => {
|
let spinner = document.querySelector('.notification-spinner');
|
||||||
let spinner = document.querySelector('.notification-spinner')
|
|
||||||
if (spinner) {
|
if (spinner) {
|
||||||
spinner.classList.add('hidden')
|
spinner.classList.add('hidden');
|
||||||
}
|
}
|
||||||
console.log("Upload successful!",response);
|
console.log("Upload successful!", response);
|
||||||
notify.showSuccess("Upload successful!");
|
notify.showSuccess("Upload successful!");
|
||||||
})
|
}).catch(error => {
|
||||||
.catch(error => {
|
let spinner = document.querySelector('.notification-spinner');
|
||||||
let spinner = document.querySelector('.notification-spinner')
|
|
||||||
if (spinner) {
|
if (spinner) {
|
||||||
spinner.classList.add('hidden')
|
spinner.classList.add('hidden');
|
||||||
}
|
}
|
||||||
notify.showError("Error uploading file: "+error);
|
notify.showError("Error uploading file: " + error);
|
||||||
|
throw error;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -54,15 +54,8 @@ export function removePrefix(path, prefix = "") {
|
||||||
if (prefix != "") {
|
if (prefix != "") {
|
||||||
prefix = "/" + trimSlashes(prefix)
|
prefix = "/" + trimSlashes(prefix)
|
||||||
}
|
}
|
||||||
const combined = trimSlashes(baseURL) + prefix
|
|
||||||
const combined2 = "/" + combined
|
|
||||||
// Remove combined (baseURL + prefix) from the start of the path if present
|
// Remove combined (baseURL + prefix) from the start of the path if present
|
||||||
if (path.startsWith(combined)) {
|
if (path.startsWith(prefix)) {
|
||||||
path = path.slice(combined.length);
|
|
||||||
} else if (path.startsWith(combined2)) {
|
|
||||||
path = path.slice(combined2.length);
|
|
||||||
} else if (path.startsWith(prefix)) {
|
|
||||||
// Fallback: remove only the prefix if the combined string isn't present
|
|
||||||
path = path.slice(prefix.length);
|
path = path.slice(prefix.length);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -70,6 +70,7 @@ export default {
|
||||||
$route: "fetchData",
|
$route: "fetchData",
|
||||||
reload(value) {
|
reload(value) {
|
||||||
if (value) {
|
if (value) {
|
||||||
|
console.log("Reloading");
|
||||||
this.fetchData();
|
this.fetchData();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -100,15 +101,12 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async fetchData() {
|
async fetchData() {
|
||||||
if (state.route.path === this.lastPath) return;
|
|
||||||
this.lastHash = "";
|
this.lastHash = "";
|
||||||
// Set loading to true and reset the error.
|
// Set loading to true and reset the error.
|
||||||
mutations.setLoading("files", true);
|
mutations.setLoading("files", true);
|
||||||
this.error = null;
|
this.error = null;
|
||||||
// Reset view information using mutations
|
// Reset view information using mutations
|
||||||
mutations.setReload(false);
|
mutations.setReload(false);
|
||||||
mutations.setMultiple(false);
|
|
||||||
mutations.closeHovers();
|
|
||||||
|
|
||||||
let data = {};
|
let data = {};
|
||||||
try {
|
try {
|
||||||
|
@ -116,13 +114,7 @@ export default {
|
||||||
let res = await filesApi.fetchFiles(getters.routePath());
|
let res = await filesApi.fetchFiles(getters.routePath());
|
||||||
// If not a directory, fetch content
|
// If not a directory, fetch content
|
||||||
if (res.type != "directory") {
|
if (res.type != "directory") {
|
||||||
let content = false;
|
const content = !getters.onlyOfficeEnabled()
|
||||||
if (
|
|
||||||
!res.onlyOfficeId &&
|
|
||||||
(res.type.startsWith("application") || res.type.startsWith("text"))
|
|
||||||
) {
|
|
||||||
content = true;
|
|
||||||
}
|
|
||||||
res = await filesApi.fetchFiles(getters.routePath(), content);
|
res = await filesApi.fetchFiles(getters.routePath(), content);
|
||||||
}
|
}
|
||||||
data = res;
|
data = res;
|
||||||
|
|
|
@ -27,7 +27,7 @@
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { state, mutations } from "@/store";
|
import { state } from "@/store";
|
||||||
import { eventBus } from "@/store/eventBus";
|
import { eventBus } from "@/store/eventBus";
|
||||||
import buttons from "@/utils/buttons";
|
import buttons from "@/utils/buttons";
|
||||||
import url from "@/utils/url.js";
|
import url from "@/utils/url.js";
|
||||||
|
|
|
@ -80,7 +80,7 @@ export default {
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
handleEditorValueRequest() {
|
handleEditorValueRequest() {
|
||||||
filesApi.put(getters.routePath("files"), this.editor.getValue());
|
filesApi.put(state.req.path, this.editor.getValue());
|
||||||
},
|
},
|
||||||
back() {
|
back() {
|
||||||
let uri = url.removeLastDir(state.route.path) + "/";
|
let uri = url.removeLastDir(state.route.path) + "/";
|
||||||
|
|
|
@ -176,6 +176,7 @@ export default {
|
||||||
this.columnWidth = 250 + state.user.gallerySize * 50;
|
this.columnWidth = 250 + state.user.gallerySize * 50;
|
||||||
this.colunmsResize();
|
this.colunmsResize();
|
||||||
},
|
},
|
||||||
|
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
isStickySidebar() {
|
isStickySidebar() {
|
||||||
|
@ -296,6 +297,7 @@ export default {
|
||||||
window.addEventListener("resize", this.windowsResize);
|
window.addEventListener("resize", this.windowsResize);
|
||||||
window.addEventListener("click", this.clickClear);
|
window.addEventListener("click", this.clickClear);
|
||||||
window.addEventListener("keyup", this.clearCtrKey);
|
window.addEventListener("keyup", this.clearCtrKey);
|
||||||
|
window.addEventListener("dragover", this.preventDefault);
|
||||||
|
|
||||||
// Adjust contextmenu listener based on browser
|
// Adjust contextmenu listener based on browser
|
||||||
if (state.isSafari) {
|
if (state.isSafari) {
|
||||||
|
@ -312,7 +314,6 @@ export default {
|
||||||
window.addEventListener("contextmenu", this.openContext);
|
window.addEventListener("contextmenu", this.openContext);
|
||||||
}
|
}
|
||||||
// if safari , make sure click and hold opens context menu, but not for any other browser
|
// if safari , make sure click and hold opens context menu, but not for any other browser
|
||||||
|
|
||||||
if (!state.user.perm?.create) return;
|
if (!state.user.perm?.create) return;
|
||||||
this.$el.addEventListener("dragenter", this.dragEnter);
|
this.$el.addEventListener("dragenter", this.dragEnter);
|
||||||
this.$el.addEventListener("dragleave", this.dragLeave);
|
this.$el.addEventListener("dragleave", this.dragLeave);
|
||||||
|
@ -775,8 +776,6 @@ export default {
|
||||||
let el = event.target;
|
let el = event.target;
|
||||||
|
|
||||||
if (dt.files.length <= 0) {
|
if (dt.files.length <= 0) {
|
||||||
mutations.setReload(true);
|
|
||||||
window.location.reload();
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -806,11 +805,11 @@ export default {
|
||||||
|
|
||||||
if (el !== null && el.classList.contains("item") && el.dataset.dir === "true") {
|
if (el !== null && el.classList.contains("item") && el.dataset.dir === "true") {
|
||||||
path = el.__vue__.url;
|
path = el.__vue__.url;
|
||||||
items = (await filesApi.fetchFiles(path)).items;
|
items = state.req.items
|
||||||
}
|
}
|
||||||
|
|
||||||
const conflict = upload.checkConflict(uploadFiles, items);
|
const conflict = upload.checkConflict(uploadFiles, items);
|
||||||
|
try {
|
||||||
if (conflict) {
|
if (conflict) {
|
||||||
mutations.showHover({
|
mutations.showHover({
|
||||||
name: "replace",
|
name: "replace",
|
||||||
|
@ -824,8 +823,11 @@ export default {
|
||||||
await upload.handleFiles(uploadFiles, path);
|
await upload.handleFiles(uploadFiles, path);
|
||||||
}
|
}
|
||||||
mutations.setReload(true);
|
mutations.setReload(true);
|
||||||
|
} catch {
|
||||||
|
console.log("failed to upload files")
|
||||||
|
}
|
||||||
},
|
},
|
||||||
uploadInput(event) {
|
async uploadInput(event) {
|
||||||
mutations.closeHovers();
|
mutations.closeHovers();
|
||||||
let files = event.currentTarget.files;
|
let files = event.currentTarget.files;
|
||||||
let folder_upload =
|
let folder_upload =
|
||||||
|
@ -843,16 +845,17 @@ export default {
|
||||||
if (conflict) {
|
if (conflict) {
|
||||||
mutations.showHover({
|
mutations.showHover({
|
||||||
name: "replace",
|
name: "replace",
|
||||||
confirm: (event) => {
|
confirm: async (event) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
mutations.closeHovers();
|
mutations.closeHovers();
|
||||||
upload.handleFiles(files, path, true);
|
await upload.handleFiles(files, path, true);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
upload.handleFiles(files, path);
|
await upload.handleFiles(files, path);
|
||||||
|
mutations.setReload(true);
|
||||||
},
|
},
|
||||||
resetOpacity() {
|
resetOpacity() {
|
||||||
let items = document.getElementsByClassName("item");
|
let items = document.getElementsByClassName("item");
|
||||||
|
|
|
@ -20,7 +20,9 @@ export default {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.content = state.req.content
|
const fileContent =
|
||||||
|
state.req.content == "empty-file-x6OlSil" ? "" : state.req.content || "";
|
||||||
|
this.content = fileContent
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -1,19 +1,39 @@
|
||||||
<template>
|
<template>
|
||||||
<div id="previewer" @mousemove="toggleNavigation" @touchstart="toggleNavigation">
|
<div id="previewer" @mousemove="toggleNavigation" @touchstart="toggleNavigation">
|
||||||
<div class="preview">
|
<div class="preview">
|
||||||
<ExtendedImage v-if="getSimpleType(currentItem.type) == 'image'" :src="raw">
|
<ExtendedImage v-if="getSimpleType(req.type) == 'image'" :src="raw">
|
||||||
</ExtendedImage>
|
</ExtendedImage>
|
||||||
<audio v-else-if="getSimpleType(currentItem.type) == 'audio'" ref="player" :src="raw" controls
|
<audio
|
||||||
:autoplay="autoPlay" @play="autoPlay = true"></audio>
|
v-else-if="getSimpleType(req.type) == 'audio'"
|
||||||
<video v-else-if="getSimpleType(currentItem.type) == 'video'" ref="player" :src="raw" controls
|
ref="player"
|
||||||
:autoplay="autoPlay" @play="autoPlay = true">
|
:src="raw"
|
||||||
<track kind="captions" v-for="(sub, index) in subtitles" :key="index" :src="sub" :label="'Subtitle ' + index"
|
controls
|
||||||
:default="index === 0" />
|
:autoplay="autoPlay"
|
||||||
Sorry, your browser doesn't support embedded videos, but don't worry, you can
|
@play="autoPlay = true"
|
||||||
<a :href="downloadUrl">download it</a>
|
></audio>
|
||||||
and watch it with your favorite video player!
|
<video
|
||||||
|
v-else-if="getSimpleType(req.type) == 'video'"
|
||||||
|
ref="player"
|
||||||
|
:src="raw"
|
||||||
|
controls
|
||||||
|
:autoplay="autoPlay"
|
||||||
|
@play="autoPlay = true"
|
||||||
|
>
|
||||||
|
<track
|
||||||
|
kind="captions"
|
||||||
|
v-for="(sub, index) in subtitlesList"
|
||||||
|
:key="index"
|
||||||
|
:src="sub.src"
|
||||||
|
:label="'Subtitle ' + sub.name"
|
||||||
|
:default="index === 0"
|
||||||
|
/>
|
||||||
</video>
|
</video>
|
||||||
<object v-else-if="getSimpleType(currentItem.type) == 'pdf'" class="pdf" :data="raw"></object>
|
|
||||||
|
<object
|
||||||
|
v-else-if="getSimpleType(req.type) == 'pdf'"
|
||||||
|
class="pdf"
|
||||||
|
:data="raw"
|
||||||
|
></object>
|
||||||
<div v-else class="info">
|
<div v-else class="info">
|
||||||
<div class="title">
|
<div class="title">
|
||||||
<i class="material-icons">feedback</i>
|
<i class="material-icons">feedback</i>
|
||||||
|
@ -25,7 +45,12 @@
|
||||||
<i class="material-icons">file_download</i>{{ $t("buttons.download") }}
|
<i class="material-icons">file_download</i>{{ $t("buttons.download") }}
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
<a target="_blank" :href="raw" class="button button--flat" v-if="currentItem.type != 'directory'">
|
<a
|
||||||
|
target="_blank"
|
||||||
|
:href="raw"
|
||||||
|
class="button button--flat"
|
||||||
|
v-if="req.type != 'directory'"
|
||||||
|
>
|
||||||
<div>
|
<div>
|
||||||
<i class="material-icons">open_in_new</i>{{ $t("buttons.openFile") }}
|
<i class="material-icons">open_in_new</i>{{ $t("buttons.openFile") }}
|
||||||
</div>
|
</div>
|
||||||
|
@ -34,13 +59,24 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button @click="prev" @mouseover="hoverNav = true" @mouseleave="hoverNav = false"
|
<button
|
||||||
:class="{ hidden: !hasPrevious || !showNav }" :aria-label="$t('buttons.previous')"
|
@click="prev"
|
||||||
:title="$t('buttons.previous')">
|
@mouseover="hoverNav = true"
|
||||||
|
@mouseleave="hoverNav = false"
|
||||||
|
:class="{ hidden: !hasPrevious || !showNav }"
|
||||||
|
:aria-label="$t('buttons.previous')"
|
||||||
|
:title="$t('buttons.previous')"
|
||||||
|
>
|
||||||
<i class="material-icons">chevron_left</i>
|
<i class="material-icons">chevron_left</i>
|
||||||
</button>
|
</button>
|
||||||
<button @click="next" @mouseover="hoverNav = true" @mouseleave="hoverNav = false"
|
<button
|
||||||
:class="{ hidden: !hasNext || !showNav }" :aria-label="$t('buttons.next')" :title="$t('buttons.next')">
|
@click="next"
|
||||||
|
@mouseover="hoverNav = true"
|
||||||
|
@mouseleave="hoverNav = false"
|
||||||
|
:class="{ hidden: !hasNext || !showNav }"
|
||||||
|
:aria-label="$t('buttons.next')"
|
||||||
|
:title="$t('buttons.next')"
|
||||||
|
>
|
||||||
<i class="material-icons">chevron_right</i>
|
<i class="material-icons">chevron_right</i>
|
||||||
</button>
|
</button>
|
||||||
<link rel="prefetch" :href="previousRaw" />
|
<link rel="prefetch" :href="previousRaw" />
|
||||||
|
@ -55,6 +91,8 @@ import throttle from "@/utils/throttle";
|
||||||
import ExtendedImage from "@/components/files/ExtendedImage.vue";
|
import ExtendedImage from "@/components/files/ExtendedImage.vue";
|
||||||
import { state, getters, mutations } from "@/store";
|
import { state, getters, mutations } from "@/store";
|
||||||
import { getTypeInfo } from "@/utils/mimetype";
|
import { getTypeInfo } from "@/utils/mimetype";
|
||||||
|
import { getFileExtension } from "@/utils/files";
|
||||||
|
import { convertToVTT } from "@/utils/subtitles";
|
||||||
|
|
||||||
const mediaTypes = ["image", "video", "audio", "blob"];
|
const mediaTypes = ["image", "video", "audio", "blob"];
|
||||||
|
|
||||||
|
@ -78,16 +116,13 @@ export default {
|
||||||
nextRaw: "",
|
nextRaw: "",
|
||||||
currentPrompt: null, // Replaces Vuex getter `currentPrompt`
|
currentPrompt: null, // Replaces Vuex getter `currentPrompt`
|
||||||
oldReq: {}, // Replace with your actual initial state
|
oldReq: {}, // Replace with your actual initial state
|
||||||
currentItem: {
|
subtitlesList: [],
|
||||||
name: "",
|
|
||||||
path: "",
|
|
||||||
url: "",
|
|
||||||
modified: "",
|
|
||||||
type: "",
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
raw() {
|
||||||
|
return filesApi.getDownloadURL(state.req.path);
|
||||||
|
},
|
||||||
isDarkMode() {
|
isDarkMode() {
|
||||||
return getters.isDarkMode();
|
return getters.isDarkMode();
|
||||||
},
|
},
|
||||||
|
@ -98,20 +133,7 @@ export default {
|
||||||
return this.nextLink !== "";
|
return this.nextLink !== "";
|
||||||
},
|
},
|
||||||
downloadUrl() {
|
downloadUrl() {
|
||||||
return filesApi.getDownloadURL(this.currentItem.url);
|
return filesApi.getDownloadURL(state.req.path);
|
||||||
},
|
|
||||||
raw() {
|
|
||||||
if (this.currentItem.url == "" || this.currentItem.url == undefined) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const previewUrl = this.fullSize
|
|
||||||
? filesApi.getDownloadURL(this.currentItem.url, "large")
|
|
||||||
: filesApi.getPreviewURL(
|
|
||||||
this.currentItem.url,
|
|
||||||
"small",
|
|
||||||
this.currentItem.modified
|
|
||||||
);
|
|
||||||
return previewUrl;
|
|
||||||
},
|
},
|
||||||
showMore() {
|
showMore() {
|
||||||
return getters.currentPromptName() === "more";
|
return getters.currentPromptName() === "more";
|
||||||
|
@ -119,11 +141,11 @@ export default {
|
||||||
isResizeEnabled() {
|
isResizeEnabled() {
|
||||||
return resizePreview;
|
return resizePreview;
|
||||||
},
|
},
|
||||||
subtitles() {
|
getSubtitles() {
|
||||||
if (this.currentItem.subtitles) {
|
return this.subtitles();
|
||||||
return filesApi.getSubtitlesURL(this.currentItem);
|
},
|
||||||
}
|
req() {
|
||||||
return [];
|
return state.req;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
|
@ -138,12 +160,35 @@ export default {
|
||||||
async mounted() {
|
async mounted() {
|
||||||
window.addEventListener("keydown", this.key);
|
window.addEventListener("keydown", this.key);
|
||||||
this.listing = this.oldReq.items;
|
this.listing = this.oldReq.items;
|
||||||
|
this.subtitlesList = await this.subtitles();
|
||||||
this.updatePreview();
|
this.updatePreview();
|
||||||
},
|
},
|
||||||
beforeUnmount() {
|
beforeUnmount() {
|
||||||
window.removeEventListener("keydown", this.key);
|
window.removeEventListener("keydown", this.key);
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
async subtitles() {
|
||||||
|
if (!state.req.subtitles || state.req.subtitles.length === 0) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
let subs = [];
|
||||||
|
for (const element of state.req.subtitles) {
|
||||||
|
const ext = getFileExtension(element);
|
||||||
|
const resp = await filesApi.fetchFiles(element, true); // Fetch .srt file
|
||||||
|
let vttContent = resp.content;
|
||||||
|
// Convert SRT to VTT (assuming srt2vtt() does this)
|
||||||
|
vttContent = convertToVTT(ext, resp.content);
|
||||||
|
// Create a virtual file (Blob) and get a URL for it
|
||||||
|
const blob = new Blob([vttContent], { type: "text/vtt" });
|
||||||
|
const vttURL = URL.createObjectURL(blob);
|
||||||
|
subs.push({
|
||||||
|
name: ext,
|
||||||
|
src: vttURL,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
console.log(subs);
|
||||||
|
return subs;
|
||||||
|
},
|
||||||
getSimpleType(mimetype) {
|
getSimpleType(mimetype) {
|
||||||
return getTypeInfo(mimetype).simpleType;
|
return getTypeInfo(mimetype).simpleType;
|
||||||
},
|
},
|
||||||
|
@ -197,8 +242,7 @@ export default {
|
||||||
if (this.$refs.player && this.$refs.player.paused && !this.$refs.player.ended) {
|
if (this.$refs.player && this.$refs.player.paused && !this.$refs.player.ended) {
|
||||||
this.autoPlay = false;
|
this.autoPlay = false;
|
||||||
}
|
}
|
||||||
let parts = state.route.path.split("/");
|
this.name = state.req.name;
|
||||||
this.name = decodeURI(parts.pop("/"));
|
|
||||||
if (!this.listing) {
|
if (!this.listing) {
|
||||||
const path = url.removeLastDir(state.route.path);
|
const path = url.removeLastDir(state.route.path);
|
||||||
const res = await filesApi.fetchFiles(path);
|
const res = await filesApi.fetchFiles(path);
|
||||||
|
@ -216,7 +260,6 @@ export default {
|
||||||
if (this.listing[i].name !== this.name) {
|
if (this.listing[i].name !== this.name) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
this.currentItem = this.listing[i];
|
|
||||||
for (let j = i - 1; j >= 0; j--) {
|
for (let j = i - 1; j >= 0; j--) {
|
||||||
let composedListing = this.listing[j];
|
let composedListing = this.listing[j];
|
||||||
composedListing.path = directoryPath + "/" + composedListing.name;
|
composedListing.path = directoryPath + "/" + composedListing.name;
|
||||||
|
|
|
@ -18,6 +18,21 @@
|
||||||
<input type="checkbox" v-model="quickDownload" />
|
<input type="checkbox" v-model="quickDownload" />
|
||||||
Always show download icon for quick access
|
Always show download icon for quick access
|
||||||
</p>
|
</p>
|
||||||
|
<div v-if="hasOnlyOfficeEnabled">
|
||||||
|
<h3>Disable onlyoffice viewer for certain file extentions</h3>
|
||||||
|
<p>A space separated list of file extensions to disable the only office viewer for. eg <code>.txt .html</code></p>
|
||||||
|
<input
|
||||||
|
class="input input--block"
|
||||||
|
:class="{'invalid-form':!formValidation()}"
|
||||||
|
type="text"
|
||||||
|
placeholder="enter file extentions"
|
||||||
|
id="onlyofficeExt"
|
||||||
|
v-model="disableOnlyOfficeExt"
|
||||||
|
/>
|
||||||
|
<button class="button" @click="submitOnlyOfficeChange" >save</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<h3>Theme Color</h3>
|
<h3>Theme Color</h3>
|
||||||
<ButtonGroup :buttons="colorChoices" @button-clicked="setColor" :initialActive="color" />
|
<ButtonGroup :buttons="colorChoices" @button-clicked="setColor" :initialActive="color" />
|
||||||
<h3>{{ $t("settings.language") }}</h3>
|
<h3>{{ $t("settings.language") }}</h3>
|
||||||
|
@ -30,6 +45,7 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { notify } from "@/notify";
|
import { notify } from "@/notify";
|
||||||
|
import { onlyOfficeUrl } from "@/utils/constants.js";
|
||||||
import { state, mutations } from "@/store";
|
import { state, mutations } from "@/store";
|
||||||
import { usersApi } from "@/api";
|
import { usersApi } from "@/api";
|
||||||
import Languages from "@/components/settings/Languages.vue";
|
import Languages from "@/components/settings/Languages.vue";
|
||||||
|
@ -50,6 +66,7 @@ export default {
|
||||||
color: "",
|
color: "",
|
||||||
showHidden: false,
|
showHidden: false,
|
||||||
quickDownload: false,
|
quickDownload: false,
|
||||||
|
disableOnlyOfficeExt: "",
|
||||||
colorChoices: [
|
colorChoices: [
|
||||||
{ label: "blue", value: "var(--blue)" },
|
{ label: "blue", value: "var(--blue)" },
|
||||||
{ label: "red", value: "var(--red)" },
|
{ label: "red", value: "var(--red)" },
|
||||||
|
@ -78,6 +95,9 @@ export default {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
hasOnlyOfficeEnabled() {
|
||||||
|
return onlyOfficeUrl != "";
|
||||||
|
},
|
||||||
settings() {
|
settings() {
|
||||||
return state.settings;
|
return state.settings;
|
||||||
},
|
},
|
||||||
|
@ -94,11 +114,23 @@ export default {
|
||||||
this.dateFormat = state.user.dateFormat;
|
this.dateFormat = state.user.dateFormat;
|
||||||
this.color = state.user.themeColor;
|
this.color = state.user.themeColor;
|
||||||
this.quickDownload = state.user?.quickDownload;
|
this.quickDownload = state.user?.quickDownload;
|
||||||
|
this.disableOnlyOfficeExt = state.user.disableOnlyOfficeExt;
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.initialized = true;
|
this.initialized = true;
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
formValidation() {
|
||||||
|
let regex = /^\.\w+(?: \.\w+)*$/;
|
||||||
|
return regex.test(this.disableOnlyOfficeExt)
|
||||||
|
},
|
||||||
|
submitOnlyOfficeChange(event) {
|
||||||
|
if (!this.formValidation()) {
|
||||||
|
notify.showError("Invalid input, does not match requirement.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.updateSettings(event)
|
||||||
|
},
|
||||||
setColor(string) {
|
setColor(string) {
|
||||||
this.color = string
|
this.color = string
|
||||||
this.updateSettings()
|
this.updateSettings()
|
||||||
|
@ -118,6 +150,7 @@ export default {
|
||||||
dateFormat: this.dateFormat,
|
dateFormat: this.dateFormat,
|
||||||
themeColor: this.color,
|
themeColor: this.color,
|
||||||
quickDownload: this.quickDownload,
|
quickDownload: this.quickDownload,
|
||||||
|
disableOnlyOfficeExt: this.disableOnlyOfficeExt,
|
||||||
};
|
};
|
||||||
const shouldReload =
|
const shouldReload =
|
||||||
rtlLanguages.includes(data.locale) !== rtlLanguages.includes(i18n.locale);
|
rtlLanguages.includes(data.locale) !== rtlLanguages.includes(i18n.locale);
|
||||||
|
@ -127,6 +160,7 @@ export default {
|
||||||
"dateFormat",
|
"dateFormat",
|
||||||
"themeColor",
|
"themeColor",
|
||||||
"quickDownload",
|
"quickDownload",
|
||||||
|
"disableOnlyOfficeExt"
|
||||||
]);
|
]);
|
||||||
mutations.updateCurrentUser(data);
|
mutations.updateCurrentUser(data);
|
||||||
if (shouldReload) {
|
if (shouldReload) {
|
||||||
|
@ -144,3 +178,9 @@ export default {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.invalid-form {
|
||||||
|
border-color: red !important;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -24,7 +24,7 @@ test("info from search", async ({ page, context }) => {
|
||||||
await expect(page.locator('.break-word')).toHaveText('Display Name: file.tar.gz');
|
await expect(page.locator('.break-word')).toHaveText('Display Name: file.tar.gz');
|
||||||
})
|
})
|
||||||
|
|
||||||
test("copy from listing", async ({ page, context }) => {
|
test("copy from listing 2x", async ({ page, context }) => {
|
||||||
await page.goto("/files/");
|
await page.goto("/files/");
|
||||||
await expect(page).toHaveTitle("Graham's Filebrowser - Files - playwright-files");
|
await expect(page).toHaveTitle("Graham's Filebrowser - Files - playwright-files");
|
||||||
await page.locator('a[aria-label="copyme.txt"]').waitFor({ state: 'visible' });
|
await page.locator('a[aria-label="copyme.txt"]').waitFor({ state: 'visible' });
|
||||||
|
@ -32,8 +32,10 @@ test("copy from listing", async ({ page, context }) => {
|
||||||
await page.locator('.selected-count-header').waitFor({ state: 'visible' });
|
await page.locator('.selected-count-header').waitFor({ state: 'visible' });
|
||||||
await expect(page.locator('.selected-count-header')).toHaveText('1 selected');
|
await expect(page.locator('.selected-count-header')).toHaveText('1 selected');
|
||||||
await page.locator('button[aria-label="Copy file"]').click();
|
await page.locator('button[aria-label="Copy file"]').click();
|
||||||
//await expect(page.locator('.searchContext')).toHaveText('Path: /');
|
await expect(page.locator('div[aria-label="filelist-path"]')).toHaveText('Path: /');
|
||||||
|
await expect(page.locator('li[aria-selected="true"]')).toHaveCount(0);
|
||||||
await page.locator('li[aria-label="myfolder"]').click();
|
await page.locator('li[aria-label="myfolder"]').click();
|
||||||
|
await expect(page.locator('li[aria-selected="true"]')).toHaveCount(1);
|
||||||
await page.locator('button[aria-label="Copy"]').click();
|
await page.locator('button[aria-label="Copy"]').click();
|
||||||
const popup = page.locator('#popup-notification-content');
|
const popup = page.locator('#popup-notification-content');
|
||||||
await popup.waitFor({ state: 'visible' });
|
await popup.waitFor({ state: 'visible' });
|
||||||
|
@ -46,12 +48,41 @@ test("copy from listing", async ({ page, context }) => {
|
||||||
await page.locator('.selected-count-header').waitFor({ state: 'visible' });
|
await page.locator('.selected-count-header').waitFor({ state: 'visible' });
|
||||||
await expect(page.locator('.selected-count-header')).toHaveText('1 selected');
|
await expect(page.locator('.selected-count-header')).toHaveText('1 selected');
|
||||||
await page.locator('button[aria-label="Copy file"]').click();
|
await page.locator('button[aria-label="Copy file"]').click();
|
||||||
//await expect(page.locator('.searchContext')).toHaveText('Path: /');
|
await expect(page.locator('div[aria-label="filelist-path"]')).toHaveText('Path: /myfolder/');
|
||||||
await page.locator('li[aria-label="testdata"]').click();
|
await page.locator('li[aria-label="testdata"]').click();
|
||||||
await page.locator('button[aria-label="Copy"]').click();
|
await page.locator('button[aria-label="Copy"]').click();
|
||||||
const popup2 = page.locator('#popup-notification-content');
|
const popup2 = page.locator('#popup-notification-content');
|
||||||
//await popup2.waitFor({ state: 'visible' });
|
await popup2.waitFor({ state: 'visible' });
|
||||||
//await expect(popup2).toHaveText("Successfully copied file/folder, redirecting...");
|
await expect(popup2).toHaveText("Successfully copied file/folder, redirecting...");
|
||||||
//await page.waitForURL('**/testdata/');
|
await page.waitForURL('**/testdata/');
|
||||||
//await expect(page).toHaveTitle("Graham's Filebrowser - Files - testdata");
|
await expect(page).toHaveTitle("Graham's Filebrowser - Files - testdata");
|
||||||
|
})
|
||||||
|
|
||||||
|
test("delete file", async ({ page, context }) => {
|
||||||
|
await page.goto("/files/");
|
||||||
|
await expect(page).toHaveTitle("Graham's Filebrowser - Files - playwright-files");
|
||||||
|
await page.locator('a[aria-label="deleteme.txt"]').waitFor({ state: 'visible' });
|
||||||
|
await page.locator('a[aria-label="deleteme.txt"]').click({ button: "right" });
|
||||||
|
await page.locator('.selected-count-header').waitFor({ state: 'visible' });
|
||||||
|
await expect(page.locator('.selected-count-header')).toHaveText('1 selected');
|
||||||
|
await page.locator('button[aria-label="Delete"]').click();
|
||||||
|
await expect( page.locator('.card-content')).toHaveText('Are you sure you want to delete this file/folder?/deleteme.txt');
|
||||||
|
await expect(page.locator('div[aria-label="delete-path"]')).toHaveText('/deleteme.txt');
|
||||||
|
await page.locator('button[aria-label="Confirm-Delete"]').click();
|
||||||
|
const popup = page.locator('#popup-notification-content');
|
||||||
|
await popup.waitFor({ state: 'visible' });
|
||||||
|
await expect(popup).toHaveText("Deleted item successfully! reloading...");
|
||||||
|
})
|
||||||
|
|
||||||
|
test("delete nested file prompt", async ({ page, context }) => {
|
||||||
|
await page.goto("/files/folder%23hash/");
|
||||||
|
await expect(page).toHaveTitle("Graham's Filebrowser - Files - folder#hash");
|
||||||
|
await page.locator('a[aria-label="file#.sh"]').waitFor({ state: 'visible' });
|
||||||
|
await page.locator('a[aria-label="file#.sh"]').click({ button: "right" });
|
||||||
|
await page.locator('.selected-count-header').waitFor({ state: 'visible' });
|
||||||
|
await expect(page.locator('.selected-count-header')).toHaveText('1 selected');
|
||||||
|
await page.locator('button[aria-label="Delete"]').click();
|
||||||
|
await expect(page.locator('.card-content')).toHaveText('Are you sure you want to delete this file/folder?/folder#hash/file#.sh');
|
||||||
|
await expect(page.locator('div[aria-label="delete-path"]')).toHaveText('/folder#hash/file#.sh');
|
||||||
|
|
||||||
})
|
})
|
|
@ -0,0 +1,14 @@
|
||||||
|
|
||||||
|
import { test, expect } from "@playwright/test";
|
||||||
|
|
||||||
|
test("navigate with hash in file name", async ({ page, context }) => {
|
||||||
|
await page.goto("/files/");
|
||||||
|
await expect(page).toHaveTitle("Graham's Filebrowser - Files - playwright-files");
|
||||||
|
await page.locator('a[aria-label="folder#hash"]').waitFor({ state: 'visible' });
|
||||||
|
await page.locator('a[aria-label="folder#hash"]').dblclick();
|
||||||
|
await expect(page).toHaveTitle("Graham's Filebrowser - Files - folder#hash");
|
||||||
|
await page.locator('a[aria-label="file#.sh"]').waitFor({ state: 'visible' });
|
||||||
|
await page.locator('a[aria-label="file#.sh"]').dblclick();
|
||||||
|
await expect(page).toHaveTitle("Graham's Filebrowser - Files - file#.sh");
|
||||||
|
await expect(page.locator('.topTitle')).toHaveText('file#.sh');
|
||||||
|
})
|
Loading…
Reference in New Issue