feat: webdav dir reander

v3
veypi 1 month ago
parent a4bb05d60d
commit acadf65b93

@ -0,0 +1,133 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>OA</title>
<style>
html {
overflow: hidden;
}
body {
overflow: auto;
height: 100vh;
width: 100vw;
font-family: Arial, sans-serif;
background-color: #f4f4f9;
margin: 0;
padding: 0;
background: #f0f0f0;
}
.header {
background: #fff;
width: 100vw;
height: 4rem;
line-height: 4rem;
font-size: 2rem;
padding-left: 4rem;
box-sizing: border-box;
box-shadow: 0px 0px 20px 0px rgb(0 0 0 / 10%);
}
.container {
padding: 2rem;
box-sizing: border-box;
width: 100vw;
height: calc(100vh - 4rem);
display: flex;
flex-direction: column;
justify-content: start;
align-items: center;
}
.core {
width: 70%;
min-height: 50%;
background: #fff;
border-radius: 1rem;
box-shadow: 0 2px 5px 1px rgb(0 0 0 / 5%);
}
.meta {
border-bottom: solid 1px #000;
height: 3rem;
font-size: 1.25rem;
line-height: 2rem;
padding: 0.5rem 2rem;
box-sizing: border-box;
display: flex;
gap: 1rem;
}
.core-box {
padding: 0.5rem 2rem;
box-sizing: border-box;
height: calc(100% - 3rem);
overflow: auto;
}
.line {
display: flex;
height: 2rem;
font-size: 1rem;
line-height: 2rem;
}
a:link {
text-decoration: none;
font-weight: bold;
}
</style>
</head>
<body>
<div class="header">
<a href="/">/</a>{{ range .path }}{{if .}}<span><a class="path_tag">{{.}}</a></span>/{{end}}{{end}}
</div>
<div class="container">
<div class="core">
<div class="meta">
<span>{{.cdir}} directory</span>
<span>{{.cfile}} files</span>
<span>{{.size}}</span>
</div>
<div class="core-box">
{{ range .files }}
<div class="line">
<div style="flex: 1"><a href="{{index . 1}}">{{index . 0}}</a></div>
<div style="flex: 0 0 10rem">{{index . 2}}</div>
<div style="flex: 0 0 10rem" class="2localtime">{{index . 3}}</div>
</div>
{{end}}
<div style="height: 5rem;"></div>
</div>
</div>
</div>
</body>
<script>
// 当文档加载完成后执行
document.addEventListener("DOMContentLoaded", function () {
var divs = document.getElementsByClassName('path_tag');
var base = "/"
for (var i = 0; i < divs.length; i++) {
var div = divs[i];
if (div.textContent) {
base = base + div.textContent + "/"
div.setAttribute('href', base)
}
}
var divs = document.getElementsByClassName('2localtime');
for (var i = 0; i < divs.length; i++) {
var div = divs[i];
if (div.textContent) {
div.textContent = new Date(div.textContent).toLocaleString();
}
}
});
</script>
</html>

@ -8,11 +8,12 @@
package webdav package webdav
import ( import (
"embed"
"fmt" "fmt"
"html/template"
"io/fs" "io/fs"
"net/http" "net/http"
"net/url" "net/url"
"sort"
"strings" "strings"
"github.com/veypi/utils/logv" "github.com/veypi/utils/logv"
@ -36,54 +37,72 @@ func (d dirEntryDirs) len() int { return len(d) }
func (d dirEntryDirs) isDir(i int) bool { return d[i].IsDir() } func (d dirEntryDirs) isDir(i int) bool { return d[i].IsDir() }
func (d dirEntryDirs) name(i int) string { return d[i].Name() } func (d dirEntryDirs) name(i int) string { return d[i].Name() }
func dirList(w http.ResponseWriter, r *http.Request, f File) { //go:embed dir.html
var dirTemplate embed.FS // 嵌入文件系统
func size2Label(s int64) string {
if s < 1024 {
return fmt.Sprintf("%d B", s)
} else if s < 1048576 {
return fmt.Sprintf("%d KB", s/1024)
} else if s < 1073741824 {
return fmt.Sprintf("%d MB", s/1024/1024)
} else {
return fmt.Sprintf("%d MB", s/1024/1024/1024)
}
}
func dirList(w http.ResponseWriter, r *http.Request, f File, rootPath string) {
// Prefer to use ReadDir instead of Readdir, // Prefer to use ReadDir instead of Readdir,
// because the former doesn't require calling // because the former doesn't require calling
// Stat on every entry of a directory on Unix. // Stat on every entry of a directory on Unix.
var dirs anyDirs
var err error var err error
if d, ok := f.(fs.ReadDirFile); ok { defer func() {
var list dirEntryDirs if e := recover(); e != nil {
list, err = d.ReadDir(-1) err = fmt.Errorf("%v", e)
dirs = list
} else {
var list fileInfoDirs
list, err = f.Readdir(-1)
dirs = list
} }
if err != nil { if err != nil {
logv.Warn().Msgf("http: error reading directory: %v", err) logv.Warn().Msgf("http: error reading directory: %v", err)
http.Error(w, "Error reading directory", http.StatusInternalServerError) http.Error(w, "Error reading directory", http.StatusInternalServerError)
return
} }
sort.Slice(dirs, func(i, j int) bool { return dirs.name(i) < dirs.name(j) }) }()
dir_count := 0
w.Header().Set("Content-Type", "text/html; charset=utf-8") f_count := 0
fmt.Fprintf(w, "<!doctype html>\n") var file_bytes int64
fmt.Fprintf(w, "<meta name=\"viewport\" content=\"width=device-width\">\n") files := make([][4]any, 0, 5)
fmt.Fprintf(w, "<pre>\n") dirs := make([][4]any, 0, 5)
for i, n := 0, dirs.len(); i < n; i++ { if d, ok := f.(fs.ReadDirFile); ok {
name := dirs.name(i) for _, item := range logv.AssertFuncErr(d.ReadDir(-1)) {
if dirs.isDir(i) { if item.IsDir() {
name += "/" name := item.Name() + "/"
dir_count += 1
fstat, _ := item.Info()
furl := url.URL{Path: name}
dirs = append(dirs, [4]any{name, furl.String(), "-----", fstat.ModTime().UTC()})
} else {
name := item.Name()
f_count += 1
fstat, _ := item.Info()
file_bytes += fstat.Size()
furl := url.URL{Path: name}
files = append(files, [4]any{name, furl.String(), size2Label(fstat.Size()), fstat.ModTime().UTC()})
} }
// name may contain '?' or '#', which must be escaped to remain
// part of the URL path, and not indicate the start of a query
// string or fragment.
url := url.URL{Path: name}
fmt.Fprintf(w, "<a href=\"%s\">%s</a>\n", url.String(), htmlReplacer.Replace(name))
} }
fmt.Fprintf(w, "</pre>\n") } else {
for _, item := range logv.AssertFuncErr(d.ReadDir(-1)) {
name := item.Name()
f_count += 1
fstat, _ := item.Info()
file_bytes += fstat.Size()
furl := url.URL{Path: name}
files = append(files, [4]any{name, furl.String(), size2Label(fstat.Size()), fstat.ModTime().UTC()})
}
} }
var htmlReplacer = strings.NewReplacer( dirBody := logv.AssertFuncErr(dirTemplate.ReadFile("dir.html"))
"&", "&amp;",
"<", "&lt;", w.Header().Set("Content-Type", "text/html; charset=utf-8")
">", "&gt;", tpl := logv.AssertFuncErr(template.New("").Parse(string(dirBody)))
// "&#34;" is shorter than "&quot;". logv.AssertError(tpl.Execute(w, map[string]any{"files": append(dirs, files...), "path": strings.Split(rootPath, "/"), "cdir": dir_count, "cfile": f_count, "size": size2Label(file_bytes)}))
`"`, "&#34;", }
// "&#39;" is shorter than "&apos;" and apos was not in HTML until HTML5.
"'", "&#39;",
)

@ -232,7 +232,7 @@ func (h *Handler) handleGetHeadPost(w http.ResponseWriter, r *http.Request) (sta
} }
if fi.IsDir() { if fi.IsDir() {
if h.EnableDirRender { if h.EnableDirRender {
dirList(w, r, f) dirList(w, r, f, r.URL.Path)
return 0, nil return 0, nil
} }
return http.StatusMethodNotAllowed, nil return http.StatusMethodNotAllowed, nil

Loading…
Cancel
Save