mirror of
				https://gitlab.com/Alamantus/Readlebee.git
				synced 2025-11-04 10:17:03 +01:00 
			
		
		
		
	Move search to handlebars format
This commit is contained in:
		
							parent
							
								
									c986cdb4fd
								
							
						
					
					
						commit
						f42f9ef987
					
				
					 9 changed files with 204 additions and 19 deletions
				
			
		
							
								
								
									
										148
									
								
								controllers/search.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										148
									
								
								controllers/search.js
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -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
 | 
			
		||||
							
								
								
									
										13
									
								
								routes/search.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								routes/search.js
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -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">
 | 
			
		||||
      </label>
 | 
			
		||||
      <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>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										31
									
								
								views/search.hbs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								views/search.hbs
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -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…
	
	Add table
		
		Reference in a new issue