Funcionalidad de borrado sin javascript
This commit is contained in:
parent
b9c8278246
commit
336140c1fa
|
@ -1,114 +0,0 @@
|
||||||
function refreshHeight(elem) {
|
|
||||||
elem.style.maxHeight = `${elem.scrollHeight}px`;
|
|
||||||
}
|
|
||||||
function validate(form) {
|
|
||||||
var valid = true;
|
|
||||||
if (form['artist'].value == '') {
|
|
||||||
form['artist'].parentElement.setAttribute('error',
|
|
||||||
'Se necesita el nombre del artista');
|
|
||||||
valid = false;
|
|
||||||
}
|
|
||||||
if (form['album'].value == '' && form['track'].value == '') {
|
|
||||||
form['album'].parentElement.setAttribute('error',
|
|
||||||
'Se necesita al menos uno de entre «canción» o «áblum»');
|
|
||||||
form['track'].parentElement.setAttribute('error',
|
|
||||||
'Se necesita al menos uno de entre «canción» o «áblum»');
|
|
||||||
valid = false;
|
|
||||||
}
|
|
||||||
refreshHeight(form.closest('.action-wrap'));
|
|
||||||
return valid;
|
|
||||||
}
|
|
||||||
|
|
||||||
function deleteSong() {
|
|
||||||
var button = this;
|
|
||||||
var data = button.closest('.data-elem');
|
|
||||||
var index = data.getAttribute('data-info');
|
|
||||||
if (confirm('¿Seguro?')) {
|
|
||||||
fetch(`del?p=${index}`).then(response => {
|
|
||||||
var wrap = button.closest('.action-wrap');
|
|
||||||
if (response.ok) {
|
|
||||||
data.parentElement.removeChild(data);
|
|
||||||
} else {
|
|
||||||
button.closest('.action')
|
|
||||||
.setAttribute('result', 'Error en la solicitud');
|
|
||||||
}
|
|
||||||
refreshHeight(wrap);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fetch('../list')
|
|
||||||
.then(response => {
|
|
||||||
if (response.ok) {
|
|
||||||
return response.json();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.then(list => {
|
|
||||||
list.forEach((elem, index) => {
|
|
||||||
var newButton = document.createElement('button');
|
|
||||||
var title = elem['track'] == undefined ? elem['album'] : elem['track'];
|
|
||||||
newButton.setAttribute('class', 'data-elem');
|
|
||||||
newButton.onclick = deleteSong;
|
|
||||||
newButton.setAttribute('data-info', index);
|
|
||||||
newButton.innerText = `${elem['artist']} - ${title}`;
|
|
||||||
document.querySelector('#dellist').appendChild(newButton);
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.catch(reason => console.log(reason));
|
|
||||||
|
|
||||||
document.querySelectorAll('.action-wrap').forEach(elem => {
|
|
||||||
var cajetin = elem.querySelector('.cajetin');
|
|
||||||
elem.setAttribute('collapsed', 'true');
|
|
||||||
elem.style.maxHeight = `${cajetin.scrollHeight}px`;
|
|
||||||
cajetin.onclick = function() {
|
|
||||||
var form = elem.querySelector('form');
|
|
||||||
if (elem.getAttribute('collapsed') == 'true') {
|
|
||||||
refreshHeight(elem);
|
|
||||||
elem.setAttribute('collapsed', 'false');
|
|
||||||
} else {
|
|
||||||
elem.style.maxHeight = `${cajetin.scrollHeight}px`;
|
|
||||||
elem.setAttribute('collapsed', 'true');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
document.querySelectorAll('.form-elem > input').forEach(elem => {
|
|
||||||
elem.addEventListener('input', () => {
|
|
||||||
elem.parentElement.removeAttribute('error');
|
|
||||||
elem.closest('form').removeAttribute('result');
|
|
||||||
refreshHeight(elem.closest('.action-wrap'));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
const add = document.querySelector('#add');
|
|
||||||
add.addEventListener('submit', (event) => {
|
|
||||||
event.preventDefault();
|
|
||||||
if (validate(add)) {
|
|
||||||
var xmlHttp = new XMLHttpRequest();
|
|
||||||
var json = {}
|
|
||||||
var data = new FormData(add);
|
|
||||||
data.forEach((v, k) => json[k] = v);
|
|
||||||
if (!json['date']) {
|
|
||||||
const date = new Date(Date.now());
|
|
||||||
const year = `${date.getFullYear()}`;
|
|
||||||
const month = `${date.getMonth() < 10 ? '0' : '' }${date.getMonth()+1}`;
|
|
||||||
const day = `${date.getDate() < 10 ? '0' : '' }${date.getDate()+1}`;
|
|
||||||
json['date'] = `${year}-${month}-${day}`;
|
|
||||||
}
|
|
||||||
fetch('add', {
|
|
||||||
method: 'POST',
|
|
||||||
body: JSON.stringify(json),
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json'
|
|
||||||
}
|
|
||||||
}).then(response => {
|
|
||||||
if (response.ok) {
|
|
||||||
add.setAttribute('result', 'Canción añadida');
|
|
||||||
} else {
|
|
||||||
add.setAttribute('result', 'Error en la solicitud');
|
|
||||||
}
|
|
||||||
refreshHeight(add.closest('.action-wrap'));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
});
|
|
40
main.go
40
main.go
|
@ -3,6 +3,7 @@ package main
|
||||||
import (
|
import (
|
||||||
"crypto/sha512"
|
"crypto/sha512"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
|
"errors"
|
||||||
"html/template"
|
"html/template"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
@ -72,24 +73,48 @@ func AddController(c echo.Context) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func DelController(c echo.Context) error {
|
func DelController(c echo.Context) error {
|
||||||
var res Response
|
var url string
|
||||||
var status int
|
var status int
|
||||||
var pos int
|
var pos int
|
||||||
|
|
||||||
pos, err := strconv.Atoi(c.QueryParam("p"))
|
pos, err := strconv.Atoi(c.Param("index"))
|
||||||
if err == nil {
|
if err == nil {
|
||||||
err = list.DeleteElement(pos)
|
err = list.DeleteElement(pos)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
res = Response{true, "OK"}
|
url = "../.."
|
||||||
status = http.StatusOK
|
status = http.StatusFound
|
||||||
} else {
|
} else {
|
||||||
res = Response{false, "Failed to delete from list"}
|
|
||||||
status = http.StatusInternalServerError
|
status = http.StatusInternalServerError
|
||||||
}
|
}
|
||||||
|
|
||||||
return c.JSON(status, res)
|
return c.Redirect(status, url)
|
||||||
|
}
|
||||||
|
|
||||||
|
func DelConfirmController(c echo.Context) error {
|
||||||
|
var entries []*storage.Entry
|
||||||
|
failed := c.QueryParam("failed")
|
||||||
|
indexStr := c.Param("index")
|
||||||
|
index, err := strconv.Atoi(indexStr)
|
||||||
|
if err == nil {
|
||||||
|
entries, err = list.GetEntries()
|
||||||
|
}
|
||||||
|
if index >= len(entries) {
|
||||||
|
err = errors.New("Index out of range")
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
err = c.Render(http.StatusOK, "del.html", struct {
|
||||||
|
Index int
|
||||||
|
Elem *storage.Entry
|
||||||
|
Failed string
|
||||||
|
}{
|
||||||
|
Index: index,
|
||||||
|
Elem: entries[index],
|
||||||
|
Failed: failed,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func auth(username, password string, c echo.Context) (bool, error) {
|
func auth(username, password string, c echo.Context) (bool, error) {
|
||||||
|
@ -122,7 +147,8 @@ func main() {
|
||||||
|
|
||||||
admin := e.Group("/admin", middleware.BasicAuth(auth))
|
admin := e.Group("/admin", middleware.BasicAuth(auth))
|
||||||
admin.POST("/add", AddController)
|
admin.POST("/add", AddController)
|
||||||
admin.GET("/del", DelController)
|
admin.POST("/del/:index", DelController)
|
||||||
|
admin.GET("/del/:index", DelConfirmController)
|
||||||
admin.GET("/", Template("admin.html").TemplateController)
|
admin.GET("/", Template("admin.html").TemplateController)
|
||||||
admin.Static("/static", "admin/static")
|
admin.Static("/static", "admin/static")
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
package storage
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
type (
|
||||||
|
Entry struct {
|
||||||
|
Artist string `json:"artist" form:"artist"`
|
||||||
|
Linkto string `json:"linkto,omitempty" form:"linkto,omitempty"`
|
||||||
|
Track string `json:"track,omitempty" form:"track,omitempty"`
|
||||||
|
Album string `json:"album,omitempty" form:"album,omitempty"`
|
||||||
|
Cover string `json:"cover,omitempty" form:"cover,omitempty"`
|
||||||
|
Date JSTime `json:"date" form:"date"`
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func (entry *Entry) Showname() string {
|
||||||
|
var name string
|
||||||
|
if len(entry.Track) > 0 {
|
||||||
|
name = entry.Track
|
||||||
|
} else {
|
||||||
|
name = entry.Album
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%s - %s", entry.Artist, name)
|
||||||
|
}
|
|
@ -0,0 +1,175 @@
|
||||||
|
package storage
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"sort"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/gorilla/feeds"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
EntryList struct {
|
||||||
|
Filename string
|
||||||
|
ModTime time.Time
|
||||||
|
v []*Entry
|
||||||
|
mu sync.RWMutex
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func (list *EntryList) Len() int { return len(list.v) }
|
||||||
|
func (list *EntryList) Swap(i, j int) { list.v[i], list.v[j] = list.v[j], list.v[i] }
|
||||||
|
func (list *EntryList) Less(i, j int) bool {
|
||||||
|
return time.Time(list.v[i].Date).After(time.Time(list.v[j].Date))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (list *EntryList) modified() (mod bool) {
|
||||||
|
info, err := os.Stat(list.Filename)
|
||||||
|
if err == nil {
|
||||||
|
mod = list.ModTime.Before(info.ModTime())
|
||||||
|
list.ModTime = info.ModTime()
|
||||||
|
} else {
|
||||||
|
mod = true
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (list *EntryList) write() (err error) {
|
||||||
|
data, err := json.MarshalIndent(list.v, "", " ")
|
||||||
|
if err == nil {
|
||||||
|
err = ioutil.WriteFile(list.Filename, data, 0644)
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
list.ModTime = time.Now()
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (list *EntryList) read() (err error) {
|
||||||
|
if list.modified() {
|
||||||
|
list.v = make([]*Entry, 0, len(list.v))
|
||||||
|
if data, err := ioutil.ReadFile(list.Filename); err == nil {
|
||||||
|
err = json.Unmarshal(data, &list.v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (list *EntryList) GetEntries() (entry []*Entry, err error) {
|
||||||
|
list.mu.RLock()
|
||||||
|
defer list.mu.RUnlock()
|
||||||
|
|
||||||
|
err = list.read()
|
||||||
|
entry = list.v
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (list *EntryList) AddEntry(entry *Entry) (err error) {
|
||||||
|
list.mu.Lock()
|
||||||
|
defer list.mu.Unlock()
|
||||||
|
|
||||||
|
if err = list.read(); err == nil {
|
||||||
|
list.v = append(list.v, entry)
|
||||||
|
sort.Sort(list)
|
||||||
|
err = list.write()
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (list *EntryList) ReadSliceList(oval int, nval int) (entries []*Entry, err error) {
|
||||||
|
list.mu.RLock()
|
||||||
|
defer list.mu.RUnlock()
|
||||||
|
|
||||||
|
if nval <= 0 {
|
||||||
|
nval = list.Len()
|
||||||
|
}
|
||||||
|
|
||||||
|
if oval <= 0 {
|
||||||
|
oval = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
err = list.read()
|
||||||
|
if err == nil {
|
||||||
|
if oval >= list.Len() {
|
||||||
|
entries = nil
|
||||||
|
} else if oval+nval >= list.Len() {
|
||||||
|
entries = list.v[oval:]
|
||||||
|
} else {
|
||||||
|
entries = list.v[oval : oval+nval]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (list *EntryList) DeleteElement(pos int) (err error) {
|
||||||
|
list.mu.Lock()
|
||||||
|
defer list.mu.Unlock()
|
||||||
|
|
||||||
|
if err = list.read(); err == nil {
|
||||||
|
if pos >= 0 && pos < list.Len() {
|
||||||
|
if pos == 0 {
|
||||||
|
list.v = list.v[pos+1:]
|
||||||
|
} else if pos == list.Len()-1 {
|
||||||
|
list.v = list.v[:pos]
|
||||||
|
} else {
|
||||||
|
list.v = append(list.v[:pos], list.v[pos+1:]...)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = list.write()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO rutas relativas
|
||||||
|
func (list *EntryList) GetFeed() (feed *feeds.Feed) {
|
||||||
|
list.mu.RLock()
|
||||||
|
defer list.mu.RUnlock()
|
||||||
|
|
||||||
|
err := list.read()
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
feed = &feeds.Feed{
|
||||||
|
Title: "danoloan.es muse",
|
||||||
|
Link: &feeds.Link{Href: "https://danoloan.es/muse"},
|
||||||
|
Description: "bitácora musical de danoloan",
|
||||||
|
Author: &feeds.Author{Name: "danoloan", Email: "danolo@danoloan.es"},
|
||||||
|
}
|
||||||
|
|
||||||
|
feed.Items = make([]*feeds.Item, 0, list.Len())
|
||||||
|
for _, elem := range list.v {
|
||||||
|
item := &feeds.Item{
|
||||||
|
Created: time.Time(elem.Date),
|
||||||
|
}
|
||||||
|
|
||||||
|
if elem.Track != "" {
|
||||||
|
item.Title = elem.Track
|
||||||
|
} else if elem.Album != "" {
|
||||||
|
item.Title = elem.Album
|
||||||
|
}
|
||||||
|
|
||||||
|
item.Description = fmt.Sprintf("<p>%s - %s</p>\n", item.Title, elem.Artist)
|
||||||
|
if elem.Cover != "" {
|
||||||
|
item.Description = fmt.Sprintf("%s<img src=\"%s\"/>\n", item.Description, elem.Cover)
|
||||||
|
}
|
||||||
|
|
||||||
|
if elem.Linkto != "" {
|
||||||
|
item.Link = &feeds.Link{Href: elem.Linkto}
|
||||||
|
} else {
|
||||||
|
item.Link = &feeds.Link{Href: "https://danoloan.es/muse"}
|
||||||
|
}
|
||||||
|
|
||||||
|
feed.Items = append(feed.Items, item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
|
@ -0,0 +1,42 @@
|
||||||
|
package storage
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
JSTime time.Time
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
jsTimeLite = "2006-01-02"
|
||||||
|
jsTimeLayout = "2006-01-02 15:04:05"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (ct JSTime) UnmarshalJSON(b []byte) (err error) {
|
||||||
|
s := strings.Trim(string(b), `"`)
|
||||||
|
loc, _ := time.LoadLocation("Europe/Madrid")
|
||||||
|
nt, err := time.ParseInLocation(jsTimeLite, s, loc)
|
||||||
|
ct = JSTime(nt)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ct JSTime) MarshalJSON() ([]byte, error) {
|
||||||
|
return []byte(strconv.Quote(ct.String())), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ct JSTime) String() string {
|
||||||
|
t := time.Time(ct)
|
||||||
|
return fmt.Sprintf("%s", t.Format(jsTimeLite))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ct JSTime) Unix() int64 {
|
||||||
|
return time.Time(ct).Unix()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ct JSTime) Local() JSTime {
|
||||||
|
return JSTime(time.Time(ct).Local())
|
||||||
|
}
|
|
@ -5,7 +5,7 @@
|
||||||
<meta name="viewport" content="initial-scale=1">
|
<meta name="viewport" content="initial-scale=1">
|
||||||
<link href="/static/style.css" rel="stylesheet" type="text/css" />
|
<link href="/static/style.css" rel="stylesheet" type="text/css" />
|
||||||
<link href="../static/admin.css" rel="stylesheet" type="text/css" />
|
<link href="../static/admin.css" rel="stylesheet" type="text/css" />
|
||||||
<title>splog: zona del admin 😎</title>
|
<title>muse: zona del admin 😎</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<h1> Zona del admin 😎 </h1>
|
<h1> Zona del admin 😎 </h1>
|
||||||
|
@ -35,10 +35,10 @@
|
||||||
</button>
|
</button>
|
||||||
<div class="action">
|
<div class="action">
|
||||||
<div class="scroll">
|
<div class="scroll">
|
||||||
{{ range . }}
|
{{ range $index, $elem := . }}
|
||||||
<button class="data-elem">
|
<a href="del/{{ $index }}" class="data-elem">
|
||||||
{{ .Showname }}
|
{{ $elem.Showname }}
|
||||||
</button>
|
</a>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="es">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="initial-scale=1">
|
||||||
|
<link href="/static/style.css" rel="stylesheet" type="text/css" />
|
||||||
|
<link href="../static/admin.css" rel="stylesheet" type="text/css" />
|
||||||
|
<title>muse: zona del admin 😎</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1> Zona del admin 😎 </h1>
|
||||||
|
<p>
|
||||||
|
<form action="{{ .Index }}" method="post">
|
||||||
|
¿Quieres borrar <b>{{ .Elem.Showname }}</b>?
|
||||||
|
<input type="submit" value="Confirmar">
|
||||||
|
</form>
|
||||||
|
</p>
|
||||||
|
{{ if .Failed }}
|
||||||
|
<p style="color: red;">El borrado falló ☹️</p>
|
||||||
|
{{ end }}
|
||||||
|
<footer>
|
||||||
|
<a href="..">Volver al inicio</a>
|
||||||
|
</footer>
|
||||||
|
</body>
|
||||||
|
</html>
|
Loading…
Reference in New Issue