Merge js, clean unused

This commit is contained in:
Meutel 2017-08-06 13:12:59 +02:00
parent ecdf3d5ae8
commit e2edf6c944
14 changed files with 418 additions and 629 deletions

View File

@ -11,7 +11,6 @@ Bouquins in Go
* tests
* UI book: cover in background
* auth downloads
* clean unused js
* version string in js url (cache)
* csrf
* vue.js dev/prod

View File

@ -1,17 +0,0 @@
var author = new Vue({
el: '#author',
data: {
tab: "books"
},
methods: {
showBooks: function() {
this.tab = "books";
},
showAuthors: function() {
this.tab = "authors";
},
showSeries: function() {
this.tab = "series";
}
}
})

View File

@ -1,73 +0,0 @@
var app = new Vue({
el: '#app',
data: {
urlParams: {},
book: {}
},
methods: {
urlParse: function() {
var match,
pl = /\+/g, // Regex for replacing addition symbol with a space
search = /([^&=]+)=?([^&]*)/g,
decode = function (s) { return decodeURIComponent(s.replace(pl, " ")); },
query = window.location.search.substring(1);
while (match = search.exec(query))
this.urlParams[decode(match[1])] = decode(match[2]);
},
sendQuery: function(url, error, success) {
var xmh = new XMLHttpRequest();
var v;
xmh.onreadystatechange = function() {
v = xmh.responseText;
if (xmh.readyState === 4 && xmh.status === 200) {
var res;
try {
res = JSON.parse(v);
} catch (err) {
if (null !== error)
error(err.name, err.message);
}
if (null !== success)
success(res);
} else if (xmh.readyState === 4) {
if (null !== error)
error(xmh.status, v);
}
};
xmh.open('GET', url, true);
xmh.send(null);
},
stdError: function(code, resp) {
console.log('ERROR ' + code + ': ' + resp);
},
formatBytes: function(bytes) {
if(bytes == 0) return '0';
var k = 1024; // or 1024 for binary
var sizes = ['Octets', 'Ko', 'Mo', 'Go', 'To', 'Po', 'Eo', 'Zo', 'Yo'];
var i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
},
bookCover: function(book) {
return '/calibre/' + encodeURI(book.path) + '/cover.jpg';
},
bookLink: function(book, data) {
return '/calibre/' + encodeURI(book.path) + '/' + encodeURI(data.name) + '.' + data.format.toLowerCase();
},
bookSuccess: function(resp) {
this.book = resp;
document.title = this.book.title +' | Bouquins';
},
loadBook: function() {
if (this.urlParams.id)
this.sendQuery('cgi-bin/bouquins/books/' + this.urlParams.id, this.stdError, this.bookSuccess);
}
},
created: function() {
this.urlParse();
},
mounted: function() {
this.loadBook();
}
})

408
assets/js/bouquins.js Normal file
View File

@ -0,0 +1,408 @@
var bus = new Vue();
// COMPONENTS //
Vue.component('results-list', {
template: '#results-list-template',
props: ['results', 'count', 'type'],
methods: {
url: function(item) {
return '/'+this.type+'/'+item.id;
},
label: function(item) {
switch (this.type) {
case 'books':
return item.title;
case 'authors':
case 'series':
return item.name;
default:
return '';
}
},
iconClass: function() {
return 'glyphicon glyphicon-' + this.icon();
},
icon: function() {
switch (this.type) {
case 'books':
return 'icon';
case 'authors':
return 'user';
case 'series':
return 'list';
default:
return '';
}
},
countlabel: function() {
switch (this.type) {
case 'books':
return this.count > 1 ? 'livres' : 'livre';
case 'authors':
return this.count > 1 ? 'auteurs' : 'auteur';
case 'series':
return this.count > 1 ? 'series' : 'serie';
default:
return '';
}
}
}
});
Vue.component('results', {
template: '#results-template',
props: ['results', 'cols','sort_by','order_desc'],
methods: {
sortBy: function(col) {
bus.$emit('sort-on', col);
}
}
});
Vue.component('result-cell', {
render: function(h) {
return h('td', this.cellContent(h));
},
props: ['item', 'col'],
methods: {
bookUrl: function(id) {
return '/books/' + id;
},
authorUrl: function(id) {
return '/authors/' + id;
},
seriesUrl: function(id) {
return '/series/' + id;
},
link: function(h, icon, text, url) {
return [
h('span',{ attrs: { class: 'glyphicon glyphicon-'+icon } },''),
' ',
h('a', { attrs: { href: url } }, text)
];
},
badge: function(h, num) {
return h('span', { attrs: { class: 'badge' } }, num);
},
cellContent: function(h) {
switch (this.col.id) {
case 'author_name':
return this.link(h, 'user', this.item.name, this.authorUrl(this.item.id));
case 'serie_name':
return this.link(h, 'list', this.item.name, this.authorUrl(this.item.id));
case 'count':
return this.item.count;
case 'title':
return this.link(h, 'book', this.item.title, this.bookUrl(this.item.id));
case 'authors':
var elts = [];
var authors = this.item.authors;
if (authors) {
for (i=0;i<authors.length;i++) {
elts[i] = this.link(h, 'user', authors[i].name, this.authorUrl(authors[i].id));
}
}
return elts;
case 'series':
var series = this.item.series;
if (series) {
return [
this.link(h, 'list', series.name, this.seriesUrl(series.id)),
h('span', { attrs: { class: 'badge' } }, this.item.series_idx)
];
}
return '';
default:
console.log('ERROR unknown col: ' + this.col.id)
return '';
}
}
}
});
Vue.component('paginate', {
template: '#paginate-template',
props: ['page','more'],
methods: {
prevPage: function() {
if (this.page > 1) bus.$emit('update-page', -1);
},
nextPage: function() {
if (this.more) bus.$emit('update-page', 1);
}
}
});
// PAGES //
if (document.getElementById("index")) {
new Vue({
el: '#index',
data: {
url: '',
page: 0,
perpage: 20,
more: false,
sort_by: null,
order_desc: false,
cols: [],
results: []
},
methods: {
sortBy: function(col) {
if (this.sort_by == col) {
if (this.order_desc) {
this.order_desc = false;
this.sort_by = null;
} else {
this.order_desc = true;
}
} else {
this.order_desc = false;
this.sort_by = col;
}
this.updateResults();
},
updatePage: function(p) {
this.page += p;
this.updateResults();
},
order: function(query) {
if (this.order_desc)
return query + '&order=desc';
return query;
},
sort: function(query) {
if (this.sort_by)
return query + '&sort=' + this.sort_by;
return query;
},
paginate: function(query) {
return query + '?page=' + this.page + '&perpage=' + this.perpage;
},
params: function(url) {
return this.order(this.sort(this.paginate(url)));
},
updateResults: function() {
this.sendQuery(this.params(this.url), this.stdError, this.loadResults);
},
showSeries: function() {
this.url = '/series/';
this.updateResults();
},
showAuthors: function() {
this.url = '/authors/';
this.updateResults();
},
showBooks: function() {
this.url = '/books/';
this.updateResults();
},
loadCols: function(type) {
switch (type) {
case 'books':
this.cols = [
{ id: 'title', name: 'Titre', sort: 'title' },
{ id: 'authors', name: 'Auteur(s)' },
{ id: 'series', name: 'Serie' }
];
break;
case 'series':
this.cols = [
{ id: 'serie_name', name: 'Nom', sort: 'name' },
{ id: 'count', name: 'Livre(s)' },
{ id: 'authors', name: 'Auteur(s)' }
];
break;
case 'authors':
this.cols = [
{ id: 'author_name', name: 'Nom', sort: 'name' },
{ id: 'count', name: 'Livre(s)' }
];
break;
}
},
loadResults(resp) {
this.results = [];
this.more = resp.more;
this.loadCols(resp.type);
if (resp.results) {
this.results = resp.results;
if (this.page == 0) this.page = 1;
} else {
this.page = 0;
}
},
sendQuery: function(url, error, success) {
var xmh = new XMLHttpRequest();
var v;
xmh.onreadystatechange = function() {
v = xmh.responseText;
if (xmh.readyState === 4 && xmh.status === 200) {
var res;
try {
res = JSON.parse(v);
} catch (err) {
if (null !== error)
error(err.name, err.message);
}
if (null !== success)
success(res);
} else if (xmh.readyState === 4) {
if (null !== error)
error(xmh.status, v);
}
};
xmh.open('GET', url, true);
xmh.setRequestHeader('Accept','application/json');
xmh.send(null);
},
stdError: function(code, resp) {
console.log('ERROR ' + code + ': ' + resp);
}
},
mounted: function() {
bus.$on('sort-on', this.sortBy);
bus.$on('update-page', this.updatePage);
}
});
}
if (document.getElementById("author")) {
new Vue({
el: '#author',
data: {
tab: "books"
},
methods: {
showBooks: function() {
this.tab = "books";
},
showAuthors: function() {
this.tab = "authors";
},
showSeries: function() {
this.tab = "series";
}
}
});
}
if (document.getElementById("search")) {
new Vue({
el: '#search',
data: {
authors: [],
books: [],
series: [],
authorsCount: 0,
booksCount: 0,
seriesCount: 0,
q: '',
which: 'all',
all: false,
perpage: 10
},
methods: {
searchParams: function(url) {
var res = url;
res += '?perpage=' + this.perpage;
for (var i=0; i<this.terms.length; i++) {
var t = this.terms[i];
if (t.trim())
res += '&term=' + encodeURIComponent(t.trim());
}
return res;
},
searchAuthorsSuccess: function(res) {
this.authorsCount = res.count;
this.authors = res.results;
},
searchAuthors: function() {
this.sendQuery(this.searchParams('/authors/'), this.stdError, this.searchAuthorsSuccess);
},
searchBooksSuccess: function(res) {
this.booksCount = res.count;
this.books = res.results;
},
searchBooks: function() {
this.sendQuery(this.searchParams('/books/'), this.stdError, this.searchBooksSuccess);
},
searchSeriesSuccess: function(res) {
this.seriesCount = res.count;
this.series = res.results;
},
searchSeries: function() {
this.sendQuery(this.searchParams('/series/'), this.stdError, this.searchSeriesSuccess);
},
searchAll: function() {
this.clear();
this.searchAuthors();
this.searchBooks();
this.searchSeries();
},
clear: function() {
this.authors = [];
this.books = [];
this.series = [];
this.authorsCount = 0;
this.booksCount = 0;
this.seriesCount = 0;
},
searchFull: function() {
if (this.q) {
this.terms = this.q.split(' ');
switch (this.which) {
case 'all':
this.searchAll();
break;
case 'authors':
this.clear();
this.searchAuthors();
break;
case 'books':
this.clear();
this.searchBooks();
break;
case 'series':
this.clear();
this.searchSeries();
break;
}
}
return false;
},
searchUrl: function() {
if (this.urlParams.q) {
this.terms = this.urlParams.q.split(' ');
this.searchAll();
this.q = this.urlParams.q;
}
},
sendQuery: function(url, error, success) {
var xmh = new XMLHttpRequest();
var v;
xmh.onreadystatechange = function() {
v = xmh.responseText;
if (xmh.readyState === 4 && xmh.status === 200) {
var res;
try {
res = JSON.parse(v);
} catch (err) {
if (null !== error)
error(err.name, err.message);
}
if (null !== success)
success(res);
} else if (xmh.readyState === 4) {
if (null !== error)
error(xmh.status, v);
}
};
xmh.open('GET', url, true);
xmh.setRequestHeader('Accept','application/json');
xmh.send(null);
},
stdError: function(code, resp) {
console.log('ERROR ' + code + ': ' + resp);
}
}
});
}

View File

@ -1,212 +0,0 @@
var bus = new Vue();
Vue.component('results', {
template: '#results-template',
props: ['results', 'cols','sort_by','order_desc'],
methods: {
sortBy: function(col) {
bus.$emit('sort-on', col);
}
}
});
Vue.component('result-cell', {
render: function(h) {
return h('td', this.cellContent(h));
},
props: ['item', 'col'],
methods: {
bookUrl: function(id) {
return '/books/' + id;
},
authorUrl: function(id) {
return '/authors/' + id;
},
seriesUrl: function(id) {
return '/series/' + id;
},
link: function(h, icon, text, url) {
return [
h('span',{ attrs: { class: 'glyphicon glyphicon-'+icon } },''),
' ',
h('a', { attrs: { href: url } }, text)
];
},
badge: function(h, num) {
return h('span', { attrs: { class: 'badge' } }, num);
},
cellContent: function(h) {
switch (this.col.id) {
case 'author_name':
return this.link(h, 'user', this.item.name, this.authorUrl(this.item.id));
case 'serie_name':
return this.link(h, 'list', this.item.name, this.authorUrl(this.item.id));
case 'count':
return this.item.count;
case 'title':
return this.link(h, 'book', this.item.title, this.bookUrl(this.item.id));
case 'authors':
var elts = [];
var authors = this.item.authors;
if (authors) {
for (i=0;i<authors.length;i++) {
elts[i] = this.link(h, 'user', authors[i].name, this.authorUrl(authors[i].id));
}
}
return elts;
case 'series':
var series = this.item.series;
if (series) {
return [
this.link(h, 'list', series.name, this.seriesUrl(series.id)),
h('span', { attrs: { class: 'badge' } }, this.item.series_idx)
];
}
return '';
default:
console.log('ERROR unknown col: ' + this.col.id)
return '';
}
}
}
});
Vue.component('paginate', {
template: '#paginate-template',
props: ['page','more'],
methods: {
prevPage: function() {
if (this.page > 1) bus.$emit('update-page', -1);
},
nextPage: function() {
if (this.more) bus.$emit('update-page', 1);
}
}
});
var index = new Vue({
el: '#index',
data: {
url: '',
page: 0,
perpage: 20,
more: false,
sort_by: null,
order_desc: false,
cols: [],
results: []
},
methods: {
sortBy: function(col) {
if (this.sort_by == col) {
if (this.order_desc) {
this.order_desc = false;
this.sort_by = null;
} else {
this.order_desc = true;
}
} else {
this.order_desc = false;
this.sort_by = col;
}
this.updateResults();
},
updatePage: function(p) {
this.page += p;
this.updateResults();
},
order: function(query) {
if (this.order_desc)
return query + '&order=desc';
return query;
},
sort: function(query) {
if (this.sort_by)
return query + '&sort=' + this.sort_by;
return query;
},
paginate: function(query) {
return query + '?page=' + this.page + '&perpage=' + this.perpage;
},
params: function(url) {
return this.order(this.sort(this.paginate(url)));
},
updateResults: function() {
this.sendQuery(this.params(this.url), this.stdError, this.loadResults);
},
showSeries: function() {
this.url = '/series/';
this.updateResults();
},
showAuthors: function() {
this.url = '/authors/';
this.updateResults();
},
showBooks: function() {
this.url = '/books/';
this.updateResults();
},
loadCols: function(type) {
switch (type) {
case 'books':
this.cols = [
{ id: 'title', name: 'Titre', sort: 'title' },
{ id: 'authors', name: 'Auteur(s)' },
{ id: 'series', name: 'Serie' }
];
break;
case 'series':
this.cols = [
{ id: 'serie_name', name: 'Nom', sort: 'name' },
{ id: 'count', name: 'Livre(s)' },
{ id: 'authors', name: 'Auteur(s)' }
];
break;
case 'authors':
this.cols = [
{ id: 'author_name', name: 'Nom', sort: 'name' },
{ id: 'count', name: 'Livre(s)' }
];
break;
}
},
loadResults(resp) {
this.results = [];
this.more = resp.more;
this.loadCols(resp.type);
if (resp.results) {
this.results = resp.results;
if (this.page == 0) this.page = 1;
} else {
this.page = 0;
}
},
sendQuery: function(url, error, success) {
var xmh = new XMLHttpRequest();
var v;
xmh.onreadystatechange = function() {
v = xmh.responseText;
if (xmh.readyState === 4 && xmh.status === 200) {
var res;
try {
res = JSON.parse(v);
} catch (err) {
if (null !== error)
error(err.name, err.message);
}
if (null !== success)
success(res);
} else if (xmh.readyState === 4) {
if (null !== error)
error(xmh.status, v);
}
};
xmh.open('GET', url, true);
xmh.setRequestHeader('Accept','application/json');
xmh.send(null);
},
stdError: function(code, resp) {
console.log('ERROR ' + code + ': ' + resp);
}
},
mounted: function() {
bus.$on('sort-on', this.sortBy);
bus.$on('update-page', this.updatePage);
}
});

View File

@ -1,166 +0,0 @@
Vue.component('results-list', {
template: '#results-list-template',
props: ['results', 'count', 'type'],
methods: {
url: function(item) {
return '/'+this.type+'/'+item.id;
},
label: function(item) {
switch (this.type) {
case 'books':
return item.title;
case 'authors':
case 'series':
return item.name;
default:
return '';
}
},
iconClass: function() {
return 'glyphicon glyphicon-' + this.icon();
},
icon: function() {
switch (this.type) {
case 'books':
return 'icon';
case 'authors':
return 'user';
case 'series':
return 'list';
default:
return '';
}
},
countlabel: function() {
switch (this.type) {
case 'books':
return this.count > 1 ? 'livres' : 'livre';
case 'authors':
return this.count > 1 ? 'auteurs' : 'auteur';
case 'series':
return this.count > 1 ? 'series' : 'serie';
default:
return '';
}
}
}
});
var search = new Vue({
el: '#search',
data: {
authors: [],
books: [],
series: [],
authorsCount: 0,
booksCount: 0,
seriesCount: 0,
q: '',
which: 'all',
all: false,
perpage: 10
},
methods: {
searchParams: function(url) {
var res = url;
res += '?perpage=' + this.perpage;
for (var i=0; i<this.terms.length; i++) {
var t = this.terms[i];
if (t.trim())
res += '&term=' + encodeURIComponent(t.trim());
}
return res;
},
searchAuthorsSuccess: function(res) {
this.authorsCount = res.count;
this.authors = res.results;
},
searchAuthors: function() {
this.sendQuery(this.searchParams('/authors/'), this.stdError, this.searchAuthorsSuccess);
},
searchBooksSuccess: function(res) {
this.booksCount = res.count;
this.books = res.results;
},
searchBooks: function() {
this.sendQuery(this.searchParams('/books/'), this.stdError, this.searchBooksSuccess);
},
searchSeriesSuccess: function(res) {
this.seriesCount = res.count;
this.series = res.results;
},
searchSeries: function() {
this.sendQuery(this.searchParams('/series/'), this.stdError, this.searchSeriesSuccess);
},
searchAll: function() {
this.clear();
this.searchAuthors();
this.searchBooks();
this.searchSeries();
},
clear: function() {
this.authors = [];
this.books = [];
this.series = [];
this.authorsCount = 0;
this.booksCount = 0;
this.seriesCount = 0;
},
searchFull: function() {
if (this.q) {
this.terms = this.q.split(' ');
switch (this.which) {
case 'all':
this.searchAll();
break;
case 'authors':
this.clear();
this.searchAuthors();
break;
case 'books':
this.clear();
this.searchBooks();
break;
case 'series':
this.clear();
this.searchSeries();
break;
}
}
return false;
},
searchUrl: function() {
if (this.urlParams.q) {
this.terms = this.urlParams.q.split(' ');
this.searchAll();
this.q = this.urlParams.q;
}
},
sendQuery: function(url, error, success) {
var xmh = new XMLHttpRequest();
var v;
xmh.onreadystatechange = function() {
v = xmh.responseText;
if (xmh.readyState === 4 && xmh.status === 200) {
var res;
try {
res = JSON.parse(v);
} catch (err) {
if (null !== error)
error(err.name, err.message);
}
if (null !== success)
success(res);
} else if (xmh.readyState === 4) {
if (null !== error)
error(xmh.status, v);
}
};
xmh.open('GET', url, true);
xmh.setRequestHeader('Accept','application/json');
xmh.send(null);
},
stdError: function(code, resp) {
console.log('ERROR ' + code + ': ' + resp);
}
}
});

View File

@ -1,60 +0,0 @@
var app = new Vue({
el: '#app',
data: {
urlParams: {},
series: {}
},
methods: {
urlParse: function() {
var match,
pl = /\+/g, // Regex for replacing addition symbol with a space
search = /([^&=]+)=?([^&]*)/g,
decode = function (s) { return decodeURIComponent(s.replace(pl, " ")); },
query = window.location.search.substring(1);
while (match = search.exec(query))
this.urlParams[decode(match[1])] = decode(match[2]);
},
sendQuery: function(url, error, success) {
var xmh = new XMLHttpRequest();
var v;
xmh.onreadystatechange = function() {
v = xmh.responseText;
if (xmh.readyState === 4 && xmh.status === 200) {
var res;
try {
res = JSON.parse(v);
} catch (err) {
if (null !== error)
error(err.name, err.message);
}
if (null !== success)
success(res);
} else if (xmh.readyState === 4) {
if (null !== error)
error(xmh.status, v);
}
};
xmh.open('GET', url, true);
xmh.send(null);
},
stdError: function(code, resp) {
console.log('ERROR ' + code + ': ' + resp);
},
seriesSucces: function(resp) {
this.series = resp;
document.title = this.series.name +' | Bouquins';
},
loadSeries: function() {
if (this.urlParams.id)
this.sendQuery('cgi-bin/bouquins/series/' + this.urlParams.id, this.stdError, this.seriesSucces);
}
},
created: function() {
this.urlParse();
},
mounted: function() {
this.loadSeries();
}
})

View File

@ -134,19 +134,12 @@ type SeriesFull struct {
}
type BouquinsModel struct {
Title string `json:"title,omitempty"`
PageJs string `json:"-"`
Title string `json:"title,omitempty"`
}
// Constructor BouquinsModel
func NewBouquinsModel(title string) *BouquinsModel {
return NewBouquinsModelJs(title, "")
}
func NewBouquinsModelJs(title, js string) *BouquinsModel {
return &BouquinsModel{
title,
js,
}
return &BouquinsModel{title}
}
type IndexModel struct {
@ -155,11 +148,8 @@ type IndexModel struct {
}
// Constructor IndexModel
func NewIndexModel(title, js string, count int64) *IndexModel {
return &IndexModel{
*NewBouquinsModelJs(title, js),
count,
}
func NewIndexModel(title string, count int64) *IndexModel {
return &IndexModel{*NewBouquinsModel(title), count}
}
type SearchModel struct {
@ -167,7 +157,7 @@ type SearchModel struct {
}
func NewSearchModel() *SearchModel {
return &SearchModel{*NewBouquinsModelJs("Recherche", "search.js")}
return &SearchModel{*NewBouquinsModel("Recherche")}
}
type ResultsModel struct {
@ -297,7 +287,7 @@ func (app *Bouquins) IndexPage(res http.ResponseWriter, req *http.Request) {
if err != nil {
log.Print(err)
}
model := NewIndexModel("", "index.js", count)
model := NewIndexModel("", count)
if isJson(req) {
err := writeJson(res, model)
if err != nil {
@ -369,7 +359,7 @@ func (app *Bouquins) AuthorPage(idParam string, res http.ResponseWriter, req *ht
if err != nil {
return err
}
return app.render(res, TPL_AUTHORS, &AuthorModel{*NewBouquinsModelJs(author.Name, "author.js"), author})
return app.render(res, TPL_AUTHORS, &AuthorModel{*NewBouquinsModel(author.Name), author})
}
func (app *Bouquins) AuthorsPage(res http.ResponseWriter, req *http.Request) {
var err error

View File

@ -1,6 +1,4 @@
<script src="/js/vue.js"></script>
{{ if .PageJs }}
<script src="/js/{{ .PageJs }}"></script>
{{ end }}
<script src="/js/bouquins.js"></script>
</body>
</html>

View File

@ -7,10 +7,8 @@
<link rel="stylesheet" href="/css/bootstrap.min.css">
<link rel="preload" href="/js/vue.js" as="script">
<link rel="prefetch" href="/js/vue.js">
{{ if .PageJs }}
<link rel="preload" href="/js/{{ .PageJs }}" as="script">
<link rel="prefetch" href="/js/{{ .PageJs }}">
{{ end }}
<link rel="preload" href="/js/bouquins.js" as="script">
<link rel="prefetch" href="/js/bouquins.js">
</head>
<body>
<nav class="navbar navbar-inverse" id="nav">

View File

@ -1,18 +0,0 @@
<table class="table table-striped">
<tr>
<th>
<a href="#" @click="sortBy('name')">Nom</a>
<span v-if="sort_by == 'name'" :class="['glyphicon', { 'glyphicon-chevron-up': order_desc , 'glyphicon-chevron-down': !order_desc}]"></span>
</th>
<th>Livre(s)</th>
</tr>
{{ range . }}
<tr>
<td>
<span class="glyphicon glyphicon-user"></span>
<a href="/authors/{{ .Id }}">{{ .Name }}</a>
</td>
<td>{{ .Count }}</td>
</tr>
{{ end }}
</table>

View File

@ -1,27 +0,0 @@
<table id="books" class="table table-striped" v-if="books.length > 0">
<tr>
<th>
<a href="#" @click="sortBy('title')">Nom</a>
<span v-if="sort_by == 'title'" :class="['glyphicon', { 'glyphicon-chevron-up': order_desc , 'glyphicon-chevron-down': !order_desc}]"></span>
</th>
<th>Auteur(s)</th>
<th>Serie</th>
</tr>
<tr v-for="book in books">
<td><span class="glyphicon glyphicon-book"></span>
<a :href="'/books/'+book.id">{{ "{{" }} book.title {{ "}}" }}</a></td>
<td>
<template v-for="author in book.authors">
<span class="glyphicon glyphicon-user"></span>
<a :href="'/authors/'+author.id">{{ "{{" }} author.name {{ "}}" }}</a>
</template>
</td>
<td>
<template v-if="book.series">
<span class="glyphicon glyphicon-list"></span>
<a :href="'/series/'+book.series.id">{{ "{{" }} book.series.name {{ "}}" }}</a>
<span class="badge">{{ "{{" }} book.series ? book.series.idx : '' {{ "}}" }}</span>
</template>
</td>
</tr>
</table>

View File

@ -1,6 +0,0 @@
<nav aria-label="Pages">
<ul class="pager">
<li class="previous" v-bind:class="{ disabled: page <= 1 }"><a href="#" @click="prevPage"><span aria-hidden="true">&larr;</span> Précédents</a></li>
<li class="next"><a href="#" @click="nextPage">Suivants <span aria-hidden="true">&rarr;</span></a></li>
</ul>
</nav>

View File

@ -1,25 +0,0 @@
<table class="table table-striped">
<tr>
<th>
<a href="#" @click="sortBy('name')">Nom</a>
<span v-if="sort_by == 'name'" :class="['glyphicon', { 'glyphicon-chevron-up': order_desc , 'glyphicon-chevron-down': !order_desc}]"></span>
</th>
<th>Livre(s)</th>
<th>Auteur(s)</th>
</tr>
<tr v-for="serie in series">
{{ range . }}
<td>
<span class="glyphicon glyphicon-list"></span>
<a href="/series/{{ .Id }}">{{ .Name }}</a>
</td>
<td>{{ .Count }}</td>
<td>
{{ range .Authors }}
<span class="glyphicon glyphicon-user"></span>
<a href="/authors/{{ .Id }}">{{ .Name }}</a>&nbsp;
{{ end }}
</td>
{{ end }}
</tr>
</table>