Current version
This commit is contained in:
parent
a3d509789b
commit
7e82a4f5e7
|
@ -14,4 +14,5 @@
|
|||
|
||||
# Dependency directories (remove the comment below to include it)
|
||||
# vendor/
|
||||
|
||||
img
|
||||
igar
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
module igar
|
||||
|
||||
go 1.16
|
||||
|
||||
require (
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible // indirect
|
||||
github.com/google/uuid v1.2.0
|
||||
github.com/gorilla/feeds v1.1.1
|
||||
github.com/kr/pretty v0.2.1 // indirect
|
||||
github.com/labstack/echo v3.3.10+incompatible
|
||||
github.com/labstack/gommon v0.3.0 // indirect
|
||||
github.com/stretchr/testify v1.7.0 // indirect
|
||||
github.com/valyala/fasttemplate v1.2.1 // indirect
|
||||
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a // indirect
|
||||
)
|
|
@ -0,0 +1,49 @@
|
|||
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
||||
github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs=
|
||||
github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/gorilla/feeds v1.1.1 h1:HwKXxqzcRNg9to+BbvJog4+f3s/xzvtZXICcQGutYfY=
|
||||
github.com/gorilla/feeds v1.1.1/go.mod h1:Nk0jZrvPFZX1OBe5NPiddPw7CfwF6Q9eqzaBbaightA=
|
||||
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
|
||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/labstack/echo v3.3.10+incompatible h1:pGRcYk231ExFAyoAjAfD85kQzRJCRI8bbnE7CX5OEgg=
|
||||
github.com/labstack/echo v3.3.10+incompatible/go.mod h1:0INS7j/VjnFxD4E2wkz67b8cVwCLbBmJyDaka6Cmk1s=
|
||||
github.com/labstack/gommon v0.3.0 h1:JEeO0bvc78PKdyHxloTKiF8BD5iGrH8T6MSeGvSgob0=
|
||||
github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k=
|
||||
github.com/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx8mU=
|
||||
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
||||
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
github.com/mattn/go-isatty v0.0.9 h1:d5US/mDsogSGW37IV293h//ZFaeajb69h+EHFsv2xGg=
|
||||
github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||
github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=
|
||||
github.com/valyala/fasttemplate v1.2.1 h1:TVEnxayobAdVkhQfrfes2IzOB6o+z4roRkPF52WA1u4=
|
||||
github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
|
||||
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a h1:kr2P4QFmQr29mSLA43kwrOcgcReGTfbE9N577tCTuBc=
|
||||
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 h1:qWPm9rbaAMKs8Bq/9LRpbMqxWRVUAQwMI9fVrssnTfw=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 h1:nxC68pudNYkKU6jWhgrqdreuFiOQWj1Fs7T3VrH4Pjw=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
|
@ -0,0 +1,143 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="es">
|
||||
<head>
|
||||
<title>bitácora fotográfica de danoloan</title>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel=stylesheet href="/static/style.css">
|
||||
<link rel="shortcut icon" type="image/jpg" href="/favicon.jpg">
|
||||
<style>
|
||||
body {
|
||||
max-width: 990px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
header {
|
||||
padding: 0px 5px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
main {
|
||||
margin: 0px auto;
|
||||
text-align: center;
|
||||
font-size: 0px;
|
||||
}
|
||||
|
||||
nav {
|
||||
position: absolute;
|
||||
top: 25px;
|
||||
right: 25px;
|
||||
}
|
||||
|
||||
nav > a {
|
||||
margin: 5px 0px;
|
||||
}
|
||||
|
||||
#load {
|
||||
text-align: center;
|
||||
padding: 20px;
|
||||
width: 10em;
|
||||
}
|
||||
|
||||
.elem {
|
||||
margin: 0px auto;
|
||||
padding: 5px;
|
||||
width: calc((100% - 30px) / 3);
|
||||
max-width: 320px;
|
||||
transition: transform .2s;
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.elem:hover {
|
||||
transform: scale(1.05);
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.elem > * {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.type {
|
||||
width: 10%;
|
||||
position: absolute;
|
||||
top: 7%;
|
||||
right: 7%;
|
||||
}
|
||||
|
||||
img.load {
|
||||
width: 50px;
|
||||
}
|
||||
|
||||
p {
|
||||
margin-top: 0px;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 990px) {
|
||||
body {
|
||||
padding: 0px;
|
||||
}
|
||||
header {
|
||||
padding: 0px 1em;
|
||||
}
|
||||
nav {
|
||||
position: initial;
|
||||
text-align: center;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
.elem {
|
||||
padding: 1px;
|
||||
width: calc((100% - 6px) / 3);
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: light) {
|
||||
nav > a {
|
||||
background-color: #eee;
|
||||
}
|
||||
nav > a:hover {
|
||||
background-color: #ddd;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<script src="static/prog.js"></script>
|
||||
<script src="static/main.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<h1>
|
||||
<a href="/">danoloan</a>
|
||||
</h1>
|
||||
<p>
|
||||
bitácora fotográfica
|
||||
</p>
|
||||
<p style="font-size: 70%">
|
||||
la representación gráfica de mi déficit de atención, ordenada de forma cronológica
|
||||
</p>
|
||||
</header>
|
||||
<nav>
|
||||
<a class="button" href="rss">RSS</a>
|
||||
<a class="button" href="json">JSON</a>
|
||||
<a class="button" href="atom">Atom</a>
|
||||
<a class="button" href="admin/">Zona del admin 😎</a>
|
||||
</nav>
|
||||
<main>
|
||||
<noscript>
|
||||
<div style="font-size: 15px; text-align: left; max-width: 600px; margin:auto">
|
||||
<p>
|
||||
Esta página utiliza código JavaScript en el navegador
|
||||
para generar los contenidos en pantalla.
|
||||
Por favor, active JavaScript para poder usarla.
|
||||
</p>
|
||||
<p>
|
||||
El código utilizado es bastante sencillo,
|
||||
si lo quiere revisar pinche
|
||||
<a href="static/main.js">aquí</a>.
|
||||
</p>
|
||||
</div>
|
||||
</noscript>
|
||||
</main>
|
||||
<div id="load">
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,445 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"crypto/sha512"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
"encoding/hex"
|
||||
"mime"
|
||||
|
||||
"github.com/labstack/echo"
|
||||
"github.com/labstack/echo/middleware"
|
||||
"github.com/gorilla/feeds"
|
||||
"github.com/google/uuid"
|
||||
//"github.com/labstack/echo/middleware"
|
||||
)
|
||||
|
||||
type (
|
||||
JSTime time.Time
|
||||
|
||||
Media struct {
|
||||
Type string `json:"type"`
|
||||
File string `json:"file"`
|
||||
Height int `json:"height"`
|
||||
Width int `json:"width"`
|
||||
}
|
||||
|
||||
Entry struct {
|
||||
Date JSTime `json:"date"`
|
||||
Description string `json:"description"`
|
||||
Post string `json:"post"`
|
||||
Likes int `json:"likes"`
|
||||
Tags []string `json:"tags"`
|
||||
Media []Media `json:"media"`
|
||||
Prev *Entry `json:"-"`
|
||||
Next *Entry `json:"-"`
|
||||
// Users []string
|
||||
}
|
||||
Response struct {
|
||||
Ok bool
|
||||
Message string
|
||||
}
|
||||
TemplateRenderer struct {
|
||||
template *template.Template
|
||||
}
|
||||
)
|
||||
|
||||
type (
|
||||
entryMap struct {
|
||||
filename string
|
||||
modtime time.Time
|
||||
mapped map[string]*Entry
|
||||
sorted []*Entry
|
||||
mu sync.Mutex
|
||||
}
|
||||
entryList []*Entry
|
||||
)
|
||||
|
||||
const (
|
||||
jsTimeLite = "2006-01-02"
|
||||
jsTimeLayout = "2006-01-02 15:04:05"
|
||||
adminUsername = "danolo"
|
||||
adminPassword = "bd4cad796950f50352225de3c773d8f3c39622bc17f34ad661eabe615cdf6d32751c5751e0648dc17d890f40330018334a2ae899878f200f6dc80121ddb70cc9"
|
||||
)
|
||||
|
||||
var (
|
||||
list *entryMap = &entryMap{
|
||||
filename: "img/list.json",
|
||||
modtime: time.Unix(0, 0),
|
||||
}
|
||||
)
|
||||
|
||||
// FUNCTIONS
|
||||
|
||||
func genName() string {
|
||||
return strings.Replace(uuid.New().String(), "-", "", -1)
|
||||
}
|
||||
|
||||
// JSTime
|
||||
|
||||
func (ct *JSTime) UnmarshalJSON(b []byte) (err error) {
|
||||
s := strings.Trim(string(b), `"`)
|
||||
nt, err := time.Parse(jsTimeLayout, s)
|
||||
*ct = JSTime(nt)
|
||||
return
|
||||
}
|
||||
|
||||
func (ct JSTime) MarshalJSON() ([]byte, error) {
|
||||
return []byte(ct.String()), nil
|
||||
}
|
||||
|
||||
func (ct JSTime) String() string {
|
||||
t := time.Time(ct)
|
||||
return fmt.Sprintf("%q", t.Format(jsTimeLayout))
|
||||
}
|
||||
|
||||
func (ct JSTime) Unix() int64 {
|
||||
return time.Time(ct).Unix()
|
||||
}
|
||||
|
||||
// Entry
|
||||
|
||||
func (entry Entry) Count() map[string]int {
|
||||
count := make(map[string]int)
|
||||
for _, media := range entry.Media {
|
||||
count[media.Type] += 1
|
||||
}
|
||||
return count
|
||||
}
|
||||
|
||||
func (entry Entry) Len() int { return len(entry.Media) }
|
||||
|
||||
// TemplateRenderer
|
||||
|
||||
func (renderer *TemplateRenderer) Render(w io.Writer, name string, data interface{}, c echo.Context) error {
|
||||
return renderer.template.ExecuteTemplate(w, name, data)
|
||||
}
|
||||
|
||||
// entryMap
|
||||
|
||||
func (list *entryMap) Len() int { return len(list.mapped) }
|
||||
|
||||
func (list *entryMap) 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 *entryMap) write() (err error) {
|
||||
data, err := json.MarshalIndent(list.mapped, "", " ")
|
||||
if err == nil {
|
||||
err = ioutil.WriteFile(list.filename, data, 0644)
|
||||
}
|
||||
if err == nil {
|
||||
list.modtime = time.Now()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (list *entryMap) read() (err error) {
|
||||
if list.modified() {
|
||||
if data, err := ioutil.ReadFile(list.filename); err == nil {
|
||||
|
||||
// read map
|
||||
list.mapped = make(map[string]*Entry)
|
||||
err = json.Unmarshal(data, &list.mapped)
|
||||
|
||||
// sort list
|
||||
list.sorted = make([]*Entry, 0, len(list.mapped))
|
||||
for _, entry:= range list.mapped {
|
||||
list.sorted = append(list.sorted, entry)
|
||||
}
|
||||
sort.Sort(entryList(list.sorted))
|
||||
|
||||
// assign prev/next
|
||||
for index, value := range list.sorted {
|
||||
if index-1 >= 0 {
|
||||
value.Next = list.sorted[index-1]
|
||||
}
|
||||
if index+1 < len(list.sorted) {
|
||||
value.Prev = list.sorted[index+1]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (list *entryMap) getSortedEntries() entryList {
|
||||
list.mu.Lock()
|
||||
defer list.mu.Unlock()
|
||||
|
||||
list.read()
|
||||
|
||||
return entryList(list.sorted)
|
||||
}
|
||||
|
||||
func (list *entryMap) readSliceList(oval int, nval int) (result entryList, err error) {
|
||||
if nval <= 0 {
|
||||
nval = list.Len()
|
||||
}
|
||||
|
||||
if oval <= 0 {
|
||||
oval = 0
|
||||
}
|
||||
|
||||
if err == nil {
|
||||
entries := list.getSortedEntries()
|
||||
if oval >= list.Len() {
|
||||
result = nil
|
||||
} else if oval+nval >= list.Len() {
|
||||
result = entries[oval:]
|
||||
} else {
|
||||
result = entries[oval : oval+nval]
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (list *entryMap) addEntry(entry *Entry) (err error) {
|
||||
list.mu.Lock()
|
||||
defer list.mu.Unlock()
|
||||
|
||||
list.read()
|
||||
|
||||
if entry != nil {
|
||||
list.mapped[entry.Post] = entry
|
||||
err = list.write()
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (list *entryMap) getEntry(uuid string) (result *Entry, err error) {
|
||||
list.mu.Lock()
|
||||
defer list.mu.Unlock()
|
||||
|
||||
if err = list.read(); err == nil {
|
||||
result = list.mapped[uuid]
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (list *entryMap) getFeed() (feed *feeds.Feed) {
|
||||
feed = &feeds.Feed {
|
||||
Title: "danoloan.es igar",
|
||||
Link: &feeds.Link { Href: "https://danoloan.es/igar/" },
|
||||
Description: "bitácora fotográfica de danoloan",
|
||||
Author: &feeds.Author { Name: "danoloan", Email: "danolo@danoloan.es" },
|
||||
}
|
||||
|
||||
feed.Items = make([]*feeds.Item, 0, list.Len())
|
||||
for _, elem := range list.getSortedEntries() {
|
||||
item := &feeds.Item {
|
||||
Title: elem.Description,
|
||||
Link: &feeds.Link{ Href: fmt.Sprintf("https://danoloan.es/igar/view?uuid=%s", elem.Post) },
|
||||
Description: fmt.Sprintf("<p>%s</p>\n<div>\n", elem.Description),
|
||||
Created: time.Time(elem.Date),
|
||||
}
|
||||
|
||||
for _, media := range elem.Media {
|
||||
if media.Type == "GraphImage" {
|
||||
// TODO ruta relativa al proxy
|
||||
item.Description = fmt.Sprintf("%s\n\t<img src=/igar/img/%s/%s>", item.Description, elem.Post, media.File)
|
||||
}
|
||||
}
|
||||
item.Description = fmt.Sprintf("%s\n</div>", item.Description)
|
||||
|
||||
feed.Items = append(feed.Items, item)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// entryList
|
||||
|
||||
func (list entryList) Len() int { return len(list) }
|
||||
func (list entryList) Swap(i, j int) { list[i], list[j] = list[j], list[i] }
|
||||
func (list entryList) Less(i, j int) bool {
|
||||
return time.Time(list[i].Date).After(time.Time(list[j].Date))
|
||||
}
|
||||
|
||||
// CONTROLLERS
|
||||
|
||||
func ListController(c echo.Context) (res error) {
|
||||
var err error
|
||||
var oval, nval int
|
||||
|
||||
oval, err = strconv.Atoi(c.QueryParam("o"))
|
||||
if err != nil {
|
||||
oval = 0
|
||||
}
|
||||
|
||||
nval, err = strconv.Atoi(c.QueryParam("n"))
|
||||
if err != nil {
|
||||
nval = 0
|
||||
}
|
||||
|
||||
slice, err := list.readSliceList(oval, nval)
|
||||
if err == nil {
|
||||
res = c.JSON(http.StatusOK, slice)
|
||||
} else {
|
||||
response := Response{false, "Error fetching song list"}
|
||||
res = c.JSON(http.StatusInternalServerError, response)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func ViewController(c echo.Context) (err error) {
|
||||
uuid := c.QueryParam("uuid")
|
||||
entry, err := list.getEntry(uuid)
|
||||
if err == nil {
|
||||
err = c.Render(http.StatusOK, "view.html", entry)
|
||||
}
|
||||
if err != nil {
|
||||
c.Logger().Error(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func RSSController(c echo.Context) (err error) {
|
||||
if blob, err := list.getFeed().ToRss(); err == nil {
|
||||
c.Blob(http.StatusOK, "application/xml", []byte(blob))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func JSONController(c echo.Context) (err error) {
|
||||
if blob, err := list.getFeed().ToJSON(); err == nil {
|
||||
c.Blob(http.StatusOK, "application/json", []byte(blob))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func AtomController(c echo.Context) (err error) {
|
||||
if blob, err := list.getFeed().ToAtom(); err == nil {
|
||||
c.Blob(http.StatusOK, "application/xml", []byte(blob))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// TODO pura mierda
|
||||
func PostController(c echo.Context) (err error) {
|
||||
desc := c.FormValue("desc")
|
||||
date := c.FormValue("date")
|
||||
|
||||
file, err := c.FormFile("file")
|
||||
|
||||
entry := &Entry {
|
||||
Description: desc,
|
||||
Post: genName(),
|
||||
}
|
||||
|
||||
if date != "" {
|
||||
time, _ := time.Parse(jsTimeLite, date)
|
||||
entry.Date = JSTime(time)
|
||||
} else {
|
||||
entry.Date = JSTime(time.Now())
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
media := Media {
|
||||
Type: "GraphImage",
|
||||
File: genName(),
|
||||
}
|
||||
|
||||
extension, err := mime.ExtensionsByType(file.Header.Get("Content-Type"))
|
||||
if err == nil {
|
||||
media.File = fmt.Sprintf("%s%s", media.File, extension[0])
|
||||
}
|
||||
|
||||
entry.Media = append(entry.Media, media)
|
||||
|
||||
src, err := file.Open()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer src.Close()
|
||||
|
||||
postdir := fmt.Sprintf("img/%s", entry.Post)
|
||||
filepath := fmt.Sprintf("%s/%s", postdir, media.File)
|
||||
|
||||
os.MkdirAll(postdir, os.ModePerm)
|
||||
dst, err := os.Create(filepath)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer dst.Close()
|
||||
|
||||
list.addEntry(entry)
|
||||
|
||||
if _, err = io.Copy(dst, src); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err = exec.Command("node", "thumbs/make_thumb.js", postdir).Run(); err != nil {
|
||||
c.Logger().Error(err.Error())
|
||||
}
|
||||
|
||||
return c.String(http.StatusCreated, "")
|
||||
}
|
||||
|
||||
func auth(username, password string, c echo.Context) (bool, error) {
|
||||
hash := sha512.Sum512([]byte(password))
|
||||
hashString := hex.EncodeToString(hash[:])
|
||||
if username == adminUsername && hashString == adminPassword {
|
||||
return true, nil
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
e := echo.New()
|
||||
|
||||
e.Renderer = &TemplateRenderer{
|
||||
template: template.Must(template.ParseGlob("templates/*.html")),
|
||||
}
|
||||
|
||||
e.Static("/", "index.html")
|
||||
|
||||
e.GET("/list", ListController)
|
||||
e.GET("/view", ViewController)
|
||||
|
||||
e.GET("/rss", RSSController)
|
||||
e.GET("/json", JSONController)
|
||||
e.GET("/atom", AtomController)
|
||||
|
||||
e.Static("/static", "static")
|
||||
e.Static("/img", "img")
|
||||
|
||||
admin := e.Group("/admin", middleware.BasicAuth(auth))
|
||||
|
||||
admin.GET("/", func(c echo.Context) error {
|
||||
return c.Render(http.StatusOK, "admin.html", struct { List entryList }{
|
||||
List: list.getSortedEntries(),
|
||||
})
|
||||
})
|
||||
admin.POST("/post", PostController)
|
||||
//admin.DELETE("/post", DeleteController)
|
||||
|
||||
admin.Static("/static", "admin/static")
|
||||
|
||||
e.Logger.Fatal(e.Start(":40404"))
|
||||
}
|
|
@ -0,0 +1,110 @@
|
|||
input {
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
padding: 0.75em;
|
||||
}
|
||||
|
||||
body {
|
||||
max-width: 40em;
|
||||
}
|
||||
|
||||
footer {
|
||||
margin: 1em auto;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
footer > a {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.action {
|
||||
padding: 20px;
|
||||
}
|
||||
.action[result]::after {
|
||||
content: attr(result);
|
||||
display: block;
|
||||
padding-top: 1em;
|
||||
text-align: center;
|
||||
font-size: 0.75em;
|
||||
}
|
||||
|
||||
.action-wrap {
|
||||
margin: 0.5em auto;
|
||||
border-radius: 6px;
|
||||
overflow: hidden;
|
||||
transition: max-height 0.2s linear;
|
||||
box-shadow: 0px 0px 0.1em #777;
|
||||
}
|
||||
|
||||
.form-elem {
|
||||
padding: 5px 0px;
|
||||
}
|
||||
.form-elem[error] > input {
|
||||
border-color: #e44;
|
||||
border-bottom: none;
|
||||
color: #e44;
|
||||
border-radius: 4px 4px 0px 0px;
|
||||
}
|
||||
.form-elem[error]::after {
|
||||
font-size: 0.7em;
|
||||
content: attr(error);
|
||||
display: block;
|
||||
color: #e44;
|
||||
border-radius: 0px 0px 4px 4px;
|
||||
border: 1px solid #e44;
|
||||
padding: 10px;
|
||||
background-color: #333;
|
||||
}
|
||||
|
||||
.scroll {
|
||||
margin: auto;
|
||||
max-height: 800px;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.cajetin {
|
||||
width: 100%;
|
||||
display: block;
|
||||
padding: 0.5em;
|
||||
}
|
||||
|
||||
#dellist > .data-elem {
|
||||
margin: 5px;
|
||||
padding: 10px;
|
||||
width: 270px;
|
||||
display: inline-block;
|
||||
border: 1px solid #555;
|
||||
border-radius: 5px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
#dellist > .data-elem > button {
|
||||
display: block;
|
||||
text-align: center;
|
||||
margin: 5px auto;
|
||||
width: 100%;
|
||||
}
|
||||
#dellist > .data-elem > .date {
|
||||
text-align: center;
|
||||
font-weight: bold;
|
||||
font-size: 1.05em;
|
||||
margin-bottom: 0.1em;
|
||||
}
|
||||
#dellist > .data-elem > .desc {
|
||||
height: 7em;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 1000px) {
|
||||
body {
|
||||
max-width: 620px;
|
||||
}
|
||||
}
|
||||
@media screen and (max-width: 700px) {
|
||||
#dellist > .data-elem {
|
||||
width: 100%;
|
||||
display: block;
|
||||
}
|
||||
form {
|
||||
padding: 10px;
|
||||
}
|
||||
}
|
Binary file not shown.
After Width: | Height: | Size: 3.5 KiB |
Binary file not shown.
After Width: | Height: | Size: 7.4 KiB |
Binary file not shown.
After Width: | Height: | Size: 9.7 KiB |
Binary file not shown.
After Width: | Height: | Size: 117 KiB |
|
@ -0,0 +1,50 @@
|
|||
function addimages(list, s) {
|
||||
let cont = document.querySelector("main");
|
||||
|
||||
if (list) {
|
||||
list.forEach((image, i) => {
|
||||
let url = "img/" + image.post;
|
||||
|
||||
if (image.hidden != true) {
|
||||
let a = document.createElement("a");
|
||||
a.setAttribute("href", "view?uuid=" + image.post);
|
||||
|
||||
let img = document.createElement("img");
|
||||
img.setAttribute("class", "thumb");
|
||||
img.setAttribute("alt", image.post);
|
||||
img.setAttribute("src", url + "/thumb.jpg");
|
||||
a.appendChild(img);
|
||||
|
||||
if (image.media.length > 1) {
|
||||
let type = document.createElement("img");
|
||||
type.setAttribute("class", "type");
|
||||
type.setAttribute("alt", "galería");
|
||||
type.setAttribute("src", "static/gallery.png");
|
||||
a.appendChild(type);
|
||||
} else if (image.media[0].type == "GraphVideo") {
|
||||
let type = document.createElement("img");
|
||||
type.setAttribute("class", "type");
|
||||
type.setAttribute("alt", "video");
|
||||
type.setAttribute("src", "static/video.png");
|
||||
a.appendChild(type);
|
||||
}
|
||||
|
||||
a.setAttribute("onclick", `window.app.store()`);
|
||||
a.setAttribute("class", "elem");
|
||||
cont.appendChild(a);
|
||||
}
|
||||
});
|
||||
}
|
||||
return !list;
|
||||
}
|
||||
|
||||
window.onload = () => {
|
||||
window.app = new ProgView(addimages);
|
||||
window.app.down();
|
||||
};
|
||||
|
||||
window.onbeforeunload = () => {
|
||||
if (!window.app.stored) {
|
||||
window.app.clear();
|
||||
}
|
||||
};
|
|
@ -0,0 +1,122 @@
|
|||
const N = 12;
|
||||
class ProgView {
|
||||
constructor(addfn) {
|
||||
this.n = null;
|
||||
this.s = null;
|
||||
this.last = 0;
|
||||
this.stored = false;
|
||||
this.first = true;
|
||||
this.loading = false;
|
||||
this.nomore = false;
|
||||
|
||||
this.addelements = addfn;
|
||||
|
||||
this.load();
|
||||
|
||||
this.down(this.n).then(() => {
|
||||
let app = this;
|
||||
(function ev() {
|
||||
if (document.documentElement.scrollHeight < app.s) {
|
||||
setTimeout(ev, 50);
|
||||
} else {
|
||||
window.scrollTo(0, app.s);
|
||||
}
|
||||
})();
|
||||
});
|
||||
}
|
||||
|
||||
get n() {
|
||||
return this.nval;
|
||||
}
|
||||
set n(sn) {
|
||||
this.nval = sn == null ? N : Number(sn);
|
||||
}
|
||||
|
||||
get s() {
|
||||
return this.sval;
|
||||
}
|
||||
set s(ss) {
|
||||
this.sval = ss == null ? 0 : Number(ss);
|
||||
}
|
||||
|
||||
arm() {
|
||||
window.onscroll = (ev) => {
|
||||
if ((window.innerHeight + window.scrollY + 50) >= document.body.offsetHeight) {
|
||||
this.down();
|
||||
}
|
||||
};
|
||||
}
|
||||
disarm() {
|
||||
window.onscroll = null;
|
||||
}
|
||||
|
||||
store(s) {
|
||||
this.stored = true;
|
||||
this.n = this.last;
|
||||
this.s = window.scrollY;
|
||||
sessionStorage.setItem("n", this.n);
|
||||
sessionStorage.setItem("s", this.s);
|
||||
}
|
||||
load() {
|
||||
this.n = window.sessionStorage.getItem("n");
|
||||
this.s = window.sessionStorage.getItem("s");
|
||||
}
|
||||
clear() {
|
||||
sessionStorage.removeItem("n");
|
||||
sessionStorage.removeItem("s");
|
||||
}
|
||||
|
||||
stopload() {
|
||||
let gif = document.querySelector("div#load > img");
|
||||
gif.remove();
|
||||
}
|
||||
startload() {
|
||||
let load = document.getElementById("load");
|
||||
let gif = document.createElement("img");
|
||||
|
||||
gif.setAttribute("src", "static/load.gif");
|
||||
gif.setAttribute("class", "load");
|
||||
|
||||
load.appendChild(gif);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
async down(n) {
|
||||
n = n == null ? this.n : n;
|
||||
|
||||
this.disarm();
|
||||
|
||||
if (!this.loading && !this.nomore) {
|
||||
this.loading = true;
|
||||
|
||||
this.startload();
|
||||
|
||||
await fetch(`list?o=${this.last}&n=${n}`)
|
||||
.then(response => {
|
||||
if (!response.ok) {
|
||||
throw `down(): error ${response.status}`;
|
||||
} else {
|
||||
return response.json();
|
||||
}
|
||||
})
|
||||
.then(data => {
|
||||
this.nomore = this.addelements(data, this.s);
|
||||
})
|
||||
.catch(error => {
|
||||
console.error(error);
|
||||
})
|
||||
.finally(() => {
|
||||
this.stopload();
|
||||
this.arm();
|
||||
this.loading = false;
|
||||
});
|
||||
|
||||
this.last += n;
|
||||
}
|
||||
}
|
||||
|
||||
addelements() {
|
||||
return true;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,109 @@
|
|||
body {
|
||||
font-family : Open Sans, Arial;
|
||||
font-size : 1.2em;
|
||||
color : #eee;
|
||||
background-color: #222;
|
||||
padding : 0em 1em;
|
||||
margin : 1em auto;
|
||||
max-width : 40em;
|
||||
line-height : 1.4;
|
||||
}
|
||||
|
||||
blockquote {
|
||||
font-size: 0.75em;
|
||||
}
|
||||
|
||||
q {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #7af;
|
||||
align-items: center;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a:visited {
|
||||
color: #77f;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: #88f;
|
||||
}
|
||||
|
||||
img.icon {
|
||||
margin-right: 5px;
|
||||
vertical-align: middle;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
input,
|
||||
button,
|
||||
.button {
|
||||
display: inline-block;
|
||||
background-color: #333;
|
||||
border-radius: 5px;
|
||||
border: none;
|
||||
outline: none;
|
||||
padding: 0.25em 0.5em;
|
||||
}
|
||||
|
||||
input, button {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
input[type=button],
|
||||
input[type=submit],
|
||||
button,
|
||||
.button {
|
||||
cursor: pointer;
|
||||
}
|
||||
input[type=button]:hover,
|
||||
input[type=submit]:hover,
|
||||
button:hover,
|
||||
.button:hover {
|
||||
background-color: #444;
|
||||
}
|
||||
input[type=button]:active,
|
||||
input[type=submit]:active,
|
||||
button:active,
|
||||
.button:active {
|
||||
box-shadow: inset 0px 0px 0.15em #777;
|
||||
}
|
||||
|
||||
hr {
|
||||
border: 0.5px solid #333;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: light) {
|
||||
body {
|
||||
background: #fff;
|
||||
color: #222;
|
||||
}
|
||||
|
||||
hr {
|
||||
border-color: #ddd;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #33f;
|
||||
}
|
||||
|
||||
a:visited,
|
||||
a:hover {
|
||||
color: #51e;
|
||||
}
|
||||
|
||||
input,
|
||||
button,
|
||||
.button {
|
||||
background-color: #eee;
|
||||
}
|
||||
|
||||
input[type=button]:hover,
|
||||
input[type=submit]:hover,
|
||||
button:hover,
|
||||
.button:hover {
|
||||
background-color: #ddd;
|
||||
}
|
||||
}
|
Binary file not shown.
After Width: | Height: | Size: 5.2 KiB |
|
@ -0,0 +1,136 @@
|
|||
class Gallery {
|
||||
constructor(uuid, meta) {
|
||||
this.uuid = uuid
|
||||
|
||||
this.frame = document.createElement("div");
|
||||
this.frame.setAttribute("class", "gallery");
|
||||
|
||||
this.count = document.createElement("div");
|
||||
this.count.setAttribute("class", "count");
|
||||
|
||||
this.nodes = [];
|
||||
|
||||
this.gallery = meta;
|
||||
}
|
||||
|
||||
set gallery(meta) {
|
||||
if (meta == null) {
|
||||
while (this.frame.firstChild) {
|
||||
this.frame.removeChild(this.frame.firstChild);
|
||||
}
|
||||
this.nodes = [];
|
||||
} else {
|
||||
var elems = meta["elems"]
|
||||
var counts = {}
|
||||
for (var i = 0; i < elems.length; i++) {
|
||||
var node = getnode(this.uuid, elems[i]);
|
||||
this.frame.appendChild(node);
|
||||
this.nodes.push(node);
|
||||
if (counts[elems[i]["type"]] == undefined) {
|
||||
counts[elems[i]["type"]] = 1
|
||||
} else {
|
||||
counts[elems[i]["type"]] += 1
|
||||
}
|
||||
}
|
||||
this.counts = counts
|
||||
}
|
||||
}
|
||||
|
||||
set counts(counts) {
|
||||
if (counts["GraphImage"] != undefined) {
|
||||
var c = document.createElement("span")
|
||||
var i = document.createElement("img");
|
||||
i.setAttribute("src", "static/image.png");
|
||||
i.setAttribute("alt", "imágenes");
|
||||
c.innerHTML += counts["GraphImage"]
|
||||
c.appendChild(i)
|
||||
c.setAttribute("class", "count");
|
||||
this.count.appendChild(c)
|
||||
}
|
||||
if (counts["GraphVideo"] != undefined) {
|
||||
var c = document.createElement("span")
|
||||
var i = document.createElement("img");
|
||||
i.setAttribute("src", "static/video.png");
|
||||
i.setAttribute("alt", "vídeos");
|
||||
c.innerHTML += counts["GraphVideo"]
|
||||
c.appendChild(i)
|
||||
c.setAttribute("class", "count");
|
||||
this.count.appendChild(c)
|
||||
}
|
||||
}
|
||||
|
||||
get node() {
|
||||
var node = document.createElement("div");
|
||||
node.appendChild(this.count);
|
||||
node.appendChild(this.frame);
|
||||
return node;
|
||||
}
|
||||
}
|
||||
|
||||
function getnode(uuid, meta) {
|
||||
function getImageNode(uuid, meta) {
|
||||
var path = "img/" + uuid + "/" + meta["file"];
|
||||
var node = document.createElement("img");
|
||||
var a = document.createElement("a");
|
||||
node.setAttribute("src", path);
|
||||
node.setAttribute("alt", path);
|
||||
return node;
|
||||
}
|
||||
|
||||
function getVideoNode(uuid, meta) {
|
||||
var path = "img/" + uuid + "/" + meta["file"];
|
||||
var video = document.createElement("video");
|
||||
video.setAttribute("src", path);
|
||||
video.setAttribute("controls", "");
|
||||
return video;
|
||||
}
|
||||
|
||||
function getGalleryNode(uuid, meta) {
|
||||
return (new Gallery(uuid, meta)).node;
|
||||
}
|
||||
|
||||
var node;
|
||||
switch (meta["type"]) {
|
||||
case "GraphImage":
|
||||
node = getImageNode(uuid, meta);
|
||||
break;
|
||||
case "GraphVideo":
|
||||
node = getVideoNode(uuid, meta);
|
||||
break;
|
||||
case "Gallery":
|
||||
node = getGalleryNode(uuid, meta);
|
||||
break;
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
function httpGetSync(url) {
|
||||
var xmlHttp = new XMLHttpRequest();
|
||||
xmlHttp.open("GET", url, false); // true for asynchronous
|
||||
xmlHttp.send(null);
|
||||
return xmlHttp.responseText;
|
||||
}
|
||||
|
||||
function load() {
|
||||
var meta = JSON.parse(httpGetSync("api/meta?uuid=" + uuid));
|
||||
var view = document.querySelector("main");
|
||||
var desc = document.querySelector("#desc");
|
||||
var date = document.querySelector("#date");
|
||||
|
||||
var node = getnode(uuid, meta);
|
||||
|
||||
view.appendChild(node);
|
||||
desc.innerText = meta["description"];
|
||||
|
||||
options = {
|
||||
weekday: "long",
|
||||
year: "numeric",
|
||||
month:"long",
|
||||
day:"numeric"
|
||||
};
|
||||
date.innerText = (new Date(meta["date"].replace(/-/g, "/"))).toLocaleString(window.navigator.language, options);
|
||||
}
|
||||
|
||||
console.log(uuid)
|
||||
|
||||
window.onload = load;
|
|
@ -0,0 +1,165 @@
|
|||
<!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/style.css" rel="stylesheet" type="text/css" />
|
||||
<link href="../static/admin.css" rel="stylesheet" type="text/css" />
|
||||
<title>igar: zona del admin 😎</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1> Zona del admin 😎 </h1>
|
||||
<div class="action-wrap">
|
||||
<button class="cajetin">Añadir imagen</button>
|
||||
<form class="action" id="add" method="post" target="add" enctype="multipart/form-data">
|
||||
<div class="form-elem"> <input name="file" type="file" placeholder="Imagen"/> </div>
|
||||
<div class="form-elem"> <input name="desc" type="text" placeholder="Descripción"/> </div>
|
||||
<div class="form-elem"> <input name="date" type="date" placeholder="Fecha"/> </div>
|
||||
<hr/>
|
||||
<input type="submit" value="Añadir">
|
||||
</form>
|
||||
</div>
|
||||
<div class="action-wrap">
|
||||
<button class="cajetin">Archivar/Eliminar imagen</button>
|
||||
<div class="action list">
|
||||
<div class="scroll" id="dellist">
|
||||
{{ range .List }}
|
||||
<div class="action">
|
||||
<div class="date"> {{ .Date }} </div>
|
||||
<div class="desc"> {{ .Description }} </div>
|
||||
<button onclick="open({{ .Post }})">
|
||||
Ir a...
|
||||
</button>
|
||||
<button onclick="archive.call(this, {{ .Post }})">
|
||||
Archivar
|
||||
</button>
|
||||
<button onclick="delpost.call(this, {{ .Post }})">
|
||||
Eliminar
|
||||
</button>
|
||||
</div>
|
||||
{{ end }}
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
|
||||
</script>
|
||||
</div>
|
||||
<footer>
|
||||
<a href="..">Volver al inicio</a>
|
||||
</footer>
|
||||
<script>
|
||||
function refreshHeight(elem) {
|
||||
elem.style.maxHeight = `${elem.scrollHeight}px`;
|
||||
}
|
||||
|
||||
function remove(elem) {
|
||||
elem.parentElement.removeChild(elem);
|
||||
}
|
||||
|
||||
function result(elem, msg) {
|
||||
elem.closest('.action').setAttribute('result', msg);
|
||||
refreshHeight(elem.closest('.action-wrap'));
|
||||
}
|
||||
|
||||
function open(uuid) {
|
||||
window.open(`../view?uuid=${uuid}`);
|
||||
}
|
||||
|
||||
async function archive(uuid) {
|
||||
if (confirm('Vas a archivar una imagen. ¿Estás seguro?')) {
|
||||
response = await fetch(`post/${uuid}`, { method: 'DELETE' })
|
||||
if (response.ok) {
|
||||
remove(this);
|
||||
result(this, 'Imagen archivada');
|
||||
} else {
|
||||
error = JSON.parse(response.body);
|
||||
alert(`Ha habido un error archivando la imagen: ${error.reason}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function delpost(uuid) {
|
||||
if (confirm('Vas a eliminar una imagen. ¿Estás seguro?')) {
|
||||
response = await fetch(`post/${uuid}?purge=true`, { method: 'DELETE' });
|
||||
if (response.ok) {
|
||||
remove(this);
|
||||
result(this, 'Imagen eliminada');
|
||||
} else {
|
||||
error = JSON.parse(response.body);
|
||||
alert(`Ha habido un error eliminando la imagen: ${error.reason}`);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// case open/close
|
||||
document.querySelectorAll('.action-wrap').forEach(elem => {
|
||||
let cajetin = elem.querySelector('.cajetin');
|
||||
elem.setAttribute('collapsed', true);
|
||||
elem.style.maxHeight = `${cajetin.scrollHeight}px`;
|
||||
cajetin.onclick = function() {
|
||||
if (elem.getAttribute('collapsed')) {
|
||||
refreshHeight(elem);
|
||||
elem.setAttribute('collapsed', '');
|
||||
} else {
|
||||
elem.style.maxHeight = `${cajetin.scrollHeight}px`;
|
||||
elem.setAttribute('collapsed', true);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// form result clear on input update
|
||||
document.querySelectorAll('.form-elem > input').forEach(elem => {
|
||||
elem.addEventListener('input', () => {
|
||||
elem.parentElement.removeAttribute('error');
|
||||
elem.closest('form').removeAttribute('result');
|
||||
refreshHeight(elem.closest('.action-wrap'));
|
||||
});
|
||||
});
|
||||
|
||||
// form AJAX
|
||||
let form = document.querySelector('#add')
|
||||
form.onsubmit = function(event) {
|
||||
event.preventDefault();
|
||||
|
||||
let files = form.file.files;
|
||||
let data = new FormData(form);
|
||||
|
||||
try {
|
||||
if (!files[0] || !files[0].type.match('image.*')) {
|
||||
result(form, 'Selecciona una imagen');
|
||||
return;
|
||||
}
|
||||
|
||||
result(form, 'Subiendo...');
|
||||
|
||||
// Set up the request
|
||||
let xhr = new XMLHttpRequest();
|
||||
|
||||
// Open the connection
|
||||
xhr.open('POST', 'post', true);
|
||||
|
||||
xhr.upload.onprogress = (prog) => {
|
||||
result(form, `${prog.loaded / prog.total * 100}%`);
|
||||
}
|
||||
|
||||
// Set up a handler for when the task for the request is complete
|
||||
xhr.onload = function () {
|
||||
if (xhr.status == 201) {
|
||||
result(form, 'Imagen publicada');
|
||||
} else {
|
||||
result(form, 'Error publicando imagen');
|
||||
}
|
||||
};
|
||||
|
||||
// Send the data.
|
||||
xhr.send(data);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
result(form, 'Error publicando imagen');
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,142 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="es">
|
||||
<head>
|
||||
<title>danoloan.es: ver imagen</title>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel=stylesheet href="/static/style.css">
|
||||
<style>
|
||||
body {
|
||||
max-width: 75vw;
|
||||
}
|
||||
|
||||
main {
|
||||
margin: 0.5em;
|
||||
margin-top: 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
main > img, main > video {
|
||||
{{if gt .Len 1 }}
|
||||
max-height: 55vh;
|
||||
{{ else }}
|
||||
max-height: 72vh;
|
||||
{{ end }}
|
||||
max-width: 100%;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
nav {
|
||||
margin: 0.5em;
|
||||
}
|
||||
|
||||
header {
|
||||
margin: 0.25em 0.5em;
|
||||
border-bottom: #b5b5b5 solid 1px;
|
||||
padding-bottom: 0.5em;
|
||||
}
|
||||
|
||||
#date {
|
||||
margin-top: 0.25em;
|
||||
font-size: 50%;
|
||||
}
|
||||
|
||||
div.count {
|
||||
margin-bottom: 0.25em;
|
||||
}
|
||||
|
||||
span.count > img {
|
||||
margin: auto 0.25em;
|
||||
max-height: 1em;
|
||||
vertical-align: middle;
|
||||
}
|
||||
span.count {
|
||||
margin: 10px 0.5em;
|
||||
font-size: 1.5em;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 680px) {
|
||||
body {
|
||||
max-width: 680px;
|
||||
padding-left: 0px;
|
||||
padding-right: 0px;
|
||||
}
|
||||
#desc {
|
||||
font-size: 90%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<script src="static/view.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<a href=".">« ir atrás</a>
|
||||
<div id="desc">
|
||||
{{ .Description }}
|
||||
</div>
|
||||
<div id="date">
|
||||
{{ .Date.String }}
|
||||
</div>
|
||||
<script>
|
||||
;
|
||||
document.getElementById("date").innerText =
|
||||
new Date({{ .Date.Unix }} * 1000)
|
||||
.toLocaleString(window.navigator.language, {
|
||||
weekday: "long",
|
||||
year: "numeric",
|
||||
month:"long",
|
||||
day:"numeric"
|
||||
})
|
||||
</script>
|
||||
</header>
|
||||
<main>
|
||||
<div class="count">
|
||||
{{ if .Count.GraphImage }}
|
||||
<span class="count">
|
||||
{{ .Count.GraphImage }}
|
||||
<img src="static/image.png" alt="imágenes">
|
||||
</span>
|
||||
{{ end }}
|
||||
{{ if .Count.GraphVideo }}
|
||||
<span class="count">
|
||||
{{ .Count.GraphVideo }}
|
||||
<img src="static/video.png" alt="vídeos">
|
||||
</span>
|
||||
{{ end }}
|
||||
</div>
|
||||
<nav>
|
||||
{{ if .Next }}
|
||||
<span class="prev">
|
||||
<a href="view?uuid={{ .Next.Post }}">« sigiente</a>
|
||||
</span>
|
||||
{{ end }}
|
||||
{{ if .Prev }}
|
||||
<span class="next">
|
||||
<a href="view?uuid={{ .Prev.Post }}">anterior »</a>
|
||||
</span>
|
||||
{{ end }}
|
||||
</nav>
|
||||
{{ $post := .Post }}
|
||||
{{ range .Media }}
|
||||
{{ if eq .Type "GraphImage" }}
|
||||
<img src="img/{{ $post }}/{{ .File }}" alt="imagen"/>
|
||||
{{ end }}
|
||||
{{ if eq .Type "GraphVideo" }}
|
||||
<video src="img/{{ $post }}/{{ .File }}" controls/></video>
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
<nav>
|
||||
{{ if .Next }}
|
||||
<span class="prev">
|
||||
<a href="view?uuid={{ .Next.Post }}">« siguiente</a>
|
||||
</span>
|
||||
{{ end }}
|
||||
{{ if .Prev }}
|
||||
<span class="next">
|
||||
<a href="view?uuid={{ .Prev.Post }}">anterior »</a>
|
||||
</span>
|
||||
{{ end }}
|
||||
</nav>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,3 @@
|
|||
const thumbs = require('./thumbs.js');
|
||||
|
||||
thumbs.dothumb(process.argv[2]);
|
|
@ -0,0 +1,77 @@
|
|||
const fs = require('fs/promises');
|
||||
const thumbs = require('./thumbs.js');
|
||||
|
||||
const dir = process.argv[2];
|
||||
const dest = process.argv[3];
|
||||
|
||||
fs.readdir(dir)
|
||||
.then(async files => {
|
||||
const list = [];
|
||||
for (const file of files) {
|
||||
const regex = /\.json/;
|
||||
if (file.match(regex)) {
|
||||
const data = await fs.readFile(`${dir}/${file}`);
|
||||
const json = JSON.parse(data);
|
||||
list.push(json);
|
||||
}
|
||||
}
|
||||
return list;
|
||||
})
|
||||
.then(list => {
|
||||