Improve templates; Add navbar; Show book details
This commit is contained in:
parent
c0b154e1f6
commit
14c0dddf93
9 changed files with 139 additions and 44 deletions
|
@ -17,7 +17,7 @@
|
||||||
"filenamify": "^2.1.0",
|
"filenamify": "^2.1.0",
|
||||||
"helmet": "^3.15.0",
|
"helmet": "^3.15.0",
|
||||||
"jquery": "^3.3.1",
|
"jquery": "^3.3.1",
|
||||||
"slugify": "^1.3.4",
|
"snarkdown": "^1.2.2",
|
||||||
"socket.io": "^2.2.0",
|
"socket.io": "^2.2.0",
|
||||||
"socket.io-client": "^2.2.0",
|
"socket.io-client": "^2.2.0",
|
||||||
"unused-filename": "^1.0.0"
|
"unused-filename": "^1.0.0"
|
||||||
|
|
|
@ -1,8 +1,13 @@
|
||||||
$(document).ready(function() {
|
$(document).ready(function() {
|
||||||
var socket = io();
|
var socket = io();
|
||||||
|
|
||||||
$('.modal-background, .modal-close').click(function() {
|
$('.modal-background, .modal-close, .modal-card-head .delete, .modal-card-foot .close').click(function() {
|
||||||
$(this).parent('.modal').removeClass('is-active');
|
$(this).closest('.modal').removeClass('is-active');
|
||||||
|
});
|
||||||
|
|
||||||
|
$('.modal-button').click(function() {
|
||||||
|
var modal = $(this).data('modal');
|
||||||
|
$('#' + modal).addClass('is-active');
|
||||||
});
|
});
|
||||||
|
|
||||||
$('#book').change(function() {
|
$('#book').change(function() {
|
||||||
|
@ -16,9 +21,4 @@ $(document).ready(function() {
|
||||||
}
|
}
|
||||||
$('#bookFileName').text(fileName ? fileName : 'None Selected');
|
$('#bookFileName').text(fileName ? fileName : 'None Selected');
|
||||||
});
|
});
|
||||||
|
|
||||||
$('.book').click(function() {
|
|
||||||
var modal = $(this).data('modal');
|
|
||||||
$('#' + modal).addClass('is-active');
|
|
||||||
});
|
|
||||||
});
|
});
|
64
server.js
64
server.js
|
@ -8,6 +8,7 @@ const bodyParser = require('body-parser');
|
||||||
const fileUpload = require('express-fileupload');
|
const fileUpload = require('express-fileupload');
|
||||||
const filenamify = require('filenamify');
|
const filenamify = require('filenamify');
|
||||||
const unusedFilename = require('unused-filename');
|
const unusedFilename = require('unused-filename');
|
||||||
|
const snarkdown = require('snarkdown');
|
||||||
|
|
||||||
const settings = require('./settings.json');
|
const settings = require('./settings.json');
|
||||||
|
|
||||||
|
@ -44,21 +45,7 @@ function Server () {
|
||||||
this.server.use('/css', express.static(path.join(__dirname, './public/css/')));
|
this.server.use('/css', express.static(path.join(__dirname, './public/css/')));
|
||||||
|
|
||||||
this.server.get('/', (req, res) => {
|
this.server.get('/', (req, res) => {
|
||||||
const files = fs.readdirSync(this.fileLocation).filter(fileName => fileName.includes('.json'));
|
const html = this.generateHomePage();
|
||||||
const books = files.map(fileName => {
|
|
||||||
const bookData = JSON.parse(fs.readFileSync(path.resolve(this.fileLocation, fileName), 'utf8'));
|
|
||||||
const id = fileName.replace('.json', '');
|
|
||||||
const content = '<div class="box">' + bookData.summary + '</div>';
|
|
||||||
const modal = this.fillTemplate('./templates/elements/modal.html', { content, id });
|
|
||||||
return this.fillTemplate('./templates/elements/book.html', {
|
|
||||||
id,
|
|
||||||
title: bookData.title,
|
|
||||||
author: bookData.author,
|
|
||||||
modal,
|
|
||||||
})
|
|
||||||
}).join('');
|
|
||||||
const body = this.fillTemplate('./templates/pages/booksList.html', { books });
|
|
||||||
const html = this.fillTemplate('./templates/htmlContainer.html', { body });
|
|
||||||
if (html) {
|
if (html) {
|
||||||
res.send(html);
|
res.send(html);
|
||||||
} else {
|
} else {
|
||||||
|
@ -66,14 +53,19 @@ function Server () {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.server.get('/give', (req, res) => {
|
||||||
|
const body = this.fillTemplate('./templates/pages/uploadForm.html');
|
||||||
|
const html = this.fillTemplate('./templates/htmlContainer.html', { title: 'Give a Book', body });
|
||||||
|
res.send(html);
|
||||||
|
});
|
||||||
this.server.post('/give', (req, res) => {
|
this.server.post('/give', (req, res) => {
|
||||||
if (Object.keys(req.files).length > 0
|
if (Object.keys(req.files).length > 0
|
||||||
&& req.body.hasOwnProperty('title') && req.body.title.trim() !== ''
|
&& req.body.hasOwnProperty('title') && req.body.title.trim() !== ''
|
||||||
&& req.body.hasOwnProperty('summary') && req.body.summary.trim() !== '') {
|
&& req.body.hasOwnProperty('summary') && req.body.summary.trim() !== '') {
|
||||||
const { book } = req.files;
|
const { book } = req.files;
|
||||||
const { title, author, summary } = req.body;
|
const { title, author, summary, contributor } = req.body;
|
||||||
const fileType = book.name.substr(book.name.lastIndexOf('.'));
|
const fileType = book.name.substr(book.name.lastIndexOf('.'));
|
||||||
this.addBook({ book, title, author, summary, fileType }, () => {
|
this.addBook({ book, title, author, summary, contributor, fileType }, () => {
|
||||||
const messageBox = this.fillTemplate('./templates/elements/messageBox.html', {
|
const messageBox = this.fillTemplate('./templates/elements/messageBox.html', {
|
||||||
style: 'is-success',
|
style: 'is-success',
|
||||||
header: 'Upload Successful',
|
header: 'Upload Successful',
|
||||||
|
@ -84,7 +76,7 @@ function Server () {
|
||||||
content: messageBox,
|
content: messageBox,
|
||||||
});
|
});
|
||||||
const body = this.fillTemplate('./templates/pages/uploadForm.html');
|
const body = this.fillTemplate('./templates/pages/uploadForm.html');
|
||||||
const html = this.fillTemplate('./templates/htmlContainer.html', { body, modal });
|
const html = this.fillTemplate('./templates/htmlContainer.html', { title: 'Give a Book', body, modal });
|
||||||
res.send(html);
|
res.send(html);
|
||||||
}, (err) => {
|
}, (err) => {
|
||||||
const messageBox = this.fillTemplate('./templates/elements/messageBox.html', {
|
const messageBox = this.fillTemplate('./templates/elements/messageBox.html', {
|
||||||
|
@ -97,7 +89,7 @@ function Server () {
|
||||||
content: messageBox,
|
content: messageBox,
|
||||||
});
|
});
|
||||||
const body = this.fillTemplate('./templates/pages/uploadForm.html');
|
const body = this.fillTemplate('./templates/pages/uploadForm.html');
|
||||||
const html = this.fillTemplate('./templates/htmlContainer.html', { body, modal });
|
const html = this.fillTemplate('./templates/htmlContainer.html', { title: 'Give a Book', body, modal });
|
||||||
res.send(html);
|
res.send(html);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
@ -117,7 +109,7 @@ function Server () {
|
||||||
message: errorMessage,
|
message: errorMessage,
|
||||||
});
|
});
|
||||||
const body = this.fillTemplate('./templates/pages/uploadForm.html');
|
const body = this.fillTemplate('./templates/pages/uploadForm.html');
|
||||||
const html = this.fillTemplate('./templates/htmlContainer.html', { body, message });
|
const html = this.fillTemplate('./templates/htmlContainer.html', { title: 'Give a Book', body, message });
|
||||||
res.send(html);
|
res.send(html);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -130,7 +122,7 @@ function Server () {
|
||||||
if (fileLocation) {
|
if (fileLocation) {
|
||||||
console.log(socket.id + ' removed ' + bookId);
|
console.log(socket.id + ' removed ' + bookId);
|
||||||
const downloadLocation = fileLocation.substr(fileLocation.lastIndexOf('/'));
|
const downloadLocation = fileLocation.substr(fileLocation.lastIndexOf('/'));
|
||||||
socket.emit('./files' + downloadLocation);
|
socket.emit('get book', './files' + downloadLocation);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -153,7 +145,9 @@ Server.prototype.fillTemplate = function (file, templateVars = {}) {
|
||||||
this.templateCache[file] = data;
|
this.templateCache[file] = data;
|
||||||
}
|
}
|
||||||
|
|
||||||
let filledTemplate = data.replace(/\{\{allowedFormats\}\}/g, settings.allowedFormats.join(','))
|
let filledTemplate = data.replace(/\{\{siteTitle\}\}/g, settings.siteTitle)
|
||||||
|
.replace(/\{\{titleSeparator\}\}/g, settings.titleSeparator)
|
||||||
|
.replace(/\{\{allowedFormats\}\}/g, settings.allowedFormats.join(','))
|
||||||
.replace(/\{\{maxFileSize\}\}/g, (settings.maxFileSize > 0 ? settings.maxFileSize + 'MB' : 'no'));
|
.replace(/\{\{maxFileSize\}\}/g, (settings.maxFileSize > 0 ? settings.maxFileSize + 'MB' : 'no'));
|
||||||
|
|
||||||
for (let templateVar in templateVars) {
|
for (let templateVar in templateVars) {
|
||||||
|
@ -170,6 +164,31 @@ Server.prototype.fillTemplate = function (file, templateVars = {}) {
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Server.prototype.generateHomePage = function () {
|
||||||
|
const files = fs.readdirSync(this.fileLocation).filter(fileName => fileName.includes('.json'));
|
||||||
|
const books = files.map(fileName => {
|
||||||
|
const bookData = JSON.parse(fs.readFileSync(path.resolve(this.fileLocation, fileName), 'utf8'));
|
||||||
|
const id = fileName.replace('.json', '');
|
||||||
|
const header = '<h2 class="title">' + bookData.title + '</h2><h4 class="subtitle">' + bookData.author + '</h4>';
|
||||||
|
const content = '<div class="content"><h4>Contributed by ' + bookData.contributor + '</h4>' + snarkdown(bookData.summary) + '</div>';
|
||||||
|
const footer = '<a class="button close">Close</a> <a class="button is-success take-book">Take Book</a>';
|
||||||
|
const modal = this.fillTemplate('./templates/elements/modalCard.html', {
|
||||||
|
id,
|
||||||
|
header,
|
||||||
|
content,
|
||||||
|
footer,
|
||||||
|
});
|
||||||
|
return this.fillTemplate('./templates/elements/book.html', {
|
||||||
|
id,
|
||||||
|
title: bookData.title,
|
||||||
|
author: bookData.author,
|
||||||
|
modal,
|
||||||
|
});
|
||||||
|
}).join('');
|
||||||
|
const body = this.fillTemplate('./templates/pages/booksList.html', { books });
|
||||||
|
return this.fillTemplate('./templates/htmlContainer.html', { title: 'View', body });
|
||||||
|
}
|
||||||
|
|
||||||
Server.prototype.broadcastVisitors = function () {
|
Server.prototype.broadcastVisitors = function () {
|
||||||
const numberConnected = this.io.of('/').clients().connected.length;
|
const numberConnected = this.io.of('/').clients().connected.length;
|
||||||
this.io.emit('connected', numberConnected);
|
this.io.emit('connected', numberConnected);
|
||||||
|
@ -190,6 +209,7 @@ Server.prototype.addBook = function (uploadData = {}, success = () => {}, error
|
||||||
title: uploadData.title.trim(),
|
title: uploadData.title.trim(),
|
||||||
author: uploadData.author.trim(),
|
author: uploadData.author.trim(),
|
||||||
summary: uploadData.summary.trim(),
|
summary: uploadData.summary.trim(),
|
||||||
|
contributor: uploadData.contributor.trim(),
|
||||||
fileType: book.name.substr(book.name.lastIndexOf('.')),
|
fileType: book.name.substr(book.name.lastIndexOf('.')),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
{
|
{
|
||||||
"port": 3000,
|
"port": 3000,
|
||||||
|
"siteTitle": "Little Library",
|
||||||
|
"titleSeparator": " | ",
|
||||||
"fileLocation": "./public/files/",
|
"fileLocation": "./public/files/",
|
||||||
"historyLocation": "./public/history/",
|
"historyLocation": "./public/history/",
|
||||||
"maxLibrarySize": 0,
|
"maxLibrarySize": 0,
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<div class="column is-one-quarter">
|
<div class="column is-one-quarter">
|
||||||
<div class="box book has-text-centered" data-modal="{{id}}">
|
<div class="box modal-button has-text-centered" data-modal="{{id}}">
|
||||||
<h2 class="title">{{title}}</h2>
|
<h2 class="title">{{title}}</h2>
|
||||||
<h4 class="subtitle">{{author}}</h4>
|
<h4 class="subtitle">{{author}}</h4>
|
||||||
</div>
|
</div>
|
||||||
|
|
15
templates/elements/modalCard.html
Normal file
15
templates/elements/modalCard.html
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
<div class="modal {{isActive}}" id="{{id}}">
|
||||||
|
<div class="modal-background"></div>
|
||||||
|
<div class="modal-card">
|
||||||
|
<header class="modal-card-head">
|
||||||
|
<div class="modal-card-title">{{header}}</div>
|
||||||
|
<button class="delete" aria-label="close"></button>
|
||||||
|
</header>
|
||||||
|
<section class="modal-card-body">
|
||||||
|
{{content}}
|
||||||
|
</section>
|
||||||
|
<footer class="modal-card-foot">
|
||||||
|
{{footer}}
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
</div>
|
|
@ -6,7 +6,7 @@
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
|
||||||
<title>{{title}}Little Library</title>
|
<title>{{title}}{{titleSeparator}}{{siteTitle}}</title>
|
||||||
<meta name="description" content="A digital give-a-book, take-a-book library for ebooks">
|
<meta name="description" content="A digital give-a-book, take-a-book library for ebooks">
|
||||||
|
|
||||||
<link rel="stylesheet" href="./css/bulma.min.css?v=1.0">
|
<link rel="stylesheet" href="./css/bulma.min.css?v=1.0">
|
||||||
|
@ -17,9 +17,57 @@
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
{{message}}
|
<nav class="navbar" role="navigation" aria-label="main navigation">
|
||||||
|
<div class="navbar-brand">
|
||||||
|
<a class="navbar-item" href="/">
|
||||||
|
<h1 class="title">{{siteTitle}}</h1>
|
||||||
|
</a>
|
||||||
|
|
||||||
{{body}}
|
<a role="button" class="navbar-burger burger" aria-label="menu" aria-expanded="false" data-target="navbarBasicExample">
|
||||||
|
<span aria-hidden="true"></span>
|
||||||
|
<span aria-hidden="true"></span>
|
||||||
|
<span aria-hidden="true"></span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="navbarBasicExample" class="navbar-menu">
|
||||||
|
<div class="navbar-start">
|
||||||
|
<a class="navbar-item" href="/">
|
||||||
|
View
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<a class="navbar-item" href="/give">
|
||||||
|
Give
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="navbar-end">
|
||||||
|
<a class="navbar-item" href="/about">
|
||||||
|
About
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<section class="section">
|
||||||
|
<div class="container">
|
||||||
|
|
||||||
|
{{message}}
|
||||||
|
|
||||||
|
{{body}}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<footer class="footer">
|
||||||
|
<div class="content has-text-centered">
|
||||||
|
<p>
|
||||||
|
<strong>Little Library</strong> by <a href="https://robbie.antenesse.net">Robbie Antenesse</a>. The source code is licensed
|
||||||
|
<a href="http://opensource.org/licenses/mit-license.php">MIT</a>. The website content
|
||||||
|
is licensed <a href="http://creativecommons.org/licenses/by-nc-sa/4.0/">CC BY NC SA 4.0</a>.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
|
||||||
{{modal}}
|
{{modal}}
|
||||||
<script src="./js/little-library.js"></script>
|
<script src="./js/little-library.js"></script>
|
||||||
|
|
|
@ -1,18 +1,28 @@
|
||||||
<form action="./give" method="post" enctype="multipart/form-data">
|
<form action="./give" method="post" enctype="multipart/form-data">
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label class="label">Book Title:
|
<label class="label" for="title">Book Title:</label>
|
||||||
|
<div class="control">
|
||||||
<input type="text" class="input" name="title" id="title">
|
<input type="text" class="input" name="title" id="title">
|
||||||
</label>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label class="label">Book Author:
|
<label class="label" for="author"><span class="is-italic has-text-weight-normal has-text-grey">(Optional)</span> Book Author:</label>
|
||||||
|
<div class="control">
|
||||||
<input type="text" class="input" name="author" id="author">
|
<input type="text" class="input" name="author" id="author">
|
||||||
</label>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label class="label">Why you're sharing it:<br>
|
<label class="label" for="summary">Why you're sharing it:</label>
|
||||||
|
<div class="control">
|
||||||
|
<div class="help">Markdown Enabled</div>
|
||||||
<textarea class="textarea" name="summary" id="summary"></textarea>
|
<textarea class="textarea" name="summary" id="summary"></textarea>
|
||||||
</label>
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<label class="label" for="contributor"><span class="is-italic has-text-weight-normal has-text-grey">(Optional)</span> Your name:</label>
|
||||||
|
<div class="control">
|
||||||
|
<input type="text" class="input" name="contributor" id="contributor">
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="field">
|
<div class="field">
|
||||||
|
|
|
@ -690,10 +690,10 @@ setprototypeof@1.1.0:
|
||||||
resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.0.tgz#d0bd85536887b6fe7c0d818cb962d9d91c54e656"
|
resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.0.tgz#d0bd85536887b6fe7c0d818cb962d9d91c54e656"
|
||||||
integrity sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==
|
integrity sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==
|
||||||
|
|
||||||
slugify@^1.3.4:
|
snarkdown@^1.2.2:
|
||||||
version "1.3.4"
|
version "1.2.2"
|
||||||
resolved "https://registry.yarnpkg.com/slugify/-/slugify-1.3.4.tgz#78d2792d7222b55cd9fc81fa018df99af779efeb"
|
resolved "https://registry.yarnpkg.com/snarkdown/-/snarkdown-1.2.2.tgz#0cfe2f3012b804de120fc0c9f7791e869c59cc74"
|
||||||
integrity sha512-KP0ZYk5hJNBS8/eIjGkFDCzGQIoZ1mnfQRYS5WM3273z+fxGWXeN0fkwf2ebEweydv9tioZIHGZKoF21U07/nw==
|
integrity sha1-DP4vMBK4BN4SD8DJ93kehpxZzHQ=
|
||||||
|
|
||||||
socket.io-adapter@~1.1.0:
|
socket.io-adapter@~1.1.0:
|
||||||
version "1.1.1"
|
version "1.1.1"
|
||||||
|
|
Loading…
Add table
Reference in a new issue