Compare commits
No commits in common. "809f07504dac5ea6f1c2d814a06c63afa7865d46" and "3507b7ea2f45c0915cd39ca1acdd10d40e7680e8" have entirely different histories.
809f07504d
...
3507b7ea2f
|
@ -1,4 +1,3 @@
|
||||||
node_modules/
|
node_modules/
|
||||||
public/
|
dist/
|
||||||
|
.cache/
|
||||||
**/*.log
|
|
28
package.json
28
package.json
|
@ -7,26 +7,24 @@
|
||||||
"author": "Robbie Antenesse <dev@alamantus.com>",
|
"author": "Robbie Antenesse <dev@alamantus.com>",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "npm run compile-sass && npm run start-server",
|
"start": "npm run watch-js",
|
||||||
"start-server": "node server.js",
|
"watch-js": "parcel watch src/index.html --no-hmr",
|
||||||
"compile-sass": "node process-styles.js"
|
"serve-js": "parcel src/index.html",
|
||||||
|
"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/{*,.*}"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"autoprefixer": "^9.6.1",
|
"autoprefixer": "^9.6.1",
|
||||||
"cssnano": "^4.1.10",
|
"choo-devtools": "^3.0.1",
|
||||||
|
"parcel-bundler": "^1.12.3",
|
||||||
|
"parcel-plugin-goodie-bag": "^2.0.0",
|
||||||
|
"rimraf": "^3.0.0",
|
||||||
"sass": "^1.23.0-module.beta.1"
|
"sass": "^1.23.0-module.beta.1"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"fastify": "^2.8.0",
|
"choo": "^7.0.0",
|
||||||
"fastify-caching": "^5.0.0",
|
"picnic": "^6.5.1"
|
||||||
"fastify-compress": "^0.11.0",
|
|
||||||
"fastify-formbody": "^3.1.0",
|
|
||||||
"fastify-helmet": "^3.0.1",
|
|
||||||
"fastify-static": "^2.5.0",
|
|
||||||
"handlebars": "^4.2.0",
|
|
||||||
"html-minifier": "^4.0.0",
|
|
||||||
"make-promises-safe": "^5.0.0",
|
|
||||||
"picnic": "^6.5.1",
|
|
||||||
"point-of-view": "^3.5.0"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,34 +0,0 @@
|
||||||
const sass = require('sass');
|
|
||||||
const autoprefixer = require('autoprefixer');
|
|
||||||
const cssnano = require('cssnano');
|
|
||||||
const postcss = require('postcss');
|
|
||||||
const fs = require('fs');
|
|
||||||
|
|
||||||
sass.render({ file: 'index.scss' }, function (sassErr, css) {
|
|
||||||
if (sassErr) {
|
|
||||||
throw sassErr;
|
|
||||||
}
|
|
||||||
|
|
||||||
// fs.writeFile('public/css/index.css', css.css, (fileErr) => {
|
|
||||||
// if (fileErr) {
|
|
||||||
// throw fileErr;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// console.log('The file has been saved!');
|
|
||||||
// });
|
|
||||||
postcss([autoprefixer,cssnano]).process(css.css, {from:undefined}).then(result => {
|
|
||||||
result.warnings().forEach(warn => {
|
|
||||||
console.warn(warn.toString());
|
|
||||||
});
|
|
||||||
|
|
||||||
fs.writeFile('public/css/index.css', result.css, (fileErr) => {
|
|
||||||
if (fileErr) {
|
|
||||||
throw fileErr;
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('The file has been saved!');
|
|
||||||
});
|
|
||||||
}).catch((postcssErr) => {
|
|
||||||
throw postcssErr;
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,16 +0,0 @@
|
||||||
async function routes(fastify, options) {
|
|
||||||
fastify.get('/', async (request, reply) => {
|
|
||||||
// return { hello: 'world' }
|
|
||||||
reply.view('home.hbs', { text: 'test' });
|
|
||||||
});
|
|
||||||
|
|
||||||
// fastify.get('/search/:id', async function (request, reply) {
|
|
||||||
// const result = await collection.findOne({ id: request.params.id })
|
|
||||||
// if (result.value === null) {
|
|
||||||
// throw new Error('Invalid value')
|
|
||||||
// }
|
|
||||||
// return result.value
|
|
||||||
// })
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = routes
|
|
|
@ -1,15 +0,0 @@
|
||||||
async function routes(fastify, options) {
|
|
||||||
fastify.get('/styles/:css', async (request, reply) => {
|
|
||||||
reply.sendFile('css/' + request.params.css);
|
|
||||||
});
|
|
||||||
|
|
||||||
// fastify.get('/search/:id', async function (request, reply) {
|
|
||||||
// const result = await collection.findOne({ id: request.params.id })
|
|
||||||
// if (result.value === null) {
|
|
||||||
// throw new Error('Invalid value')
|
|
||||||
// }
|
|
||||||
// return result.value
|
|
||||||
// })
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = routes
|
|
50
server.js
50
server.js
|
@ -1,50 +0,0 @@
|
||||||
'use strict'
|
|
||||||
|
|
||||||
require('make-promises-safe'); // installs an 'unhandledRejection' handler
|
|
||||||
|
|
||||||
const path = require('path');
|
|
||||||
const fastify = require('fastify')({
|
|
||||||
logger: true,
|
|
||||||
});
|
|
||||||
fastify.register(require('fastify-helmet'));
|
|
||||||
fastify.register(require('fastify-compress'));
|
|
||||||
fastify.register(require('fastify-formbody'));
|
|
||||||
fastify.register(require('fastify-static'), {
|
|
||||||
root: path.join(__dirname, 'public'),
|
|
||||||
});
|
|
||||||
fastify.register(require('point-of-view'), {
|
|
||||||
engine: {
|
|
||||||
handlebars: require('handlebars'),
|
|
||||||
},
|
|
||||||
templates: 'views',
|
|
||||||
options: {
|
|
||||||
useHtmlMinifier: require('html-minifier'),
|
|
||||||
htmlMinifierOptions: {
|
|
||||||
removeComments: true,
|
|
||||||
removeCommentsFromCDATA: true,
|
|
||||||
collapseWhitespace: true,
|
|
||||||
collapseBooleanAttributes: true,
|
|
||||||
removeAttributeQuotes: true,
|
|
||||||
removeEmptyAttributes: true
|
|
||||||
},
|
|
||||||
partials: {
|
|
||||||
head: 'partials/head.hbs',
|
|
||||||
header: 'partials/header.hbs',
|
|
||||||
foot: 'partials/foot.hbs',
|
|
||||||
footer: 'partials/footer.hbs',
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// Routes
|
|
||||||
fastify.register(require('./routes/resources'));
|
|
||||||
fastify.register(require('./routes/home'));
|
|
||||||
|
|
||||||
// Start the server
|
|
||||||
fastify.listen(3000, function (err, address) {
|
|
||||||
if (err) {
|
|
||||||
fastify.log.error(err);
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
fastify.log.info(`server listening on ${address}`);
|
|
||||||
});
|
|
|
@ -39,7 +39,5 @@ app.use((state, emitter) => {
|
||||||
// which is given the app's state from above and the emitter.emit method that
|
// which is given the app's state from above and the emitter.emit method that
|
||||||
// triggers the app's emitter listeners.
|
// triggers the app's emitter listeners.
|
||||||
app.route('/', viewManager);
|
app.route('/', viewManager);
|
||||||
app.route('/:page', viewManager);
|
|
||||||
app.route('/404', viewManager);
|
|
||||||
|
|
||||||
app.mount('body'); // Overwrite the `<body>` tag with the content of the Choo app
|
app.mount('body'); // Overwrite the `<body>` tag with the content of the Choo app
|
|
@ -7,7 +7,10 @@ export const viewManager = (state, emit) => {
|
||||||
// In viewManager all we are doing is checking the app's state
|
// In viewManager all we are doing is checking the app's state
|
||||||
// and passing the state and emit to the relevant view.
|
// and passing the state and emit to the relevant view.
|
||||||
let htmlContent = html`<div>loading</div>`;
|
let htmlContent = html`<div>loading</div>`;
|
||||||
switch (state.params.page) {
|
if (state.query.hasOwnProperty('search')) {
|
||||||
|
state.currentView = 'search'; // Override view if there's a search query
|
||||||
|
}
|
||||||
|
switch (state.currentView) {
|
||||||
case 'home':
|
case 'home':
|
||||||
default: {
|
default: {
|
||||||
htmlContent = homeView(state, emit);
|
htmlContent = homeView(state, emit);
|
||||||
|
@ -34,12 +37,11 @@ export const viewManager = (state, emit) => {
|
||||||
<label for="navMenu" class="burger pseudo button">≡</label>
|
<label for="navMenu" class="burger pseudo button">≡</label>
|
||||||
|
|
||||||
<div class="menu">
|
<div class="menu">
|
||||||
<label style="display: inline-block;">
|
<form method="GET" style="display:inline-block;">
|
||||||
<input type="text" name="search" placeholder="Search" onchange=${e => {
|
<label>
|
||||||
console.log(encodeURIComponent(e.target.value.trim()));
|
<input type="text" name="search" placeholder="Search">
|
||||||
emit('pushState', '/search?for=' + encodeURIComponent(e.target.value.trim()));
|
</label>
|
||||||
}}>
|
</form>
|
||||||
</label>
|
|
||||||
<a href="https://gitlab.com/Alamantus/book-tracker" class="pseudo button">Repo</a>
|
<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="https://gitter.im/book-tracker/general" class="pseudo button">Chat</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -18,153 +18,7 @@ export class SearchController extends ViewController {
|
||||||
return [...this.state.results];
|
return [...this.state.results];
|
||||||
}
|
}
|
||||||
|
|
||||||
get hasQuery() {
|
|
||||||
return this.appState.query.hasOwnProperty('for') && this.appState.query.for.trim() !== '';
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Query a MediaWiki api.php instance with the given options
|
|
||||||
*/
|
|
||||||
mediaWikiQuery(endpoint, options) {
|
|
||||||
/**
|
|
||||||
* Create a uniquely-named callback that will process the JSONP results
|
|
||||||
*/
|
|
||||||
var createCallback = function (k) {
|
|
||||||
var i = 1;
|
|
||||||
var callbackName;
|
|
||||||
do {
|
|
||||||
callbackName = 'searchCallback' + i;
|
|
||||||
i = i + 1;
|
|
||||||
} while (window[callbackName])
|
|
||||||
window[callbackName] = k;
|
|
||||||
return callbackName;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Flatten an object into a URL query string.
|
|
||||||
* For example: { foo: 'bar', baz: 42 } becomes 'foo=bar&baz=42'
|
|
||||||
*/
|
|
||||||
var queryStr = function (options) {
|
|
||||||
var query = [];
|
|
||||||
for (var i in options) {
|
|
||||||
if (options.hasOwnProperty(i)) {
|
|
||||||
query.push(encodeURIComponent(i) + '=' + encodeURIComponent(options[i]));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return query.join('&');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Build a function that can be applied to a callback. The callback processes
|
|
||||||
* the JSON results of the API call.
|
|
||||||
*/
|
|
||||||
return function (k) {
|
|
||||||
options.format = 'json';
|
|
||||||
options.callback = createCallback(k);
|
|
||||||
var script = document.createElement('script');
|
|
||||||
script.id = 'searchResults';
|
|
||||||
script.src = endpoint + '?' + queryStr(options);
|
|
||||||
var head = document.getElementsByTagName('head')[0];
|
|
||||||
head.appendChild(script);
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
search(term) {
|
search(term) {
|
||||||
const query = this.mediaWikiQuery('https://en.wikibooks.org/w/api.php', {
|
|
||||||
action: 'query',
|
|
||||||
list: 'search',
|
|
||||||
// list: 'categorymembers',
|
|
||||||
// cmtitle: 'Category:Subject:Books by subject/all books',
|
|
||||||
srsearch: term,
|
|
||||||
srprop: '',
|
|
||||||
// pageids: 20308,
|
|
||||||
// prop: 'categories|pageprops',
|
|
||||||
});
|
|
||||||
query(response => {
|
|
||||||
console.log(response);
|
|
||||||
const searchScript = document.getElementById('searchResults');
|
|
||||||
searchScript.parentNode.removeChild(searchScript);
|
|
||||||
for (let property in window) {
|
|
||||||
if (property.includes('searchCallback')) {
|
|
||||||
delete window[property];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const bookResults = [];
|
|
||||||
const pageids = response.query.search.map(item => item.pageid);
|
|
||||||
const propsQuery = this.mediaWikiQuery('https://en.wikibooks.org/w/api.php', {
|
|
||||||
action: 'query',
|
|
||||||
pageids: pageids.join('|'),
|
|
||||||
prop: 'categories|pageprops',
|
|
||||||
});
|
|
||||||
propsQuery(propsResponse => {
|
|
||||||
console.log(propsResponse);
|
|
||||||
for (let pageid in propsResponse.query.pages) {
|
|
||||||
if (propsResponse.query.pages[pageid].hasOwnProperty('categories')) {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
// this.state.results = results;
|
|
||||||
this.state.done = true;
|
|
||||||
});
|
|
||||||
|
|
||||||
// return fetch(`https://en.wikipedia.org/w/api.php?action=query&list=search&srsearch=${term}&format=json&callback=searchCallback`, {
|
|
||||||
// method: 'GET',
|
|
||||||
// mode: 'no-cors',
|
|
||||||
// headers: new Headers(
|
|
||||||
// {
|
|
||||||
// "Accept": "text/plain"
|
|
||||||
// }
|
|
||||||
// ),
|
|
||||||
// // body: JSON.stringify({
|
|
||||||
// // action: 'opensearch',
|
|
||||||
// // search: term,
|
|
||||||
// // format: 'json',
|
|
||||||
// // }),
|
|
||||||
// })
|
|
||||||
// .then(res => res.text())
|
|
||||||
// .then(response => {
|
|
||||||
// console.log(response);
|
|
||||||
// // if (response.hasOwnProperty('docs')) {
|
|
||||||
// // // Format the response into usable objects
|
|
||||||
// // const docs = response.docs.map(doc => {
|
|
||||||
// // return {
|
|
||||||
// // title: doc.title_suggest.trim(),
|
|
||||||
// // authors: doc.hasOwnProperty('author_name') ? doc.author_name.map(name => name.trim()) : [],
|
|
||||||
// // cover: doc.hasOwnProperty('cover_i') ? `//covers.openlibrary.org/b/id/${doc.cover_i}-S.jpg` : false,
|
|
||||||
// // };
|
|
||||||
// // });
|
|
||||||
|
|
||||||
// // // Filter out duplicate items with the same title and author
|
|
||||||
// // const results = docs.filter((doc, index, allDocs) => {
|
|
||||||
// // return typeof allDocs.find((filterResult, filterIndex) => {
|
|
||||||
// // return index !== filterIndex && filterResult.title === doc.title
|
|
||||||
// // && JSON.stringify(filterResult.authors) === JSON.stringify(doc.authors);
|
|
||||||
// // }) === 'undefined';
|
|
||||||
// // }).map(result => {
|
|
||||||
// // // Find any duplicates in case they have different cover data
|
|
||||||
// // const duplicates = docs.filter(doc => {
|
|
||||||
// // return doc.title.toLowerCase() === result.title.toLowerCase() && JSON.stringify(doc.authors) === JSON.stringify(result.authors);
|
|
||||||
// // });
|
|
||||||
// // result.covers = [];
|
|
||||||
// // duplicates.forEach(duplicate => {
|
|
||||||
// // if (duplicate.cover !== false) {
|
|
||||||
// // result.covers.push(duplicate.cover);
|
|
||||||
// // }
|
|
||||||
// // });
|
|
||||||
// // return result;
|
|
||||||
// // });
|
|
||||||
|
|
||||||
// // this.state.results = results;
|
|
||||||
// this.state.done = true;
|
|
||||||
// // }
|
|
||||||
// });
|
|
||||||
}
|
|
||||||
|
|
||||||
searchOpenLibrary(term) {
|
|
||||||
this.state.done = false;
|
|
||||||
return fetch('http://openlibrary.org/search.json?q=' + encodeURIComponent(term))
|
return fetch('http://openlibrary.org/search.json?q=' + encodeURIComponent(term))
|
||||||
.then(res => res.json())
|
.then(res => res.json())
|
||||||
.then(response => {
|
.then(response => {
|
||||||
|
|
|
@ -7,8 +7,8 @@ import { SearchController } from './controller'; // The controller for this vie
|
||||||
export const searchView = (state, emit) => {
|
export const searchView = (state, emit) => {
|
||||||
const controller = new SearchController(state);
|
const controller = new SearchController(state);
|
||||||
|
|
||||||
if (!controller.state.done && controller.hasQuery) {
|
if (!controller.state.done) {
|
||||||
controller.searchOpenLibrary(state.query.for).then(() => {
|
controller.search(state.query.search).then(() => {
|
||||||
emit('render');
|
emit('render');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,28 +0,0 @@
|
||||||
{{> header }}
|
|
||||||
|
|
||||||
<section>
|
|
||||||
<h2 class="subtitle">An attempt at a viable alternative to Goodreads</h2>
|
|
||||||
|
|
||||||
<article class="flex two">
|
|
||||||
<div class="half">
|
|
||||||
<div class="card">
|
|
||||||
<header>
|
|
||||||
<p>Still gotta figure out a design.</p>
|
|
||||||
</header>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="half">
|
|
||||||
<div class="card">
|
|
||||||
<header>
|
|
||||||
<p>It's early days, my friends!</p>
|
|
||||||
</header>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</article>
|
|
||||||
|
|
||||||
<article class="test">
|
|
||||||
{{ text }}
|
|
||||||
</article>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
{{> footer }}
|
|
|
@ -1,7 +0,0 @@
|
||||||
<script>
|
|
||||||
(function() {
|
|
||||||
alert('test');
|
|
||||||
})();
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
|
@ -1,7 +0,0 @@
|
||||||
</main>
|
|
||||||
|
|
||||||
<footer>
|
|
||||||
<p>test footer</p>
|
|
||||||
</footer>
|
|
||||||
|
|
||||||
{{> foot }}
|
|
|
@ -1,15 +0,0 @@
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
||||||
<title>{{ pageTitle }}</title>
|
|
||||||
<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="/styles/index.css">{{!-- This is controlled by the resources router. --}}
|
|
||||||
<script src="index.js"></script>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
|
@ -1,25 +0,0 @@
|
||||||
{{> head }}
|
|
||||||
|
|
||||||
<header>
|
|
||||||
<nav>
|
|
||||||
<div class="brand">
|
|
||||||
<a href="./">
|
|
||||||
<h1>Unnamed Book Tracker</h1>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- responsive-->
|
|
||||||
<input id="navMenu" type="checkbox" class="show">
|
|
||||||
<label for="navMenu" class="burger pseudo button">≡</label>
|
|
||||||
|
|
||||||
<div class="menu">
|
|
||||||
<label style="display: inline-block;">
|
|
||||||
<input type="text" id="headerSearchBar" placeholder="Search">
|
|
||||||
</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>
|
|
||||||
</div>
|
|
||||||
</nav>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<main class="container">
|
|
Loading…
Reference in New Issue