Redesign details view.
The details view is now implemented with css grid, in a full-window no-scroll layout (except for small screens, where the design is still sequential and scroll is used) This incudes some cleanup of the markup and corresponding changes in the admin script.
This commit is contained in:
parent
89a92c1738
commit
8ee8b809bc
150
res/admin.js
150
res/admin.js
@ -1,8 +1,8 @@
|
|||||||
// Admin functionality for rphotos
|
// Admin functionality for rphotos
|
||||||
(function (d) {
|
(function (d) {
|
||||||
var details = d.querySelector('.details');
|
var details = d.querySelector('main.details'), p;
|
||||||
if (!details) {
|
if (!details) {
|
||||||
return;
|
return; // Admin is for single image only
|
||||||
}
|
}
|
||||||
|
|
||||||
function rotate(event) {
|
function rotate(event) {
|
||||||
@ -32,22 +32,29 @@
|
|||||||
r.send("angle=" + angle + "&image=" + imgid)
|
r.send("angle=" + angle + "&image=" + imgid)
|
||||||
}
|
}
|
||||||
|
|
||||||
function tag_form(event, category) {
|
function makeform(category) {
|
||||||
event.target.disabled = true;
|
let oldform = p.querySelector('form');
|
||||||
var imgid = details.dataset.imgid;
|
if (oldform) {
|
||||||
|
oldform.remove();
|
||||||
|
}
|
||||||
var f = d.createElement("form");
|
var f = d.createElement("form");
|
||||||
f.className = "admin " + category;
|
f.className = "admin " + category;
|
||||||
f.action = "/adm/" + category;
|
f.action = "/adm/" + category;
|
||||||
f.method = "post";
|
f.method = "post";
|
||||||
|
var i = d.createElement("input");
|
||||||
|
i.type="hidden";
|
||||||
|
i.name="image";
|
||||||
|
i.value = details.dataset.imgid;
|
||||||
|
f.appendChild(i);
|
||||||
|
return f;
|
||||||
|
}
|
||||||
|
function tag_form(event, category) {
|
||||||
|
//event.target.disabled = true; - FIXME?
|
||||||
|
var f = makeform(category);
|
||||||
var l = d.createElement("label");
|
var l = d.createElement("label");
|
||||||
l.innerHTML = event.target.title;
|
l.innerHTML = event.target.title;
|
||||||
f.appendChild(l);
|
f.appendChild(l);
|
||||||
var i = d.createElement("input");
|
var i = d.createElement("input");
|
||||||
i.type="hidden";
|
|
||||||
i.name="image";
|
|
||||||
i.value = imgid;
|
|
||||||
f.appendChild(i);
|
|
||||||
i = d.createElement("input");
|
|
||||||
i.type = "text";
|
i.type = "text";
|
||||||
i.autocomplete="off";
|
i.autocomplete="off";
|
||||||
i.tabindex="1";
|
i.tabindex="1";
|
||||||
@ -139,27 +146,18 @@
|
|||||||
event.target.focus();
|
event.target.focus();
|
||||||
};
|
};
|
||||||
f.appendChild(c);
|
f.appendChild(c);
|
||||||
meta.insertBefore(f, meta.querySelector('#map'));
|
p.append(f);
|
||||||
i.focus();
|
i.focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
function grade_form(event) {
|
function grade_form(event) {
|
||||||
event.target.disabled = true;
|
//event.target.disabled = true; - FIXME?
|
||||||
var imgid = details.dataset.imgid;
|
|
||||||
var grade = details.dataset.grade;
|
var grade = details.dataset.grade;
|
||||||
var f = d.createElement("form");
|
var f = makeform("grade");
|
||||||
f.className = "admin grade";
|
|
||||||
f.action = "/adm/grade";
|
|
||||||
f.method = "post";
|
|
||||||
var l = d.createElement("label");
|
var l = d.createElement("label");
|
||||||
l.innerHTML = event.target.title;
|
l.innerHTML = event.target.title;
|
||||||
f.appendChild(l);
|
f.appendChild(l);
|
||||||
var i = d.createElement("input");
|
var i = d.createElement("input");
|
||||||
i.type="hidden";
|
|
||||||
i.name="image";
|
|
||||||
i.value = imgid;
|
|
||||||
f.appendChild(i);
|
|
||||||
i = d.createElement("input");
|
|
||||||
i.type="range";
|
i.type="range";
|
||||||
i.name="grade";
|
i.name="grade";
|
||||||
if (grade) {
|
if (grade) {
|
||||||
@ -199,23 +197,14 @@
|
|||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
meta.insertBefore(f, meta.querySelector('#map'));
|
p.append(f);
|
||||||
i.focus();
|
i.focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
function location_form(event) {
|
function location_form(event) {
|
||||||
event.target.disabled = true;
|
//event.target.disabled = true; - FIXME?
|
||||||
var imgid = details.dataset.imgid;
|
|
||||||
var position = details.dataset.position || localStorage.getItem('lastpos');
|
var position = details.dataset.position || localStorage.getItem('lastpos');
|
||||||
var f = d.createElement("form");
|
var f = makeform("locate");
|
||||||
f.className = "admin location";
|
|
||||||
f.action = "/adm/locate";
|
|
||||||
f.method = "post";
|
|
||||||
var i = d.createElement("input");
|
|
||||||
i.type="hidden";
|
|
||||||
i.name="image";
|
|
||||||
i.value = imgid;
|
|
||||||
f.appendChild(i);
|
|
||||||
|
|
||||||
var lat = d.createElement("input");
|
var lat = d.createElement("input");
|
||||||
lat.type="hidden";
|
lat.type="hidden";
|
||||||
@ -303,58 +292,55 @@
|
|||||||
};
|
};
|
||||||
f.appendChild(c);
|
f.appendChild(c);
|
||||||
f.addEventListener('keydown', keyHandler);
|
f.addEventListener('keydown', keyHandler);
|
||||||
meta.insertBefore(f, meta.querySelector('#map'));
|
p.append(f);
|
||||||
}
|
}
|
||||||
|
|
||||||
var meta = details.querySelector('.meta');
|
p = d.createElement("div");
|
||||||
if (meta) {
|
p.className = 'admbuttons';
|
||||||
p = d.createElement("p");
|
r = d.createElement("button");
|
||||||
p.className = 'admbuttons';
|
r.onclick = rotate;
|
||||||
r = d.createElement("button");
|
r.innerHTML = "\u27f2";
|
||||||
r.onclick = rotate;
|
r.dataset.angle = "-90";
|
||||||
r.innerHTML = "\u27f2";
|
r.title = "Rotate left";
|
||||||
r.dataset.angle = "-90";
|
p.appendChild(r);
|
||||||
r.title = "Rotate left";
|
p.appendChild(d.createTextNode(" "));
|
||||||
p.appendChild(r);
|
r = d.createElement("button");
|
||||||
p.appendChild(d.createTextNode(" "));
|
r.onclick = rotate;
|
||||||
r = d.createElement("button");
|
r.innerHTML = "\u27f3";
|
||||||
r.onclick = rotate;
|
r.dataset.angle = "90";
|
||||||
r.innerHTML = "\u27f3";
|
r.title = "Rotate right";
|
||||||
r.dataset.angle = "90";
|
p.appendChild(r);
|
||||||
r.title = "Rotate right";
|
|
||||||
p.appendChild(r);
|
|
||||||
|
|
||||||
p.appendChild(d.createTextNode(" "));
|
p.appendChild(d.createTextNode(" "));
|
||||||
r = d.createElement("button");
|
r = d.createElement("button");
|
||||||
r.onclick = e => tag_form(e, 'tag');
|
r.onclick = e => tag_form(e, 'tag');
|
||||||
r.innerHTML = "🏷";
|
r.innerHTML = "🏷";
|
||||||
r.title = "Tag";
|
r.title = "Tag";
|
||||||
r.accessKey = "t";
|
r.accessKey = "t";
|
||||||
p.appendChild(r);
|
p.appendChild(r);
|
||||||
|
|
||||||
p.appendChild(d.createTextNode(" "));
|
p.appendChild(d.createTextNode(" "));
|
||||||
r = d.createElement("button");
|
r = d.createElement("button");
|
||||||
r.onclick = e => tag_form(e, 'person');
|
r.onclick = e => tag_form(e, 'person');
|
||||||
r.innerHTML = "\u263a";
|
r.innerHTML = "\u263a";
|
||||||
r.title = "Person";
|
r.title = "Person";
|
||||||
r.accessKey = "p";
|
r.accessKey = "p";
|
||||||
p.appendChild(r);
|
p.appendChild(r);
|
||||||
|
|
||||||
p.appendChild(d.createTextNode(" "));
|
p.appendChild(d.createTextNode(" "));
|
||||||
r = d.createElement("button");
|
r = d.createElement("button");
|
||||||
r.onclick = e => location_form(e);
|
r.onclick = e => location_form(e);
|
||||||
r.innerHTML = "\u{1f5fa}";
|
r.innerHTML = "\u{1f5fa}";
|
||||||
r.title = "Location";
|
r.title = "Location";
|
||||||
r.accessKey = "l";
|
r.accessKey = "l";
|
||||||
p.appendChild(r);
|
p.appendChild(r);
|
||||||
|
|
||||||
p.appendChild(d.createTextNode(" "));
|
p.appendChild(d.createTextNode(" "));
|
||||||
r = d.createElement("button");
|
r = d.createElement("button");
|
||||||
r.onclick = e => grade_form(e);
|
r.onclick = e => grade_form(e);
|
||||||
r.innerHTML = "\u2606";
|
r.innerHTML = "\u2606";
|
||||||
r.title = "Grade";
|
r.title = "Grade";
|
||||||
r.accessKey = "g";
|
r.accessKey = "g";
|
||||||
p.appendChild(r);
|
p.appendChild(r);
|
||||||
meta.appendChild(p);
|
details.appendChild(p);
|
||||||
}
|
|
||||||
})(document)
|
})(document)
|
||||||
|
112
res/photos.scss
112
res/photos.scss
@ -15,6 +15,9 @@ body {
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
min-height: 100%;
|
min-height: 100%;
|
||||||
|
|
||||||
|
// FIXME? Only on details?
|
||||||
|
max-height: 100vh;
|
||||||
}
|
}
|
||||||
|
|
||||||
h1 {
|
h1 {
|
||||||
@ -237,39 +240,79 @@ div.group {
|
|||||||
justify-content: space-around;
|
justify-content: space-around;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
div.details {
|
|
||||||
display: flex;
|
|
||||||
flex-flow: row wrap;
|
|
||||||
margin: -1ex;
|
|
||||||
|
|
||||||
.item, .meta {
|
main.details {
|
||||||
margin: 1ex;
|
margin: 0;
|
||||||
}
|
padding: 1ex;
|
||||||
.item {
|
|
||||||
align-self: flex-start;
|
img.item {
|
||||||
flex-grow: 4;
|
height: auto;
|
||||||
text-align: center;
|
width: -moz-available;
|
||||||
width: 30em;
|
width: -webkit-fill-available;
|
||||||
|
width: available;
|
||||||
&.zoom {
|
&.zoom {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
width: -moz-available;
|
width: -moz-available;
|
||||||
width: -webkit-fill-available;
|
height: -moz-available;
|
||||||
width: available;
|
z-index: 10000;
|
||||||
|
object-fit: contain;
|
||||||
}
|
}
|
||||||
img {
|
}
|
||||||
width: -moz-available;
|
}
|
||||||
width: -webkit-fill-available;
|
|
||||||
width: available;
|
@media screen and (min-width: 50em) {
|
||||||
height: auto;
|
main.details {
|
||||||
|
align-items: start;
|
||||||
|
display: grid;
|
||||||
|
flex: content 1 1;
|
||||||
|
grid-gap: 1ex;
|
||||||
|
grid-template-columns: 1fr fit-content(29%);
|
||||||
|
grid-template-rows: min-content 1fr min-content 1fr;
|
||||||
|
max-height: -moz-available;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
grid-column: 2;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
img.item {
|
||||||
display: block;
|
display: block;
|
||||||
|
grid-row: 1 / -1;
|
||||||
|
margin: 0 auto auto;
|
||||||
|
max-width: -moz-available;
|
||||||
|
max-height: -moz-available;
|
||||||
|
max-height: calc(100% - 2px);
|
||||||
|
max-width: calc(100% - 2px);
|
||||||
|
object-fit: scale-down;
|
||||||
|
width: auto;
|
||||||
|
height: auto;
|
||||||
|
|
||||||
|
.zoom {
|
||||||
|
grid-column: 1 / 3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.places a:nth-child(n+2) {
|
||||||
|
font-size: 80%;
|
||||||
|
}
|
||||||
|
.meta {
|
||||||
|
overflow: auto;
|
||||||
|
height: -moz-available;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
#map {
|
||||||
|
margin: 0;
|
||||||
|
height: calc(100% - 2px) !important;
|
||||||
|
width: -moz-available;
|
||||||
|
}
|
||||||
|
.admbuttons {
|
||||||
|
flex-flow: row wrap;
|
||||||
|
margin: 0;
|
||||||
|
button {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
.meta {
|
|
||||||
flex-grow: 1;
|
|
||||||
flex-basis: 20em;
|
|
||||||
padding-top: 0;
|
|
||||||
}
|
|
||||||
.places a:nth-child(n+2) {
|
|
||||||
font-size: 80%;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -285,13 +328,13 @@ ul.alltags, ul.allpeople, ul.allplaces {
|
|||||||
max-height: 60vh;
|
max-height: 60vh;
|
||||||
}
|
}
|
||||||
|
|
||||||
p.admbuttons {
|
div.admbuttons {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-flow: row wrap;
|
flex-flow: row wrap;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
margin: 1ex -.1em 0;
|
margin: 1ex -.1em 0;
|
||||||
button {
|
button {
|
||||||
flex-grow: .1;
|
flex: min-content .1 1;
|
||||||
margin: .1em;
|
margin: .1em;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
@ -310,7 +353,7 @@ form {
|
|||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
}
|
}
|
||||||
label {
|
label {
|
||||||
padding: .2em 1em .2em 0;
|
padding: .2em .6em .2em 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -341,15 +384,16 @@ form {
|
|||||||
|
|
||||||
// Relevant for admin forms only. Move to separate file?
|
// Relevant for admin forms only. Move to separate file?
|
||||||
form.admin {
|
form.admin {
|
||||||
position: relative;
|
|
||||||
padding: 1.8em 1ex;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
|
margin: .3em .1em 0;
|
||||||
|
padding: 1.6em 1ex 1.2em;
|
||||||
|
position: relative;
|
||||||
width: -moz-available;
|
width: -moz-available;
|
||||||
width: -webkit-fill-available;
|
width: -webkit-fill-available;
|
||||||
width: available;
|
width: available;
|
||||||
|
|
||||||
input[type="text"], input[type="range"] {
|
input[type="text"], input[type="range"] {
|
||||||
flex-grow: 1;
|
flex: min-content 1 1;
|
||||||
margin-right: 1ex;
|
margin-right: 1ex;
|
||||||
}
|
}
|
||||||
button.close {
|
button.close {
|
||||||
@ -360,7 +404,7 @@ form.admin {
|
|||||||
right: -1ex;
|
right: -1ex;
|
||||||
top: -1ex;
|
top: -1ex;
|
||||||
}
|
}
|
||||||
&.location {
|
&.locate {
|
||||||
background: #eee;
|
background: #eee;
|
||||||
box-shadow: .2em .4em 1em rgba(0,0,0,.7);
|
box-shadow: .2em .4em 1em rgba(0,0,0,.7);
|
||||||
display: flex;
|
display: flex;
|
||||||
|
@ -43,7 +43,7 @@
|
|||||||
csslink.rel = 'stylesheet';
|
csslink.rel = 'stylesheet';
|
||||||
csslink.href = '/static/l140/leaflet.css';
|
csslink.href = '/static/l140/leaflet.css';
|
||||||
h.append(csslink);
|
h.append(csslink);
|
||||||
let m = d.querySelector('.meta') || d.querySelector('main');
|
let m = d.querySelector('main');
|
||||||
m.insertAdjacentHTML('beforeend', '<div id="map"></div>');
|
m.insertAdjacentHTML('beforeend', '<div id="map"></div>');
|
||||||
var slink = d.createElement('script');
|
var slink = d.createElement('script');
|
||||||
slink.type = 'text/javascript';
|
slink.type = 'text/javascript';
|
||||||
|
31
templates/base.rs.html
Normal file
31
templates/base.rs.html
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
@use super::statics::{photos_css, admin_js, ux_js};
|
||||||
|
@use super::head;
|
||||||
|
@use crate::server::{Context, Link};
|
||||||
|
|
||||||
|
@(context: &Context, title: &str, lpath: &[Link], meta: Content, content: Content)
|
||||||
|
|
||||||
|
<!doctype html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>@title</title>
|
||||||
|
<meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
||||||
|
<link rel="stylesheet" href="/static/@photos_css.name" type="text/css"/>
|
||||||
|
@if context.is_authorized() {
|
||||||
|
<script src="/static/@admin_js.name" type="text/javascript" defer>
|
||||||
|
</script>
|
||||||
|
}
|
||||||
|
<script src="/static/@ux_js.name" type="text/javascript" defer>
|
||||||
|
</script>
|
||||||
|
@:meta()
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
@:head(context, lpath)
|
||||||
|
@:content()
|
||||||
|
<footer>
|
||||||
|
<p>Managed by
|
||||||
|
<a href="https://github.com/kaj/rphotos">rphotos
|
||||||
|
@env!("CARGO_PKG_VERSION")</a>.</p>
|
||||||
|
</footer>
|
||||||
|
</body>
|
||||||
|
</html>
|
@ -1,16 +1,17 @@
|
|||||||
@use super::page_base;
|
@use super::base;
|
||||||
@use crate::models::{Photo, Person, Place, Tag, Camera, Coord, SizeTag};
|
@use crate::models::{Photo, Person, Place, Tag, Camera, Coord, SizeTag};
|
||||||
@use crate::server::{Context, Link};
|
@use crate::server::{Context, Link};
|
||||||
|
|
||||||
@(context: &Context, lpath: &[Link], people: &[Person], places: &[Place], tags: &[Tag], position: &Option<Coord>, attribution: &Option<String>, camera: &Option<Camera>, photo: &Photo)
|
@(context: &Context, lpath: &[Link], people: &[Person], places: &[Place], tags: &[Tag], position: &Option<Coord>, attribution: &Option<String>, camera: &Option<Camera>, photo: &Photo)
|
||||||
@:page_base(context, "Photo details", lpath, {
|
@:base(context, "Photo details", lpath, {
|
||||||
<meta property='og:title' content='Photo @if let Some(d) = photo.date {(@d.format("%F"))}'>
|
<meta property='og:title' content='Photo @if let Some(d) = photo.date {(@d.format("%F"))}'>
|
||||||
<meta property='og:type' content='image' />
|
<meta property='og:type' content='image' />
|
||||||
<meta property='og:image' content='/img/@photo.id-m.jpg' />
|
<meta property='og:image' content='/img/@photo.id-m.jpg' />
|
||||||
<meta property='og:description' content='@for p in people {@p.person_name, }@for t in tags {#@t.tag_name, }@if let Some(p) = places.first() {@p.place_name}'>
|
<meta property='og:description' content='@for p in people {@p.person_name, }@for t in tags {#@t.tag_name, }@if let Some(p) = places.first() {@p.place_name}'>
|
||||||
}, {
|
}, {
|
||||||
<div class="details" data-imgid="@photo.id"@if let Some(g) = photo.grade { data-grade="@g"}@if let Some(ref p) = *position { data-position="[@p.x, @p.y]"}>
|
<main class="details" data-imgid="@photo.id"@if let Some(g) = photo.grade { data-grade="@g"}@if let Some(ref p) = *position { data-position="[@p.x, @p.y]"}>
|
||||||
<div class="item"><img src="/img/@photo.id-m.jpg" width="@photo.get_size(SizeTag::Medium).0" height="@photo.get_size(SizeTag::Medium).1"></div>
|
<h1>Photo details</h1>
|
||||||
|
<img class="item" src="/img/@photo.id-m.jpg" width="@photo.get_size(SizeTag::Medium).0" height="@photo.get_size(SizeTag::Medium).1">
|
||||||
<div class="meta">
|
<div class="meta">
|
||||||
@if context.is_authorized() {
|
@if context.is_authorized() {
|
||||||
<p><a href="/img/@photo.id-l.jpg">@photo.path</a></p>
|
<p><a href="/img/@photo.id-l.jpg">@photo.path</a></p>
|
||||||
@ -29,5 +30,5 @@
|
|||||||
@if let Some(ref a) = *attribution {<p>Av: @a</p>}
|
@if let Some(ref a) = *attribution {<p>Av: @a</p>}
|
||||||
@if let Some(ref c) = *camera {<p>Camera: @c.model (@c.manufacturer)</p>}
|
@if let Some(ref c) = *camera {<p>Camera: @c.model (@c.manufacturer)</p>}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</main>
|
||||||
})
|
})
|
||||||
|
@ -1,34 +1,10 @@
|
|||||||
@use super::statics::{photos_css, admin_js, ux_js};
|
@use super::base;
|
||||||
@use crate::server::{Context, Link};
|
@use crate::server::{Context, Link};
|
||||||
@use crate::templates::head;
|
|
||||||
|
|
||||||
@(context: &Context, title: &str, lpath: &[Link], meta: Content, content: Content)
|
@(context: &Context, title: &str, lpath: &[Link], meta: Content, content: Content)
|
||||||
|
@:base(context, title, lpath, meta, {
|
||||||
<!doctype html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<title>@title</title>
|
|
||||||
<meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
|
||||||
<link rel="stylesheet" href="/static/@photos_css.name" type="text/css"/>
|
|
||||||
@if context.is_authorized() {
|
|
||||||
<script src="/static/@admin_js.name" type="text/javascript" defer>
|
|
||||||
</script>
|
|
||||||
}
|
|
||||||
<script src="/static/@ux_js.name" type="text/javascript" defer>
|
|
||||||
</script>
|
|
||||||
@:meta()
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
@:head(context, lpath)
|
|
||||||
<main>
|
<main>
|
||||||
<h1>@title</h1>
|
<h1>@title</h1>
|
||||||
@:content()
|
@:content()
|
||||||
</main>
|
</main>
|
||||||
<footer>
|
})
|
||||||
<p>Managed by
|
|
||||||
<a href="https://github.com/kaj/rphotos">rphotos
|
|
||||||
@env!("CARGO_PKG_VERSION")</a>.</p>
|
|
||||||
</footer>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
Loading…
Reference in New Issue
Block a user