mirror of
				https://gitlab.com/Alamantus/Readlebee.git
				synced 2025-11-04 02:07:11 +01:00 
			
		
		
		
	Remove old Choo files
This commit is contained in:
		
							parent
							
								
									f42f9ef987
								
							
						
					
					
						commit
						7941460d96
					
				
					 9 changed files with 0 additions and 448 deletions
				
			
		| 
						 | 
				
			
			@ -1,19 +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>book-tracker</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="index.scss">
 | 
			
		||||
  <script src="index.js"></script>
 | 
			
		||||
</head>
 | 
			
		||||
 | 
			
		||||
<!-- Choo replaces the body tag with the app. -->
 | 
			
		||||
<body></body>
 | 
			
		||||
 | 
			
		||||
</html>
 | 
			
		||||
							
								
								
									
										45
									
								
								src/index.js
									
										
									
									
									
								
							
							
						
						
									
										45
									
								
								src/index.js
									
										
									
									
									
								
							| 
						 | 
				
			
			@ -1,45 +0,0 @@
 | 
			
		|||
import choo from 'choo';
 | 
			
		||||
 | 
			
		||||
import { viewManager } from './views/manager';
 | 
			
		||||
 | 
			
		||||
const app = choo();
 | 
			
		||||
 | 
			
		||||
if (process.env.NODE_ENV !== 'production') {
 | 
			
		||||
  // Only runs in development and will be stripped from production build.
 | 
			
		||||
  app.use(require('choo-devtools')());  // Exposes `choo` to the console for debugging!
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// App state and emitters
 | 
			
		||||
app.use((state, emitter) => {
 | 
			
		||||
  // Default state variables
 | 
			
		||||
  state.currentView = 'home';
 | 
			
		||||
  state.viewStates = {};
 | 
			
		||||
 | 
			
		||||
  // Listeners
 | 
			
		||||
  emitter.on('DOMContentLoaded', () => {
 | 
			
		||||
    // Emitter listeners
 | 
			
		||||
    emitter.on('render', callback => {
 | 
			
		||||
      // This is a dirty hack to get the callback to call *after* re-rendering.
 | 
			
		||||
      if (callback && typeof callback === "function") {
 | 
			
		||||
        setTimeout(() => {
 | 
			
		||||
          callback();
 | 
			
		||||
        }, 50);
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    emitter.on('changeView', newView => {
 | 
			
		||||
      // Change the view and call render. Makes it easier to call within views.
 | 
			
		||||
      state.currentView = newView;
 | 
			
		||||
      emitter.emit('render', () => {});
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
// For the main screen, pass the viewManager function in viewManager.js,
 | 
			
		||||
// which is given the app's state from above and the emitter.emit method that
 | 
			
		||||
// triggers the app's emitter listeners.
 | 
			
		||||
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
 | 
			
		||||
| 
						 | 
				
			
			@ -1,12 +0,0 @@
 | 
			
		|||
export class ViewController {
 | 
			
		||||
  constructor(state, viewName, defaultState = {}) {
 | 
			
		||||
    // Store the global app state so it's accessible but out of the way.
 | 
			
		||||
    this.appState = state;
 | 
			
		||||
 | 
			
		||||
    // Give this view its own state within the appState.
 | 
			
		||||
    if (!this.appState.viewStates.hasOwnProperty(viewName)) {
 | 
			
		||||
      this.appState.viewStates[viewName] = defaultState;
 | 
			
		||||
    }
 | 
			
		||||
    this.state = this.appState.viewStates[viewName];
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,23 +0,0 @@
 | 
			
		|||
import { ViewController } from '../controller';
 | 
			
		||||
 | 
			
		||||
export class HomeController extends ViewController {
 | 
			
		||||
  constructor(state) {
 | 
			
		||||
    // Super passes state, view name, and default state to ViewController,
 | 
			
		||||
    // which stores state in this.appState and the view controller's state to this.state
 | 
			
		||||
    super(state, 'home', {
 | 
			
		||||
      messages: [
 | 
			
		||||
        'hello',
 | 
			
		||||
        'test',
 | 
			
		||||
        'yay',
 | 
			
		||||
      ],
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    // If using controller methods in an input's onchange or onclick instance,
 | 
			
		||||
    // either bind the class's 'this' instance to the method first...
 | 
			
		||||
    // or use `onclick=${() => controller.submit()}` to maintain the 'this' of the class instead.
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  get messages() {
 | 
			
		||||
    return [...this.state.messages];
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,41 +0,0 @@
 | 
			
		|||
import html from 'choo/html';
 | 
			
		||||
 | 
			
		||||
import './styles.scss'; // Creates a separate CSS file, but allows better code splitting.
 | 
			
		||||
// We'll see if code splitting is worth it in the end or if we should combine everything into `src/index.scss`
 | 
			
		||||
import { HomeController } from './controller';  // The controller for this view, where processing should happen.
 | 
			
		||||
 | 
			
		||||
// This is the view function that is exported and used in the view manager.
 | 
			
		||||
export const homeView = (state, emit) => {
 | 
			
		||||
  const controller = new HomeController(state);
 | 
			
		||||
 | 
			
		||||
  // Returning an array in a view allows non-shared parent HTML elements.
 | 
			
		||||
  // This one doesn't have the problem right now, but it's good to remember.
 | 
			
		||||
  return [
 | 
			
		||||
    html`<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">
 | 
			
		||||
        ${controller.messages.map(message => {
 | 
			
		||||
          return html`<p>${message}</p>`;
 | 
			
		||||
        })}
 | 
			
		||||
      </article>
 | 
			
		||||
    </section>`,
 | 
			
		||||
  ];
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,8 +0,0 @@
 | 
			
		|||
.test {
 | 
			
		||||
  background-color: #dddddd;
 | 
			
		||||
  padding: 10px;
 | 
			
		||||
 | 
			
		||||
  p {
 | 
			
		||||
    border: 1px solid #444444;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,55 +0,0 @@
 | 
			
		|||
import html from 'choo/html';
 | 
			
		||||
 | 
			
		||||
import { homeView } from './home';
 | 
			
		||||
import { searchView } from './search';
 | 
			
		||||
 | 
			
		||||
export const viewManager = (state, emit) => {
 | 
			
		||||
  // In viewManager all we are doing is checking the app's state
 | 
			
		||||
  // and passing the state and emit to the relevant view.
 | 
			
		||||
  let htmlContent = html`<div>loading</div>`;
 | 
			
		||||
  switch (state.params.page) {
 | 
			
		||||
    case 'home':
 | 
			
		||||
    default: {
 | 
			
		||||
      htmlContent = homeView(state, emit);
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
    case 'search': {
 | 
			
		||||
      htmlContent = searchView(state, emit);
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Create a wrapper for view content that includes global header/footer
 | 
			
		||||
  let view = html`<body>
 | 
			
		||||
  <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" name="search" placeholder="Search" onchange=${e => {
 | 
			
		||||
            console.log(encodeURIComponent(e.target.value.trim()));
 | 
			
		||||
            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>
 | 
			
		||||
      </div>
 | 
			
		||||
    </nav>
 | 
			
		||||
  </header>
 | 
			
		||||
 | 
			
		||||
  <main class="container">
 | 
			
		||||
    ${htmlContent}
 | 
			
		||||
  </main>
 | 
			
		||||
</body>`;
 | 
			
		||||
 | 
			
		||||
  return view;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,206 +0,0 @@
 | 
			
		|||
import { ViewController } from '../controller';
 | 
			
		||||
 | 
			
		||||
export class SearchController extends ViewController {
 | 
			
		||||
  constructor(state) {
 | 
			
		||||
    // Super passes state, view name, and default state to ViewController,
 | 
			
		||||
    // which stores state in this.appState and the view controller's state to this.state
 | 
			
		||||
    super(state, 'search', {
 | 
			
		||||
      done: false,
 | 
			
		||||
      results: [],
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    // If using controller methods in an input's onchange or onclick instance,
 | 
			
		||||
    // either bind the class's 'this' instance to the method first...
 | 
			
		||||
    // or use `onclick=${() => controller.submit()}` to maintain the 'this' of the class instead.
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  get 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) {
 | 
			
		||||
    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))
 | 
			
		||||
      .then(res => res.json())
 | 
			
		||||
      .then(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;
 | 
			
		||||
        }
 | 
			
		||||
      });
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,39 +0,0 @@
 | 
			
		|||
import html from 'choo/html';
 | 
			
		||||
 | 
			
		||||
// We'll see if code splitting is worth it in the end or if we should combine everything into `src/index.scss`
 | 
			
		||||
import { SearchController } from './controller';  // The controller for this view, where processing should happen.
 | 
			
		||||
 | 
			
		||||
// This is the view function that is exported and used in the view manager.
 | 
			
		||||
export const searchView = (state, emit) => {
 | 
			
		||||
  const controller = new SearchController(state);
 | 
			
		||||
 | 
			
		||||
  if (!controller.state.done && controller.hasQuery) {
 | 
			
		||||
    controller.searchOpenLibrary(state.query.for).then(() => {
 | 
			
		||||
      emit('render');
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Returning an array in a view allows non-shared parent HTML elements.
 | 
			
		||||
  // This one doesn't have the problem right now, but it's good to remember.
 | 
			
		||||
  return [
 | 
			
		||||
    html`<section>
 | 
			
		||||
      <h2 class="subtitle">An attempt at a viable alternative to Goodreads</h2>
 | 
			
		||||
 | 
			
		||||
      <article>
 | 
			
		||||
        ${controller.results.map(result => {
 | 
			
		||||
          return html`<div class="card">
 | 
			
		||||
            <header>
 | 
			
		||||
              ${result.covers.map(cover => {
 | 
			
		||||
                return html`<img src=${cover} />`;
 | 
			
		||||
              })}
 | 
			
		||||
              <h1 class="title">${result.title}</h1>
 | 
			
		||||
              ${result.authors.map(author => {
 | 
			
		||||
                return html`<h2 class="subtitle">${author}</h2>`;
 | 
			
		||||
              })}
 | 
			
		||||
            </header>
 | 
			
		||||
          </div>`;
 | 
			
		||||
        })}
 | 
			
		||||
      </article>
 | 
			
		||||
    </section>`,
 | 
			
		||||
  ];
 | 
			
		||||
}
 | 
			
		||||
		Loading…
	
	Add table
		
		Reference in a new issue