diff --git a/_assets/_old/js/common_old.js b/_assets/_old/js/common_old.js
index 6ab995cb..24807bbc 100644
--- a/_assets/_old/js/common_old.js
+++ b/_assets/_old/js/common_old.js
@@ -75,22 +75,6 @@ buttons.setDone = function (name, success = true) {
* EVENTS *
* *
* * * * * * * * * * * * * * * */
-function closePrompt (event) {
- let prompt = document.querySelector('.prompt')
-
- if (!prompt) return
-
- if (typeof event !== 'undefined') {
- event.preventDefault()
- }
-
- document.querySelector('.overlay').classList.remove('active')
- prompt.classList.remove('active')
-
- setTimeout(() => {
- prompt.remove()
- }, 100)
-}
function notImplemented (event) {
event.preventDefault()
@@ -194,26 +178,7 @@ function deleteEvent (event) {
* * * * * * * * * * * * * * * */
document.addEventListener('DOMContentLoaded', function (event) {
- overlay = document.querySelector('.overlay')
- clickOverlay = document.querySelector('#click-overlay')
- buttons.logout = document.getElementById('logout')
- buttons.delete = document.getElementById('delete')
- buttons.previous = document.getElementById('previous')
- buttons.info = document.getElementById('info')
-
- // Attach event listeners
- buttons.logout.addEventListener('click', logoutEvent)
- buttons.info.addEventListener('click', infoEvent)
-
- templates.question = document.querySelector('#question-template')
- templates.info = document.querySelector('#info-template')
- templates.message = document.querySelector('#message-template')
- templates.move = document.querySelector('#move-template')
-
- if (data.user.AllowEdit) {
- buttons.delete.addEventListener('click', deleteEvent)
- }
let dropdownButtons = document.querySelectorAll('.action[data-dropdown]')
Array.from(dropdownButtons).forEach(button => {
@@ -228,15 +193,6 @@ document.addEventListener('DOMContentLoaded', function (event) {
})
})
- overlay.addEventListener('click', event => {
- if (document.querySelector('.help.active')) {
- closeHelp(event)
- return
- }
-
- closePrompt(event)
- })
-
let mainActions = document.getElementById('main-actions')
document.getElementById('more').addEventListener('click', event => {
diff --git a/_assets/src/App.vue b/_assets/src/App.vue
index 67caad42..a24a751a 100644
--- a/_assets/src/App.vue
+++ b/_assets/src/App.vue
@@ -50,7 +50,7 @@
-
+
@@ -78,14 +78,18 @@ function updateColumnSizes () {
items.style.width = `calc(${100 / columns}% - 1em)`
}
+function resetPrompts () {
+ window.info.showHelp = false
+ window.info.showInfo = false
+ window.info.showDelete = false
+ window.info.showRename = false
+ window.info.showMove = false
+}
+
window.addEventListener('keydown', (event) => {
// Esc!
if (event.keyCode === 27) {
- window.info.showHelp = false
- window.info.showInfo = false
- window.info.showDelete = false
- window.info.showRename = false
- window.info.showMove = false
+ resetPrompts()
// Unselect all files and folders.
if (window.info.req.kind === 'listing') {
@@ -166,7 +170,8 @@ export default {
showUpload: function () {
if (this.req.kind === 'editor') return false
return this.user.allowNew
- }
+ },
+ resetPrompts: resetPrompts
}
}
diff --git a/editor.go b/editor.go
deleted file mode 100644
index 49518eb4..00000000
--- a/editor.go
+++ /dev/null
@@ -1,121 +0,0 @@
-package filemanager
-
-import (
- "bytes"
- "errors"
- "net/http"
- "path/filepath"
- "strings"
-
- "github.com/hacdias/filemanager/frontmatter"
- "github.com/spf13/hugo/parser"
-)
-
-// editor contains the information to fill the editor template.
-type editor struct {
- *fileInfo
- Class string `json:"class"`
- Mode string `json:"mode"`
- Visual bool `json:"visual"`
- Content string `json:"content"`
- FrontMatter struct {
- Content *frontmatter.Content
- Rune rune
- } `json:"frontmatter"`
-}
-
-// getEditor gets the editor based on a Info struct
-func getEditor(r *http.Request, i *fileInfo) (*editor, error) {
- var err error
-
- // Create a new editor variable and set the mode
- e := &editor{fileInfo: i}
- e.Mode = editorMode(i.Name)
- e.Class = editorClass(e.Mode)
-
- if e.Class == "frontmatter-only" || e.Class == "complete" {
- e.Visual = true
- }
-
- if r.URL.Query().Get("visual") == "false" {
- e.Class = "content-only"
- }
-
- hasRune := frontmatter.HasRune(i.content)
-
- if e.Class == "frontmatter-only" && !hasRune {
- e.FrontMatter.Rune, err = frontmatter.StringFormatToRune(e.Mode)
- if err != nil {
- goto Error
- }
- i.content = frontmatter.AppendRune(i.content, e.FrontMatter.Rune)
- hasRune = true
- }
-
- if e.Class == "frontmatter-only" && hasRune {
- e.FrontMatter.Content, _, err = frontmatter.Pretty(i.content)
- if err != nil {
- goto Error
- }
- }
-
- if e.Class == "complete" && hasRune {
- var page parser.Page
- // Starts a new buffer and parses the file using Hugo's functions
- buffer := bytes.NewBuffer(i.content)
- page, err = parser.ReadFrom(buffer)
-
- if err != nil {
- goto Error
- }
-
- // Parses the page content and the frontmatter
- e.Content = strings.TrimSpace(string(page.Content()))
- e.FrontMatter.Rune = rune(i.content[0])
- e.FrontMatter.Content, _, err = frontmatter.Pretty(page.FrontMatter())
- }
-
- if e.Class == "complete" && !hasRune {
- err = errors.New("Complete but without rune")
- }
-
-Error:
- if e.Class == "content-only" || err != nil {
- e.Class = "content-only"
- e.Content = i.StringifyContent()
- }
-
- return e, nil
-}
-
-func editorClass(mode string) string {
- switch mode {
- case "json", "toml", "yaml":
- return "frontmatter-only"
- case "markdown", "asciidoc", "rst":
- return "complete"
- }
-
- return "content-only"
-}
-
-func editorMode(filename string) string {
- mode := strings.TrimPrefix(filepath.Ext(filename), ".")
-
- switch mode {
- case "md", "markdown", "mdown", "mmark":
- mode = "markdown"
- case "asciidoc", "adoc", "ad":
- mode = "asciidoc"
- case "rst":
- mode = "rst"
- case "html", "htm":
- mode = "html"
- case "js":
- mode = "javascript"
- case "go":
- mode = "golang"
- }
-
- return mode
-}
diff --git a/file.go b/file.go
index 7df7cd37..b80f7dd1 100644
--- a/file.go
+++ b/file.go
@@ -1,6 +1,7 @@
package filemanager
import (
+ "bytes"
"context"
"crypto/md5"
"crypto/sha1"
@@ -19,16 +20,17 @@ import (
"sort"
"strings"
"time"
+
+ "github.com/hacdias/filemanager/frontmatter"
+ "github.com/spf13/hugo/parser"
)
var (
errInvalidOption = errors.New("Invalid option")
)
-// fileInfo contains the information about a particular file or directory.
-type fileInfo struct {
- // Used to store the file's content temporarily.
- content []byte
+// file contains the information about a particular file or directory.
+type file struct {
// The name of the file.
Name string `json:"name"`
// The Size of the file.
@@ -50,14 +52,17 @@ type fileInfo struct {
// Indicates the file content type: video, text, image, music or blob.
Type string `json:"type"`
// Stores the content of a text file.
- Content string `json:"content"`
+ Content string `json:"content,omitempty"`
+
+ Editor *editor `json:"editor,omitempty"`
+
+ *listing `json:",omitempty"`
}
// A listing is the context used to fill out a template.
type listing struct {
- *fileInfo
// The items (files and folders) in the path.
- Items []fileInfo `json:"items"`
+ Items []file `json:"items"`
// The number of directories in the listing.
NumDirs int `json:"numDirs"`
// The number of files (items that aren't directories) in the listing.
@@ -66,17 +71,30 @@ type listing struct {
Sort string `json:"sort"`
// And which order.
Order string `json:"order"`
- // If ≠0 then Items have been limited to that many elements.
- ItemsLimitedTo int `json:"ItemsLimitedTo"`
- Display string `json:"display"`
+ // Displays in mosaic or list.
+ Display string `json:"display"`
+}
+
+// editor contains the information to fill the editor template.
+type editor struct {
+ // Indicates if the content has only frontmatter, only content, or both.
+ Mode string `json:"type"`
+ // File content language.
+ Language string `json:"language"`
+ // This indicates if the editor should be visual or not.
+ Visual bool `json:"visual"`
+ FrontMatter struct {
+ Content *frontmatter.Content `json:"content"`
+ Rune rune `json:"rune"`
+ } `json:"frontmatter"`
}
// getInfo gets the file information and, in case of error, returns the
// respective HTTP error code
-func getInfo(url *url.URL, c *FileManager, u *User) (*fileInfo, error) {
+func getInfo(url *url.URL, c *FileManager, u *User) (*file, error) {
var err error
- i := &fileInfo{URL: c.RootURL() + url.Path}
+ i := &file{URL: c.RootURL() + url.Path}
i.VirtualPath = url.Path
i.VirtualPath = strings.TrimPrefix(i.VirtualPath, "/")
i.VirtualPath = "/" + i.VirtualPath
@@ -99,29 +117,31 @@ func getInfo(url *url.URL, c *FileManager, u *User) (*fileInfo, error) {
}
// getListing gets the information about a specific directory and its files.
-func getListing(u *User, filePath string, baseURL string, i *fileInfo) (*listing, error) {
+func (i *file) getListing(c *requestContext, r *http.Request) error {
+ baseURL := c.fm.RootURL() + r.URL.Path
+
// Gets the directory information using the Virtual File System of
// the user configuration.
- file, err := u.fileSystem.OpenFile(context.TODO(), filePath, os.O_RDONLY, 0)
+ f, err := c.us.fileSystem.OpenFile(context.TODO(), c.fi.VirtualPath, os.O_RDONLY, 0)
if err != nil {
- return nil, err
+ return err
}
- defer file.Close()
+ defer f.Close()
// Reads the directory and gets the information about the files.
- files, err := file.Readdir(-1)
+ files, err := f.Readdir(-1)
if err != nil {
- return nil, err
+ return err
}
var (
- fileinfos []fileInfo
+ fileinfos []file
dirCount, fileCount int
)
for _, f := range files {
name := f.Name()
- allowed := u.Allowed("/" + name)
+ allowed := c.us.Allowed("/" + name)
if !allowed {
continue
@@ -137,7 +157,7 @@ func getListing(u *User, filePath string, baseURL string, i *fileInfo) (*listing
// Absolute URL
url := url.URL{Path: baseURL + name}
- i := fileInfo{
+ i := file{
Name: f.Name(),
Size: f.Size(),
ModTime: f.ModTime(),
@@ -150,29 +170,101 @@ func getListing(u *User, filePath string, baseURL string, i *fileInfo) (*listing
fileinfos = append(fileinfos, i)
}
- return &listing{
- fileInfo: i,
+ i.listing = &listing{
Items: fileinfos,
NumDirs: dirCount,
NumFiles: fileCount,
- }, nil
+ }
+
+ return nil
+}
+
+// getEditor gets the editor based on a Info struct
+func (i *file) getEditor(r *http.Request) error {
+ var err error
+
+ // Create a new editor variable and set the mode
+ e := &editor{
+ Language: editorLanguage(i.Extension),
+ }
+
+ e.Mode = editorMode(e.Language)
+
+ if e.Mode == "frontmatter-only" || e.Mode == "complete" {
+ e.Visual = true
+ }
+
+ if r.URL.Query().Get("visual") == "false" {
+ e.Mode = "content-only"
+ }
+
+ hasRune := frontmatter.HasRune(i.Content)
+
+ if e.Mode == "frontmatter-only" && !hasRune {
+ e.FrontMatter.Rune, err = frontmatter.StringFormatToRune(e.Mode)
+ if err != nil {
+ goto Error
+ }
+ i.Content = frontmatter.AppendRune(i.Content, e.FrontMatter.Rune)
+ hasRune = true
+ }
+
+ if e.Mode == "frontmatter-only" && hasRune {
+ e.FrontMatter.Content, _, err = frontmatter.Pretty([]byte(i.Content))
+ if err != nil {
+ goto Error
+ }
+ }
+
+ if e.Mode == "complete" && hasRune {
+ var page parser.Page
+ content := []byte(i.Content)
+ // Starts a new buffer and parses the file using Hugo's functions
+
+ buffer := bytes.NewBuffer(content)
+ page, err = parser.ReadFrom(buffer)
+
+ if err != nil {
+ goto Error
+ }
+
+ // Parses the page content and the frontmatter
+ i.Content = strings.TrimSpace(string(page.Content()))
+ e.FrontMatter.Rune = rune(content[0])
+ e.FrontMatter.Content, _, err = frontmatter.Pretty(page.FrontMatter())
+ }
+
+ if e.Mode == "complete" && !hasRune {
+ err = errors.New("Complete but without rune")
+ }
+
+Error:
+ if e.Mode == "content-only" || err != nil {
+ e.Mode = "content-only"
+ }
+
+ i.Editor = e
+ return nil
}
// RetrieveFileType obtains the mimetype and converts it to a simple
// type nomenclature.
-func (i *fileInfo) RetrieveFileType() error {
+func (i *file) RetrieveFileType() error {
+ var content []byte
+ var err error
+
// Tries to get the file mimetype using its extension.
mimetype := mime.TypeByExtension(i.Extension)
if mimetype == "" {
- err := i.Read()
+ content, err = ioutil.ReadFile(i.Path)
if err != nil {
return err
}
// Tries to get the file mimetype using its first
// 512 bytes.
- mimetype = http.DetectContentType(i.content)
+ mimetype = http.DetectContentType(content)
}
if strings.HasPrefix(mimetype, "video") {
@@ -192,12 +284,12 @@ func (i *fileInfo) RetrieveFileType() error {
if strings.HasPrefix(mimetype, "text") {
i.Type = "text"
- return nil
+ goto End
}
if strings.HasPrefix(mimetype, "application/javascript") {
i.Type = "text"
- return nil
+ goto End
}
// If the type isn't text (and is blob for example), it will check some
@@ -210,24 +302,24 @@ func (i *fileInfo) RetrieveFileType() error {
}
i.Type = "blob"
+
+End:
+ // If the file type is text, save its content.
+ if i.Type == "text" {
+ if len(content) == 0 {
+ content, err = ioutil.ReadFile(i.Path)
+ if err != nil {
+ return err
+ }
+ }
+
+ i.Content = string(content)
+ }
+
return nil
}
-// Reads the file.
-func (i *fileInfo) Read() error {
- if len(i.content) != 0 {
- return nil
- }
-
- var err error
- i.content, err = ioutil.ReadFile(i.Path)
- if err != nil {
- return err
- }
- return nil
-}
-
-func (i fileInfo) Checksum(kind string) (string, error) {
+func (i file) Checksum(kind string) (string, error) {
file, err := os.Open(i.Path)
if err != nil {
return "", err
@@ -258,13 +350,8 @@ func (i fileInfo) Checksum(kind string) (string, error) {
return hex.EncodeToString(h.Sum(nil)), nil
}
-// StringifyContent returns a string with the file content.
-func (i fileInfo) StringifyContent() string {
- return string(i.content)
-}
-
// CanBeEdited checks if the extension of a file is supported by the editor
-func (i fileInfo) CanBeEdited() bool {
+func (i file) CanBeEdited() bool {
return i.Type == "text"
}
@@ -373,3 +460,35 @@ var textExtensions = [...]string{
".c", ".cc", ".h", ".hh", ".cpp", ".hpp", ".f90",
".f", ".bas", ".d", ".ada", ".nim", ".cr", ".java", ".cs", ".vala", ".vapi",
}
+
+func editorMode(language string) string {
+ switch language {
+ case "json", "toml", "yaml":
+ return "frontmatter-only"
+ case "markdown", "asciidoc", "rst":
+ return "complete"
+ }
+
+ return "content-only"
+}
+
+func editorLanguage(mode string) string {
+ mode = strings.TrimPrefix(".", mode)
+
+ switch mode {
+ case "md", "markdown", "mdown", "mmark":
+ mode = "markdown"
+ case "asciidoc", "adoc", "ad":
+ mode = "asciidoc"
+ case "rst":
+ mode = "rst"
+ case "html", "htm":
+ mode = "html"
+ case "js":
+ mode = "javascript"
+ case "go":
+ mode = "golang"
+ }
+
+ return mode
+}
diff --git a/frontmatter/frontmatter.go b/frontmatter/frontmatter.go
index ba28af20..230d3926 100644
--- a/frontmatter/frontmatter.go
+++ b/frontmatter/frontmatter.go
@@ -136,7 +136,7 @@ type Block struct {
Type string
HTMLType string
Content *Content
- Parent *Block
+ Parent *Block `json:"-"`
}
func rawToPretty(config interface{}, parent *Block) *Content {
diff --git a/frontmatter/runes.go b/frontmatter/runes.go
index b4ad1dc2..c6bff60c 100644
--- a/frontmatter/runes.go
+++ b/frontmatter/runes.go
@@ -1,29 +1,28 @@
package frontmatter
import (
- "bytes"
"errors"
"strings"
)
// HasRune checks if the file has the frontmatter rune
-func HasRune(file []byte) bool {
- return strings.HasPrefix(string(file), "---") ||
- strings.HasPrefix(string(file), "+++") ||
- strings.HasPrefix(string(file), "{")
+func HasRune(file string) bool {
+ return strings.HasPrefix(file, "---") ||
+ strings.HasPrefix(file, "+++") ||
+ strings.HasPrefix(file, "{")
}
// AppendRune appends the frontmatter rune to a file
-func AppendRune(frontmatter []byte, mark rune) []byte {
- frontmatter = bytes.TrimSpace(frontmatter)
+func AppendRune(frontmatter string, mark rune) string {
+ frontmatter = strings.TrimSpace(frontmatter)
switch mark {
case '-':
- return []byte("---\n" + string(frontmatter) + "\n---")
+ return "---\n" + frontmatter + "\n---"
case '+':
- return []byte("+++\n" + string(frontmatter) + "\n+++")
+ return "+++\n" + frontmatter + "\n+++"
case '{':
- return []byte("{\n" + string(frontmatter) + "\n}")
+ return "{\n" + frontmatter + "\n}"
}
return frontmatter
diff --git a/http.go b/http.go
index c6c135a2..4bdf7d77 100644
--- a/http.go
+++ b/http.go
@@ -15,7 +15,7 @@ const assetsURL = "/_"
type requestContext struct {
us *User
fm *FileManager
- fi *fileInfo
+ fi *file
pg *page
}
@@ -82,7 +82,7 @@ func serveHTTP(c *requestContext, w http.ResponseWriter, r *http.Request) (int,
}
if r.Method == http.MethodGet {
- var f *fileInfo
+ var f *file
// Obtains the information of the directory/file.
f, err = getInfo(r.URL, c.fm, c.us)
diff --git a/page.go b/page.go
index 3adf8f3d..9070f335 100644
--- a/page.go
+++ b/page.go
@@ -26,13 +26,11 @@ type page struct {
User *User `json:"-"`
BaseURL string `json:"-"`
WebDavURL string `json:"-"`
-
- Name string `json:"name"`
- Path string `json:"path"`
- Kind string `json:"kind"` // listing, editor or preview
- Data interface{} `json:"data"`
+ Kind string `json:"kind"`
+ Data *file `json:"data"`
}
+/*
// breadcrumbItem contains the Name and the URL of a breadcrumb piece.
type breadcrumbItem struct {
Name string
@@ -90,7 +88,7 @@ func (p page) PreviousLink() string {
}
return path
-}
+} */
func (p page) Render(c *requestContext, w http.ResponseWriter, r *http.Request) (int, error) {
if strings.Contains(r.Header.Get("Accept"), "application/json") {
diff --git a/put.go b/put.go
index dec63d98..3100b0ca 100644
--- a/put.go
+++ b/put.go
@@ -128,7 +128,7 @@ func parseCompleteFile(data map[string]interface{}, filename string, mark rune)
return []byte{}, err
}
- front = frontmatter.AppendRune(front, mark)
+ front = []byte(frontmatter.AppendRune(string(front), mark))
// Generates the final file
f := new(bytes.Buffer)
diff --git a/serve.go b/serve.go
index 0fe1344c..11e1e0c8 100644
--- a/serve.go
+++ b/serve.go
@@ -2,18 +2,17 @@ package filemanager
import (
"net/http"
- "strconv"
)
func serveDefault(c *requestContext, w http.ResponseWriter, r *http.Request) (int, error) {
var err error
+ // Starts building the page.
c.pg = &page{
- Name: c.fi.Name,
- Path: c.fi.VirtualPath,
User: c.us,
BaseURL: c.fm.RootURL(),
WebDavURL: c.fm.WebDavURL(),
+ Data: c.fi,
}
// If it is a dir, go and serve the listing.
@@ -26,27 +25,15 @@ func serveDefault(c *requestContext, w http.ResponseWriter, r *http.Request) (in
return errorToHTTP(err, true), err
}
- // If it is a text file, reads its content.
- if c.fi.Type == "text" {
- if err = c.fi.Read(); err != nil {
- return errorToHTTP(err, true), err
- }
- }
-
// If it can't be edited or the user isn't allowed to,
// serve it as a listing, with a preview of the file.
if !c.fi.CanBeEdited() || !c.us.AllowEdit {
- if c.fi.Type == "text" {
- c.fi.Content = string(c.fi.content)
- }
-
c.pg.Kind = "preview"
- c.pg.Data = c.fi
} else {
// Otherwise, we just bring the editor in!
c.pg.Kind = "editor"
- c.pg.Data, err = getEditor(r, c.fi)
+ err = c.fi.getEditor(r)
if err != nil {
return http.StatusInternalServerError, err
}
@@ -57,40 +44,31 @@ func serveDefault(c *requestContext, w http.ResponseWriter, r *http.Request) (in
// serveListing presents the user with a listage of a directory folder.
func serveListing(c *requestContext, w http.ResponseWriter, r *http.Request) (int, error) {
- var (
- err error
- listing *listing
- )
+ var err error
c.pg.Kind = "listing"
- listing, err = getListing(c.us, c.fi.VirtualPath, c.fm.RootURL()+r.URL.Path, c.fi)
+ err = c.fi.getListing(c, r)
if err != nil {
return errorToHTTP(err, true), err
}
+ listing := c.fi.listing
+
cookieScope := c.fm.RootURL()
if cookieScope == "" {
cookieScope = "/"
}
// Copy the query values into the Listing struct
- var limit int
- listing.Sort, listing.Order, limit, err = handleSortOrder(w, r, cookieScope)
+ listing.Sort, listing.Order, err = handleSortOrder(w, r, cookieScope)
if err != nil {
return http.StatusBadRequest, err
}
listing.ApplySort()
- if limit > 0 && limit <= len(listing.Items) {
- listing.Items = listing.Items[:limit]
- listing.ItemsLimitedTo = limit
- }
-
listing.Display = displayMode(w, r, cookieScope)
- c.pg.Data = listing
-
return c.pg.Render(c, w, r)
}
@@ -121,10 +99,9 @@ func displayMode(w http.ResponseWriter, r *http.Request, scope string) string {
// handleSortOrder gets and stores for a Listing the 'sort' and 'order',
// and reads 'limit' if given. The latter is 0 if not given. Sets cookies.
-func handleSortOrder(w http.ResponseWriter, r *http.Request, scope string) (sort string, order string, limit int, err error) {
+func handleSortOrder(w http.ResponseWriter, r *http.Request, scope string) (sort string, order string, err error) {
sort = r.URL.Query().Get("sort")
order = r.URL.Query().Get("order")
- limitQuery := r.URL.Query().Get("limit")
// If the query 'sort' or 'order' is empty, use defaults or any values
// previously saved in Cookies.
@@ -158,13 +135,5 @@ func handleSortOrder(w http.ResponseWriter, r *http.Request, scope string) (sort
})
}
- if limitQuery != "" {
- limit, err = strconv.Atoi(limitQuery)
- // If the 'limit' query can't be interpreted as a number, return err.
- if err != nil {
- return
- }
- }
-
return
}