Add first version (from old git)
This commit is contained in:
parent
714268ff5a
commit
26e22f8bd0
|
@ -0,0 +1,35 @@
|
||||||
|
<!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>splog: zona del admin 😎</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1> Zona del admin 😎 </h1>
|
||||||
|
<div class="action-wrap">
|
||||||
|
<button class="cajetin">Añadir canción</button>
|
||||||
|
<form id="add" class="action">
|
||||||
|
<div class="form-elem"> <input name="artist" type="text" placeholder="Artista"/> </div>
|
||||||
|
<div class="form-elem"> <input name="track" type="text" placeholder="Canción"/> </div>
|
||||||
|
<div class="form-elem"> <input name="album" type="text" placeholder="Álbum"/> </div>
|
||||||
|
<div class="form-elem"> <input name="linkto" type="text" placeholder="Enlace"/> </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">Eliminar canción</button>
|
||||||
|
<div class="action">
|
||||||
|
<div class="scroll" id="dellist"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<footer>
|
||||||
|
<a href="..">Volver al inicio</a>
|
||||||
|
</footer>
|
||||||
|
<script src="static/main.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,114 @@
|
||||||
|
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;
|
||||||
|
});
|
|
@ -0,0 +1,2 @@
|
||||||
|
node_modules/
|
||||||
|
package-lock.json
|
|
@ -0,0 +1,53 @@
|
||||||
|
const fetch = require('node-fetch');
|
||||||
|
const allSettled = require('promise.allsettled');
|
||||||
|
const lastfm = 'http://ws.audioscrobbler.com/2.0/'
|
||||||
|
const quality = 3;
|
||||||
|
|
||||||
|
allSettled.shim();
|
||||||
|
|
||||||
|
async function asklastfm(apikey, query) {
|
||||||
|
try {
|
||||||
|
let url = `${lastfm}?format=json&api_key=${apikey}&${query}`;
|
||||||
|
let res = await fetch(url);
|
||||||
|
return res.json();
|
||||||
|
} catch(e) {
|
||||||
|
console.log(query);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getalbum(apikey, elem) {
|
||||||
|
let query, info = undefined;
|
||||||
|
|
||||||
|
if (elem['album'] !== undefined) {
|
||||||
|
query =`method=album.getinfo&artist=${encodeURIComponent(elem['artist'])}&album=${encodeURIComponent(elem['album'])}`;
|
||||||
|
info = await asklastfm(apikey, query);
|
||||||
|
} else if (elem['track'] !== undefined) {
|
||||||
|
query =`method=track.getinfo&artist=${encodeURIComponent(elem['artist'])}&track=${encodeURIComponent(elem['track'])}`;
|
||||||
|
info = (await asklastfm(apikey, query))['track']
|
||||||
|
}
|
||||||
|
|
||||||
|
if (info !== undefined) {
|
||||||
|
return info['album'];
|
||||||
|
} else {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.updatecovers = async function (apikey, list) {
|
||||||
|
let promises = list.map(async elem => {
|
||||||
|
if (!elem.cover) {
|
||||||
|
let info = await getalbum(apikey, elem);
|
||||||
|
if (typeof info === 'object' && info['image'] instanceof Array) {
|
||||||
|
let count = info['image'].length;
|
||||||
|
let index = count > quality ? quality : count;
|
||||||
|
let image = info['image'][index]['#text'];
|
||||||
|
if (image) {
|
||||||
|
elem['cover'] = image;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return elem;
|
||||||
|
});
|
||||||
|
return Promise.allSettled(promises);
|
||||||
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
#!/bin/node
|
||||||
|
const fs = require('fs');
|
||||||
|
const fsp = fs.promises;
|
||||||
|
const covers = require('./covers.js')
|
||||||
|
const apikey = '77456d34d3c9185016ef4535935dccf3'
|
||||||
|
|
||||||
|
function listener() {
|
||||||
|
console.log('File changed, updating covers...');
|
||||||
|
watcher.close();
|
||||||
|
fsp.readFile('../list.json')
|
||||||
|
.then(data => {
|
||||||
|
return covers.updatecovers(apikey, JSON.parse(data.toString()));
|
||||||
|
})
|
||||||
|
.then(list => {
|
||||||
|
list = list.map(result => {
|
||||||
|
if (result.status === 'rejected') {
|
||||||
|
throw result.reason;
|
||||||
|
}
|
||||||
|
return result['value'];
|
||||||
|
});
|
||||||
|
console.log(`Updated ${list.length} entries`);
|
||||||
|
return fsp.writeFile('../list.json', JSON.stringify(list, null, 4));
|
||||||
|
})
|
||||||
|
.catch(reason => {
|
||||||
|
console.log(reason);
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
watcher = fs.watch('../list.json', listener);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let watcher = fs.watch('../list.json', listener);
|
|
@ -0,0 +1,6 @@
|
||||||
|
{
|
||||||
|
"dependencies": {
|
||||||
|
"node-fetch": "^2.6.1",
|
||||||
|
"promise.allsettled": "^1.0.4"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
module muse
|
||||||
|
|
||||||
|
go 1.16
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/dgrijalva/jwt-go v3.2.0+incompatible // indirect
|
||||||
|
github.com/gorilla/feeds v1.1.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,42 @@
|
||||||
|
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/gorilla/feeds v1.1.1 h1:HwKXxqzcRNg9to+BbvJog4+f3s/xzvtZXICcQGutYfY=
|
||||||
|
github.com/gorilla/feeds v1.1.1/go.mod h1:Nk0jZrvPFZX1OBe5NPiddPw7CfwF6Q9eqzaBbaightA=
|
||||||
|
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,169 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="es">
|
||||||
|
<head>
|
||||||
|
<title>bitácora musical de danoloan</title>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="initial-scale=1">
|
||||||
|
<link rel=stylesheet href="/static/style.css">
|
||||||
|
<link rel=stylesheet href="static/style.css">
|
||||||
|
<style>
|
||||||
|
|
||||||
|
body {
|
||||||
|
max-width: 990px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.elem {
|
||||||
|
border-radius: 5px;
|
||||||
|
width: calc((100% - (5px * 6)) / 3);
|
||||||
|
margin: 5px;
|
||||||
|
display: inline-table;
|
||||||
|
background-color: #333;
|
||||||
|
color: #ddd;
|
||||||
|
transition: transform .1s;
|
||||||
|
box-sizing: border-box;
|
||||||
|
word-break: break-word;
|
||||||
|
font-size: calc(0.5em + 1vw);
|
||||||
|
}
|
||||||
|
|
||||||
|
.elem:hover {
|
||||||
|
transform: scale(1.05);
|
||||||
|
background-color: #373737;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
margin: 10px 20px;
|
||||||
|
font-weight: bold;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.date {
|
||||||
|
color: gray;
|
||||||
|
font-size: 70%;
|
||||||
|
text-align: right;
|
||||||
|
margin: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
div#load {
|
||||||
|
text-align: center;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#load > img {
|
||||||
|
width: 5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
img {
|
||||||
|
width: 100%;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
nav {
|
||||||
|
position: absolute;
|
||||||
|
top: 25px;
|
||||||
|
right: 25px;
|
||||||
|
}
|
||||||
|
|
||||||
|
nav > a {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 5px 15px;
|
||||||
|
margin: 5px 0px;
|
||||||
|
border-radius: 5px;
|
||||||
|
background-color: #444;
|
||||||
|
}
|
||||||
|
|
||||||
|
nav > a:hover {
|
||||||
|
background-color: #555;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 990px) {
|
||||||
|
nav {
|
||||||
|
position: initial;
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 25px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@media screen and (max-width: 700px) {
|
||||||
|
.elem {
|
||||||
|
width: calc((100% - (5px * 6)) / 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@media screen and (max-width: 320px) {
|
||||||
|
.elem {
|
||||||
|
width: 100%;
|
||||||
|
display: block;
|
||||||
|
font-size: calc(0.65em + 1vw);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: light) {
|
||||||
|
body {
|
||||||
|
background: #fff;
|
||||||
|
color: #222;
|
||||||
|
}
|
||||||
|
|
||||||
|
.elem {
|
||||||
|
color: #333;
|
||||||
|
background-color: #eee;
|
||||||
|
}
|
||||||
|
|
||||||
|
.elem:hover {
|
||||||
|
color: #333;
|
||||||
|
background-color: #ddd;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 musical
|
||||||
|
</p>
|
||||||
|
<p style="font-size: 75%">
|
||||||
|
la música que me va
|
||||||
|
</p>
|
||||||
|
<p style="font-size: 75%">
|
||||||
|
pincha en una canción para abrirla en <em>spotify</em>
|
||||||
|
</p>
|
||||||
|
<p style="font-size: 75%">
|
||||||
|
en <strong><a href="https://open.spotify.com/playlist/2J5wJd8yzwswKgC52f4iRV?si=lLqi6f0_T4Ki509CEceglQ">este enlace</a></strong> está la lista de reproducción de Spotify con todas las canciones <span style="font-size:50%">bueno no todas que soy un vago y no la actualizo</span>
|
||||||
|
</p>
|
||||||
|
</header>
|
||||||
|
<nav>
|
||||||
|
<a href="rss">RSS</a>
|
||||||
|
<a href="admin/">Zona del admin <span class="emoji">😎</span></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 por la página 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,317 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"encoding/json"
|
||||||
|
"crypto/sha512"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"io/ioutil"
|
||||||
|
"sort"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
"encoding/hex"
|
||||||
|
|
||||||
|
"github.com/labstack/echo"
|
||||||
|
"github.com/labstack/echo/middleware"
|
||||||
|
"github.com/gorilla/feeds"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
JSTime time.Time
|
||||||
|
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"`
|
||||||
|
}
|
||||||
|
Response struct {
|
||||||
|
Ok bool `json:"ok"`
|
||||||
|
Message string `json:"message"`
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
entryList struct {
|
||||||
|
filename string
|
||||||
|
modtime time.Time
|
||||||
|
v []Entry
|
||||||
|
mu sync.RWMutex
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
jsTimeLayout = "2006-01-02"
|
||||||
|
adminUsername = "danolo"
|
||||||
|
adminPassword = "bd4cad796950f50352225de3c773d8f3c39622bc17f34ad661eabe615cdf6d32751c5751e0648dc17d890f40330018334a2ae899878f200f6dc80121ddb70cc9"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
list *entryList = &entryList{
|
||||||
|
filename: "list.json",
|
||||||
|
modtime: time.Unix(0, 0),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
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 (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) 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
|
||||||
|
}
|
||||||
|
|
||||||
|
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 AddController(c echo.Context) (error) {
|
||||||
|
var res Response
|
||||||
|
var status int
|
||||||
|
var err error
|
||||||
|
|
||||||
|
entry := new(Entry)
|
||||||
|
if err = c.Bind(entry); err != nil {
|
||||||
|
res = Response{ false, err.Error() }
|
||||||
|
status = http.StatusBadRequest
|
||||||
|
} else if err = list.addEntry(entry); err != nil {
|
||||||
|
res = Response{ false, "Failed to write into list" }
|
||||||
|
status = http.StatusInternalServerError
|
||||||
|
} else {
|
||||||
|
res = Response{ true, "OK" }
|
||||||
|
status = http.StatusOK
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.JSON(status, res)
|
||||||
|
}
|
||||||
|
|
||||||
|
func DelController(c echo.Context) (error) {
|
||||||
|
var res Response
|
||||||
|
var status int
|
||||||
|
var pos int
|
||||||
|
|
||||||
|
pos, err := strconv.Atoi(c.QueryParam("p"))
|
||||||
|
if err == nil {
|
||||||
|
err = list.deleteElement(pos)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
res = Response{ true, "OK" }
|
||||||
|
status = http.StatusOK
|
||||||
|
} else {
|
||||||
|
res = Response{ false, "Failed to delete from list" }
|
||||||
|
status = http.StatusInternalServerError
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.JSON(status, res)
|
||||||
|
}
|
||||||
|
|
||||||
|
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 RSSController(c echo.Context) (err error) {
|
||||||
|
if blob, err := list.getFeed().ToRss(); err == nil {
|
||||||
|
c.Blob(http.StatusOK, "application/xml", []byte(blob))
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
e := echo.New()
|
||||||
|
|
||||||
|
e.GET("/list", ListController)
|
||||||
|
|
||||||
|
e.GET("/rss", RSSController)
|
||||||
|
e.Static("/", "index.html")
|
||||||
|
e.Static("/static", "static")
|
||||||
|
|
||||||
|
admin := e.Group("/admin", middleware.BasicAuth(auth))
|
||||||
|
admin.POST("/add", AddController)
|
||||||
|
admin.GET("/del", DelController)
|
||||||
|
admin.Static("/", "admin/index.html")
|
||||||
|
admin.Static("/static", "admin/static")
|
||||||
|
|
||||||
|
e.Logger.Fatal(e.Start(":30303"))
|
||||||
|
}
|
|
@ -0,0 +1,99 @@
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scroll {
|
||||||
|
margin: auto;
|
||||||
|
max-height: 800px;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cajetin {
|
||||||
|
width: 100%;
|
||||||
|
display: block;
|
||||||
|
padding: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.data-elem {
|
||||||
|
display: block;
|
||||||
|
margin: 10px 0px;
|
||||||
|
padding: 10px;
|
||||||
|
width: 100%;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
.data-elem:hover {
|
||||||
|
background-color: #a22;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 1000px) {
|
||||||
|
body {
|
||||||
|
max-width: 620px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@media screen and (max-width: 700px) {
|
||||||
|
.data-elem {
|
||||||
|
width: 100%;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
form {
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@media (prefers-color-scheme: light) {
|
||||||
|
.data-elem:hover {
|
||||||
|
background-color: #e77;
|
||||||
|
}
|
||||||
|
}
|
Binary file not shown.
After Width: | Height: | Size: 117 KiB |
|
@ -0,0 +1,64 @@
|
||||||
|
body {
|
||||||
|
font-family : Open Sans, Arial;
|
||||||
|
font-size : 20px;
|
||||||
|
color : #eee;
|
||||||
|
background-color: #222;
|
||||||
|
margin : 0em auto;
|
||||||
|
padding : 25px;
|
||||||
|
line-height : 1.4;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: #7af;
|
||||||
|
align-items: center;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:hover {
|
||||||
|
color: #77f;
|
||||||
|
}
|
||||||
|
|
||||||
|
img.icon {
|
||||||
|
margin-right: 5px;
|
||||||
|
vertical-align: middle;
|
||||||
|
height: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
input,
|
||||||
|
button {
|
||||||
|
background-color: #333;
|
||||||
|
border-radius: 4px;
|
||||||
|
border: none;
|
||||||
|
outline: none;
|
||||||
|
color: inherit;
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type=button],
|
||||||
|
input[type=submit],
|
||||||
|
button {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
input[type=button]:hover,
|
||||||
|
input[type=submit]:hover,
|
||||||
|
button:hover {
|
||||||
|
background-color: #444;
|
||||||
|
}
|
||||||
|
input[type=button]:active,
|
||||||
|
input[type=submit]:active,
|
||||||
|
button:active {
|
||||||
|
box-shadow: inset 0px 0px 2px #aaa;
|
||||||
|
}
|
||||||
|
input[type=button]::-moz-focus-inner,
|
||||||
|
input[type=submit]::-moz-focus-inner,
|
||||||
|
button::-moz-focus-inner {
|
||||||
|
border: 0;
|
||||||
|
}
|
||||||
|
input[type=text] {
|
||||||
|
border: 1px solid #555;
|
||||||
|
}
|
||||||
|
|
||||||
|
hr {
|
||||||
|
border: 1px solid #555;
|
||||||
|
margin: 20px 0px;
|
||||||
|
}
|
|
@ -0,0 +1,65 @@
|
||||||
|
function songNode(info) {
|
||||||
|
let wrap = document.createElement("div");
|
||||||
|
let a = document.createElement("a");
|
||||||
|
let cover = document.createElement("img");
|
||||||
|
let title = document.createElement("div");
|
||||||
|
let date = document.createElement("div");
|
||||||
|
|
||||||
|
title.innerText = "" + info["artist"] + " - " + (info["track"] == undefined ? info["album"] : info["track"]);
|
||||||
|
|
||||||
|
options = {
|
||||||
|
year: "numeric",
|
||||||
|
month:"numeric",
|
||||||
|
day:"numeric"
|
||||||
|
};
|
||||||
|
|
||||||
|
if (info["date"] !== undefined) {
|
||||||
|
date.innerText = new Date(info["date"].replace(/-/g, "/"))
|
||||||
|
.toLocaleString(window.navigator.language, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (info["cover"] !== undefined) {
|
||||||
|
cover.setAttribute("src", info["cover"]);
|
||||||
|
} else {
|
||||||
|
cover.setAttribute("src", "static/noalbum.jpg");
|
||||||
|
}
|
||||||
|
|
||||||
|
title.setAttribute("class", "title");
|
||||||
|
cover.setAttribute("class", "cover");
|
||||||
|
date.setAttribute("class", "date");
|
||||||
|
|
||||||
|
wrap.setAttribute("class", "elem");
|
||||||
|
wrap.appendChild(cover);
|
||||||
|
wrap.appendChild(title);
|
||||||
|
wrap.appendChild(date);
|
||||||
|
|
||||||
|
if (info["linkto"]) {
|
||||||
|
a.setAttribute("href", info["linkto"]);
|
||||||
|
}
|
||||||
|
a.appendChild(wrap);
|
||||||
|
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
|
||||||
|
function addimages(list) {
|
||||||
|
let cont = document.querySelector("main");
|
||||||
|
let ret = false;
|
||||||
|
if (typeof list === "object" && list !== null) {
|
||||||
|
for (let i = 0; i < list.length; i++) {
|
||||||
|
cont.appendChild(songNode(list[i]));
|
||||||
|
}
|
||||||
|
ret = !list.length;
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
window.onload = () => {
|
||||||
|
window.app = new ProgView("list", addimages);
|
||||||
|
window.app.down();
|
||||||
|
}
|
||||||
|
|
||||||
|
window.onbeforeunload = () => {
|
||||||
|
if (!window.app.stored) {
|
||||||
|
window.app.clear();
|
||||||
|
}
|
||||||
|
};
|
Binary file not shown.
After Width: | Height: | Size: 32 KiB |
|
@ -0,0 +1,123 @@
|
||||||
|
const N = 12;
|
||||||
|
class ProgView {
|
||||||
|
constructor(list, addfn) {
|
||||||
|
this.n = null;
|
||||||
|
this.s = null;
|
||||||
|
this.last = 0;
|
||||||
|
this.list = list;
|
||||||
|
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) {
|
||||||
|
console.log(document.documentElement.scrollHeight);
|
||||||
|
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(`${this.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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
[Unit]
|
||||||
|
Description=muse-covers
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
Restart=always
|
||||||
|
RestartSec=5s
|
||||||
|
WorkingDirectory=/var/www/apps/muse/covers/
|
||||||
|
ExecStart=/usr/bin/node fetch_covers.js
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
|
@ -0,0 +1,12 @@
|
||||||
|
[Unit]
|
||||||
|
Description=muse
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
Restart=always
|
||||||
|
RestartSec=5s
|
||||||
|
WorkingDirectory=/var/www/apps/muse/
|
||||||
|
ExecStart=/var/www/apps/muse/muse
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
Loading…
Reference in New Issue