Book Manager
Application to manage eBooks.

Motivation
After having bought several books, I saw the need to be able to manage and synchronize them, because it always took me too much time to do this task, by copying the relevant files manually.
Capabilities
- Import books from directory or listing (HTML dump)
- Edit book information
- Gather book information from meta data
- Support for multiple storages
- Sync to device
The implementation started as a web application, but with the possibility in mind to create an Electron app.
this.add = function(item) {
if (!(item instanceof QueueItem)) {
throw new TypeError('Item must be an instance of QueueItem')
}
this.items.push(item)
if (this.state === 'idle') {
this.processNext();
}
};
Command queue.
Technologies
- Node, Express
- Bower, Grunt, Mocha, Istanbul
- Backbone, Underscore, Lodash
- Browserify, Less, Uglify, JSHint
- jQuery, Handlebars, Moment, Bootstrap, Font Awesome
Highlights
- Usage of JS modules and other ES2016 capabilities
- Code coverage with Istanbul
- Usage of Streams, Promises
- Acceptance tests with 'jasmine-jquery'
- Backend tests with 'supertest'
Transformer test in Sublime
Mocha/Istanbul test run in Terminal
this.handle = function(item) {
if (!(item instanceof QueueItem)) {
throw new TypeError('Item must be instance of QueueItem, but "' + (typeof item) + '" was given')
}
const handleName = 'handle' + item.__proto__.constructor.name;
if (typeof handlers[handleName] === 'function') {
return handlers[handleName](item)
}
else {
return Q.fcall(function() {
throw new Error('No handler found to handle item of type ' + item.__proto__.constructor.name)
})
}
};
Queue handler.
it('should process items with handler', function() {
const tracker = {
fn: function() {}
}
const handler = {
handle: function(item) {
tracker.fn(item)
return Q.fcall(function() {})
}
}
const spy = expect.spyOn(tracker, 'fn')
const queue = new Queue({handler: handler});
const item = new JobQueueItem({
type: 'folder_present',
target: 'dummy'
});
queue.add(item);
expect(spy).toHaveBeenCalledWith(item)
});
Queue handler test.
const BooksListPage = {
components: {
BooksTable: BooksTable,
BooksCards: BooksCards,
BooksFilter: BooksFilter,
BooksDisplay: BooksDisplay
},
data: function() {
return {
display: 'table'
}
},
template: `
<section>
<h1>Books</h1>
<books-filter></books-filter>
<books-display></books-display>
<div class="btn-group" data-toggle="buttons">
<label class="btn btn-default active" @click="display = 'table'">
<input type="radio" name="display"><i class="fa fa-th-list"></i>
</label>
<label class="btn btn-default" @click="display = 'cards'">
<input type="radio" name="display"><i class="fa fa-th-large"></i>
</label>
</div>
<component :is="booksComponent" :books="$store.state.books"></component>
</section>
`,
computed: {
booksComponent: function() {
if (this.display == 'cards') {
return BooksCards
}
return BooksTable
}
}
}
Page component to manage rendering of books (`BooksListPage`). The book listing can be displayed in either table view or card view, using different components for the layout.
Layout
Using Gravit to work on the layout of the application.