mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Grouping for category views
This commit is contained in:
parent
29b91f532e
commit
31cde51c11
@ -148,19 +148,6 @@ h2.library_name {
|
|||||||
padding-top: 40px;
|
padding-top: 40px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* }}} */
|
|
||||||
|
|
||||||
/* Widgets {{{ */
|
|
||||||
|
|
||||||
#content .ui-widget { font-size: medium; font-family: monospace; }
|
|
||||||
|
|
||||||
#content .ui-button, #content .ui-button-text {
|
|
||||||
padding: 0.2em 0.5em;
|
|
||||||
font-family: monospace;
|
|
||||||
line-height: 1
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* }}} */
|
/* }}} */
|
||||||
|
|
||||||
/* Sort select {{{ */
|
/* Sort select {{{ */
|
||||||
@ -179,7 +166,6 @@ h2.library_name {
|
|||||||
|
|
||||||
/* }}} */
|
/* }}} */
|
||||||
|
|
||||||
|
|
||||||
/* Search bar {{{ */
|
/* Search bar {{{ */
|
||||||
|
|
||||||
#search_box {
|
#search_box {
|
||||||
@ -192,7 +178,6 @@ h2.library_name {
|
|||||||
|
|
||||||
/* }}} */
|
/* }}} */
|
||||||
|
|
||||||
|
|
||||||
/* Top level {{{ */
|
/* Top level {{{ */
|
||||||
.toplevel ul {
|
.toplevel ul {
|
||||||
list-style-type: none;
|
list-style-type: none;
|
||||||
@ -220,7 +205,6 @@ h2.library_name {
|
|||||||
|
|
||||||
/* }}} */
|
/* }}} */
|
||||||
|
|
||||||
|
|
||||||
/* Category {{{ */
|
/* Category {{{ */
|
||||||
.category ul {
|
.category ul {
|
||||||
list-style-type: none;
|
list-style-type: none;
|
||||||
@ -247,16 +231,19 @@ h2.library_name {
|
|||||||
.category li.category-item h4 { display: inline }
|
.category li.category-item h4 { display: inline }
|
||||||
.category li.category-item span.href { display: none }
|
.category li.category-item span.href { display: none }
|
||||||
|
|
||||||
/*
|
#groups span.load_href { display: none }
|
||||||
.category a.navlink {
|
|
||||||
text-decoration: none;
|
#groups h3 {
|
||||||
color: inherit;
|
font-weight: bold;
|
||||||
|
margin-top: 1ex;
|
||||||
|
padding-left: 2em;
|
||||||
|
padding-top: 0.5ex;
|
||||||
|
padding-bottom: 0.5ex;
|
||||||
}
|
}
|
||||||
|
|
||||||
.category a.navlink:hover {
|
#groups h3 span {
|
||||||
color: red;
|
font-weight: normal
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
|
|
||||||
/* }}} */
|
/* }}} */
|
||||||
|
|
||||||
|
@ -86,6 +86,10 @@ function toplevel() {
|
|||||||
}
|
}
|
||||||
// }}}
|
// }}}
|
||||||
|
|
||||||
|
function render_error(msg) {
|
||||||
|
return '<div class="ui-widget"><div class="ui-state-error ui-corner-all" style="padding: 0pt 0.7em"><p><span class="ui-icon ui-icon-alert" style="float: left; margin-right: 0.3em"> </span><strong>Error: </strong>'+msg+"</p></div></div>"
|
||||||
|
}
|
||||||
|
|
||||||
// Category feed {{{
|
// Category feed {{{
|
||||||
function category() {
|
function category() {
|
||||||
$(".category li").corner("15px");
|
$(".category li").corner("15px");
|
||||||
@ -96,6 +100,39 @@ function category() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
$(".category a.navlink").button();
|
$(".category a.navlink").button();
|
||||||
|
|
||||||
|
$("#groups").accordion({
|
||||||
|
collapsible: true,
|
||||||
|
active: false,
|
||||||
|
autoHeight: false,
|
||||||
|
clearStyle: true,
|
||||||
|
header: "h3",
|
||||||
|
|
||||||
|
change: function(event, ui) {
|
||||||
|
if (ui.newContent) {
|
||||||
|
var href = ui.newContent.children("span.load_href").html();
|
||||||
|
ui.newContent.children(".loading").show();
|
||||||
|
if (href) {
|
||||||
|
$.ajax({
|
||||||
|
url:href,
|
||||||
|
success: function(data) {
|
||||||
|
this.children(".loaded").html(data);
|
||||||
|
this.children(".loaded").show();
|
||||||
|
this.children(".loading").hide();
|
||||||
|
},
|
||||||
|
context: ui.newContent,
|
||||||
|
dataType: "json",
|
||||||
|
timeout: 600000, //milliseconds (10 minutes)
|
||||||
|
error: function(xhr, stat, err) {
|
||||||
|
this.children(".loaded").html(render_error(stat));
|
||||||
|
this.children(".loaded").show();
|
||||||
|
this.children(".loading").hide();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
// }}}
|
// }}}
|
||||||
|
|
||||||
|
@ -5,7 +5,7 @@ __license__ = 'GPL v3'
|
|||||||
__copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>'
|
__copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>'
|
||||||
__docformat__ = 'restructuredtext en'
|
__docformat__ = 'restructuredtext en'
|
||||||
|
|
||||||
import operator, os
|
import operator, os, json
|
||||||
from urllib import quote
|
from urllib import quote
|
||||||
from binascii import hexlify
|
from binascii import hexlify
|
||||||
|
|
||||||
@ -13,6 +13,7 @@ import cherrypy
|
|||||||
|
|
||||||
from calibre.constants import filesystem_encoding
|
from calibre.constants import filesystem_encoding
|
||||||
from calibre import isbytestring, force_unicode, prepare_string_for_xml as xml
|
from calibre import isbytestring, force_unicode, prepare_string_for_xml as xml
|
||||||
|
from calibre.utils.ordered_dict import OrderedDict
|
||||||
|
|
||||||
def paginate(offsets, content, base_url, up_url=None): # {{{
|
def paginate(offsets, content, base_url, up_url=None): # {{{
|
||||||
'Create markup for pagination'
|
'Create markup for pagination'
|
||||||
@ -102,7 +103,7 @@ def render_rating(rating, container='span'): # {{{
|
|||||||
|
|
||||||
# }}}
|
# }}}
|
||||||
|
|
||||||
def get_category_items(category, items, db): # {{{
|
def get_category_items(category, items, db, datatype): # {{{
|
||||||
|
|
||||||
def item(i):
|
def item(i):
|
||||||
templ = (u'<li title="{4}" class="category-item">'
|
templ = (u'<li title="{4}" class="category-item">'
|
||||||
@ -110,6 +111,8 @@ def get_category_items(category, items, db): # {{{
|
|||||||
'<span class="href">{3}</span></li>')
|
'<span class="href">{3}</span></li>')
|
||||||
rating, rstring = render_rating(i.avg_rating)
|
rating, rstring = render_rating(i.avg_rating)
|
||||||
name = xml(i.name)
|
name = xml(i.name)
|
||||||
|
if datatype == 'rating':
|
||||||
|
name = xml(_('%d stars')%int(i.avg_rating))
|
||||||
id_ = i.id
|
id_ = i.id
|
||||||
if id_ is None:
|
if id_ is None:
|
||||||
id_ = hexlify(force_unicode(name).encode('utf-8'))
|
id_ = hexlify(force_unicode(name).encode('utf-8'))
|
||||||
@ -164,6 +167,9 @@ class BrowseServer(object):
|
|||||||
connect('browse', base_href, self.browse_catalog)
|
connect('browse', base_href, self.browse_catalog)
|
||||||
connect('browse_catalog', base_href+'/category/{category}',
|
connect('browse_catalog', base_href+'/category/{category}',
|
||||||
self.browse_catalog)
|
self.browse_catalog)
|
||||||
|
connect('browse_category_group',
|
||||||
|
base_href+'/category_group/{category}/{group}',
|
||||||
|
self.browse_category_group)
|
||||||
connect('browse_list', base_href+'/list/{query}', self.browse_list)
|
connect('browse_list', base_href+'/list/{query}', self.browse_list)
|
||||||
connect('browse_search', base_href+'/search/{query}',
|
connect('browse_search', base_href+'/search/{query}',
|
||||||
self.browse_search)
|
self.browse_search)
|
||||||
@ -243,32 +249,66 @@ class BrowseServer(object):
|
|||||||
return self.browse_template('name').format(title='',
|
return self.browse_template('name').format(title='',
|
||||||
script='toplevel();', main=main)
|
script='toplevel();', main=main)
|
||||||
|
|
||||||
def browse_category(self, category, sort):
|
def browse_sort_categories(self, items, sort):
|
||||||
if sort not in ('rating', 'name', 'popularity'):
|
if sort not in ('rating', 'name', 'popularity'):
|
||||||
sort = 'name'
|
sort = 'name'
|
||||||
|
def sorter(x):
|
||||||
|
ans = getattr(x, 'sort', x.name)
|
||||||
|
if hasattr(ans, 'upper'):
|
||||||
|
ans = ans.upper()
|
||||||
|
return ans
|
||||||
|
items.sort(key=sorter)
|
||||||
|
if sort == 'popularity':
|
||||||
|
items.sort(key=operator.attrgetter('count'), reverse=True)
|
||||||
|
elif sort == 'rating':
|
||||||
|
items.sort(key=operator.attrgetter('avg_rating'), reverse=True)
|
||||||
|
return sort
|
||||||
|
|
||||||
|
def browse_category(self, category, sort):
|
||||||
categories = self.categories_cache()
|
categories = self.categories_cache()
|
||||||
category_meta = self.db.field_metadata
|
category_meta = self.db.field_metadata
|
||||||
category_name = category_meta[category]['name']
|
category_name = category_meta[category]['name']
|
||||||
|
datatype = category_meta[category]['datatype']
|
||||||
|
|
||||||
if category not in categories:
|
if category not in categories:
|
||||||
raise cherrypy.HTTPError(404, 'category not found')
|
raise cherrypy.HTTPError(404, 'category not found')
|
||||||
|
|
||||||
items = categories[category]
|
items = categories[category]
|
||||||
|
sort = self.browse_sort_categories(items, sort)
|
||||||
|
|
||||||
name_keyg = lambda x: getattr(x, 'sort', x.name).lower()
|
script = 'true'
|
||||||
items.sort(key=name_keyg)
|
|
||||||
if sort == 'popularity':
|
|
||||||
items.sort(key=operator.attrgetter('count'), reverse=True)
|
|
||||||
elif sort == 'rating':
|
|
||||||
items.sort(key=operator.attrgetter('avg_rating'), reverse=True)
|
|
||||||
|
|
||||||
base_url='/browse/category/'+category
|
if len(items) <= self.opts.max_opds_ungrouped_items:
|
||||||
if sort is not None:
|
script = 'false'
|
||||||
base_url += '?sort='+quote(sort)
|
items = get_category_items(category, items, self.db, datatype)
|
||||||
|
else:
|
||||||
|
getter = lambda x: unicode(getattr(x, 'sort', x.name))
|
||||||
|
starts = set([])
|
||||||
|
for x in items:
|
||||||
|
val = getter(x)
|
||||||
|
if not val:
|
||||||
|
val = u'A'
|
||||||
|
starts.add(val[0].upper())
|
||||||
|
category_groups = OrderedDict()
|
||||||
|
for x in sorted(starts):
|
||||||
|
category_groups[x] = len([y for y in items if
|
||||||
|
getter(y).upper().startswith(x)])
|
||||||
|
items = [(u'<h3 title="{0}">{0} <span>[{2}]</span></h3><div>'
|
||||||
|
u'<div class="loaded" style="display:none"></div>'
|
||||||
|
u'<div class="loading"><em>{1}</em></div>'
|
||||||
|
u'<span class="load_href">{3}</span></div>').format(
|
||||||
|
xml(s, True),
|
||||||
|
xml(_('Loading, please wait'))+'…',
|
||||||
|
unicode(c),
|
||||||
|
xml(u'/browse/category_group/%s/%s'%(category, s)))
|
||||||
|
for s, c in category_groups.items()]
|
||||||
|
items = '\n\n'.join(items)
|
||||||
|
items = u'<div id="groups">\n{0}</div>'.format(items)
|
||||||
|
|
||||||
script = 'category();'
|
|
||||||
|
|
||||||
items = get_category_items(category, items, self.db)
|
|
||||||
|
script = 'category(%s);'%script
|
||||||
|
|
||||||
main = u'''
|
main = u'''
|
||||||
<div class="category">
|
<div class="category">
|
||||||
<h3>{0}</h3>
|
<h3>{0}</h3>
|
||||||
@ -283,6 +323,35 @@ class BrowseServer(object):
|
|||||||
return self.browse_template(sort).format(title=category_name,
|
return self.browse_template(sort).format(title=category_name,
|
||||||
script=script, main=main)
|
script=script, main=main)
|
||||||
|
|
||||||
|
@Endpoint(mimetype='application/json; charset=utf-8')
|
||||||
|
def browse_category_group(self, category=None, group=None,
|
||||||
|
category_sort=None):
|
||||||
|
sort = category_sort
|
||||||
|
if sort not in ('rating', 'name', 'popularity'):
|
||||||
|
sort = 'name'
|
||||||
|
categories = self.categories_cache()
|
||||||
|
category_meta = self.db.field_metadata
|
||||||
|
datatype = category_meta[category]['datatype']
|
||||||
|
|
||||||
|
if category not in categories:
|
||||||
|
raise cherrypy.HTTPError(404, 'category not found')
|
||||||
|
if not group:
|
||||||
|
raise cherrypy.HTTPError(404, 'invalid group')
|
||||||
|
|
||||||
|
items = categories[category]
|
||||||
|
entries = []
|
||||||
|
getter = lambda x: unicode(getattr(x, 'sort', x.name))
|
||||||
|
for x in items:
|
||||||
|
val = getter(x)
|
||||||
|
if not val:
|
||||||
|
val = u'A'
|
||||||
|
if val.upper().startswith(group):
|
||||||
|
entries.append(x)
|
||||||
|
|
||||||
|
sort = self.browse_sort_categories(entries, sort)
|
||||||
|
entries = get_category_items(category, entries, self.db, datatype)
|
||||||
|
return json.dumps(entries, ensure_ascii=False)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@Endpoint()
|
@Endpoint()
|
||||||
|
Loading…
x
Reference in New Issue
Block a user