solve some back end bugs

pull/144/head
Henrique Dias 2017-06-29 10:17:35 +01:00
parent 193adea6bb
commit 2819ab24b8
No known key found for this signature in database
GPG Key ID: 936F5EB68D786730
10 changed files with 207 additions and 282 deletions

View File

@ -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 => {

View File

@ -50,7 +50,7 @@
<info-prompt v-show="showInfo" :class="{ active: showInfo }"></info-prompt>
<help v-show="showHelp" :class="{ active: showHelp }"></help>
<div v-show="showOverlay()" class="overlay" :class="{ active: showOverlay() }"></div>
<div v-show="showOverlay()" @click="resetPrompts" class="overlay" :class="{ active: showOverlay() }"></div>
<footer>Served with <a rel="noopener noreferrer" href="https://github.com/hacdias/caddy-filemanager">File Manager</a>.</footer>
</div>
@ -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
}
}
</script>

121
editor.go
View File

@ -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
}

219
file.go
View File

@ -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
}

View File

@ -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 {

View File

@ -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

View File

@ -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)

10
page.go
View File

@ -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") {

2
put.go
View File

@ -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)

View File

@ -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
}