Move search to handlebars format
This commit is contained in:
parent
c986cdb4fd
commit
f42f9ef987
|
@ -0,0 +1,148 @@
|
|||
const fetch = require('node-fetch');
|
||||
|
||||
class SearchController {
|
||||
constructor(searchTerm) {
|
||||
this.term = searchTerm;
|
||||
}
|
||||
|
||||
get hasQuery() {
|
||||
return typeof this.term !== 'undefined' && this.term !== '';
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
async searchWikiBooks(term) {
|
||||
if (!this.hasQuery) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const query = this.mediaWikiQuery('https://en.wikibooks.org/w/api.php', {
|
||||
action: 'query',
|
||||
list: 'search',
|
||||
srsearch: this.term,
|
||||
srprop: '',
|
||||
});
|
||||
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')) {
|
||||
|
||||
}
|
||||
}
|
||||
});
|
||||
return bookResults;
|
||||
});
|
||||
}
|
||||
|
||||
async searchOpenLibrary() {
|
||||
if (!this.hasQuery) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return fetch('http://openlibrary.org/search.json?q=' + encodeURIComponent(this.term))
|
||||
.then(res => res.json())
|
||||
.then(response => {
|
||||
if (!response.hasOwnProperty('docs')) {
|
||||
return [];
|
||||
}
|
||||
// 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;
|
||||
});
|
||||
|
||||
return results;
|
||||
}).catch(error => {
|
||||
console.log(error);
|
||||
return [];
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = SearchController;
|
|
@ -26,6 +26,7 @@
|
|||
"handlebars": "^4.2.0",
|
||||
"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"
|
||||
}
|
||||
|
|
|
@ -1,16 +1,7 @@
|
|||
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
|
|
@ -0,0 +1,13 @@
|
|||
const SearchController = require('../controllers/search');
|
||||
|
||||
async function routes(fastify, options) {
|
||||
fastify.get('/search', async (request, reply) => {
|
||||
const searchTerm = typeof request.query.for !== 'undefined' ? request.query.for.trim() : '';
|
||||
const search = new SearchController(searchTerm);
|
||||
|
||||
const results = await search.searchOpenLibrary();
|
||||
reply.view('search.hbs', { results, searchTerm });
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = routes
|
|
@ -24,7 +24,6 @@ fastify.register(require('point-of-view'), {
|
|||
removeCommentsFromCDATA: true,
|
||||
collapseWhitespace: true,
|
||||
collapseBooleanAttributes: true,
|
||||
removeAttributeQuotes: true,
|
||||
removeEmptyAttributes: true
|
||||
},
|
||||
partials: {
|
||||
|
@ -38,6 +37,7 @@ fastify.register(require('point-of-view'), {
|
|||
// Routes
|
||||
fastify.register(require('./routes/resources'));
|
||||
fastify.register(require('./routes/home'));
|
||||
fastify.register(require('./routes/search'));
|
||||
|
||||
// Start the server
|
||||
fastify.listen(3000, function (err, address) {
|
||||
|
|
|
@ -10,7 +10,6 @@
|
|||
<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>
|
||||
|
@ -29,11 +28,6 @@
|
|||
{{/footer-block}}
|
||||
|
||||
{{#> scripts-block }}
|
||||
<script>
|
||||
(function () {
|
||||
alert('test');
|
||||
})();
|
||||
</script>
|
||||
{{/scripts-block}}
|
||||
</body>
|
||||
|
||||
|
|
|
@ -11,9 +11,11 @@
|
|||
<label for="navMenu" class="burger pseudo button">≡</label>
|
||||
|
||||
<div class="menu">
|
||||
<label style="display: inline-block;">
|
||||
<input type="text" id="headerSearchBar" placeholder="Search">
|
||||
<form action="/search" style="display: inline-block;">
|
||||
<label>
|
||||
<input type="text" id="headerSearchBar" name="for" placeholder="Search" value="{{searchTerm}}">
|
||||
</label>
|
||||
</form>
|
||||
<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>
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
{{#> layout }}
|
||||
|
||||
{{#*inline "page-content-block"}}
|
||||
<section>
|
||||
<h2 class="subtitle">An attempt at a viable alternative to Goodreads</h2>
|
||||
|
||||
<article>
|
||||
|
||||
{{#each results }}
|
||||
<div class="card">
|
||||
<header>
|
||||
|
||||
{{#each covers }}
|
||||
<img src="{{this}}" />
|
||||
{{/each}}
|
||||
|
||||
<h1 class="title">{{title}}</h1>
|
||||
|
||||
{{#each authors }}
|
||||
<h2 class="subtitle">{{this}}</h2>
|
||||
{{/each}}
|
||||
|
||||
</header>
|
||||
</div>
|
||||
{{/each}}
|
||||
|
||||
</article>
|
||||
</section>
|
||||
{{/inline}}
|
||||
|
||||
{{/layout}}
|
|
@ -1335,6 +1335,11 @@ nocache@^2.0.0:
|
|||
resolved "https://registry.yarnpkg.com/nocache/-/nocache-2.1.0.tgz#120c9ffec43b5729b1d5de88cd71aa75a0ba491f"
|
||||
integrity sha512-0L9FvHG3nfnnmaEQPjT9xhfN4ISk0A8/2j4M37Np4mcDesJjHgEUfgPhdCyZuFI954tjokaIj/A3NdpFNdEh4Q==
|
||||
|
||||
node-fetch@^2.6.0:
|
||||
version "2.6.0"
|
||||
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.0.tgz#e633456386d4aa55863f676a7ab0daa8fdecb0fd"
|
||||
integrity sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA==
|
||||
|
||||
node-releases@^1.1.29:
|
||||
version "1.1.29"
|
||||
resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.29.tgz#86a57c6587a30ecd6726449e5d293466b0a0bb86"
|
||||
|
|
Loading…
Reference in New Issue