Compare commits

...

8 Commits

39 changed files with 177 additions and 157 deletions

3
.gitignore vendored
View File

@ -1,5 +1,8 @@
node_modules/
public/
.cache/
dist/
dev/
**/*.log
config.json

3
app/config.example.json Normal file
View File

@ -0,0 +1,3 @@
{
"siteName": "book-tracker"
}

View File

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@ -9,11 +9,18 @@
<meta name="description" content="An attempt at a viable alternative to Goodreads">
<meta name="keywords" content="books, tracking, lists, bookshelves, bookshelf, rating, reviews, reading">
<link rel="stylesheet" href="index.scss">
<link rel="manifest" href="manifest.webmanifest">
<meta name="theme-color" content="#000000">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<meta name="apple-mobile-web-app-title" content="book-tracker">
<link rel="apple-touch-icon" href="../dev/images/favicon.png">
<link rel="stylesheet" href="styles/index.scss">
<script src="index.js"></script>
</head>
<!-- Choo replaces the body tag with the app. -->
<body></body>
<body>Loading...</body>
</html>

View File

@ -1,5 +1,6 @@
import choo from 'choo';
import config from './config.json';
import { viewManager } from './views/manager';
const app = choo();
@ -17,6 +18,7 @@ app.use((state, emitter) => {
// Listeners
emitter.on('DOMContentLoaded', () => {
document.title = config.siteName;
// Emitter listeners
emitter.on('render', callback => {
// This is a dirty hack to get the callback to call *after* re-rendering.
@ -27,7 +29,7 @@ app.use((state, emitter) => {
}
});
emitter.on('changeView', newView => {
emitter.on('change-view', newView => {
// Change the view and call render. Makes it easier to call within views.
state.currentView = newView;
emitter.emit('render', () => {});

41
app/manifest.webmanifest Normal file
View File

@ -0,0 +1,41 @@
{
"name": "book-tracker",
"short_name": "book-tracker",
"icons": [
{
"src": "../dev/images/icon-128.png",
"sizes": "128x128",
"type": "image/png"
},
{
"src": "../dev/images/icon-144.png",
"sizes": "144x144",
"type": "image/png"
},
{
"src": "../dev/images/icon-152.png",
"sizes": "152x152",
"type": "image/png"
},
{
"src": "../dev/images/icon-192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "../dev/images/icon-256.png",
"sizes": "256x256",
"type": "image/png"
},
{
"src": "../dev/images/icon-512.png",
"sizes": "512x512",
"type": "image/png"
}
],
"start_url": "/",
"display": "standalone",
"orientation": "portrait",
"background_color": "#000000",
"theme_color": "#1C4AFF"
}

42
app/styles/index.scss Normal file
View File

@ -0,0 +1,42 @@
//! Picnic CSS http://www.picnicss.com/
// Imports the base variable styles
@import './picnic-customizations/theme/theme';
@import '../../node_modules/picnic/src/vendor/compass-breakpoint/stylesheets/breakpoint';
// Normalize.css (external library)
@import '../../node_modules/picnic/src/plugins/normalize/plugin';
// Generic styles for things like <body>, <a> and others
// It also overwrites normalize.css a bit
@import '../../node_modules/picnic/src/plugins/generic/plugin';
@import '../../node_modules/picnic/src/plugins/fontello/plugin';
// Simple elements
@import '../../node_modules/picnic/src/plugins/label/plugin';
@import '../../node_modules/picnic/src/plugins/button/plugin';
// Forms
@import '../../node_modules/picnic/src/plugins/input/plugin';
@import '../../node_modules/picnic/src/plugins/select/plugin';
@import '../../node_modules/picnic/src/plugins/radio/plugin';
@import '../../node_modules/picnic/src/plugins/checkbox/plugin';
// Components
@import '../../node_modules/picnic/src/plugins/table/plugin';
@import '../../node_modules/picnic/src/plugins/grid/plugin';
// Extra
@import '../../node_modules/picnic/src/plugins/nav/plugin';
@import '../../node_modules/picnic/src/plugins/stack/plugin';
@import '../../node_modules/picnic/src/plugins/card/plugin';
@import '../../node_modules/picnic/src/plugins/modal/plugin';
// @import '../../node_modules/picnic/src/plugins/dropimage/plugin';
// @import '../../node_modules/picnic/src/plugins/tabs/plugin';
// @import '../../node_modules/picnic/src/plugins/tooltip/plugin';
// Custom global styling
@import './picnic-customizations/custom';

21
app/views/login/index.js Normal file
View File

@ -0,0 +1,21 @@
import html from 'choo/html';
export const loginView = (state, emit) => {
return html`<section>
<article class="card">
<div class="container wide">
<label>
<span>Email</span>
<input type="email" name="email">
</label>
<label>
<span>Password</span>
<input type="password" name="password">
</label>
<input type="submit" value="Log In!">
</div>
</article>
</section>`;
}

View File

@ -1,6 +1,7 @@
import html from 'choo/html';
import { homeView } from './home';
import { loginView } from './login';
import { searchView } from './search';
export const viewManager = (state, emit) => {
@ -13,6 +14,10 @@ export const viewManager = (state, emit) => {
htmlContent = homeView(state, emit);
break;
}
case 'login': {
htmlContent = loginView(state, emit);
break;
}
case 'search': {
htmlContent = searchView(state, emit);
break;
@ -24,8 +29,8 @@ export const viewManager = (state, emit) => {
<header>
<nav>
<div class="brand">
<a href="./">
<h1>Unnamed Book Tracker</h1>
<a href="/">
<span class="title">Unnamed Book Tracker</span>
</a>
</div>
@ -40,8 +45,8 @@ export const viewManager = (state, emit) => {
emit('pushState', '/search?for=' + encodeURIComponent(e.target.value.trim()));
}}>
</label>
<a href="https://gitlab.com/Alamantus/book-tracker" class="pseudo button">Repo</a>
<a href="https://gitter.im/book-tracker/general" class="pseudo button">Chat</a>
<a href="/login" class="pseudo button">Log In</a>
<a href="/logout" class="pseudo button">Log Out</a>
</div>
</nav>
</header>
@ -49,6 +54,15 @@ export const viewManager = (state, emit) => {
<main class="container">
${htmlContent}
</main>
<footer>
<nav>
<div class="links">
<a href="https://gitlab.com/Alamantus/book-tracker" class="pseudo button">Repo</a>
<a href="https://gitter.im/book-tracker/general" class="pseudo button">Chat</a>
</div>
</nav>
</footer>
</body>`;
return view;

View File

@ -1,42 +0,0 @@
//! Picnic CSS http://www.picnicss.com/
// Imports the base variable styles
@import './styles/picnic-customizations/theme/theme';
@import './node_modules/picnic/src/vendor/compass-breakpoint/stylesheets/breakpoint';
// Normalize.css (external library)
@import './node_modules/picnic/src/plugins/normalize/plugin';
// Generic styles for things like <body>, <a> and others
// It also overwrites normalize.css a bit
@import './node_modules/picnic/src/plugins/generic/plugin';
@import './node_modules/picnic/src/plugins/fontello/plugin';
// Simple elements
@import './node_modules/picnic/src/plugins/label/plugin';
@import './node_modules/picnic/src/plugins/button/plugin';
// Forms
@import './node_modules/picnic/src/plugins/input/plugin';
@import './node_modules/picnic/src/plugins/select/plugin';
@import './node_modules/picnic/src/plugins/radio/plugin';
@import './node_modules/picnic/src/plugins/checkbox/plugin';
// Components
@import './node_modules/picnic/src/plugins/table/plugin';
@import './node_modules/picnic/src/plugins/grid/plugin';
// Extra
@import './node_modules/picnic/src/plugins/nav/plugin';
@import './node_modules/picnic/src/plugins/stack/plugin';
@import './node_modules/picnic/src/plugins/card/plugin';
@import './node_modules/picnic/src/plugins/modal/plugin';
// @import './node_modules/picnic/src/plugins/dropimage/plugin';
// @import './node_modules/picnic/src/plugins/tabs/plugin';
// @import './node_modules/picnic/src/plugins/tooltip/plugin';
// Custom global styling
@import './styles/picnic-customizations/custom';

View File

@ -7,14 +7,14 @@
"author": "Robbie Antenesse <dev@alamantus.com>",
"license": "MIT",
"scripts": {
"install": "npm run process-images",
"dev": "npm run watch-js",
"start": "npm run build && npm run serve",
"watch-js": "parcel watch src/index.html --no-hmr",
"serve": "node server.js",
"build": "parcel build src/index.html --no-source-maps",
"clear": "npm run clear-dist && npm run clear-cache",
"clear-dist": "rimraf dist/{*,.*}",
"clear-cache": "rimraf .cache/{*,.*}"
"watch-js": "parcel watch app/index.html --out-dir public --no-hmr --no-cache",
"serve": "node server/index.js",
"build": "parcel build app/index.html --out-dir public --no-source-maps --no-cache",
"process-images": "node ./process-images.js",
"clear": "rimraf public/{*,.*}"
},
"devDependencies": {
"autoprefixer": "^9.6.1",
@ -27,13 +27,12 @@
"sharp": "^0.23.0"
},
"dependencies": {
"cross-env": "^5.2.1",
"choo": "^7.0.0",
"cross-env": "^5.2.1",
"fastify": "^2.8.0",
"fastify-caching": "^5.0.0",
"fastify-compress": "^0.11.0",
"fastify-cookie": "^3.1.0",
"fastify-formbody": "^3.1.0",
"fastify-helmet": "^3.0.1",
"fastify-jwt": "^1.0.0",
"fastify-static": "^2.5.0",
@ -41,7 +40,6 @@
"html-minifier": "^4.0.0",
"make-promises-safe": "^5.0.0",
"node-fetch": "^2.6.0",
"picnic": "^6.5.1",
"point-of-view": "^3.5.0"
"picnic": "^6.5.1"
}
}

View File

@ -1,16 +1,16 @@
const fs = require('fs');
const sharp = require('sharp');
const folder = './public/images/';
const folder = './dev/images/';
if (!fs.existsSync('./public')) {
fs.mkdirSync('./public');
if (!fs.existsSync('./dev')) {
fs.mkdirSync('./dev');
}
if (!fs.existsSync('./public/images')) {
fs.mkdirSync('./public/images');
if (!fs.existsSync('./dev/images')) {
fs.mkdirSync('./dev/images');
}
const favicon = sharp('./images/book-pile.svg');
const favicon = sharp('./app/images/book-pile.svg');
// sharp('./src/images/social.jpg').toFile(folder + 'social.jpg', (err, info) => {
// if (err) return console.error(err);

View File

@ -1,57 +0,0 @@
async function routes(fastify, options) {
fastify.get('/styles/:css', async (request, reply) => {
reply.sendFile('css/' + request.params.css);
});
fastify.get('/images/:image', async (request, reply) => {
reply.sendFile('images/' + request.params.image);
});
fastify.get('/manifest.webmanifest', async (request, reply) => {
const manifest = {
name: typeof fastify.siteConfig !== 'undefined' ? fastify.siteConfig.siteName : 'name not configured',
short_name: typeof fastify.siteConfig !== 'undefined' ? fastify.siteConfig.siteName : 'name not configured',
icons: [
{
src: '/images/icon-128.png',
sizes: '128x128',
type: 'image/png',
},
{
src: '/images/icon-144.png',
sizes: '144x144',
type: 'image/png',
},
{
src: '/images/icon-152.png',
sizes: '152x152',
type: 'image/png',
},
{
src: '/images/icon-192.png',
sizes: '192x192',
type: 'image/png',
},
{
src: '/images/icon-256.png',
sizes: '256x256',
type: 'image/png',
},
{
src: '/images/icon-512.png',
sizes: '512x512',
type: 'image/png',
}
],
start_url: '/',
display: 'standalone',
orientation: 'portrait',
background_color: '#000000',
theme_color: '#1C4AFF',
};
return JSON.stringify(manifest);
});
}
module.exports = routes

View File

@ -1,6 +1,5 @@
{
"port": 3000,
"siteName": "book-tracker",
"jwtSecretKey": "SomethingAtLeast32CharactersLong!",
"tokenExpireDays": 7
}

View File

@ -17,11 +17,9 @@ const fastify = require('fastify')({
fastify.decorate('siteConfig', siteConfig); // Insert siteConfig into global fastify instance
fastify.register(require('fastify-helmet')); // Add security stuff
fastify.register(require('fastify-compress')); // Compress output data for smaller packet delivery
fastify.register(require('fastify-formbody')); // Enable fastify to parse data sent by POST from forms
fastify.register(require('fastify-static'), { // Enable delivering static content efficiently
root: path.join(__dirname, 'public'), // all static content will be delivered from the public/ folder
root: path.resolve(__dirname, '../public'), // all static content will be delivered from the public/ folder
});
fastify.register(require('point-of-view'), require('./views/viewSetup')); // Adds the `view()` function to fastify's `reply` objects
fastify.register(require('fastify-cookie')); // Enable reading and setting http-level cookies for the sole purpose of storing login tokens
fastify.register(require('fastify-jwt'), { // Enable creating, parsing, and verifying JSON Web Tokens from the global fastify object
secret: fastify.siteConfig.jwtSecretKey, // The secret key used to generate JWTs. Make it big and random!
@ -35,10 +33,10 @@ fastify.addHook('onRequest', (request, reply, done) => {
// Routes
fastify.register(require('./routes/resources'));
fastify.register(require('./routes/home'));
fastify.register(require('./routes/account'));
fastify.register(require('./routes/search'));
fastify.register(require('./routes/public'));
// fastify.register(require('./routes/home'));
// fastify.register(require('./routes/account'));
// fastify.register(require('./routes/search'));
// Start the server
fastify.listen(fastify.siteConfig.port, function (err, address) {

17
server/routes/public.js Normal file
View File

@ -0,0 +1,17 @@
async function routes(fastify, options) {
// This route is not totally necessary because fastify-static serves public/ wholesale, but it's good to be verbose!
fastify.get('/', async (request, reply) => {
return reply.sendFile('index.html');
});
// This is overridden by any explicitly named routes, so the API will be fine.
fastify.get('/:chooRoute', async (request, reply) => {
if (/\.\w+$/.test(request.params.chooRoute)) { // If the :chooRoute is a filename, serve the file instead
return reply.sendFile(request.params.chooRoute);
}
// Otherwise, send index.html and allow Choo to route it.
return reply.sendFile('index.html');
});
}
module.exports = routes

View File

@ -2653,14 +2653,6 @@ fastify-cookie@^3.1.0:
cookie "^0.3.1"
fastify-plugin "^1.4.0"
fastify-formbody@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/fastify-formbody/-/fastify-formbody-3.1.0.tgz#604cdafdb9e3af6068e6d9940985baf692d6e922"
integrity sha512-GQQtRmI8w07SMcnXiWrk9H8GAMJwheKAkQS4q0FbJ56Qu8bV39GOffiZ8GLHQmvcJ2B65S+4IAtNjsG6vtMEig==
dependencies:
fastify-plugin "^1.0.0"
qs "^6.5.1"
fastify-helmet@^3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/fastify-helmet/-/fastify-helmet-3.0.1.tgz#799f8b4beddec922805c87cc3c6a3151d8946d9c"
@ -2693,7 +2685,7 @@ fastify-jwt@^1.0.0:
jsonwebtoken "^8.3.0"
steed "^1.1.3"
fastify-plugin@^1.0.0, fastify-plugin@^1.2.0, fastify-plugin@^1.2.1, fastify-plugin@^1.4.0, fastify-plugin@^1.5.0, fastify-plugin@^1.6.0:
fastify-plugin@^1.0.0, fastify-plugin@^1.2.0, fastify-plugin@^1.2.1, fastify-plugin@^1.4.0, fastify-plugin@^1.6.0:
version "1.6.0"
resolved "https://registry.yarnpkg.com/fastify-plugin/-/fastify-plugin-1.6.0.tgz#c8198b08608f20c502b5dad26b36e9ae27206d7c"
integrity sha512-lFa9txg8LZx4tljj33oG53nUXhVg0baZxtP9Pxi0dJmI0NQxzkDk5DS9kr3D7iMalUAp3mvIq16OQumc7eIvLA==
@ -3097,11 +3089,6 @@ hash.js@^1.0.0, hash.js@^1.0.3:
inherits "^2.0.3"
minimalistic-assert "^1.0.1"
hashlru@^2.3.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/hashlru/-/hashlru-2.3.0.tgz#5dc15928b3f6961a2056416bb3a4910216fdfb51"
integrity sha512-0cMsjjIC8I+D3M44pOQdsy0OHXGLVz6Z0beRuufhKa0KfaD2wGwAev6jILzXsd3/vpnNQJmWyZtIILqM1N+n5A==
he@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f"
@ -4931,14 +4918,6 @@ pn@^1.1.0:
resolved "https://registry.yarnpkg.com/pn/-/pn-1.1.0.tgz#e2f4cef0e219f463c179ab37463e4e1ecdccbafb"
integrity sha512-2qHaIQr2VLRFoxe2nASzsV6ef4yOOH+Fi9FBOVH6cqeSgUnoyySPZkxzLuzd+RYOQTRpROA0ztTMqxROKSb/nA==
point-of-view@^3.5.0:
version "3.5.0"
resolved "https://registry.yarnpkg.com/point-of-view/-/point-of-view-3.5.0.tgz#56ec984e9615f2ddad9b26383f3b73b8f4fab558"
integrity sha512-eiv49vaVAUSmrReRKlWgmYJ7wgcr3rGT6eAVuD6HmXTt1AKLY0CgYeu3msGW0n/X4iefdVsfpcPRsttWvS2CGQ==
dependencies:
fastify-plugin "^1.5.0"
hashlru "^2.3.0"
posix-character-classes@^0.1.0:
version "0.1.1"
resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab"
@ -5440,11 +5419,6 @@ q@^1.1.2:
resolved "https://registry.yarnpkg.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7"
integrity sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=
qs@^6.5.1:
version "6.8.0"
resolved "https://registry.yarnpkg.com/qs/-/qs-6.8.0.tgz#87b763f0d37ca54200334cd57bb2ef8f68a1d081"
integrity sha512-tPSkj8y92PfZVbinY1n84i1Qdx75lZjMQYx9WZhnkofyxzw2r7Ho39G3/aEvSUdebxpnnM4LZJCtvE/Aq3+s9w==
qs@~6.5.2:
version "6.5.2"
resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36"