Improve templates; Add navbar; Show book details

This commit is contained in:
Robbie Antenesse 2018-12-27 10:55:59 -07:00
parent c0b154e1f6
commit 14c0dddf93
9 changed files with 139 additions and 44 deletions

View File

@ -17,7 +17,7 @@
"filenamify": "^2.1.0",
"helmet": "^3.15.0",
"jquery": "^3.3.1",
"slugify": "^1.3.4",
"snarkdown": "^1.2.2",
"socket.io": "^2.2.0",
"socket.io-client": "^2.2.0",
"unused-filename": "^1.0.0"

View File

@ -1,8 +1,13 @@
$(document).ready(function() {
var socket = io();
$('.modal-background, .modal-close').click(function() {
$(this).parent('.modal').removeClass('is-active');
$('.modal-background, .modal-close, .modal-card-head .delete, .modal-card-foot .close').click(function() {
$(this).closest('.modal').removeClass('is-active');
});
$('.modal-button').click(function() {
var modal = $(this).data('modal');
$('#' + modal).addClass('is-active');
});
$('#book').change(function() {
@ -16,9 +21,4 @@ $(document).ready(function() {
}
$('#bookFileName').text(fileName ? fileName : 'None Selected');
});
$('.book').click(function() {
var modal = $(this).data('modal');
$('#' + modal).addClass('is-active');
});
});

View File

@ -8,6 +8,7 @@ const bodyParser = require('body-parser');
const fileUpload = require('express-fileupload');
const filenamify = require('filenamify');
const unusedFilename = require('unused-filename');
const snarkdown = require('snarkdown');
const settings = require('./settings.json');
@ -44,21 +45,7 @@ function Server () {
this.server.use('/css', express.static(path.join(__dirname, './public/css/')));
this.server.get('/', (req, res) => {
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 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 });
const html = this.generateHomePage();
if (html) {
res.send(html);
} 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) => {
if (Object.keys(req.files).length > 0
&& req.body.hasOwnProperty('title') && req.body.title.trim() !== ''
&& req.body.hasOwnProperty('summary') && req.body.summary.trim() !== '') {
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('.'));
this.addBook({ book, title, author, summary, fileType }, () => {
this.addBook({ book, title, author, summary, contributor, fileType }, () => {
const messageBox = this.fillTemplate('./templates/elements/messageBox.html', {
style: 'is-success',
header: 'Upload Successful',
@ -84,7 +76,7 @@ function Server () {
content: messageBox,
});
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);
}, (err) => {
const messageBox = this.fillTemplate('./templates/elements/messageBox.html', {
@ -97,7 +89,7 @@ function Server () {
content: messageBox,
});
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);
});
} else {
@ -117,7 +109,7 @@ function Server () {
message: errorMessage,
});
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);
}
});
@ -130,7 +122,7 @@ function Server () {
if (fileLocation) {
console.log(socket.id + ' removed ' + bookId);
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;
}
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'));
for (let templateVar in templateVars) {
@ -170,6 +164,31 @@ Server.prototype.fillTemplate = function (file, templateVars = {}) {
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 () {
const numberConnected = this.io.of('/').clients().connected.length;
this.io.emit('connected', numberConnected);
@ -190,6 +209,7 @@ Server.prototype.addBook = function (uploadData = {}, success = () => {}, error
title: uploadData.title.trim(),
author: uploadData.author.trim(),
summary: uploadData.summary.trim(),
contributor: uploadData.contributor.trim(),
fileType: book.name.substr(book.name.lastIndexOf('.')),
}

View File

@ -1,5 +1,7 @@
{
"port": 3000,
"siteTitle": "Little Library",
"titleSeparator": " | ",
"fileLocation": "./public/files/",
"historyLocation": "./public/history/",
"maxLibrarySize": 0,

View File

@ -1,5 +1,5 @@
<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>
<h4 class="subtitle">{{author}}</h4>
</div>

View 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>

View File

@ -6,7 +6,7 @@
<meta charset="utf-8">
<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">
<link rel="stylesheet" href="./css/bulma.min.css?v=1.0">
@ -17,10 +17,58 @@
</head>
<body>
<nav class="navbar" role="navigation" aria-label="main navigation">
<div class="navbar-brand">
<a class="navbar-item" href="/">
<h1 class="title">{{siteTitle}}</h1>
</a>
<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}}
<script src="./js/little-library.js"></script>
</body>

View File

@ -1,18 +1,28 @@
<form action="./give" method="post" enctype="multipart/form-data">
<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">
</label>
</div>
</div>
<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">
</label>
</div>
</div>
<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>
</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 class="field">

View File

@ -690,10 +690,10 @@ setprototypeof@1.1.0:
resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.0.tgz#d0bd85536887b6fe7c0d818cb962d9d91c54e656"
integrity sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==
slugify@^1.3.4:
version "1.3.4"
resolved "https://registry.yarnpkg.com/slugify/-/slugify-1.3.4.tgz#78d2792d7222b55cd9fc81fa018df99af779efeb"
integrity sha512-KP0ZYk5hJNBS8/eIjGkFDCzGQIoZ1mnfQRYS5WM3273z+fxGWXeN0fkwf2ebEweydv9tioZIHGZKoF21U07/nw==
snarkdown@^1.2.2:
version "1.2.2"
resolved "https://registry.yarnpkg.com/snarkdown/-/snarkdown-1.2.2.tgz#0cfe2f3012b804de120fc0c9f7791e869c59cc74"
integrity sha1-DP4vMBK4BN4SD8DJ93kehpxZzHQ=
socket.io-adapter@~1.1.0:
version "1.1.1"