Promise and display fine home Page o/
This commit is contained in:
parent
59e7b3cb8d
commit
d8db4101d0
53
app.js
53
app.js
|
@ -1,10 +1,10 @@
|
||||||
const express = require('express'),
|
const express = require('express'),
|
||||||
async = require('async'),
|
|
||||||
cors = require("cors"),
|
cors = require("cors"),
|
||||||
app = express(),
|
app = express(),
|
||||||
log = require("color-logs")(true, true, "Dryusdan.fr"),
|
log = require("color-logs")(true, true, "Dryusdan.fr"),
|
||||||
fs = require("fs"),
|
fs = require("fs"),
|
||||||
path = require('path'),
|
path = require('path'),
|
||||||
|
promise = require('promise'),
|
||||||
hbs = require('hbs');
|
hbs = require('hbs');
|
||||||
|
|
||||||
log.info("Application started and now preparing");
|
log.info("Application started and now preparing");
|
||||||
|
@ -15,35 +15,46 @@ var corsOptions = {
|
||||||
"methods": "GET,HEAD,PUT,PATCH,POST,DELETE",
|
"methods": "GET,HEAD,PUT,PATCH,POST,DELETE",
|
||||||
"preflightContinue": false
|
"preflightContinue": false
|
||||||
};
|
};
|
||||||
|
/**
|
||||||
hbs.registerHelper('list', function(tabs, options) {
|
* Create menu html
|
||||||
|
*/
|
||||||
var out = '';
|
|
||||||
for(var x in tabs){
|
|
||||||
for(var i in tabs[x]){
|
|
||||||
out = out + "" + options.fn(tabs[x][i]) + "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return out;
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
const config = require("./config.json");
|
const config = require("./config.json");
|
||||||
var Template = require("./classes/Template.class");
|
var Template = require("./classes/Template.class");
|
||||||
|
|
||||||
class App {
|
class App {
|
||||||
constructor(rootPath, port){
|
constructor(rootPath, port){
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Listen to http://0.0.0.0:port
|
||||||
|
*/
|
||||||
app.listen(port);
|
app.listen(port);
|
||||||
log.info("HTTP server listening on port", port);
|
log.info("HTTP server listening on port", port);
|
||||||
log.info("Application ready");
|
log.info("Application ready");
|
||||||
var template = new Template();
|
var template = new Template();
|
||||||
|
|
||||||
app.get("/", function(req, res){
|
app.get("/", function(req, res){
|
||||||
template.render(function(tplData){
|
Promise.all([
|
||||||
|
template.getNav(),
|
||||||
|
template.getHome()
|
||||||
|
]).then(data => {
|
||||||
|
var nav = data[0];
|
||||||
|
var content = data[1];
|
||||||
res.setHeader("Content-type", "text/html");
|
res.setHeader("Content-type", "text/html");
|
||||||
|
var tplData = {
|
||||||
|
"blogName": config.blogName,
|
||||||
|
"title": 'Accueil',
|
||||||
|
"nav": nav,
|
||||||
|
"content": content
|
||||||
|
};
|
||||||
res.render('template.hbs', tplData);
|
res.render('template.hbs', tplData);
|
||||||
|
}).catch(err => {
|
||||||
|
console.error(err);
|
||||||
|
res.setHeader("Content-type", "text/plain");
|
||||||
|
|
||||||
|
res.end("erreur");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
});
|
});
|
||||||
app.get("/:slug", function(req, res){
|
app.get("/:slug", function(req, res){
|
||||||
res.setHeader("Content-type", "text/plain");
|
res.setHeader("Content-type", "text/plain");
|
||||||
|
@ -60,14 +71,4 @@ class App {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
new App("/", config.appPort);
|
new App("/", config.appPort);
|
||||||
|
|
||||||
/** Preparing cache **/
|
|
||||||
/*async.waterfall([
|
|
||||||
function generateCache(callback){
|
|
||||||
callback;
|
|
||||||
}
|
|
||||||
], function (err){
|
|
||||||
|
|
||||||
}
|
|
||||||
);*/
|
|
13
classes/Article.class.js
Normal file
13
classes/Article.class.js
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
const log = require('color-logs')(true, true, "Articles.class.js"),
|
||||||
|
fs = require("fs");
|
||||||
|
|
||||||
|
var title,
|
||||||
|
author,
|
||||||
|
content,
|
||||||
|
date,
|
||||||
|
slug;
|
||||||
|
|
||||||
|
class Article {
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = Article;
|
|
@ -1,30 +0,0 @@
|
||||||
const log = require('color-logs')(true, true, "Articles.class.js"),
|
|
||||||
fs = require("fs");
|
|
||||||
|
|
||||||
|
|
||||||
class Articles {
|
|
||||||
|
|
||||||
constructor(r, rtdbConn){
|
|
||||||
this.rdbConn = rtdbConn;
|
|
||||||
this.db = r;
|
|
||||||
}
|
|
||||||
|
|
||||||
listHome(callback) {
|
|
||||||
// r.db("dryusdan").table("articles").filter(r.row('time')).filter(r.row('title')).limit(1) get article mis en avant !
|
|
||||||
// r.db("dryusdan").table("articles").filter(r.row('time')).limit(9)
|
|
||||||
|
|
||||||
this.db.table('articles').fil.run(this.rdbConn, function(err, cursor){
|
|
||||||
if(err) throw err;
|
|
||||||
cursor.toArray(function(err, result){
|
|
||||||
if(err) throw err;
|
|
||||||
callback(result);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
list(ctg, callback){
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = Articles;
|
|
20
classes/Categorie.class.js
Normal file
20
classes/Categorie.class.js
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
const log = require('color-logs')(true, true, "Categories.class.js"),
|
||||||
|
path = require('path'),
|
||||||
|
fs = require("fs");
|
||||||
|
|
||||||
|
var title,
|
||||||
|
slug,
|
||||||
|
position;
|
||||||
|
|
||||||
|
class Categorie {
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {string} folder where located categories
|
||||||
|
* @returns {nm$_Categorie.class.Categorie}
|
||||||
|
*/
|
||||||
|
constructor(folder){
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = Categorie;
|
|
@ -1,20 +0,0 @@
|
||||||
const log = require('color-logs')(true, true, "Categories.class.js"),
|
|
||||||
path = require('path'),
|
|
||||||
fs = require("fs");
|
|
||||||
|
|
||||||
|
|
||||||
class Categories {
|
|
||||||
|
|
||||||
list(callback) {
|
|
||||||
const srcpath = 'ressources';
|
|
||||||
var listDir = fs.readdirSync(srcpath).filter(file => fs.lstatSync(path.join(srcpath, file)).isDirectory());
|
|
||||||
var listCtg = {};
|
|
||||||
for(var x = 0; x < listDir.length; x++){
|
|
||||||
var params = JSON.parse(fs.readFileSync(srcpath+'/'+listDir[x]+'/params.json', 'utf8'));
|
|
||||||
listCtg[x] = [{'title': params.title, 'slug': listDir[x]}]
|
|
||||||
}
|
|
||||||
callback(listCtg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = Categories;
|
|
|
@ -1,81 +1,75 @@
|
||||||
const log = require('color-logs')(true, true, "Template.class.js"),
|
const log = require('color-logs')(true, true, "Template.class.js"),
|
||||||
fs = require("fs"),
|
fs = require("fs"),
|
||||||
Handlebars = require('handlebars'),
|
hbs = require('handlebars'),
|
||||||
config = require('../config.json');
|
promise = require('promise'),
|
||||||
|
config = require('../config.json');
|
||||||
|
|
||||||
|
var Categorie = require('../classes/Categorie.class.js');
|
||||||
|
var Article = require('../classes/Article.class.js');
|
||||||
|
|
||||||
|
hbs.registerHelper('listNav', function(tabs, options) {
|
||||||
|
var out = '';
|
||||||
|
for(var x in options['data']['root']){
|
||||||
|
console.log(x);
|
||||||
|
out = out + "" + options.fn(options['data']['root'][x]) + "";
|
||||||
|
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
});
|
||||||
|
|
||||||
|
hbs.registerHelper('listArticle', function(tabs, options) {
|
||||||
|
var i = 1;
|
||||||
|
var out = '';
|
||||||
|
for(var x in options['data']['root']){
|
||||||
|
|
||||||
|
out = out + "" + options.fn(options['data']['root'][x]) + "";
|
||||||
|
if(i == 3){
|
||||||
|
i= 1;
|
||||||
|
out = out + "</div><div class=\"row\">";
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
});
|
||||||
|
|
||||||
var Categories = require('../classes/Categories.class.js');
|
|
||||||
class Template {
|
class Template {
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @returns {Promise} the navbar in html compiled
|
||||||
|
*/
|
||||||
|
|
||||||
getNav(callback){
|
getNav(){
|
||||||
var categories = new Categories();
|
return new Promise((resolve, reject) => {
|
||||||
categories.list(function(ctgs){
|
fs.readFile('./views/_nav.hbs', 'utf-8', (error, source) => {
|
||||||
callback(ctgs);
|
const articles = require("../ressources/categories.json");
|
||||||
});
|
hbs.registerHelper('nav', (nav) =>{
|
||||||
|
return nav;
|
||||||
|
});
|
||||||
|
var template = hbs.compile(source);
|
||||||
|
var html = template(articles);
|
||||||
|
//console.log(html);
|
||||||
|
resolve(html);
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
render(callback){
|
getHome(){
|
||||||
this.getNav(function(nav){
|
return new Promise((resolve, reject) => {
|
||||||
var tplData = {
|
fs.readFile('./views/articles/list.hbs', 'utf-8', (error, source) => {
|
||||||
"blogName": config.blogName,
|
const data = require('../ressources/lastArticles.json');
|
||||||
"title": 'Accueil',
|
hbs.registerHelper('articles', (articles) =>{
|
||||||
"nav": nav,
|
return articles;
|
||||||
};
|
});
|
||||||
callback(tplData);
|
var template = hbs.compile(source);
|
||||||
})
|
var html = template(data);
|
||||||
|
console.log(html);
|
||||||
|
resolve(html);
|
||||||
|
});
|
||||||
//return tplData;
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
generateHTML(cb){
|
|
||||||
|
|
||||||
console.log("passage !");
|
|
||||||
cb;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*generateTemplate(){
|
|
||||||
var categories = new Categories(this.db, this.rdbConn);
|
|
||||||
var tplSource = fs.readFileSync("./view/template.hbs", "utf8");
|
|
||||||
|
|
||||||
categories.list(function(ctgs, source){
|
|
||||||
console.log(ctgs);
|
|
||||||
});
|
|
||||||
|
|
||||||
var template = Handlebars.compile(tplSource);
|
|
||||||
var tplData = {
|
|
||||||
"blogName": config.blogName,
|
|
||||||
"title": title,
|
|
||||||
"nav": '',
|
|
||||||
};
|
|
||||||
return template(tplData);
|
|
||||||
|
|
||||||
}*/
|
|
||||||
|
|
||||||
/*generateHTML(ctgs){
|
|
||||||
var tplData = {
|
|
||||||
"blogName": config.blogName,
|
|
||||||
"title": title,
|
|
||||||
"nav": ctgs,
|
|
||||||
};
|
|
||||||
console.log(generateSource("./view/template.hbs", "utf8", tplData));
|
|
||||||
}
|
|
||||||
|
|
||||||
static generateSource(source, data){
|
|
||||||
var tplSource = fs.readFileSync(source);
|
|
||||||
var template = Handlebars.compile(tplSource);
|
|
||||||
return template(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
returnHTML(source, data, title){
|
|
||||||
var categories = new Categories(this.db, this.rdbConn);
|
|
||||||
categories.list((function(ctgs){
|
|
||||||
return generateHTML(ctgs);
|
|
||||||
})(generateHTML(ctgs))); //cette methode n'est pas appelé
|
|
||||||
|
|
||||||
}*/
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,4 @@
|
||||||
{
|
{
|
||||||
"appPort": "8888",
|
"appPort": "8888",
|
||||||
"rtdbHost": "ssh.dryusdan.fr",
|
|
||||||
"rtdbPort": "28015",
|
|
||||||
"blogName": "Dryusdan"
|
"blogName": "Dryusdan"
|
||||||
}
|
}
|
||||||
|
|
18
package-lock.json
generated
18
package-lock.json
generated
|
@ -19,14 +19,15 @@
|
||||||
"version": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
|
"version": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
|
||||||
"integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI="
|
"integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI="
|
||||||
},
|
},
|
||||||
|
"asap": {
|
||||||
|
"version": "2.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/asap/-/asap-2.0.5.tgz",
|
||||||
|
"integrity": "sha1-UidltQw1EEkOUtfc/ghe+bqWlY8="
|
||||||
|
},
|
||||||
"async": {
|
"async": {
|
||||||
"version": "https://registry.npmjs.org/async/-/async-1.5.2.tgz",
|
"version": "https://registry.npmjs.org/async/-/async-1.5.2.tgz",
|
||||||
"integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo="
|
"integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo="
|
||||||
},
|
},
|
||||||
"bluebird": {
|
|
||||||
"version": "https://registry.npmjs.org/bluebird/-/bluebird-2.11.0.tgz",
|
|
||||||
"integrity": "sha1-U0uQM8AiyVecVro7Plpcqvu2UOE="
|
|
||||||
},
|
|
||||||
"camelcase": {
|
"camelcase": {
|
||||||
"version": "https://registry.npmjs.org/camelcase/-/camelcase-1.2.1.tgz",
|
"version": "https://registry.npmjs.org/camelcase/-/camelcase-1.2.1.tgz",
|
||||||
"integrity": "sha1-m7UwTS4LVmmLLHWLCKPqqdqlijk=",
|
"integrity": "sha1-m7UwTS4LVmmLLHWLCKPqqdqlijk=",
|
||||||
|
@ -231,6 +232,11 @@
|
||||||
"version": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
|
"version": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
|
||||||
"integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w="
|
"integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w="
|
||||||
},
|
},
|
||||||
|
"promise": {
|
||||||
|
"version": "8.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/promise/-/promise-8.0.0.tgz",
|
||||||
|
"integrity": "sha512-eT7BkLTa15CBmuufE1MPVIdyYpLKbq5dSEtJicDKtPilr3gJuTPkQ+LDuZ363Bxp5pfpL218u4udvYnnIjV/Ow=="
|
||||||
|
},
|
||||||
"proxy-addr": {
|
"proxy-addr": {
|
||||||
"version": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-1.1.4.tgz",
|
"version": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-1.1.4.tgz",
|
||||||
"integrity": "sha1-J+VF9pYKRKYn2bREZ+NcG2tM4vM="
|
"integrity": "sha1-J+VF9pYKRKYn2bREZ+NcG2tM4vM="
|
||||||
|
@ -247,10 +253,6 @@
|
||||||
"version": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz",
|
"version": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz",
|
||||||
"integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc="
|
"integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc="
|
||||||
},
|
},
|
||||||
"rethinkdb": {
|
|
||||||
"version": "https://registry.npmjs.org/rethinkdb/-/rethinkdb-2.3.3.tgz",
|
|
||||||
"integrity": "sha1-PcZYbiL6HavuDSVOZL0ON5+tL3I="
|
|
||||||
},
|
|
||||||
"right-align": {
|
"right-align": {
|
||||||
"version": "https://registry.npmjs.org/right-align/-/right-align-0.1.3.tgz",
|
"version": "https://registry.npmjs.org/right-align/-/right-align-0.1.3.tgz",
|
||||||
"integrity": "sha1-YTObci/mo1FWiSENJOFMlhSGE+8=",
|
"integrity": "sha1-YTObci/mo1FWiSENJOFMlhSGE+8=",
|
||||||
|
|
10
package.json
10
package.json
|
@ -10,14 +10,16 @@
|
||||||
],
|
],
|
||||||
"author": "dryusdan",
|
"author": "dryusdan",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
"dev": "nodemon app.js",
|
||||||
"start": "node app.js"
|
"start": "node app.js"
|
||||||
},
|
},
|
||||||
"contributors": [],
|
"contributors": [],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"handlebars": "4.0.6",
|
|
||||||
"express": "4.15.2",
|
|
||||||
"cors": "2.8.1",
|
|
||||||
"color-logs": "0.6.1",
|
"color-logs": "0.6.1",
|
||||||
"hbs": "4.0.1"
|
"cors": "2.8.1",
|
||||||
|
"express": "4.15.2",
|
||||||
|
"handlebars": "4.0.6",
|
||||||
|
"hbs": "4.0.1",
|
||||||
|
"promise": "^8.0.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,35 +0,0 @@
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="fr">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<title>{{title}} - {{blogName}}</title>
|
|
||||||
<link rel="stylesheet" href="/assets/css/global.css">
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<header>
|
|
||||||
<div id="logo"><a href="/">{{blogName}}</a></div>
|
|
||||||
<nav>
|
|
||||||
{{nav}}
|
|
||||||
<!--<div class="navItem"><a href="index.html">Accueil</a></div>
|
|
||||||
<div class="navItem"><a href="portfolio.html">Portfolio</a></div>
|
|
||||||
<div class="navItem">Contact</div>-->
|
|
||||||
<div class="navItem" id="openSearchTool">
|
|
||||||
<img class="navIcon" src="assets/img/search.png">
|
|
||||||
</div>
|
|
||||||
</nav>
|
|
||||||
</header>
|
|
||||||
<div class="search">
|
|
||||||
<div class="inputContainer">
|
|
||||||
<input type="search" placeholder="Rechercher...">
|
|
||||||
<button> </button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{{content}}
|
|
||||||
<footer>
|
|
||||||
|
|
||||||
</footer>
|
|
||||||
<script src="/assets/js/jquery-2.2.0.min.js"></script>
|
|
||||||
<script src="/assets/js/parallax.js"></script>
|
|
||||||
<script src="/assets/js/searchInput.js"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
10
views/_nav.hbs
Normal file
10
views/_nav.hbs
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
<nav>
|
||||||
|
|
||||||
|
{{#listNav data}}<div class="navItem"><a href="{{slug}}">{{title}}</a></div>{{/listNav}}
|
||||||
|
<!--<div class="navItem"><a href="index.html">Accueil</a></div>
|
||||||
|
<div class="navItem"><a href="portfolio.html">Portfolio</a></div>
|
||||||
|
<div class="navItem">Contact</div>-->
|
||||||
|
<div class="navItem" id="openSearchTool">
|
||||||
|
<img class="navIcon" src="assets/img/search.png">
|
||||||
|
</div>
|
||||||
|
</nav>
|
25
views/articles/list.hbs
Normal file
25
views/articles/list.hbs
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
<main class="transparent">
|
||||||
|
<section>
|
||||||
|
<div class="flexgrid">
|
||||||
|
<div class="row">
|
||||||
|
{{#listArticle articles}}
|
||||||
|
<div class="cell tile">
|
||||||
|
<a href="{{slug}}">
|
||||||
|
<div class="tileCover">
|
||||||
|
<img src="{{img}}">
|
||||||
|
</div>
|
||||||
|
<div class="tileLegend">
|
||||||
|
<div class="content">
|
||||||
|
<div class="titleLegend">{{categorie}}</div>
|
||||||
|
<img class="blackAsset separator" src="assets/img/separator.png">
|
||||||
|
<h2>{{title}}</h2>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
{{/listArticle}}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</main>
|
|
@ -8,15 +8,7 @@
|
||||||
<body>
|
<body>
|
||||||
<header>
|
<header>
|
||||||
<div id="logo"><a href="/">{{blogName}}</a></div>
|
<div id="logo"><a href="/">{{blogName}}</a></div>
|
||||||
<nav>
|
{{{nav}}}
|
||||||
{{#list nav}}<div class="navItem"><a href="{{slug}}">{{title}}</a></div>{{/list}}
|
|
||||||
<!--<div class="navItem"><a href="index.html">Accueil</a></div>
|
|
||||||
<div class="navItem"><a href="portfolio.html">Portfolio</a></div>
|
|
||||||
<div class="navItem">Contact</div>-->
|
|
||||||
<div class="navItem" id="openSearchTool">
|
|
||||||
<img class="navIcon" src="assets/img/search.png">
|
|
||||||
</div>
|
|
||||||
</nav>
|
|
||||||
</header>
|
</header>
|
||||||
<div class="search">
|
<div class="search">
|
||||||
<div class="inputContainer">
|
<div class="inputContainer">
|
||||||
|
@ -24,7 +16,7 @@
|
||||||
<button> </button>
|
<button> </button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{{content}}
|
{{{content}}}
|
||||||
<footer>
|
<footer>
|
||||||
|
|
||||||
</footer>
|
</footer>
|
||||||
|
|
Reference in a new issue