mirror of
https://github.com/filebrowser/filebrowser.git
synced 2024-06-07 23:00:43 +00:00
parent
7d9fb5995a
commit
103457f4e3
@ -768,6 +768,69 @@ header #logout {
|
||||
color: rgba(255, 255, 255, .5);
|
||||
}
|
||||
|
||||
|
||||
/* BREADCRUMB */
|
||||
|
||||
header>div:last-child>div:first-child>* {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
header>div:last-child>div:first-child>i {
|
||||
margin-right: .3em;
|
||||
}
|
||||
|
||||
#breadcrumbs-button {
|
||||
padding: .4em 0.3em;
|
||||
border-radius: .1em;
|
||||
cursor: pointer;
|
||||
transition: .1s ease all;
|
||||
}
|
||||
|
||||
#breadcrumbs-button.active,
|
||||
#breadcrumbs-button:hover {
|
||||
box-shadow: 0 0 5px rgba(0, 0, 0, 0.1);
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
#current-file {
|
||||
line-height: 2.7em;
|
||||
}
|
||||
|
||||
#breadcrumbs {
|
||||
transition: .1s ease all;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
list-style: none;
|
||||
display: inline-flex;
|
||||
flex-direction: column;
|
||||
border-radius: 2px;
|
||||
border-top-left-radius: 0;
|
||||
box-shadow: 0 0 5px rgba(0, 0, 0, 0.1);
|
||||
background: #fff;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 2.3em;
|
||||
min-width: 7em;
|
||||
z-index: 999;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
color: #656565;
|
||||
}
|
||||
|
||||
#breadcrumbs.active {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
#breadcrumbs li {
|
||||
line-height: 1.5em;
|
||||
padding: .3em;
|
||||
}
|
||||
|
||||
|
||||
/* MORE STUFF */
|
||||
|
||||
header>div {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
@ -775,7 +838,7 @@ header>div {
|
||||
}
|
||||
|
||||
header>div:first-child>div:nth-child(1) {
|
||||
margin-right: 2em;
|
||||
margin-right: 1em;
|
||||
font-weight: 500;
|
||||
font-size: 1.5em;
|
||||
line-height: 2;
|
||||
@ -802,10 +865,12 @@ header #file-only {
|
||||
padding-right: .3em;
|
||||
margin-right: .3em;
|
||||
transition: .2s ease all;
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
#file-only.disabled {
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
header p {
|
||||
@ -921,8 +986,8 @@ header .action span {
|
||||
}
|
||||
|
||||
.floating .action {
|
||||
background-color: #68EFAD;
|
||||
color: #306e50;
|
||||
background-color: #2196f3 !important;
|
||||
color: #fff;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, .06), 0 1px 2px rgba(0, 0, 0, .12);
|
||||
}
|
||||
|
||||
@ -954,31 +1019,36 @@ header .action span {
|
||||
/* LISTING */
|
||||
|
||||
#listing {
|
||||
max-width: calc(100% - 1.2em);
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#listing h2 {
|
||||
margin: 0 0 0 0.5em;
|
||||
font-size: 1em;
|
||||
color: rgba(0, 0, 0, 0.2);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
#listing.list h2 {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#listing>div {
|
||||
display: flex;
|
||||
padding: 0;
|
||||
flex-wrap: wrap;
|
||||
justify-content: flex-start;
|
||||
max-width: calc(100% - 2.2em);
|
||||
width: 100%;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
#listing.list {
|
||||
flex-direction: column;
|
||||
margin-top: -1em;
|
||||
margin-top: 2em;
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
#listing.list .item {
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
border: 0;
|
||||
box-shadow: none;
|
||||
border-radius: 0;
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
|
||||
padding: 1em;
|
||||
}
|
||||
|
||||
#listing .item {
|
||||
margin: .5em;
|
||||
padding: 0.5em;
|
||||
@ -991,15 +1061,7 @@ header .action span {
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
color: #6f6f6f;
|
||||
}
|
||||
|
||||
.item:hover {
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, .12), 0 1px 2px rgba(0, 0, 0, .24) !important;
|
||||
}
|
||||
|
||||
.item[aria-selected=true] {
|
||||
background: #2196f3 !important;
|
||||
color: #fff !important;
|
||||
transition: .1s ease all;
|
||||
}
|
||||
|
||||
.item div:first-of-type {
|
||||
@ -1028,6 +1090,56 @@ header .action span {
|
||||
vertical-align: bottom;
|
||||
}
|
||||
|
||||
#listing .item.header {
|
||||
display: none !important;
|
||||
background-color: #ccc;
|
||||
}
|
||||
|
||||
#listing.list .item.header {
|
||||
display: flex !important;
|
||||
background: #fafafa;
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
top: 8em;
|
||||
left: 0;
|
||||
z-index: 999;
|
||||
}
|
||||
|
||||
#listing.list .item.header>div:first-child {
|
||||
width: 0;
|
||||
}
|
||||
|
||||
#listing.list .item.header>div:last-child {
|
||||
/* width: 100%; */
|
||||
}
|
||||
|
||||
#listing.list .item.header>div:last-child *:first-child {
|
||||
margin-right: 3em;
|
||||
}
|
||||
|
||||
#listing.list .item {
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
border: 0;
|
||||
box-shadow: none;
|
||||
border-radius: 0;
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
|
||||
padding: 1em;
|
||||
}
|
||||
|
||||
#listing .item:hover {
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, .12), 0 1px 2px rgba(0, 0, 0, .24) !important;
|
||||
}
|
||||
|
||||
#listing.list .item:hover {
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
#listing .item[aria-selected=true] {
|
||||
background: #2196f3 !important;
|
||||
color: #fff !important;
|
||||
}
|
||||
|
||||
#listing.list .item div:first-of-type {
|
||||
width: 3em;
|
||||
}
|
||||
@ -1038,6 +1150,21 @@ header .action span {
|
||||
|
||||
#listing.list .item div:last-of-type {
|
||||
width: calc(100% - 3em);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
#listing .item div:last-of-type * {
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#listing.list .item div:last-of-type span {
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
#listing.list .item div:last-of-type p:first-of-type {
|
||||
width: 25%;
|
||||
}
|
||||
|
||||
|
||||
|
@ -185,7 +185,7 @@ function resetSearchText() {
|
||||
let box = document.querySelector('#search > div div');
|
||||
|
||||
if (user.AllowCommands) {
|
||||
box.innerHTML = `Search or use one of your supported commands: ${user.Commands.join(", ")} `;
|
||||
box.innerHTML = `Search or use one of your supported commands: ${user.Commands.join(", ")}.`;
|
||||
} else {
|
||||
box.innerHTML = "Type and press enter to search.";
|
||||
}
|
||||
@ -323,6 +323,11 @@ document.addEventListener("DOMContentLoaded", function(event) {
|
||||
if (user.AllowEdit) {
|
||||
buttons.delete.addEventListener("click", deleteEvent);
|
||||
}
|
||||
|
||||
document.getElementById("breadcrumbs-button").addEventListener("click", event => {
|
||||
event.currentTarget.classList.toggle("active");
|
||||
document.getElementById("breadcrumbs").classList.toggle("active");
|
||||
});
|
||||
|
||||
setupSearch();
|
||||
return false;
|
||||
|
@ -41,13 +41,12 @@ var renameEvent = function(event) {
|
||||
let keyDownEvent = (event) => {
|
||||
if (event.keyCode == 13) {
|
||||
let newName = span.innerHTML,
|
||||
newLink = removeLastDirectoryPartOf(toWebDavURL(link)) + newName,
|
||||
newLink = removeLastDirectoryPartOf(toWebDavURL(link)) + "/" + newName,
|
||||
html = document.getElementById('rename').changeToLoading(),
|
||||
request = new XMLHttpRequest();
|
||||
|
||||
request.open('MOVE', toWebDavURL(link));
|
||||
request.setRequestHeader('Destination', newLink);
|
||||
request.setRequestHeader('Content-type', 'text/plain; charset=utf-8');
|
||||
request.send();
|
||||
request.onreadystatechange = function() {
|
||||
// TODO: redirect if it's moved to another folder
|
||||
|
@ -33,7 +33,7 @@
|
||||
<div>
|
||||
<div><p>File Manager</p></div>
|
||||
<div id="search">
|
||||
<i class="material-icons" title="Storage">storage</i>
|
||||
<i class="material-icons" title="Search">search</i>
|
||||
<input type="text" placeholder="Search or execute a command...">
|
||||
<div>
|
||||
<div>Loading...</div>
|
||||
@ -49,20 +49,17 @@
|
||||
<!-- BOTTOM BAR -->
|
||||
<div>
|
||||
<div>
|
||||
{{ $lnk := .PreviousLink }}
|
||||
<div class="action{{ if eq $lnk ""}} disabled{{ end }}" id="prev">
|
||||
{{ if ne $lnk ""}}<a href="{{ $lnk }}">{{ end }}
|
||||
<i class="material-icons" title="Previous">subdirectory_arrow_left</i>
|
||||
{{ if ne $lnk ""}}</a>{{ end }}
|
||||
|
||||
{{ if ne $lnk ""}}
|
||||
<ul class="prev-links">
|
||||
{{ range $link, $name := .BreadcrumbMap }}<a href="{{ $absURL }}{{ $link }}"><li>{{ $name }}</li></a>{{ end }}
|
||||
</ul>
|
||||
{{ end }}
|
||||
</div>
|
||||
{{ if ne .Name "/"}}
|
||||
<p id="breadcrumbs-button">Previous</p>
|
||||
<ul id="breadcrumbs">
|
||||
{{ range $item := .BreadcrumbMap }}
|
||||
<a href="{{ $absURL }}{{ $item.URL }}"><li>{{ $item.Name }}</li></a>
|
||||
{{ end }}
|
||||
</ul><i class="material-icons">keyboard_arrow_right</i>
|
||||
|
||||
{{ if ne .Name "/"}}<p>{{ .Name }}</p>{{ end }}
|
||||
{{ end }}
|
||||
|
||||
<p id="current-file">{{ if ne .Name "/"}}{{ .Name }}{{ else }}Root{{ end }}</p>
|
||||
</div>
|
||||
|
||||
<!-- ACTIONS -->
|
||||
|
@ -2,41 +2,76 @@
|
||||
{{ with .Data }}
|
||||
<div class="listing">
|
||||
<div class="container" id="listing">
|
||||
{{- range .Items}}
|
||||
{{ if .UserAllowed }}
|
||||
<div ondragstart="itemDragStart(event)"
|
||||
{{ if .IsDir}}ondragover="itemDragOver(event)" ondrop="itemDrop(event)"{{ end }}
|
||||
draggable="true"
|
||||
class="item"
|
||||
onclick="selectItemEvent(event)"
|
||||
ondblclick="openItemEvent(event)"
|
||||
data-dir="{{ .IsDir }}"
|
||||
data-url="{{ .URL }}"
|
||||
id="{{ EncodeBase64 .Name }}">
|
||||
<div>
|
||||
{{- if .IsDir}}
|
||||
<i class="material-icons">folder</i>
|
||||
{{- else}}
|
||||
<i class="material-icons">insert_drive_file</i>
|
||||
{{- end}}
|
||||
</div>
|
||||
<div>
|
||||
<span class="name">{{.Name}}</span>
|
||||
{{- if .IsDir}}
|
||||
<p data-order="-1">—</p>
|
||||
{{- else}}
|
||||
<p data-order="{{.Size}}">{{.HumanSize}}</p>
|
||||
{{- end}}
|
||||
<p>
|
||||
<time datetime="{{.HumanModTime "2006-01-02T15:04:05Z"}}">{{.HumanModTime "2 Jan 2006 03:04 PM"}}</time>
|
||||
</p>
|
||||
<div>
|
||||
<div class="item header">
|
||||
<div>
|
||||
|
||||
</div>
|
||||
<div>
|
||||
<span class="name">Name</span>
|
||||
<p>Size</p>
|
||||
<p>Modified time</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{ end }}
|
||||
{{- end}}
|
||||
<h2>Folders</h2>
|
||||
<div>
|
||||
{{- range .Items}}
|
||||
{{ if and (.UserAllowed) (.IsDir) }}
|
||||
{{ template "item" .}}
|
||||
{{ end }}
|
||||
{{- end}}
|
||||
</div>
|
||||
<h2>Files</h2>
|
||||
<div>
|
||||
{{- range .Items}}
|
||||
{{ if and (.UserAllowed) (not .IsDir) }}
|
||||
{{ template "item" .}}
|
||||
{{ end }}
|
||||
{{- end}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<input style="display:none" type="file" id="upload-input" onchange="handleFiles(this.files, '')" value="Upload" multiple>
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
|
||||
{{ define "item" }}
|
||||
<div ondragstart="itemDragStart(event)"
|
||||
{{ if .IsDir}}ondragover="itemDragOver(event)" ondrop="itemDrop(event)"{{ end }}
|
||||
draggable="true"
|
||||
class="item"
|
||||
onclick="selectItemEvent(event)"
|
||||
ondblclick="openItemEvent(event)"
|
||||
data-dir="{{ .IsDir }}"
|
||||
data-url="{{ .URL }}"
|
||||
id="{{ EncodeBase64 .Name }}">
|
||||
<div>
|
||||
{{- if .IsDir}}
|
||||
<i class="material-icons">folder</i>
|
||||
{{- else}}
|
||||
{{ if eq .Type "image" }}
|
||||
<i class="material-icons">insert_photo</i>
|
||||
{{ else if eq .Type "audio" }}
|
||||
<i class="material-icons">volume_up</i>
|
||||
{{ else if eq .Type "video" }}
|
||||
<i class="material-icons">movie</i>
|
||||
{{ else }}
|
||||
<i class="material-icons">insert_drive_file</i>
|
||||
{{ end }}
|
||||
{{- end}}
|
||||
</div>
|
||||
<div>
|
||||
<span class="name">{{.Name}}</span>
|
||||
{{- if .IsDir}}
|
||||
<p data-order="-1">—</p>
|
||||
{{- else}}
|
||||
<p data-order="{{.Size}}">{{.HumanSize}}</p>
|
||||
{{- end}}
|
||||
<p>
|
||||
<time datetime="{{.HumanModTime "2006-01-02T15:04:05Z"}}">{{.HumanModTime "2 Jan 2006 03:04 PM"}}</time>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
{{ end }}
|
||||
|
@ -67,11 +67,15 @@ func GetListing(u *config.User, filePath string, baseURL string) (*Listing, erro
|
||||
|
||||
// Absolute URL
|
||||
url := url.URL{Path: baseURL + name}
|
||||
fileinfos = append(fileinfos, Info{
|
||||
|
||||
i := Info{
|
||||
FileInfo: f,
|
||||
URL: url.String(),
|
||||
UserAllowed: u.Allowed(filePath),
|
||||
})
|
||||
UserAllowed: u.Allowed("/" + name),
|
||||
}
|
||||
i.RetrieveFileType()
|
||||
|
||||
fileinfos = append(fileinfos, i)
|
||||
}
|
||||
|
||||
return &Listing{
|
||||
|
@ -31,7 +31,7 @@ func ServeListing(w http.ResponseWriter, r *http.Request, c *config.Config, u *c
|
||||
|
||||
// Copy the query values into the Listing struct
|
||||
var limit int
|
||||
listing.Sort, listing.Order, limit, err = handleSortOrder(w, r, c.Scope)
|
||||
listing.Sort, listing.Order, limit, err = handleSortOrder(w, r, c.BaseURL)
|
||||
if err != nil {
|
||||
return http.StatusBadRequest, err
|
||||
}
|
||||
|
22
page/page.go
22
page/page.go
@ -32,10 +32,16 @@ type Info struct {
|
||||
Token string
|
||||
}
|
||||
|
||||
// BreadcrumbMapItem ...
|
||||
type BreadcrumbMapItem struct {
|
||||
Name string
|
||||
URL string
|
||||
}
|
||||
|
||||
// BreadcrumbMap returns p.Path where every element is a map
|
||||
// of URLs and path segment names.
|
||||
func (i Info) BreadcrumbMap() map[string]string {
|
||||
result := map[string]string{}
|
||||
func (i Info) BreadcrumbMap() []BreadcrumbMapItem {
|
||||
result := []BreadcrumbMapItem{}
|
||||
|
||||
if len(i.Path) == 0 {
|
||||
return result
|
||||
@ -54,11 +60,17 @@ func (i Info) BreadcrumbMap() map[string]string {
|
||||
}
|
||||
|
||||
if i == 0 && part == "" {
|
||||
// Leading slash (root)
|
||||
result["/"] = "/"
|
||||
result = append([]BreadcrumbMapItem{{
|
||||
Name: "/",
|
||||
URL: "/",
|
||||
}}, result...)
|
||||
continue
|
||||
}
|
||||
result[strings.Join(parts[:i+1], "/")] = part
|
||||
|
||||
result = append([]BreadcrumbMapItem{{
|
||||
Name: part,
|
||||
URL: strings.Join(parts[:i+1], "/"),
|
||||
}}, result...)
|
||||
}
|
||||
|
||||
return result
|
||||
|
Loading…
Reference in New Issue
Block a user