From 2819ab24b85a578dc1f572f1e64eccc98cabd6c3 Mon Sep 17 00:00:00 2001 From: Henrique Dias Date: Thu, 29 Jun 2017 10:17:35 +0100 Subject: [PATCH] solve some back end bugs --- _assets/_old/js/common_old.js | 44 ------- _assets/src/App.vue | 19 +-- editor.go | 121 ------------------- file.go | 219 ++++++++++++++++++++++++++-------- frontmatter/frontmatter.go | 2 +- frontmatter/runes.go | 19 ++- http.go | 4 +- page.go | 10 +- put.go | 2 +- serve.go | 49 ++------ 10 files changed, 207 insertions(+), 282 deletions(-) delete mode 100644 editor.go 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 }