Merge branch 'main' into group-list-button

Hopefully fixes failing tests!
This commit is contained in:
Hugh Rundle 2022-01-05 08:47:01 +11:00
commit 2153afe743
40 changed files with 549 additions and 301 deletions

View file

@ -0,0 +1,19 @@
# Generated by Django 3.2.5 on 2022-01-04 18:59
import bookwyrm.models.user
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("bookwyrm", "0121_user_summary_keys"),
]
operations = [
migrations.AlterField(
model_name="annualgoal",
name="year",
field=models.IntegerField(default=bookwyrm.models.user.get_current_year),
),
]

View file

@ -415,12 +415,17 @@ class KeyPair(ActivitypubMixin, BookWyrmModel):
return activity_object return activity_object
def get_current_year():
"""sets default year for annual goal to this year"""
return timezone.now().year
class AnnualGoal(BookWyrmModel): class AnnualGoal(BookWyrmModel):
"""set a goal for how many books you read in a year""" """set a goal for how many books you read in a year"""
user = models.ForeignKey("User", on_delete=models.PROTECT) user = models.ForeignKey("User", on_delete=models.PROTECT)
goal = models.IntegerField(validators=[MinValueValidator(1)]) goal = models.IntegerField(validators=[MinValueValidator(1)])
year = models.IntegerField(default=timezone.now().year) year = models.IntegerField(default=get_current_year)
privacy = models.CharField( privacy = models.CharField(
max_length=255, default="public", choices=fields.PrivacyLevels.choices max_length=255, default="public", choices=fields.PrivacyLevels.choices
) )

View file

@ -135,6 +135,18 @@ button::-moz-focus-inner {
border-bottom: 1px solid #ededed; border-bottom: 1px solid #ededed;
border-right: 0; border-right: 0;
} }
.is-flex-direction-row-mobile {
flex-direction: row !important;
}
.is-flex-direction-column-mobile {
flex-direction: column !important;
}
}
.tag.is-small {
height: auto;
} }
.button.is-transparent { .button.is-transparent {
@ -215,7 +227,7 @@ input[type=file]::file-selector-button:hover {
/** General `details` element styles /** General `details` element styles
******************************************************************************/ ******************************************************************************/
summary { details summary {
cursor: pointer; cursor: pointer;
} }
@ -223,22 +235,22 @@ summary::-webkit-details-marker {
display: none; display: none;
} }
summary::marker { details summary::marker {
content: none; content: none;
} }
.detail-pinned-button summary { details.detail-pinned-button summary {
position: absolute; position: absolute;
right: 0; right: 0;
} }
.detail-pinned-button form { details.detail-pinned-button form {
float: left; float: left;
width: -webkit-fill-available; width: 100%;
margin-top: 1em; margin-top: 1em;
} }
/** Details dropdown /** Dropdown w/ Details element
******************************************************************************/ ******************************************************************************/
details.dropdown[open] summary.dropdown-trigger::before { details.dropdown[open] summary.dropdown-trigger::before {
@ -250,11 +262,11 @@ details.dropdown[open] summary.dropdown-trigger::before {
right: 0; right: 0;
} }
details .dropdown-menu { details.dropdown .dropdown-menu {
display: block !important; display: block !important;
} }
details .dropdown-menu button { details.dropdown .dropdown-menu button {
/* Fix weird Safari defaults */ /* Fix weird Safari defaults */
box-sizing: border-box; box-sizing: border-box;
} }
@ -289,6 +301,46 @@ details.dropdown .dropdown-menu a:focus-visible {
} }
} }
/** Details panel
******************************************************************************/
details.details-panel {
box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.1);
transition: box-shadow 0.2s ease;
padding: 0.75rem;
}
details[open].details-panel,
details.details-panel:hover {
box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.2);
}
details.details-panel summary {
position: relative;
}
details.details-panel summary .details-close {
position: absolute;
right: 0;
top: 0;
transform: rotate(45deg);
transition: transform 0.2s ease;
}
details[open].details-panel summary .details-close {
transform: rotate(0deg);
}
@media only screen and (min-width: 769px) {
.details-panel .filters-field:not(:last-child) {
border-right: 1px solid rgba(0, 0, 0, 0.1);
margin-top: 0.75rem;
margin-bottom: 0.75rem;
padding-top: 0.25rem;
padding-bottom: 0.25rem;
}
}
/** Shelving /** Shelving
******************************************************************************/ ******************************************************************************/
@ -1149,3 +1201,93 @@ ol.ordered-list li::before {
margin-bottom: 0.75rem !important; margin-bottom: 0.75rem !important;
} }
} }
/* Gaps (for Flexbox and Grid)
*
* Those are supplementary rules to Bulmas. They follow the same conventions.
* Add those youll need.
******************************************************************************/
.is-gap-0 {
gap: 0;
}
.is-gap-1 {
gap: 0.25rem;
}
.is-gap-2 {
gap: 0.5rem;
}
.is-gap-3 {
gap: 0.75rem;
}
.is-gap-4 {
gap: 1rem;
}
.is-gap-5 {
gap: 1.5rem;
}
.is-gap-6 {
gap: 3rem;
}
.is-row-gap-0 {
row-gap: 0;
}
.is-row-gap-1 {
row-gap: 0.25rem;
}
.is-row-gap-2 {
row-gap: 0.5rem;
}
.is-row-gap-3 {
row-gap: 0.75rem;
}
.is-row-gap-4 {
row-gap: 1rem;
}
.is-row-gap-5 {
row-gap: 1.5rem;
}
.is-row-gap-6 {
row-gap: 3rem;
}
.is-column-gap-0 {
column-gap: 0;
}
.is-column-gap-1 {
column-gap: 0.25rem;
}
.is-column-gap-2 {
column-gap: 0.5rem;
}
.is-column-gap-3 {
column-gap: 0.75rem;
}
.is-column-gap-4 {
column-gap: 1rem;
}
.is-column-gap-5 {
column-gap: 1.5rem;
}
.is-column-gap-6 {
column-gap: 3rem;
}

View file

@ -25,6 +25,10 @@
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
} }
.icon.is-small {
font-size: small;
}
.icon-book:before { .icon-book:before {
content: "\e901"; content: "\e901";
} }

View file

@ -41,6 +41,7 @@ let BookWyrm = new (class {
document document
.querySelectorAll("[data-duplicate]") .querySelectorAll("[data-duplicate]")
.forEach((node) => node.addEventListener("click", this.duplicateInput.bind(this))); .forEach((node) => node.addEventListener("click", this.duplicateInput.bind(this)));
document document
.querySelectorAll("details.dropdown") .querySelectorAll("details.dropdown")
.forEach((node) => .forEach((node) =>
@ -60,6 +61,9 @@ let BookWyrm = new (class {
.querySelectorAll('input[type="file"]') .querySelectorAll('input[type="file"]')
.forEach(bookwyrm.disableIfTooLarge.bind(bookwyrm)); .forEach(bookwyrm.disableIfTooLarge.bind(bookwyrm));
document.querySelectorAll("[data-copytext]").forEach(bookwyrm.copyText.bind(bookwyrm)); document.querySelectorAll("[data-copytext]").forEach(bookwyrm.copyText.bind(bookwyrm));
document
.querySelectorAll(".modal.is-active")
.forEach(bookwyrm.handleActiveModal.bind(bookwyrm));
}); });
} }
@ -405,7 +409,7 @@ let BookWyrm = new (class {
} }
/** /**
* Handle the modal component. * Handle the modal component with a button trigger.
* *
* @param {Event} event - Event fired by an element * @param {Event} event - Event fired by an element
* with the `data-modal-open` attribute * with the `data-modal-open` attribute
@ -416,6 +420,7 @@ let BookWyrm = new (class {
* for information about using the modal. * for information about using the modal.
*/ */
handleModalButton(event) { handleModalButton(event) {
const { handleFocusTrap } = this;
const modalButton = event.currentTarget; const modalButton = event.currentTarget;
const targetModalId = modalButton.dataset.modalOpen; const targetModalId = modalButton.dataset.modalOpen;
const htmlElement = document.querySelector("html"); const htmlElement = document.querySelector("html");
@ -427,6 +432,8 @@ let BookWyrm = new (class {
// Helper functions // Helper functions
function handleModalOpen(modalElement) { function handleModalOpen(modalElement) {
event.preventDefault();
htmlElement.classList.add("is-clipped"); htmlElement.classList.add("is-clipped");
modalElement.classList.add("is-active"); modalElement.classList.add("is-active");
modalElement.getElementsByClassName("modal-card")[0].focus(); modalElement.getElementsByClassName("modal-card")[0].focus();
@ -455,40 +462,46 @@ let BookWyrm = new (class {
modalButton.focus(); modalButton.focus();
} }
function handleFocusTrap(event) { // Open modal
if (event.key !== "Tab") { handleModalOpen(modal);
}
/**
* Handle the modal component when opened at page load.
*
* @param {Element} modalElement - Active modal element
* @return {undefined}
*
*/
handleActiveModal(modalElement) {
if (!modalElement) {
return; return;
} }
const focusableEls = event.currentTarget.querySelectorAll( const { handleFocusTrap } = this;
[
"a[href]:not([disabled])",
"button:not([disabled])",
"textarea:not([disabled])",
'input:not([type="hidden"]):not([disabled])',
"select:not([disabled])",
"details:not([disabled])",
'[tabindex]:not([tabindex="-1"]):not([disabled])',
].join(",")
);
const firstFocusableEl = focusableEls[0];
const lastFocusableEl = focusableEls[focusableEls.length - 1];
if (event.shiftKey) { modalElement.getElementsByClassName("modal-card")[0].focus();
/* Shift + tab */ if (document.activeElement === firstFocusableEl) {
lastFocusableEl.focus();
event.preventDefault();
}
} /* Tab */ else {
if (document.activeElement === lastFocusableEl) {
firstFocusableEl.focus();
event.preventDefault();
}
}
}
// Open modal const closeButtons = modalElement.querySelectorAll("[data-modal-close]");
handleModalOpen(modal);
closeButtons.forEach((button) => {
button.addEventListener("click", function () {
handleModalClose(modalElement);
});
});
document.addEventListener("keydown", function (event) {
if (event.key === "Escape") {
handleModalClose(modalElement);
}
});
modalElement.addEventListener("keydown", handleFocusTrap);
function handleModalClose(modalElement) {
modalElement.removeEventListener("keydown", handleFocusTrap);
history.back();
}
} }
/** /**

View file

@ -192,6 +192,31 @@
</div> </div>
</div> </div>
{% if goal_status and goal_status.percent >= 100 %}
<div class="columns">
<div class="column has-text-centered">
<h2 class="title is-3 is-serif">
{% with goal=goal_status.goal goal_percent=goal_status.percent %}
{% blocktrans trimmed count counter=goal %}
{{ display_name }} set a goal of reading {{ goal }} book in {{ year }},<br />
and achieved {{ goal_percent }}% of that goal
{% plural %}
{{ display_name }} set a goal of reading {{ goal }} books in {{ year }},<br />
and achieved {{ goal_percent }}% of that goal
{% endblocktrans %}
{% endwith %}
</h2>
<p class="subtitle is-5">{% trans "Way to go!" %}</p>
</div>
</div>
<div class="columns">
<div class="column is-one-fifth is-offset-two-fifths">
<hr />
</div>
</div>
{% endif %}
{% if ratings_total > 0 %} {% if ratings_total > 0 %}
<div class="columns"> <div class="columns">
<div class="column has-text-centered"> <div class="column has-text-centered">

View file

@ -92,10 +92,11 @@
{% trans "View on OpenLibrary" %} {% trans "View on OpenLibrary" %}
</a> </a>
{% if request.user.is_authenticated and perms.bookwyrm.edit_book %} {% if request.user.is_authenticated and perms.bookwyrm.edit_book %}
{% with controls_text="ol_sync" controls_uid=author.id %} <button class="button is-small" type="button" data-modal-open="openlibrary_sync">
{% include 'snippets/toggle/toggle_button.html' with text=button_text focus="modal_title_ol_sync" class="is-small" icon_with_text="download" %} <span class="icon icon-download" title="{{ button_text }}"></span>
{% include "author/sync_modal.html" with source="openlibrary.org" source_name="OpenLibrary" %} <span class="is-sr-only-mobile">{{ button_text }}</span>
{% endwith %} </button>
{% include "author/sync_modal.html" with source="openlibrary.org" source_name="OpenLibrary" id="openlibrary_sync" %}
{% endif %} {% endif %}
</div> </div>
{% endif %} {% endif %}
@ -107,10 +108,11 @@
</a> </a>
{% if request.user.is_authenticated and perms.bookwyrm.edit_book %} {% if request.user.is_authenticated and perms.bookwyrm.edit_book %}
{% with controls_text="iv_sync" controls_uid=author.id %} <button class="button is-small" type="button" data-modal-open="inventaire_sync">
{% include 'snippets/toggle/toggle_button.html' with text=button_text focus="modal_title_iv_sync" class="is-small" icon_with_text="download" %} <span class="icon icon-download" title="{{ button_text }}"></span>
{% include "author/sync_modal.html" with source="inventaire.io" source_name="Inventaire" %} <span class="is-sr-only-mobile">{{ button_text }}</span>
{% endwith %} </button>
{% include "author/sync_modal.html" with source="inventaire.io" source_name="Inventaire" id="inventaire_sync" %}
{% endif %} {% endif %}
</div> </div>
{% endif %} {% endif %}

View file

@ -19,12 +19,8 @@
{% endblock %} {% endblock %}
{% block modal-footer %} {% block modal-footer %}
<button class="button is-primary" type="submit"> <button class="button is-primary" type="submit">{% trans "Confirm" %}</button>
<span>{% trans "Confirm" %}</span> <button type="button" class="button" data-modal-close>{% trans "Cancel" %}</button>
</button>
{% trans "Cancel" as button_text %}
{% include 'snippets/toggle/toggle_button.html' with text=button_text %}
{% endblock %} {% endblock %}
{% block modal-form-close %}</form>{% endblock %} {% block modal-form-close %}</form>{% endblock %}

View file

@ -63,7 +63,11 @@
<div class="column is-one-fifth"> <div class="column is-one-fifth">
{% if not book.cover %} {% if not book.cover %}
{% if user_authenticated %} {% if user_authenticated %}
<button type="button" data-controls="add_cover_{{ book.id }}" data-focus-target="modal_title_add_cover_{{ book.id }}" aria-pressed="false" class="cover-container no-cover is-h-m-mobile"> <button
type="button"
class="cover-container no-cover is-h-m-mobile"
data-modal-open="add_cover_{{ book.id }}"
>
<img <img
class="book-cover" class="book-cover"
src="{% static "images/no_cover.jpg" %}" src="{% static "images/no_cover.jpg" %}"
@ -78,7 +82,8 @@
{% trans "Click to add cover" %} {% trans "Click to add cover" %}
</span> </span>
</button> </button>
{% include 'book/cover_add_modal.html' with book=book controls_text="add_cover" controls_uid=book.id %} {% join "add_cover" book.id as modal_id %}
{% include 'book/cover_add_modal.html' with id=modal_id %}
{% if request.GET.cover_error %} {% if request.GET.cover_error %}
<p class="help is-danger">{% trans "Failed to load cover" %}</p> <p class="help is-danger">{% trans "Failed to load cover" %}</p>
{% endif %} {% endif %}
@ -117,23 +122,30 @@
{% trans "Load data" as button_text %} {% trans "Load data" as button_text %}
{% if book.openlibrary_key %} {% if book.openlibrary_key %}
<p> <p>
<a href="{{ book.openlibrary_link }}" target="_blank" rel="noopener">{% trans "View on OpenLibrary" %}</a> <a href="{{ book.openlibrary_link }}" target="_blank" rel="noopener">
{% trans "View on OpenLibrary" %}
</a>
{% if request.user.is_authenticated and perms.bookwyrm.edit_book %} {% if request.user.is_authenticated and perms.bookwyrm.edit_book %}
{% with controls_text="ol_sync" controls_uid=book.id %} <button class="button is-small" type="button" data-modal-open="openlibrary_sync">
{% include 'snippets/toggle/toggle_button.html' with text=button_text focus="modal_title_ol_sync" class="is-small" icon_with_text="download" %} <span class="icon icon-download" title="{{ button_text }}"></span>
{% include "book/sync_modal.html" with source="openlibrary.org" source_name="OpenLibrary" %} <span class="is-sr-only-mobile">{{ button_text }}</span>
{% endwith %} </button>
{% include "book/sync_modal.html" with source="openlibrary.org" source_name="OpenLibrary" id="openlibrary_sync" %}
{% endif %} {% endif %}
</p> </p>
{% endif %} {% endif %}
{% if book.inventaire_id %} {% if book.inventaire_id %}
<p> <p>
<a href="{{ book.inventaire_link }}" target="_blank" rel="noopener">{% trans "View on Inventaire" %}</a> <a href="{{ book.inventaire_link }}" target="_blank" rel="noopener">
{% trans "View on Inventaire" %}
</a>
{% if request.user.is_authenticated and perms.bookwyrm.edit_book %} {% if request.user.is_authenticated and perms.bookwyrm.edit_book %}
{% with controls_text="iv_sync" controls_uid=book.id %} <button class="button is-small" type="button" data-modal-open="inventaire_sync">
{% include 'snippets/toggle/toggle_button.html' with text=button_text focus="modal_title_iv_sync" class="is-small" icon_with_text="download" %} <span class="icon icon-download" title="{{ button_text }}"></span>
{% include "book/sync_modal.html" with source="inventaire.io" source_name="Inventaire" %} <span class="is-sr-only-mobile">{{ button_text }}</span>
{% endwith %} </button>
{% include "book/sync_modal.html" with source="inventaire.io" source_name="Inventaire" id="inventaire_sync" %}
{% endif %} {% endif %}
</p> </p>
{% endif %} {% endif %}

View file

@ -29,8 +29,7 @@
{% block modal-footer %} {% block modal-footer %}
<button class="button is-primary" type="submit">{% trans "Add" %}</button> <button class="button is-primary" type="submit">{% trans "Add" %}</button>
{% trans "Cancel" as button_text %} <button type="button" class="button" data-modal-close>{% trans "Cancel" %}</button>
{% include 'snippets/toggle/toggle_button.html' with text=button_text %}
{% endblock %} {% endblock %}
{% block modal-form-close %}</form>{% endblock %}
{% block modal-form-close %}</form>{% endblock %}

View file

@ -2,11 +2,17 @@
{% load i18n %} {% load i18n %}
{% block modal-title %}{% trans "Delete these read dates?" %}{% endblock %} {% block modal-title %}{% trans "Delete these read dates?" %}{% endblock %}
{% block modal-body %} {% block modal-body %}
{% if readthrough.progress_updates|length > 0 %} {% if readthrough.progress_updates|length > 0 %}
{% blocktrans with count=readthrough.progress_updates|length %}You are deleting this readthrough and its {{ count }} associated progress updates.{% endblocktrans %} {% blocktrans trimmed with count=readthrough.progress_updates|length %}
You are deleting this readthrough and its {{ count }} associated progress updates.
{% endblocktrans %}
{% else %}
{% trans "This action cannot be un-done" %}
{% endif %} {% endif %}
{% endblock %} {% endblock %}
{% block modal-footer %} {% block modal-footer %}
<form name="delete-readthrough-{{ readthrough.id }}" action="/delete-readthrough" method="POST"> <form name="delete-readthrough-{{ readthrough.id }}" action="/delete-readthrough" method="POST">
{% csrf_token %} {% csrf_token %}
@ -14,7 +20,6 @@
<button class="button is-danger" type="submit"> <button class="button is-danger" type="submit">
{% trans "Delete" %} {% trans "Delete" %}
</button> </button>
{% trans "Cancel" as button_text %} <button type="button" class="button" data-modal-close>{% trans "Cancel" %}</button>
{% include 'snippets/toggle/toggle_button.html' with text=button_text controls_text="delete_readthrough" controls_uid=readthrough.id %}
</form> </form>
{% endblock %} {% endblock %}

View file

@ -1,6 +1,7 @@
{% load i18n %} {% load i18n %}
{% load humanize %} {% load humanize %}
{% load tz %} {% load tz %}
{% load utilities %}
<div class="content"> <div class="content">
<div id="hide_edit_readthrough_{{ readthrough.id }}" class="box is-shadowless has-background-white-bis"> <div id="hide_edit_readthrough_{{ readthrough.id }}" class="box is-shadowless has-background-white-bis">
<div class="columns"> <div class="columns">
@ -47,6 +48,7 @@
{% endif %} {% endif %}
</li> </li>
{% endif %} {% endif %}
{% if readthrough.start_date %} {% if readthrough.start_date %}
<li>{{ readthrough.start_date | localtime | naturalday }}: {% trans "started" %}</li> <li>{{ readthrough.start_date | localtime | naturalday }}: {% trans "started" %}</li>
{% endif %} {% endif %}
@ -60,7 +62,11 @@
</div> </div>
<div class="control"> <div class="control">
{% trans "Delete these read dates" as button_text %} {% trans "Delete these read dates" as button_text %}
{% include 'snippets/toggle/toggle_button.html' with class="is-small" text=button_text icon="x" controls_text="delete_readthrough" controls_uid=readthrough.id focus="modal_title_delete_readthrough" %} <button class="button is-small" type="button" data-modal-open="delete_readthrough_{{ readthrough.id }}">
<span class="icon icon-x" title="{{ button_text }}">
<span class="is-sr-only">{{ button_text }}</span>
</span>
</button>
</div> </div>
</div> </div>
</div> </div>
@ -79,4 +85,5 @@
</div> </div>
</form> </form>
</div> </div>
{% include 'snippets/delete_readthrough_modal.html' with controls_text="delete_readthrough" controls_uid=readthrough.id no_body=True %} {% join "delete_readthrough" readthrough.id as modal_id %}
{% include 'book/delete_readthrough_modal.html' with id=modal_id %}

View file

@ -19,12 +19,8 @@
{% endblock %} {% endblock %}
{% block modal-footer %} {% block modal-footer %}
<button class="button is-primary" type="submit"> <button class="button is-primary" type="submit">{% trans "Confirm" %}</button>
<span>{% trans "Confirm" %}</span> <button type="button" class="button" data-modal-close>{% trans "Cancel" %}</button>
</button>
{% trans "Cancel" as button_text %}
{% include 'snippets/toggle/toggle_button.html' with text=button_text %}
{% endblock %} {% endblock %}
{% block modal-form-close %}</form>{% endblock %} {% block modal-form-close %}</form>{% endblock %}

View file

@ -1,40 +1,33 @@
{% load i18n %} {% load i18n %}
<div
role="dialog" <div class="modal {% if active %}is-active{% endif %}" id="{{ id }}">
class="modal {% if active %}is-active{% else %}is-hidden{% endif %}" <div class="modal-background" data-modal-close></div>
id="{{ controls_text }}_{{ controls_uid }}" <div class="modal-card" role="dialog" aria-modal="true" tabindex="-1" aria-described-by="{{ id }}_header">
aria-labelledby="modal_card_title_{{ controls_text }}_{{ controls_uid }}" <header class="modal-card-head">
aria-modal="true" <h2 class="modal-card-title mb-0" id="{{ id }}_header">
>
{# @todo Implement focus traps to prevent tabbing out of the modal. #}
<div class="modal-background"></div>
{% trans "Close" as label %}
<div class="modal-card">
<header class="modal-card-head" tabindex="0" id="modal_title_{{ controls_text }}_{{ controls_uid }}">
<h2 class="modal-card-title is-flex-shrink-1" id="modal_card_title_{{ controls_text }}_{{ controls_uid }}">
{% block modal-title %}{% endblock %} {% block modal-title %}{% endblock %}
</h2> </h2>
{% if static %} <button
<a href="/" class="delete">{{ label }}</a> type="button"
{% else %} class="delete"
{% include 'snippets/toggle/toggle_button.html' with label=label class="delete" nonbutton=True %} aria-label="{% trans 'Close' %}"
{% endif %} data-modal-close
></button>
</header> </header>
{% block modal-form-open %}{% endblock %} {% block modal-form-open %}{% endblock %}
{% if not no_body %}
<section class="modal-card-body"> <section class="modal-card-body">
{% block modal-body %}{% endblock %} {% block modal-body %}{% endblock %}
</section> </section>
{% endif %}
<footer class="modal-card-foot"> <footer class="modal-card-foot">
{% block modal-footer %}{% endblock %} {% block modal-footer %}{% endblock %}
</footer> </footer>
{% block modal-form-close %}{% endblock %} {% block modal-form-close %}{% endblock %}
</div> </div>
{% if static %} <button
<a href="/" class="modal-close is-large">{{ label }}</a> type="button"
{% else %} class="modal-close is-large"
{% include 'snippets/toggle/toggle_button.html' with label=label class="modal-close is-large" nonbutton=True %} aria-label="{% trans 'Close' %}"
{% endif %} data-modal-close
></button>
</div> </div>

View file

@ -1,31 +0,0 @@
{% load i18n %}
<div class="modal" id="{{ id }}">
<div class="modal-background" data-modal-close></div>
<div class="modal-card is-thin" role="dialog" aria-modal="true" tabindex="-1">
<header class="modal-card-head">
<h2 class="modal-card-title mb-0">
{% block modal-title %}{% endblock %}
</h2>
<button
type="button"
class="delete"
aria-label="{% trans 'Close' %}"
data-modal-close
></button>
</header>
<section class="modal-card-body">
{% block modal-body %}{% endblock %}
</section>
<footer class="modal-card-foot">
{% block modal-footer %}{% endblock %}
</footer>
</div>
<button
type="button"
class="modal-close is-large"
aria-label="{% trans 'Close' %}"
data-modal-close
></button>
</div>

View file

@ -6,9 +6,8 @@
<h1 class="title"> <h1 class="title">
{{ tab.name }} {{ tab.name }}
</h1> </h1>
<div class="block is-clipped">
<div class="is-pulled-left"> <div class="tabs">
<div class="tabs">
<ul> <ul>
{% for stream in streams %} {% for stream in streams %}
<li class="{% if tab.key == stream.key %}is-active{% endif %}"{% if tab.key == stream.key %} aria-current="page"{% endif %}> <li class="{% if tab.key == stream.key %}is-active{% endif %}"{% if tab.key == stream.key %} aria-current="page"{% endif %}>
@ -16,47 +15,13 @@
</li> </li>
{% endfor %} {% endfor %}
</ul> </ul>
</div>
</div>
{# feed settings #}
<details class="detail-pinned-button" {% if settings_saved %}open{% endif %}>
<summary class="control">
<span class="button">
<span class="icon icon-dots-three m-0-mobile" aria-hidden="true"></span>
<span class="is-sr-only-mobile">{{ _("Feed settings") }}</span>
</span>
</summary>
<form class="notification level is-align-items-flex-end" method="post" action="/{{ tab.key }}#feed">
{% csrf_token %}
<div class="level-left">
<div class="field">
<div class="control">
<span class="is-flex is-align-items-baseline">
<label class="label mt-2 mb-1">Status types</label>
{% if settings_saved %}
<span class="tag is-success is-light ml-2">{{ _("Saved!") }}</span>
{% endif %}
</span>
{% for name, value in feed_status_types_options %}
<label class="mr-2">
<input type="checkbox" name="feed_status_types" value="{{ name }}" {% if name in user.feed_status_types %}checked=""{% endif %}/>
{{ value }}
</label>
{% endfor %}
</div>
</div>
</div>
<div class="level-right control">
<button class="button is-small is-primary is-outlined" type="submit">
{{ _("Save settings") }}
</button>
</div>
</form>
</details>
</div> </div>
{# feed settings #}
{% with "/"|add:tab.key|add:"#feed" as action %}
{% include 'feed/feed_filters.html' with size="small" method="post" action=action %}
{% endwith %}
{# announcements and system messages #} {# announcements and system messages #}
{% if not activities.number > 1 %} {% if not activities.number > 1 %}
<a href="{{ request.path }}" class="transition-y is-hidden notification is-primary is-block" data-poll-wrapper> <a href="{{ request.path }}" class="transition-y is-hidden notification is-primary is-block" data-poll-wrapper>

View file

@ -0,0 +1,5 @@
{% extends 'snippets/filters_panel/filters_panel.html' %}
{% block filter_fields %}
{% include 'feed/status_types_filter.html' %}
{% endblock %}

View file

@ -0,0 +1,16 @@
{% extends 'snippets/filters_panel/filter_field.html' %}
{% load i18n %}
{% block filter %}
<label class="label mt-2 mb-1">Status types</label>
<div class="is-flex is-flex-direction-row is-flex-direction-column-mobile">
{% for name, value in feed_status_types_options %}
<label class="mr-2">
<input type="checkbox" name="feed_status_types" value="{{ name }}" {% if name in user.feed_status_types %}checked=""{% endif %}/>
{{ value }}
</label>
{% endfor %}
</div>
{% endblock %}

View file

@ -14,8 +14,7 @@
<button class="button is-danger" type="submit"> <button class="button is-danger" type="submit">
{% trans "Delete" %} {% trans "Delete" %}
</button> </button>
{% trans "Cancel" as button_text %} <button type="button" class="button" data-modal-close>{% trans "Cancel" %}</button>
{% include 'snippets/toggle/toggle_button.html' with text=button_text controls_text="delete_group" controls_uid=group.id %}
</form> </form>
{% endblock %} {% endblock %}

View file

@ -9,5 +9,5 @@
<form name="edit-group" method="post" action="{% url 'group' group.id %}"> <form name="edit-group" method="post" action="{% url 'group' group.id %}">
{% include 'groups/form.html' %} {% include 'groups/form.html' %}
</form> </form>
{% include "groups/delete_group_modal.html" with controls_text="delete_group" controls_uid=group.id %} {% include "groups/delete_group_modal.html" with id="delete_group" %}
{% endblock %} {% endblock %}

View file

@ -14,8 +14,14 @@
</div> </div>
</div> </div>
</div> </div>
<div class="columns is-mobile"> <div class="is-flex">
<div class="column"> {% if group.id %}
<div class="is-flex-grow-1">
<button type="button" data-modal-open="delete_group" class="button is-danger">
{% trans "Delete group" %}
</button>
</div>
{% endif %}
<div class="field has-addons"> <div class="field has-addons">
<div class="control"> <div class="control">
{% include 'snippets/privacy_select_no_followers.html' with current=group.privacy %} {% include 'snippets/privacy_select_no_followers.html' with current=group.privacy %}
@ -24,11 +30,4 @@
<button type="submit" class="button is-primary">{% trans "Save" %}</button> <button type="submit" class="button is-primary">{% trans "Save" %}</button>
</div> </div>
</div> </div>
</div>
{% if group.id %}
<div class="column is-narrow">
{% trans "Delete group" as button_text %}
{% include 'snippets/toggle/toggle_button.html' with class="is-danger" text=button_text icon_with_text="x" controls_text="delete_group" controls_uid=group.id focus="modal_title_delete_group" %}
</div>
{% endif %}
</div> </div>

View file

@ -1,4 +1,4 @@
{% extends 'components/new_modal.html' %} {% extends 'components/modal.html' %}
{% load i18n %} {% load i18n %}
{% block modal-title %}{% trans "Delete this list?" %}{% endblock %} {% block modal-title %}{% trans "Delete this list?" %}{% endblock %}

View file

@ -8,7 +8,7 @@
<ul class="block"> <ul class="block">
{% for result in local_results.results %} {% for result in local_results.results %}
<li class="pd-4 mb-5"> <li class="pd-4 mb-5">
<div class="columns is-mobile is-gapless"> <div class="columns is-mobile is-gapless mb-0">
<div class="column is-cover"> <div class="column is-cover">
{% include 'snippets/book_cover.html' with book=result cover_class='is-w-xs is-h-xs' %} {% include 'snippets/book_cover.html' with book=result cover_class='is-w-xs is-h-xs' %}
</div> </div>
@ -34,34 +34,28 @@
<div class="block"> <div class="block">
{% for result_set in results|slice:"1:" %} {% for result_set in results|slice:"1:" %}
{% if result_set.results %} {% if result_set.results %}
<section class="box has-background-white-bis"> <section class="mb-5">
{% if not result_set.connector.local %} {% if not result_set.connector.local %}
<header class="columns is-mobile"> <details class="details-panel box" {% if forloop.first %}open{% endif %}>
<div class="column"> {% endif %}
<h3 class="title is-5"> {% if not result_set.connector.local %}
<summary class="is-flex is-align-items-center is-flex-wrap-wrap is-gap-2">
<span class="mb-0 title is-5">
{% trans 'Results from' %} {% trans 'Results from' %}
<a href="{{ result_set.connector.base_url }}" target="_blank">{{ result_set.connector.name|default:result_set.connector.identifier }}</a> <a href="{{ result_set.connector.base_url }}" target="_blank">{{ result_set.connector.name|default:result_set.connector.identifier }}</a>
</h3> </span>
</div>
<div class="column is-narrow"> <span class="details-close icon icon-x" aria-hidden></span>
{% trans "Open" as button_text %} </summary>
{% include 'snippets/toggle/open_button.html' with text=button_text controls_text="more_results_panel" controls_uid=result_set.connector.identifier class="is-small" icon_with_text="arrow-down" pressed=forloop.first %}
{% trans "Close" as button_text %}
{% include 'snippets/toggle/close_button.html' with text=button_text controls_text="more_results_panel" controls_uid=result_set.connector.identifier class="is-small" icon_with_text="arrow-up" pressed=forloop.first %}
</div>
</header>
{% endif %} {% endif %}
<div class="box has-background-white is-shadowless{% if not forloop.first %} is-hidden{% endif %}" id="more_results_panel_{{ result_set.connector.identifier }}"> <div class="mt-5">
<div class="is-flex is-flex-direction-row-reverse"> <div class="is-flex is-flex-direction-row-reverse">
<div>
</div>
<ul class="is-flex-grow-1"> <ul class="is-flex-grow-1">
{% for result in result_set.results %} {% for result in result_set.results %}
<li class="mb-5"> <li class="{% if not forloop.last %}mb-5{% endif %}">
<div class="columns is-mobile is-gapless">
<div class="columns is-mobile is-gapless"> <div class="columns is-mobile is-gapless">
<div class="column is-1 is-cover">
{% include 'snippets/book_cover.html' with book=result cover_class='is-w-xs is-h-xs' external_path=True %} {% include 'snippets/book_cover.html' with book=result cover_class='is-w-xs is-h-xs' external_path=True %}
</div> </div>
<div class="column is-10 ml-3"> <div class="column is-10 ml-3">
@ -92,6 +86,9 @@
</ul> </ul>
</div> </div>
</div> </div>
{% if not result_set.connector.local %}
</details>
{% endif %}
</section> </section>
{% endif %} {% endif %}
{% endfor %} {% endfor %}

View file

@ -1,6 +1,4 @@
<div class="column is-flex"> <div class="filters-field column">
<div class="box is-flex-grow-1">
{% block filter %} {% block filter %}
{% endblock %} {% endblock %}
</div>
</div> </div>

View file

@ -1,28 +1,47 @@
{% load i18n %} {% load i18n %}
<div class="notification content"> <details class="details-panel box is-size-{{ size|default:'normal' }}" {% if filters_applied %}open{% endif %}>
<h2 class="columns is-mobile mb-0"> <summary class="is-flex is-align-items-center is-flex-wrap-wrap is-gap-2">
<span class="column pb-0">Filters</span> <span class="mb-0 title {% if size == 'small' %}is-6{% else %}is-5{% endif %} is-flex-shrink-0">
{% trans "Filters" %}
<span class="column is-narrow pb-0">
{% trans "Show filters" as text %}
{% include 'snippets/toggle/open_button.html' with text=text controls_text="filters" icon_with_text="arrow-down" class="is-small" focus="filters" %}
{% trans "Hide filters" as text %}
{% include 'snippets/toggle/close_button.html' with text=text controls_text="filters" icon_with_text="arrow-up" class="is-small" %}
</span> </span>
</h2>
<form class="is-hidden mt-3" id="filters" method="get" action="{{ request.path }}" tabindex="0"> {% if filters_applied %}
<span class="tag is-success is-light ml-2 mb-0 is-{{ size|default:'normal' }}">
{{ _("Filters are applied") }}
</span>
{% endif %}
{% if request.GET %}
<span class="mb-0 tags has-addons">
<span class="mb-0 tag is-success is-light is-{{ size|default:'normal' }}">
{% trans "Filters are applied" %}
</span>
<a class="mb-0 tag is-success is-{{ size|default:'normal' }}" href="{{ request.path }}">
{% trans "Clear filters" %}
</a>
</span>
{% endif %}
<span class="details-close icon icon-x is-{{ size|default:'normal' }}" aria-hidden></span>
</summary>
<div class="mt-3">
<form id="filters" method="{{ method|default:'get' }}" action="{{ action|default:request.path }}">
{% if method == 'post' %}
{% csrf_token %}
{% endif %}
{% if sort %} {% if sort %}
<input type="hidden" name="sort" value="{{ sort }}"> <input type="hidden" name="sort" value="{{ sort }}">
{% endif %} {% endif %}
<div class="columns"> <div class="mt-3 columns filters-fields is-align-items-stretch">
{% block filter_fields %} {% block filter_fields %}
{% endblock %} {% endblock %}
</div> </div>
<button class="button is-primary">{% trans "Apply filters" %}</button> <button type="submit" class="button is-primary is-small">
{% trans "Apply filters" %}
</button>
</form> </form>
</div>
{% if request.GET %} </details>
<a class="help" href="{{ request.path }}">{% trans "Clear filters" %}</a>
{% endif %}
</div>

View file

@ -5,7 +5,7 @@
type="number" type="number"
name="progress" name="progress"
class="input" class="input"
id="id_progress_{{ readthrough.id }}{{ controls_uid }}" id="{{ field_id }}"
value="{{ readthrough.progress }}" value="{{ readthrough.progress }}"
{% if progress_required %}required{% endif %} {% if progress_required %}required{% endif %}
> >

View file

@ -1,5 +1,6 @@
{% extends 'snippets/reading_modals/layout.html' %} {% extends 'snippets/reading_modals/layout.html' %}
{% load i18n %} {% load i18n %}
{% load utilities %}
{% block modal-title %} {% block modal-title %}
{% trans "Update progress" %} {% trans "Update progress" %}
@ -12,8 +13,9 @@
{% endblock %} {% endblock %}
{% block reading-dates %} {% block reading-dates %}
<label for="id_progress_{{ readthrough.id }}{{ controls_uid }}" class="label">{% trans "Progress:" %}</label> {% join "id_progress" uuid as field_id %}
{% include "snippets/progress_field.html" with progress_required=True %} <label for="{{ field_id }}" class="label">{% trans "Progress:" %}</label>
{% include "snippets/progress_field.html" with progress_required=True id=field_id %}
{% endblock %} {% endblock %}
{% block form %} {% block form %}

View file

@ -1,4 +1,6 @@
{% load i18n %} {% load i18n %}
{% load utilities %}
{% csrf_token %} {% csrf_token %}
<input type="hidden" name="id" value="{{ readthrough.id }}"> <input type="hidden" name="id" value="{{ readthrough.id }}">
<input type="hidden" name="book" value="{{ book.id }}"> <input type="hidden" name="book" value="{{ book.id }}">
@ -10,10 +12,11 @@
</div> </div>
{# Only show progress for editing existing readthroughs #} {# Only show progress for editing existing readthroughs #}
{% if readthrough.id and not readthrough.finish_date %} {% if readthrough.id and not readthrough.finish_date %}
<label class="label" for="id_progress_{{ readthrough.id }}{{ controls_uid }}"> {% join "id_progress" readthrough.id as field_id %}
<label class="label" for="{{ field_id }}">
{% trans "Progress" %} {% trans "Progress" %}
</label> </label>
{% include "snippets/progress_field.html" %} {% include "snippets/progress_field.html" with id=field_id %}
{% endif %} {% endif %}
<div class="field"> <div class="field">
<label class="label" for="id_finish_date_{{ readthrough.id }}"> <label class="label" for="id_finish_date_{{ readthrough.id }}">

View file

@ -3,9 +3,15 @@
{% with 0|uuid as report_uuid %} {% with 0|uuid as report_uuid %}
{% trans "Report" as button_text %} {% join "report" report_uuid as modal_id %}
{% include 'snippets/toggle/toggle_button.html' with class="is-danger is-light is-small is-fullwidth" text=button_text controls_text="report" controls_uid=report_uuid focus="modal_title_report" disabled=is_current %} <button
class="button is-small is-danger is-light is-fullwidth"
{% include 'snippets/report_modal.html' with user=user reporter=request.user controls_text="report" controls_uid=report_uuid %} type="button"
data-modal-open="{{ modal_id }}"
{% if is_current %}disabled{% endif %}
>
{% trans "Report" %}
</button>
{% include 'snippets/report_modal.html' with user=user reporter=request.user id=modal_id %}
{% endwith %} {% endwith %}

View file

@ -21,8 +21,12 @@
<section class="content"> <section class="content">
<p>{% blocktrans with site_name=site.name %}This report will be sent to {{ site_name }}'s moderators for review.{% endblocktrans %}</p> <p>{% blocktrans with site_name=site.name %}This report will be sent to {{ site_name }}'s moderators for review.{% endblocktrans %}</p>
<label class="label" for="id_{{ controls_uid }}_report_note">{% trans "More info about this report:" %}</label> <div class="control">
<label class="label" for="id_{{ controls_uid }}_report_note">
{% trans "More info about this report:" %}
</label>
<textarea class="textarea" name="note" id="id_{{ controls_uid }}_report_note"></textarea> <textarea class="textarea" name="note" id="id_{{ controls_uid }}_report_note"></textarea>
</div>
</section> </section>
{% endblock %} {% endblock %}
@ -31,9 +35,9 @@
{% block modal-footer %} {% block modal-footer %}
<button class="button is-success" type="submit">{% trans "Submit" %}</button> <button class="button is-success" type="submit">{% trans "Submit" %}</button>
{% trans "Cancel" as button_text %} <button type="button" class="button" data-modal-close>{% trans "Cancel" %}</button>
{% include 'snippets/toggle/toggle_button.html' with text=button_text controls_text="report" controls_uid=report_uuid class="" %}
{% endblock %} {% endblock %}
{% block modal-form-close %}</form>{% endblock %} {% block modal-form-close %}</form>{% endblock %}

View file

@ -88,11 +88,14 @@
</li> </li>
{% endif %} {% endif %}
{% include 'snippets/reading_modals/want_to_read_modal.html' with book=active_shelf.book controls_text="want_to_read" controls_uid=uuid move_from=current.id refresh=True class="" %} {% join "want_to_read" uuid as modal_id %}
{% include 'snippets/reading_modals/want_to_read_modal.html' with book=active_shelf.book id=modal_id move_from=current.id refresh=True class="" %}
{% include 'snippets/reading_modals/start_reading_modal.html' with book=active_shelf.book controls_text="start_reading" controls_uid=uuid move_from=current.id refresh=True class="" %} {% join "start_reading" uuid as modal_id %}
{% include 'snippets/reading_modals/start_reading_modal.html' with book=active_shelf.book id=modal_id move_from=current.id refresh=True class="" %}
{% include 'snippets/reading_modals/finish_reading_modal.html' with book=active_shelf.book controls_text="finish_reading" controls_uid=uuid move_from=current.id readthrough=readthrough refresh=True class="" %} {% join "finish_reading" uuid as modal_id %}
{% include 'snippets/reading_modals/finish_reading_modal.html' with book=active_shelf.book id=modal_id move_from=current.id readthrough=readthrough refresh=True class="" %}
{% endwith %} {% endwith %}
{% endblock %} {% endblock %}

View file

@ -0,0 +1,10 @@
{% load utilities %}
<form name="fallback_form_{{ 0|uuid }}" method="GET" action="{{ fallback_url }}" autocomplete="off">
<button
type="submit"
class="button {{ class }}"
data-modal-open="{{ modal_id }}"
>
{{ button_text }}
</button>
</form>

View file

@ -6,7 +6,7 @@
{% with book.id|uuid as uuid %} {% with book.id|uuid as uuid %}
{% active_shelf book as active_shelf %} {% active_shelf book as active_shelf %}
{% latest_read_through book request.user as readthrough %} {% latest_read_through book request.user as readthrough %}
<div class="field has-addons mb-0" data-shelve-button-book="{{ book.id }}"> <div class="field has-addons mb-0 has-text-weight-normal" data-shelve-button-book="{{ book.id }}">
{% if switch_mode and active_shelf.book != book %} {% if switch_mode and active_shelf.book != book %}
<div class="control"> <div class="control">
{% include 'snippets/switch_edition_button.html' with edition=book size='is-small' %} {% include 'snippets/switch_edition_button.html' with edition=book size='is-small' %}
@ -19,13 +19,17 @@
{% endif %} {% endif %}
</div> </div>
{% include 'snippets/reading_modals/want_to_read_modal.html' with book=active_shelf.book controls_text="want_to_read" controls_uid=uuid %} {% join "want_to_read" uuid as modal_id %}
{% include 'snippets/reading_modals/want_to_read_modal.html' with book=active_shelf.book id=modal_id class="" %}
{% include 'snippets/reading_modals/start_reading_modal.html' with book=active_shelf.book controls_text="start_reading" controls_uid=uuid %} {% join "start_reading" uuid as modal_id %}
{% include 'snippets/reading_modals/start_reading_modal.html' with book=active_shelf.book id=modal_id class="" %}
{% include 'snippets/reading_modals/finish_reading_modal.html' with book=active_shelf.book controls_text="finish_reading" controls_uid=uuid readthrough=readthrough %} {% join "finish_reading" uuid as modal_id %}
{% include 'snippets/reading_modals/finish_reading_modal.html' with book=active_shelf.book id=modal_id readthrough=readthrough class="" %}
{% include 'snippets/reading_modals/progress_update_modal.html' with book=active_shelf.book controls_text="progress_update" controls_uid=uuid readthrough=readthrough %} {% join "progress_update" uuid as modal_id %}
{% include 'snippets/reading_modals/progress_update_modal.html' with book=active_shelf.book id=modal_id readthrough=readthrough class="" %}
{% endwith %} {% endwith %}
{% endif %} {% endif %}

View file

@ -16,19 +16,22 @@
{% trans "Start reading" as button_text %} {% trans "Start reading" as button_text %}
{% url 'reading-status' 'start' book.id as fallback_url %} {% url 'reading-status' 'start' book.id as fallback_url %}
{% include 'snippets/toggle/toggle_button.html' with class=class text=button_text controls_text="start_reading" controls_uid=button_uuid focus="modal_title_start_reading" disabled=is_current fallback_url=fallback_url %} {% join "start_reading" button_uuid as modal_id %}
{% include 'snippets/shelve_button/modal_button.html' with class=class fallback_url=fallback_url %}
{% elif shelf.identifier == 'read' %} {% elif shelf.identifier == 'read' %}
{% trans "Read" as button_text %} {% trans "Read" as button_text %}
{% url 'reading-status' 'finish' book.id as fallback_url %} {% url 'reading-status' 'finish' book.id as fallback_url %}
{% include 'snippets/toggle/toggle_button.html' with class=class text=button_text controls_text="finish_reading" controls_uid=button_uuid focus="modal_title_finish_reading" disabled=is_current fallback_url=fallback_url %} {% join "finish_reading" button_uuid as modal_id %}
{% include 'snippets/shelve_button/modal_button.html' with class=class fallback_url=fallback_url %}
{% elif shelf.identifier == 'to-read' %} {% elif shelf.identifier == 'to-read' %}
{% trans "Want to read" as button_text %} {% trans "Want to read" as button_text %}
{% url 'reading-status' 'want' book.id as fallback_url %} {% url 'reading-status' 'want' book.id as fallback_url %}
{% include 'snippets/toggle/toggle_button.html' with class=class text=button_text controls_text="want_to_read" controls_uid=button_uuid focus="modal_title_want_to_read" disabled=is_current fallback_url=fallback_url %} {% join "want_to_read" button_uuid as modal_id %}
{% include 'snippets/shelve_button/modal_button.html' with class=class fallback_url=fallback_url %}
{% elif shelf.editable %} {% elif shelf.editable %}
@ -47,8 +50,9 @@
{% if readthrough and active_shelf.shelf.identifier != 'read' %} {% if readthrough and active_shelf.shelf.identifier != 'read' %}
<li role="menuitem" class="dropdown-item p-0" data-extra-options> <li role="menuitem" class="dropdown-item p-0" data-extra-options>
{% trans "Update progress" as button_text %} <button type="submit" class="button {{ class }}" data-modal-open="progress_update_{{ button_uuid }}">
{% include 'snippets/toggle/toggle_button.html' with class=class text=button_text controls_text="progress_update" controls_uid=button_uuid focus="modal_title_progress_update" %} {% trans "Update progress" %}
</button>
</li> </li>
{% endif %} {% endif %}

View file

@ -23,19 +23,22 @@
{% trans "Start reading" as button_text %} {% trans "Start reading" as button_text %}
{% url 'reading-status' 'start' book.id as fallback_url %} {% url 'reading-status' 'start' book.id as fallback_url %}
{% include 'snippets/toggle/toggle_button.html' with class=class text=button_text controls_text="start_reading" controls_uid=button_uuid focus="modal_title_start_reading" fallback_url=fallback_url %} {% join "start_reading" button_uuid as modal_id %}
{% include 'snippets/shelve_button/modal_button.html' with class=class fallback_url=fallback_url %}
{% elif shelf.identifier == 'read' %} {% elif shelf.identifier == 'read' %}
{% trans "Finish reading" as button_text %} {% trans "Finish reading" as button_text %}
{% url 'reading-status' 'finish' book.id as fallback_url %} {% url 'reading-status' 'finish' book.id as fallback_url %}
{% include 'snippets/toggle/toggle_button.html' with class=class text=button_text controls_text="finish_reading" controls_uid=button_uuid focus="modal_title_finish_reading" fallback_url=fallback_url %} {% join "finish_reading" button_uuid as modal_id %}
{% include 'snippets/shelve_button/modal_button.html' with class=class fallback_url=fallback_url %}
{% elif shelf.identifier == 'to-read' %} {% elif shelf.identifier == 'to-read' %}
{% trans "Want to read" as button_text %} {% trans "Want to read" as button_text %}
{% url 'reading-status' 'want' book.id as fallback_url %} {% url 'reading-status' 'want' book.id as fallback_url %}
{% include 'snippets/toggle/toggle_button.html' with class=class text=button_text controls_text="want_to_read" controls_uid=button_uuid focus="modal_title_want_to_read" fallback_url=fallback_url %} {% join "want_to_read" button_uuid as modal_id %}
{% include 'snippets/shelve_button/modal_button.html' with class=class fallback_url=fallback_url %}
{% elif shelf.editable %} {% elif shelf.editable %}

View file

@ -17,6 +17,12 @@ def get_uuid(identifier):
return f"{identifier}{uuid4()}" return f"{identifier}{uuid4()}"
@register.simple_tag(takes_context=False)
def join(*args):
"""concatenate an arbitary set of values"""
return "_".join(str(a) for a in args)
@register.filter(name="username") @register.filter(name="username")
def get_user_identifier(user): def get_user_identifier(user):
"""use localname for local users, username for remote""" """use localname for local users, username for remote"""

View file

@ -483,7 +483,6 @@ class ModelFields(TestCase):
instance.set_field_from_activity(book, mock_activity) instance.set_field_from_activity(book, mock_activity)
self.assertIsNotNone(book.cover.name) self.assertIsNotNone(book.cover.name)
self.assertEqual(book.cover.size, 43200)
@responses.activate @responses.activate
def test_image_field_set_field_from_activity_no_overwrite_no_cover(self, *_): def test_image_field_set_field_from_activity_no_overwrite_no_cover(self, *_):
@ -510,7 +509,6 @@ class ModelFields(TestCase):
instance.set_field_from_activity(book, mock_activity, overwrite=False) instance.set_field_from_activity(book, mock_activity, overwrite=False)
self.assertIsNotNone(book.cover.name) self.assertIsNotNone(book.cover.name)
self.assertEqual(book.cover.size, 43200)
@responses.activate @responses.activate
def test_image_field_set_field_from_activity_no_overwrite_with_cover(self, *_): def test_image_field_set_field_from_activity_no_overwrite_with_cover(self, *_):
@ -539,14 +537,15 @@ class ModelFields(TestCase):
) )
book = Edition.objects.create(title="hello") book = Edition.objects.create(title="hello")
book.cover.save("test.jpg", ContentFile(output.getvalue())) book.cover.save("test.jpg", ContentFile(output.getvalue()))
self.assertEqual(book.cover.size, 2136) cover_size = book.cover.size
self.assertIsNotNone(cover_size)
MockActivity = namedtuple("MockActivity", ("cover")) MockActivity = namedtuple("MockActivity", ("cover"))
mock_activity = MockActivity("http://www.example.com/image.jpg") mock_activity = MockActivity("http://www.example.com/image.jpg")
instance.set_field_from_activity(book, mock_activity, overwrite=False) instance.set_field_from_activity(book, mock_activity, overwrite=False)
# same cover as before # same cover as before
self.assertEqual(book.cover.size, 2136) self.assertEqual(book.cover.size, cover_size)
@responses.activate @responses.activate
def test_image_field_set_field_from_activity_with_overwrite_with_cover(self, *_): def test_image_field_set_field_from_activity_with_overwrite_with_cover(self, *_):
@ -559,7 +558,8 @@ class ModelFields(TestCase):
image.save(output, format=image.format) image.save(output, format=image.format)
book = Edition.objects.create(title="hello") book = Edition.objects.create(title="hello")
book.cover.save("test.jpg", ContentFile(output.getvalue())) book.cover.save("test.jpg", ContentFile(output.getvalue()))
self.assertEqual(book.cover.size, 2136) cover_size = book.cover.size
self.assertIsNotNone(cover_size)
another_image_file = pathlib.Path(__file__).parent.joinpath( another_image_file = pathlib.Path(__file__).parent.joinpath(
"../../static/images/logo.png" "../../static/images/logo.png"
@ -583,7 +583,7 @@ class ModelFields(TestCase):
instance.set_field_from_activity(book, mock_activity, overwrite=True) instance.set_field_from_activity(book, mock_activity, overwrite=True)
# new cover # new cover
self.assertIsNotNone(book.cover.name) self.assertIsNotNone(book.cover.name)
self.assertEqual(book.cover.size, 376800) self.assertNotEqual(book.cover.size, cover_size)
def test_datetime_field(self, *_): def test_datetime_field(self, *_):
"""this one is pretty simple, it just has to use isoformat""" """this one is pretty simple, it just has to use isoformat"""

View file

@ -24,7 +24,7 @@ LAST_DAY = 15
class AnnualSummary(View): class AnnualSummary(View):
"""display a summary of the year for the current user""" """display a summary of the year for the current user"""
def get(self, request, username, year): def get(self, request, username, year): # pylint: disable=too-many-locals
"""get response""" """get response"""
user = get_user_from_username(request.user, username) user = get_user_from_username(request.user, username)
@ -79,6 +79,9 @@ class AnnualSummary(View):
) )
ratings_stats = ratings.aggregate(Avg("rating")) ratings_stats = ratings.aggregate(Avg("rating"))
# annual goal status
goal_status = get_goal_status(user, year)
data = { data = {
"summary_user": user, "summary_user": user,
"year": year, "year": year,
@ -101,6 +104,7 @@ class AnnualSummary(View):
review.book.id for review in ratings.filter(rating=5) review.book.id for review in ratings.filter(rating=5)
], ],
"paginated_years": paginated_years, "paginated_years": paginated_years,
"goal_status": goal_status,
} }
return TemplateResponse(request, "annual_summary/layout.html", data) return TemplateResponse(request, "annual_summary/layout.html", data)
@ -208,3 +212,17 @@ def get_books_from_shelfbooks(books_ids):
books = models.Edition.objects.filter(id__in=books_ids).order_by(ordered) books = models.Edition.objects.filter(id__in=books_ids).order_by(ordered)
return books return books
def get_goal_status(user, year):
"""return a dict with the year's goal status"""
try:
goal = models.AnnualGoal.objects.get(user=user, year=year)
except models.AnnualGoal.DoesNotExist:
return None
if goal.privacy != "public":
return None
return dict(**goal.progress, **{"goal": goal.goal})

View file

@ -26,15 +26,15 @@ class Feed(View):
def post(self, request, tab): def post(self, request, tab):
"""save feed settings form, with a silent validation fail""" """save feed settings form, with a silent validation fail"""
settings_saved = False filters_applied = False
form = forms.FeedStatusTypesForm(request.POST, instance=request.user) form = forms.FeedStatusTypesForm(request.POST, instance=request.user)
if form.is_valid(): if form.is_valid():
form.save() form.save()
settings_saved = True filters_applied = True
return self.get(request, tab, settings_saved) return self.get(request, tab, filters_applied)
def get(self, request, tab, settings_saved=False): def get(self, request, tab, filters_applied=False):
"""user's homepage with activity feed""" """user's homepage with activity feed"""
tab = [s for s in STREAMS if s["key"] == tab] tab = [s for s in STREAMS if s["key"] == tab]
tab = tab[0] if tab else STREAMS[0] tab = tab[0] if tab else STREAMS[0]
@ -61,7 +61,7 @@ class Feed(View):
"goal_form": forms.GoalForm(), "goal_form": forms.GoalForm(),
"feed_status_types_options": FeedFilterChoices, "feed_status_types_options": FeedFilterChoices,
"allowed_status_types": request.user.feed_status_types, "allowed_status_types": request.user.feed_status_types,
"settings_saved": settings_saved, "filters_applied": filters_applied,
"path": f"/{tab['key']}", "path": f"/{tab['key']}",
"annual_summary_year": get_annual_summary_year(), "annual_summary_year": get_annual_summary_year(),
}, },