mirror of
				https://github.com/janeczku/calibre-web
				synced 2025-10-30 23:03:02 +00:00 
			
		
		
		
	fix linter errors
Fix the sorting Save the sorting state Remove unnecessary filter Add support for grid view
This commit is contained in:
		
							
								
								
									
										17
									
								
								cps/static/css/caliBlur_override.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								cps/static/css/caliBlur_override.css
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,17 @@ | ||||
| body.serieslist.grid-view div.container-fluid>div>div.col-sm-10:before{ | ||||
|     display: none; | ||||
| } | ||||
|  | ||||
| .cover .badge{ | ||||
|     position: absolute; | ||||
|     top: 0; | ||||
|     right: 0; | ||||
|     background-color: #cc7b19; | ||||
|     border-radius: 0; | ||||
|     padding: 0 8px; | ||||
|     box-shadow: 0 0 4px rgba(0,0,0,.6); | ||||
|     line-height: 24px; | ||||
| } | ||||
| .cover{ | ||||
|     box-shadow: 0 0 4px rgba(0,0,0,.6); | ||||
| } | ||||
| @@ -64,6 +64,12 @@ span.glyphicon.glyphicon-tags {padding-right: 5px;color: #999;vertical-align: te | ||||
| .navbar-default .navbar-toggle .icon-bar {background-color: #000;} | ||||
| .navbar-default .navbar-toggle {border-color: #000;} | ||||
| .cover { margin-bottom: 10px;} | ||||
| .cover .badge{ | ||||
|    position: absolute; | ||||
|    top: 10px; | ||||
|    right: 10px; | ||||
|    background-color: #45b29d; | ||||
| } | ||||
| .cover-height { max-height: 100px;} | ||||
| .col-sm-2 a .cover-small { | ||||
|     margin:5px; | ||||
|   | ||||
							
								
								
									
										62
									
								
								cps/static/js/filter_grid.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										62
									
								
								cps/static/js/filter_grid.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,62 @@ | ||||
| /* This file is part of the Calibre-Web (https://github.com/janeczku/calibre-web) | ||||
|  *    Copyright (C) 2018 OzzieIsaacs | ||||
|  * | ||||
|  *  This program is free software: you can redistribute it and/or modify | ||||
|  *  it under the terms of the GNU General Public License as published by | ||||
|  *  the Free Software Foundation, either version 3 of the License, or | ||||
|  *  (at your option) any later version. | ||||
|  * | ||||
|  *  This program is distributed in the hope that it will be useful, | ||||
|  *  but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
|  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
|  *  GNU General Public License for more details. | ||||
|  * | ||||
|  *  You should have received a copy of the GNU General Public License | ||||
|  *  along with this program. If not, see <http://www.gnu.org/licenses/>. | ||||
|  */ | ||||
|  | ||||
| var $list = $("#list").isotope({ | ||||
|     itemSelector: ".book", | ||||
|     layoutMode: "fitRows", | ||||
|     getSortData: { | ||||
|         title: ".title", | ||||
|     } | ||||
| }); | ||||
|  | ||||
| $("#desc").click(function() { | ||||
|     $list.isotope({ | ||||
|         sortBy: "name", | ||||
|         sortAscending: false | ||||
|     }); | ||||
|     return; | ||||
| }); | ||||
|  | ||||
| $("#asc").click(function() { | ||||
|     $list.isotope({ | ||||
|         sortBy: "name", | ||||
|         sortAscending: true | ||||
|     }); | ||||
|     return; | ||||
| }); | ||||
|  | ||||
| $("#all").click(function() { | ||||
|     // go through all elements and make them visible | ||||
|     $(".sortable").each(function() { | ||||
|         $(this).show(); | ||||
|     }); | ||||
|     // We need to trigger the resize event to have all the grid item to re-align. | ||||
|     window.dispatchEvent(new Event('resize')); | ||||
| }); | ||||
|  | ||||
| $(".char").click(function() { | ||||
|     var character = this.innerText; | ||||
|     $(".sortable").each(function() { | ||||
|         if (this.attributes["data-id"].value.charAt(0).toUpperCase() !== character) { | ||||
|             $(this).hide(); | ||||
|         } else { | ||||
|             $(this).show(); | ||||
|         } | ||||
|     }); | ||||
|     // We need to trigger the resize event to have all the grid item to re-align. | ||||
|     window.dispatchEvent(new Event("resize")); | ||||
| }); | ||||
| @@ -325,4 +325,17 @@ $(function() { | ||||
|         $(".discover .row").isotope("layout"); | ||||
|     }); | ||||
|  | ||||
|     $(".update-view").click(function(e) { | ||||
|         var target = $(this).data("target"); | ||||
|         var view = $(this).data("view"); | ||||
|  | ||||
|         e.preventDefault(); | ||||
|         e.stopPropagation(); | ||||
|         var data = {}; | ||||
|         data[target] = view; | ||||
|         console.debug("Updating view data: ", data); | ||||
|         $.post( "/ajax/view", data).done(function( ) { | ||||
|             location.reload(); | ||||
|         }); | ||||
|     }); | ||||
| }); | ||||
|   | ||||
							
								
								
									
										54
									
								
								cps/templates/grid.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								cps/templates/grid.html
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,54 @@ | ||||
| {% extends "layout.html" %} | ||||
| {% block body %} | ||||
| <div class="discover load-more grid-view"> | ||||
|   <h2 class="{{title}}">{{_(title)}}</h2> | ||||
|  | ||||
|     <div class="filterheader hidden-xs hidden-sm"> | ||||
|       <button id="asc" class="btn btn-success"><span class="glyphicon glyphicon-sort-by-alphabet"></span></button> | ||||
|       <button id="desc" class="btn btn-success"><span class="glyphicon glyphicon-sort-by-alphabet-alt"></span></button> | ||||
|       {% if charlist|length %} | ||||
|       <button id="all" class="btn btn-success">{{_('All')}}</button> | ||||
|       {% endif %} | ||||
|       <div class="btn-group character" role="group"> | ||||
|         {% for char in charlist%} | ||||
|         <button class="btn btn-success char">{{char.char}}</button> | ||||
|         {% endfor %} | ||||
|       </div> | ||||
|  | ||||
|         <!-- View selection --> | ||||
|         <div class="btn-group"> | ||||
|           <button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"> | ||||
|             View <span class="caret"></span> | ||||
|           </button> | ||||
|           <ul class="dropdown-menu"> | ||||
|             <li><a class="update-view" href="#" data-target="series_view" data-view="grid">{{_('Grid')}}</a></li> | ||||
|             <li><a class="update-view" href="#"  data-target="series_view" data-view="list">{{_('List')}}</a></li> | ||||
|           </ul> | ||||
|         </div> | ||||
|     </div> | ||||
|  | ||||
|     {% if entries[0] %} | ||||
|         <div id="list" class="row"> | ||||
|           {% for entry in entries %} | ||||
|               <div class="col-sm-3 col-lg-2 col-xs-6 book sortable" {% if entry[0].sort %}data-name="{{entry[0].series[0].name}}"{% endif %} data-id="{% if entry[0].series[0].name %}{{entry[0].series[0].name}}{% endif %}"> | ||||
|                   <div class="cover"> | ||||
|                       <a href="{{url_for('web.books_list', data=data, sort='new', book_id=entry[0].series[0].id )}}"> | ||||
|                           <img src="{{ url_for('web.get_cover', book_id=entry[0].id) }}" alt="{{ entry[0].name }}"/> | ||||
|                           <span class="badge">{{entry.count}}</span> | ||||
|                       </a> | ||||
|                   </div> | ||||
|                   <div class="meta"> | ||||
|                       <a href="{{url_for('web.books_list', data=data, sort='new', book_id=entry[0].series[0].id )}}"> | ||||
|                           <p class="title">{{entry[0].series[0].name|shortentitle}}</p> | ||||
|                       </a> | ||||
|                   </div> | ||||
|               </div> | ||||
|         {% endfor %} | ||||
|         </div> | ||||
|     {% endif %} | ||||
|  | ||||
|  | ||||
| {% endblock %} | ||||
| {% block js %} | ||||
| <script src="{{ url_for('static', filename='js/filter_grid.js') }}"></script> | ||||
| {% endblock %} | ||||
| @@ -17,6 +17,7 @@ | ||||
|     <link href="{{ url_for('static', filename='css/upload.css') }}" rel="stylesheet" media="screen"> | ||||
|     {% if g.current_theme == 1 %} | ||||
|        <link href="{{ url_for('static', filename='css/caliBlur.min.css') }}" rel="stylesheet" media="screen"> | ||||
|        <link href="{{ url_for('static', filename='css/caliBlur_override.css') }}" rel="stylesheet" media="screen"> | ||||
|     {% endif %} | ||||
|     <!-- HTML5 Shim and Respond.js IE8 support of HTML5 elements and media queries --> | ||||
|     <!-- WARNING: Respond.js doesn't work if you view the page via file:// --> | ||||
| @@ -25,7 +26,7 @@ | ||||
|       <script src="https://oss.maxcdn.com/libs/respond.js/1.3.0/respond.min.js"></script> | ||||
|     <![endif]--> | ||||
|   </head> | ||||
|   <body class="{{ page }}" data-text="{{_('Home')}}" data-textback="{{_('Back')}}"> | ||||
|   <body class="{{ page }} {{ bodyClass }}" data-text="{{_('Home')}}" data-textback="{{_('Back')}}"> | ||||
|     <!-- Static navbar --> | ||||
|     <div class="navbar navbar-default navbar-static-top" role="navigation"> | ||||
|       <div class="container-fluid"> | ||||
|   | ||||
| @@ -18,6 +18,17 @@ | ||||
|         <button class="btn btn-primary char">{{char.char}}</button> | ||||
|         {% endfor %} | ||||
|       </div> | ||||
|  | ||||
|         <!-- View selection --> | ||||
|         <div class="btn-group"> | ||||
|           <button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"> | ||||
|             View <span class="caret"></span> | ||||
|           </button> | ||||
|           <ul class="dropdown-menu"> | ||||
|             <li><a class="update-view" href="#" data-target="series_view" data-view="grid">{{_('Grid')}}</a></li> | ||||
|             <li><a class="update-view" href="#"  data-target="series_view" data-view="list">{{_('List')}}</a></li> | ||||
|           </ul> | ||||
|         </div> | ||||
|     </div> | ||||
|   <div class="container"> | ||||
|     <div id="list" class="col-xs-12 col-sm-6"> | ||||
|   | ||||
							
								
								
									
										15
									
								
								cps/ub.py
									
									
									
									
									
								
							
							
						
						
									
										15
									
								
								cps/ub.py
									
									
									
									
									
								
							| @@ -204,6 +204,7 @@ class User(UserBase, Base): | ||||
|     denied_column_value = Column(String, default="") | ||||
|     allowed_column_value = Column(String, default="") | ||||
|     remote_auth_token = relationship('RemoteAuthToken', backref='user', lazy='dynamic') | ||||
|     series_view = Column(String(10), default="list") | ||||
|  | ||||
|  | ||||
| if oauth_support: | ||||
| @@ -243,6 +244,7 @@ class Anonymous(AnonymousUserMixin, UserBase): | ||||
|         self.allowed_tags = data.allowed_tags | ||||
|         self.denied_column_value = data.denied_column_value | ||||
|         self.allowed_column_value = data.allowed_column_value | ||||
|         self.series_view = data.series_view | ||||
|  | ||||
|     def role_admin(self): | ||||
|         return False | ||||
| @@ -349,7 +351,7 @@ class RemoteAuthToken(Base): | ||||
|  | ||||
|  | ||||
| # Migrate database to current version, has to be updated after every database change. Currently migration from | ||||
| # everywhere to curent should work. Migration is done by checking if relevant coloums are existing, and than adding | ||||
| # everywhere to current should work. Migration is done by checking if relevant columns are existing, and than adding | ||||
| # rows with SQL commands | ||||
| def migrate_Database(session): | ||||
|     engine = session.bind | ||||
| @@ -428,6 +430,12 @@ def migrate_Database(session): | ||||
|         conn.execute("ALTER TABLE user ADD column `allowed_tags` String DEFAULT ''") | ||||
|         conn.execute("ALTER TABLE user ADD column `denied_column_value` DEFAULT ''") | ||||
|         conn.execute("ALTER TABLE user ADD column `allowed_column_value` DEFAULT ''") | ||||
|     try: | ||||
|         session.query(exists().where(User.series_view)).scalar() | ||||
|     except exc.OperationalError: | ||||
|         conn = engine.connect() | ||||
|         conn.execute("ALTER TABLE user ADD column `series_view` VARCHAR(10) DEFAULT 'list'") | ||||
|  | ||||
|     if session.query(User).filter(User.role.op('&')(constants.ROLE_ANONYMOUS) == constants.ROLE_ANONYMOUS).first() is None: | ||||
|         create_anonymous_user(session) | ||||
|     try: | ||||
| @@ -446,10 +454,11 @@ def migrate_Database(session): | ||||
|                             "locale VARCHAR(2)," | ||||
|                             "sidebar_view INTEGER," | ||||
|                             "default_language VARCHAR(3)," | ||||
|                             "series_view VARCHAR(10)," | ||||
|                             "UNIQUE (nickname)," | ||||
|                             "UNIQUE (email))") | ||||
|         conn.execute("INSERT INTO user_id(id, nickname, email, role, password, kindle_mail,locale," | ||||
|                         "sidebar_view, default_language) " | ||||
|                         "sidebar_view, default_language, series_view) " | ||||
|                      "SELECT id, nickname, email, role, password, kindle_mail, locale," | ||||
|                         "sidebar_view, default_language FROM user") | ||||
|         # delete old user table and rename new user_id table to user: | ||||
| @@ -486,7 +495,7 @@ def delete_download(book_id): | ||||
|     session.query(Downloads).filter(book_id == Downloads.book_id).delete() | ||||
|     session.commit() | ||||
|  | ||||
| # Generate user Guest (translated text), as anoymous user, no rights | ||||
| # Generate user Guest (translated text), as anonymous user, no rights | ||||
| def create_anonymous_user(session): | ||||
|     user = User() | ||||
|     user.nickname = "Guest" | ||||
|   | ||||
							
								
								
									
										48
									
								
								cps/web.py
									
									
									
									
									
								
							
							
						
						
									
										48
									
								
								cps/web.py
									
									
									
									
									
								
							| @@ -37,7 +37,7 @@ from flask import Blueprint | ||||
| from flask import render_template, request, redirect, send_from_directory, make_response, g, flash, abort, url_for | ||||
| from flask_babel import gettext as _ | ||||
| from flask_login import login_user, logout_user, login_required, current_user | ||||
| from sqlalchemy.exc import IntegrityError | ||||
| from sqlalchemy.exc import IntegrityError, InvalidRequestError | ||||
| from sqlalchemy.sql.expression import text, func, true, false, not_, and_, exists, or_ | ||||
| from werkzeug.exceptions import default_exceptions | ||||
| from werkzeug.datastructures import Headers | ||||
| @@ -416,6 +416,25 @@ def toggle_read(book_id): | ||||
|     return "" | ||||
|  | ||||
|  | ||||
| @web.route("/ajax/view", methods=["POST"]) | ||||
| @login_required | ||||
| def update_view(): | ||||
|     to_save = request.form.to_dict() | ||||
|     allowed_view = ['grid', 'list'] | ||||
|     if "series_view" in to_save and to_save["series_view"] in allowed_view: | ||||
|         current_user.series_view = to_save["series_view"] | ||||
|     else: | ||||
|         log.error("Invalid request received: %r %r", request, to_save) | ||||
|         return "Invalid request", 400 | ||||
|  | ||||
|     try: | ||||
|         ub.session.commit() | ||||
|     except InvalidRequestError: | ||||
|         log.error("Invalid request received: %r ", request, ) | ||||
|         return "Invalid request", 400 | ||||
|     return "", 200 | ||||
|  | ||||
|  | ||||
| ''' | ||||
| @web.route("/ajax/getcomic/<int:book_id>/<book_format>/<int:page>") | ||||
| @login_required | ||||
| @@ -775,14 +794,25 @@ def publisher_list(): | ||||
| @login_required_if_no_ano | ||||
| def series_list(): | ||||
|     if current_user.check_visibility(constants.SIDEBAR_SERIES): | ||||
|         entries = db.session.query(db.Series, func.count('books_series_link.book').label('count')) \ | ||||
|             .join(db.books_series_link).join(db.Books).filter(common_filters()) \ | ||||
|             .group_by(text('books_series_link.series')).order_by(db.Series.sort).all() | ||||
|         charlist = db.session.query(func.upper(func.substr(db.Series.sort, 1, 1)).label('char')) \ | ||||
|             .join(db.books_series_link).join(db.Books).filter(common_filters()) \ | ||||
|             .group_by(func.upper(func.substr(db.Series.sort, 1, 1))).all() | ||||
|         return render_title_template('list.html', entries=entries, folder='web.books_list', charlist=charlist, | ||||
|                                      title=_(u"Series"), page="serieslist", data="series") | ||||
|         if current_user.series_view == 'list': | ||||
|             entries = db.session.query(db.Series, func.count('books_series_link.book').label('count')) \ | ||||
|                 .join(db.books_series_link).join(db.Books).filter(common_filters()) \ | ||||
|                 .group_by(text('books_series_link.series')).order_by(db.Series.sort).all() | ||||
|             charlist = db.session.query(func.upper(func.substr(db.Series.sort, 1, 1)).label('char')) \ | ||||
|                 .join(db.books_series_link).join(db.Books).filter(common_filters()) \ | ||||
|                 .group_by(func.upper(func.substr(db.Series.sort, 1, 1))).all() | ||||
|             return render_title_template('list.html', entries=entries, folder='web.books_list', charlist=charlist, | ||||
|                                          title=_(u"Series"), page="serieslist", data="series") | ||||
|         else: | ||||
|             entries = db.session.query(db.Books, func.count('books_series_link').label('count')) \ | ||||
|                 .join(db.books_series_link).join(db.Series).filter(common_filters()) \ | ||||
|                 .group_by(text('books_series_link.series')).order_by(db.Series.sort).all() | ||||
|             charlist = db.session.query(func.upper(func.substr(db.Series.sort, 1, 1)).label('char')) \ | ||||
|                 .join(db.books_series_link).join(db.Books).filter(common_filters()) \ | ||||
|                 .group_by(func.upper(func.substr(db.Series.sort, 1, 1))).all() | ||||
|  | ||||
|             return render_title_template('grid.html', entries=entries, folder='web.books_list', charlist=charlist, | ||||
|                                          title=_(u"Series list"), page="serieslist", data="series", bodyClass="grid-view") | ||||
|     else: | ||||
|         abort(404) | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 pthiben
					pthiben