Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add bulk delete, bulk archive/unarchive, and bulk metadata edit buttons in books table page #3113

Open
wants to merge 20 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
d0d9985
Add bulk delete button
jmarmstrong1207 Aug 2, 2024
d9a2a7a
Add bulk archive/unarchive buttons
jmarmstrong1207 Aug 2, 2024
ca9fc74
typo fix
jmarmstrong1207 Aug 2, 2024
34fec0e
Move buttons into table
jmarmstrong1207 Aug 2, 2024
4ed0633
Add bulk read/unread buttons; Fix buttons not working when moved into…
jmarmstrong1207 Aug 2, 2024
ecda717
Add bulk metadata edit button
jmarmstrong1207 Aug 2, 2024
ab3d4e4
Fix emptying metadata form after submit
jmarmstrong1207 Aug 3, 2024
9def910
switch title_sort to sort in api edit request
jmarmstrong1207 Aug 3, 2024
96fb2c1
Auto disable author/title sort input in metadata edit form
jmarmstrong1207 Aug 3, 2024
a335dd7
Make edit metadata pass all data in a single REST call. Modularize ed…
jmarmstrong1207 Aug 3, 2024
bee6a35
remove spacing
jmarmstrong1207 Aug 3, 2024
e31763d
Fix kobo sync status marking as archived even though state = false
jmarmstrong1207 Aug 3, 2024
2ae80d3
Fix book_read_status marking as read even though read_status is passe…
jmarmstrong1207 Aug 3, 2024
2afce66
Fix change_archived so state=none is a toggle. Fixes /togglearchived …
jmarmstrong1207 Aug 3, 2024
de3f883
Add change_archived_books() description
jmarmstrong1207 Aug 3, 2024
fe78222
Add shift-click to select multiple books at once
jmarmstrong1207 Aug 5, 2024
7e5d897
Merge branch 'master' into bulk-delete
jmarmstrong1207 Sep 11, 2024
31380f2
fix typo
jmarmstrong1207 Sep 17, 2024
338441f
fix author sort not updating when bulk editing
jmarmstrong1207 Sep 17, 2024
54d9d33
Revert "Add shift-click to select multiple books at once"
jmarmstrong1207 Sep 23, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Add bulk read/unread buttons; Fix buttons not working when moved into…
… table
  • Loading branch information
jmarmstrong1207 committed Aug 2, 2024
commit 4ed0633edfd4a5cbbc355756e3e1da8084a7dd68
21 changes: 21 additions & 0 deletions cps/editbooks.py
Original file line number Diff line number Diff line change
Expand Up @@ -525,6 +525,27 @@ def delete_selected_books():
return json.dumps({'success': True})
return ""

@editbook.route("/ajax/readselectedbooks", methods=['POST'])
@user_login_required
@edit_required
def read_selected_books():
vals = request.get_json().get('selections')
markAsRead = request.get_json().get('markAsRead')
if vals:
try:
for book_id in vals:
ret = helper.edit_book_read_status(book_id, markAsRead)

except (OperationalError, IntegrityError, StaleDataError) as e:
calibre_db.session.rollback()
log.error_or_exception("Database error: {}".format(e))
ret = Response(json.dumps({'success': False,
'msg': 'Database error: {}'.format(e.orig if hasattr(e, "orig") else e)}),
mimetype='application/json')

return json.dumps({'success': True})
return ""

@editbook.route("/ajax/mergebooks", methods=['POST'])
@user_login_required
@edit_required
Expand Down
96 changes: 90 additions & 6 deletions cps/static/js/table.js
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,12 @@ $(function() {

$("#unarchive_selected_books").removeClass("disabled");
$("#unarchive_selected_books").attr("aria-disabled", false);

$("#read_selected_books").removeClass("disabled");
$("#read_selected_books").attr("aria-disabled", false);

$("#unread_selected_books").removeClass("disabled");
$("#unread_selected_books").attr("aria-disabled", false);
} else {
$("#delete_selected_books").addClass("disabled");
$("#delete_selected_books").attr("aria-disabled", true);
Expand All @@ -99,6 +105,12 @@ $(function() {

$("#unarchive_selected_books").addClass("disabled");
$("#unarchive_selected_books").attr("aria-disabled", true);

$("#read_selected_books").addClass("disabled");
$("#read_selected_books").attr("aria-disabled", true);

$("#unread_selected_books").addClass("disabled");
$("#unread_selected_books").attr("aria-disabled", true);
}
if (selections.length < 1) {
$("#delete_selection").addClass("disabled");
Expand Down Expand Up @@ -154,7 +166,7 @@ $(function() {
});
});

$("#archive_selected_books").click(function(event) {
$(document).on('click', '#archive_selected_books', function(event) {
if ($(this).hasClass("disabled")) {
event.stopPropagation()
} else {
Expand All @@ -176,7 +188,7 @@ $(function() {
});
});

$("#archive_selected_confirm").click(function(event) {
$(document).on('click', '#archive_selected_confirm', function(event) {
$.ajax({
method:"post",
contentType: "application/json; charset=utf-8",
Expand All @@ -190,7 +202,7 @@ $(function() {
});
});

$("#unarchive_selected_books").click(function(event) {
$(document).on('click', '#unarchive_selected_books', function(event) {
if ($(this).hasClass("disabled")) {
event.stopPropagation()
} else {
Expand All @@ -212,7 +224,7 @@ $(function() {
});
});

$("#unarchive_selected_confirm").click(function(event) {
$(document).on('click', '#unarchive_selected_confirm', function(event) {
$.ajax({
method:"post",
contentType: "application/json; charset=utf-8",
Expand All @@ -226,7 +238,7 @@ $(function() {
});
});

$("#delete_selected_books").click(function(event) {
$(document).on('click', '#delete_selected_books', function(event) {
if ($(this).hasClass("disabled")) {
event.stopPropagation()
} else {
Expand All @@ -248,7 +260,7 @@ $(function() {
});
});

$("#delete_selected_confirm").click(function(event) {
$(document).on('click', '#delete_selected_confirm', function(event) {
$.ajax({
method:"post",
contentType: "application/json; charset=utf-8",
Expand All @@ -262,6 +274,78 @@ $(function() {
});
});

$(document).on('click', '#read_selected_books', function(event) {
if ($(this).hasClass("disabled")) {
event.stopPropagation()
} else {
$('#read_selected_modal').modal("show");
}
$.ajax({
method:"post",
contentType: "application/json; charset=utf-8",
dataType: "json",
url: window.location.pathname + "/../ajax/displayselectedbooks",
data: JSON.stringify({"selections":selections}),
success: function success(booTitles) {
$('#display-read-selected-books').empty();
$.each(booTitles.books, function(i, item) {
$("<span>- " + item + "</span><p></p>").appendTo("#display-read-selected-books");
});

}
});
});

$(document).on('click', '#read_selected_confirm', function(event) {
$.ajax({
method:"post",
contentType: "application/json; charset=utf-8",
dataType: "json",
url: window.location.pathname + "/../ajax/readselectedbooks",
data: JSON.stringify({"selections":selections, "markAsRead": true}),
success: function success(booTitles) {
$("#books-table").bootstrapTable("refresh");
$("#books-table").bootstrapTable("uncheckAll");
}
});
});

$(document).on('click', '#unread_selected_books', function(event) {
if ($(this).hasClass("disabled")) {
event.stopPropagation()
} else {
$('#unread_selected_modal').modal("show");
}
$.ajax({
method:"post",
contentType: "application/json; charset=utf-8",
dataType: "json",
url: window.location.pathname + "/../ajax/displayselectedbooks",
data: JSON.stringify({"selections":selections}),
success: function success(booTitles) {
$('#display-unread-selected-books').empty();
$.each(booTitles.books, function(i, item) {
$("<span>- " + item + "</span><p></p>").appendTo("#display-unread-selected-books");
});

}
});
});

$(document).on('click', '#unread_selected_confirm', function(event) {
$.ajax({
method:"post",
contentType: "application/json; charset=utf-8",
dataType: "json",
url: window.location.pathname + "/../ajax/readselectedbooks",
data: JSON.stringify({"selections":selections, "markAsRead": false}),
success: function success(booTitles) {
$("#books-table").bootstrapTable("refresh");
$("#books-table").bootstrapTable("uncheckAll");
}
});
});

$("#table_xchange").click(function() {
$.ajax({
method:"post",
Expand Down
84 changes: 73 additions & 11 deletions cps/templates/book_table.html
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,26 @@
{%- endmacro %}

{% macro book_checkbox_row(parameter, show_text, sort) -%}
<th data-name="{{parameter}}" data-field="{{parameter}}"
<th data-name="{{parameter}}" data-field="{{parameter}}" data-switchable="false"
{% if sort %}data-sortable="true" {% endif %}
data-visible="{{visiblility.get(parameter)}}"
data-formatter="bookCheckboxFormatter">
{% if parameter == "is_archived"%}
<div class="btn btn-default disabled" id="archive_selected_books" aria-disabled="true">{{_('Archive selected books')}}</div>
{% if parameter == "is_archived" %}
<div class="btn btn-default disabled" id="archive_selected_books" aria-disabled="true">
{{_('Archive selected books')}}
</div>
<br>
<div class="btn btn-default disabled" id="unarchive_selected_books" aria-disabled="true">
{{_('Unarchive selected books')}}
</div>
<br>
{% elif parameter == "read_status" %}
<div class="btn btn-default disabled" id="read_selected_books" aria-disabled="true">
{{_('Mark selected books as read')}}
</div>
<br>
<div class="btn btn-default disabled" id="unarchive_selected_books" aria-disabled="true">{{_('Unarchive selected books')}}</div>
<div class="btn btn-default disabled" id="unread_selected_books" aria-disabled="true">
{{_('Mark selected books as unread')}}</div>
<br>
{% endif %}
{{show_text}}
Expand All @@ -40,8 +52,12 @@ <h2 class="{{page}}">{{_(title)}}</h2>
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<div class="col-xs-12 col-sm-6">
<div class="row form-group">
<div class="btn btn-default disabled" id="merge_books" aria-disabled="true">{{_('Merge selected books')}}</div>
<div class="btn btn-default disabled" id="delete_selection" aria-disabled="true">{{_('Clear selections')}}</div>
<div class="btn btn-default disabled" id="merge_books" aria-disabled="true">
{{_('Merge selected books')}}
</div>
<div class="btn btn-default disabled" id="delete_selection" aria-disabled="true">
{{_('Clear selections')}}
</div>
</div>
<div class="row form-group">
<div class="btn btn-default disabled" id="table_xchange" ><span class="glyphicon glyphicon-arrow-up"></span><span class="glyphicon glyphicon-arrow-down"></span>{{_('Exchange author and title')}}</div>
Expand Down Expand Up @@ -103,7 +119,13 @@ <h2 class="{{page}}">{{_(title)}}</h2>
{% endif %}
{% endfor %}
{% if current_user.role_delete_books() and current_user.role_edit()%}
<th data-align="right" data-formatter="EbookActions" data-switchable="false"><div class="btn btn-default disabled" id="delete_selected_books" aria-disabled="true">{{_('Delete selected books')}}</div><br>{{_('Delete')}}</th>
<th data-align="right" data-formatter="EbookActions" data-switchable="false">
<div class="btn btn-default disabled" id="delete_selected_books" aria-disabled="true">
{{_('Delete selected books')}}
</div>
<br>
{{_('Delete')}}
</th>
{% endif %}
</tr>
</thead>
Expand All @@ -130,7 +152,7 @@ <h2 class="{{page}}">{{_(title)}}</h2>
<div class="text-left" id="merge_to"></div>
</div>
<div class="modal-footer">
<input id="merge_confirm" type="button" class="btn btn-danger" value="{{_('Merge')}}" name="merge_confirm" id="merge_confirm" data-dismiss="modal">
<input id="merge_confirm" type="button" class="btn btn-danger" value="{{_('Merge')}}" name="merge_confirm" data-dismiss="modal">
<button id="merge_abort" type="button" class="btn btn-default" data-dismiss="modal">{{_('Cancel')}}</button>
</div>
</div>
Expand All @@ -149,7 +171,7 @@ <h2 class="{{page}}">{{_(title)}}</h2>
<p></p>
<div class="text-left" id="display-delete-selected-books"></div>
<div class="modal-footer">
<input id="delete_selected_confirm" type="button" class="btn btn-danger" value="{{_('Delete')}}" name="delete_selected_confirm" id="delete_selected_confirm" data-dismiss="modal">
<input id="delete_selected_confirm" type="button" class="btn btn-danger" value="{{_('Delete')}}" name="delete_selected_confirm" data-dismiss="modal">
<button id="delete_selected_abort" type="button" class="btn btn-default" data-dismiss="modal">{{_('Cancel')}}</button>
</div>
</div>
Expand All @@ -169,7 +191,7 @@ <h2 class="{{page}}">{{_(title)}}</h2>
<p></p>
<div class="text-left" id="display-archive-selected-books"></div>
<div class="modal-footer">
<input id="archive_selected_confirm" type="button" class="btn btn-danger" value="{{_('Archive')}}" name="archive_selected_confirm" id="archive_selected_confirm" data-dismiss="modal">
<input id="archive_selected_confirm" type="button" class="btn btn-danger" value="{{_('Archive')}}" name="archive_selected_confirm" data-dismiss="modal">
<button id="archive_selected_abort" type="button" class="btn btn-default" data-dismiss="modal">{{_('Cancel')}}</button>
</div>
</div>
Expand All @@ -189,13 +211,53 @@ <h2 class="{{page}}">{{_(title)}}</h2>
<p></p>
<div class="text-left" id="display-unarchive-selected-books"></div>
<div class="modal-footer">
<input id="unarchive_selected_confirm" type="button" class="btn btn-danger" value="{{_('Unarchive')}}" name="unarchive_selected_confirm" id="unarchive_selected_confirm" data-dismiss="modal">
<input id="unarchive_selected_confirm" type="button" class="btn btn-danger" value="{{_('Unarchive')}}" name="unarchive_selected_confirm" data-dismiss="modal">
<button id="unarchive_selected_abort" type="button" class="btn btn-default" data-dismiss="modal">{{_('Cancel')}}</button>
</div>
</div>
</div>
</div>
</div>

<div class="modal fade" id="read_selected_modal" role="dialog" aria-labelledby="metaReadSelectedLabel">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header bg-danger text-center">
<span>{{_('Are you really sure?')}}</span>
</div>
<div class="modal-body">
<p></p>
<div class="text-left">{{_('The following books will be marked read:')}}</div>
<p></p>
<div class="text-left" id="display-read-selected-books"></div>
<div class="modal-footer">
<input id="read_selected_confirm" type="button" class="btn btn-danger" value="{{_('Mark as read')}}" name="read_selected_confirm" data-dismiss="modal">
<button id="read_selected_abort" type="button" class="btn btn-default" data-dismiss="modal">{{_('Cancel')}}</button>
</div>
</div>
</div>
</div>
</div>

<div class="modal fade" id="unread_selected_modal" role="dialog" aria-labelledby="metaReadSelectedLabel">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header bg-danger text-center">
<span>{{_('Are you really sure?')}}</span>
</div>
<div class="modal-body">
<p></p>
<div class="text-left">{{_('The following books will be marked unread:')}}</div>
<p></p>
<div class="text-left" id="display-unread-selected-books"></div>
<div class="modal-footer">
<input id="unread_selected_confirm" type="button" class="btn btn-danger" value="{{_('Mark as unread')}}" name="unread_selected_confirm" data-dismiss="modal">
<button id="read_selected_abort" type="button" class="btn btn-default" data-dismiss="modal">{{_('Cancel')}}</button>
</div>
</div>
</div>
</div>
</div>
{% endif %}
{% endblock %}

Expand Down