2
0
Fork 0

Tag version 0.7.0.

-----BEGIN PGP SIGNATURE-----
 
 iQIzBAABCAAdFiEEHCg/i5LQ+bsUm3NgpDqESxFPmwgFAmUz+08ACgkQpDqESxFP
 mwgs3w/7BgDnbP68IKA6r59JqWGIYmtSdP+U092b7tyrKWsK/RbEfuUowt6qx2+I
 p2BHmWNOToueMHr8nWwWEVyZvXfuFxFV03a3WXD6ifCss0zPZVRVfpENPptxqyxy
 1A8dVveycM8Bv3ZcemMsHsx2ocPqdeBFB+TjHespq2RFg8miWK4D3+UpgguorCa0
 qiS8R0ZQERRIWZnnKxgaJ+Sme+2aMbF/lUslL+1dnTeHrEMVGpmBiKXELV6TYYE2
 Tmw8gSZKpe9K98e3NN1K94TGEruAJuZtGc8ekxRMaEXRDMvjasK1Nznz4FUNHsbM
 ExwDY1+7FEA5D3T/PIlhtAdf6szeHvRs1mIoEbtLw3T+OIIfNugkqnUz0Xv5Bz9+
 kKoWxxFmvYNHZxEggTQT4ik0ufaMuEHMQofYtKHJPqzottgS0cdV+cusda87hZ95
 3+0RH8vJYzYk1iPlkdtct5p6f9mMljF9T91PcHrbb9GgOI8NKBmHyQWtpIDB00lT
 MQEhbtz2VUy572JB3w8vXXPb79L700Obrw7DZXk/K4c0ULM27fZX+PCbXKKITR5H
 vnsrhQcVT4FP46nRDAz0COWVdAsC7SrLPhoq9WmWR4WVea/PpvIsODURLdmmsGIy
 lqj2WnF6LiY965JLvCtbuyDO2wNMXjv24j+22aPmZ12wgwe14N8=
 =vWVR
 -----END PGP SIGNATURE-----

Merge tag 'v0.7.0' into nix
This commit is contained in:
Dee Anzorge 2023-10-24 23:07:11 +02:00
commit 23c8ffeae9
48 changed files with 820 additions and 552 deletions

1
.prettierrc Normal file
View file

@ -0,0 +1 @@
'trailingComma': 'es5'

1
VERSION Normal file
View file

@ -0,0 +1 @@
0.6.6

View file

@ -201,14 +201,13 @@ class Book(BookDataModel):
@property
def alt_text(self):
"""image alt test"""
text = self.title
if self.edition_info:
text += f" ({self.edition_info})"
return text
author = f"{name}: " if (name := self.author_text) else ""
edition = f" ({info})" if (info := self.edition_info) else ""
return f"{author}{self.title}{edition}"
def save(self, *args: Any, **kwargs: Any) -> None:
"""can't be abstract for query reasons, but you shouldn't USE it"""
if not isinstance(self, Edition) and not isinstance(self, Work):
if not isinstance(self, (Edition, Work)):
raise ValueError("Books should be added as Editions or Works")
return super().save(*args, **kwargs)

View file

@ -1,5 +1,6 @@
""" activitypub-aware django model fields """
from dataclasses import MISSING
from datetime import datetime
import re
from uuid import uuid4
from urllib.parse import urljoin
@ -534,8 +535,10 @@ class DateTimeField(ActivitypubFieldMixin, models.DateTimeField):
return value.isoformat()
def field_from_activity(self, value, allow_external_connections=True):
missing_fields = datetime(1970, 1, 1) # "2022-10" => "2022-10-01"
try:
date_value = dateutil.parser.parse(value)
# TODO(dato): investigate `ignoretz=True` wrt bookwyrm#3028.
date_value = dateutil.parser.parse(value, default=missing_fields)
try:
return timezone.make_aware(date_value)
except ValueError:

View file

@ -1,4 +1,5 @@
""" track progress of goodreads imports """
from datetime import datetime
import math
import re
import dateutil.parser
@ -259,38 +260,30 @@ class ImportItem(models.Model):
except ValueError:
return None
def _parse_datefield(self, field, /):
if not (date := self.normalized_data.get(field)):
return None
defaults = datetime(1970, 1, 1) # "2022-10" => "2022-10-01"
parsed = dateutil.parser.parse(date, default=defaults)
# Keep timezone if import already had one, else use default.
return parsed if timezone.is_aware(parsed) else timezone.make_aware(parsed)
@property
def date_added(self):
"""when the book was added to this dataset"""
if self.normalized_data.get("date_added"):
parsed_date_added = dateutil.parser.parse(
self.normalized_data.get("date_added")
)
if timezone.is_aware(parsed_date_added):
# Keep timezone if import already had one
return parsed_date_added
return timezone.make_aware(parsed_date_added)
return None
return self._parse_datefield("date_added")
@property
def date_started(self):
"""when the book was started"""
if self.normalized_data.get("date_started"):
return timezone.make_aware(
dateutil.parser.parse(self.normalized_data.get("date_started"))
)
return None
return self._parse_datefield("date_started")
@property
def date_read(self):
"""the date a book was completed"""
if self.normalized_data.get("date_finished"):
return timezone.make_aware(
dateutil.parser.parse(self.normalized_data.get("date_finished"))
)
return None
return self._parse_datefield("date_finished")
@property
def reads(self):

View file

@ -366,7 +366,8 @@ class Quotation(BookStatus):
quote = re.sub(r"^<p>", '<p>"', self.quote)
quote = re.sub(r"</p>$", '"</p>', quote)
title, href = self.book.title, self.book.remote_id
citation = f'— <a href="{href}"><i>{title}</i></a>'
author = f"{name}: " if (name := self.book.author_text) else ""
citation = f'{author}<a href="{href}"><i>{title}</i></a>'
if position := self._format_position():
citation += f", {position}"
return f"{quote} <p>{citation}</p>{self.content}"

View file

@ -4,6 +4,7 @@ from typing import AnyStr
from environs import Env
import requests
from django.utils.translation import gettext_lazy as _
from django.core.exceptions import ImproperlyConfigured
@ -14,7 +15,13 @@ from django.core.exceptions import ImproperlyConfigured
env = Env()
env.read_env()
DOMAIN = env("DOMAIN")
VERSION = "0.6.6"
with open("VERSION", encoding="utf-8") as f:
version = f.read()
version = version.replace("\n", "")
f.close()
VERSION = version
RELEASE_API = env(
"RELEASE_API",

View file

@ -1,4 +1,3 @@
@charset "utf-8";
@import "vendor/bulma/bulma.sass";
@import "bookwyrm/all.scss";
@import "vendor/bulma/bulma";
@import "bookwyrm/all";

View file

@ -16,9 +16,7 @@
@import "components/status";
@import "components/tabs";
@import "components/toggle";
@import "overrides/bulma_overrides";
@import "utilities/a11y";
@import "utilities/alignments";
@import "utilities/colors";
@ -40,10 +38,12 @@ body {
width: 12px;
height: 12px;
}
::-webkit-scrollbar-thumb {
background: $scrollbar-thumb;
border-radius: 0.5em;
}
::-webkit-scrollbar-track {
background: $scrollbar-track;
}
@ -89,7 +89,6 @@ button::-moz-focus-inner {
/** Utilities not covered by Bulma
******************************************************************************/
.tag.is-small {
height: auto;
}
@ -144,7 +143,6 @@ button.button-paragraph {
vertical-align: middle;
}
/** States
******************************************************************************/
@ -159,7 +157,6 @@ button.button-paragraph {
cursor: not-allowed;
}
/* Notifications page
******************************************************************************/

View file

@ -43,7 +43,7 @@
max-height: 100%;
/* Useful when stretching under-sized images. */
image-rendering: optimizeQuality;
image-rendering: optimizequality;
image-rendering: smooth;
}

View file

@ -30,20 +30,20 @@
}
.copy-tooltip {
overflow: visible;
visibility: hidden;
width: 140px;
background-color: #555;
color: #fff;
text-align: center;
border-radius: 6px;
padding: 5px;
position: absolute;
z-index: 1;
margin-left: -30px;
margin-top: -45px;
opacity: 0;
transition: opacity 0.3s;
overflow: visible;
visibility: hidden;
width: 140px;
background-color: #555;
color: #fff;
text-align: center;
border-radius: 6px;
padding: 5px;
position: absolute;
z-index: 1;
margin-left: -30px;
margin-top: -45px;
opacity: 0;
transition: opacity 0.3s;
}
.copy-tooltip::after {
@ -54,5 +54,5 @@
margin-left: -60px;
border-width: 5px;
border-style: solid;
border-color: #555 transparent transparent transparent;
}
border-color: #555 transparent transparent;
}

View file

@ -44,12 +44,12 @@
.bw-tabs a:hover {
border-bottom-color: transparent;
color: $text
color: $text;
}
.bw-tabs a.is-active {
border-bottom-color: transparent;
color: $link
color: $link;
}
.bw-tabs.is-left {

View file

@ -43,6 +43,8 @@
<glyph unicode="&#xe937;" glyph-name="barcode" d="M0 832h128v-640h-128zM192 832h64v-640h-64zM320 832h64v-640h-64zM512 832h64v-640h-64zM768 832h64v-640h-64zM960 832h64v-640h-64zM640 832h32v-640h-32zM448 832h32v-640h-32zM864 832h32v-640h-32zM0 128h64v-64h-64zM192 128h64v-64h-64zM320 128h64v-64h-64zM640 128h64v-64h-64zM960 128h64v-64h-64zM768 128h128v-64h-128zM448 128h128v-64h-128z" />
<glyph unicode="&#xe97a;" glyph-name="spinner" d="M384 832c0 70.692 57.308 128 128 128s128-57.308 128-128c0-70.692-57.308-128-128-128s-128 57.308-128 128zM655.53 719.53c0 70.692 57.308 128 128 128s128-57.308 128-128c0-70.692-57.308-128-128-128s-128 57.308-128 128zM832 448c0 35.346 28.654 64 64 64s64-28.654 64-64c0-35.346-28.654-64-64-64s-64 28.654-64 64zM719.53 176.47c0 35.346 28.654 64 64 64s64-28.654 64-64c0-35.346-28.654-64-64-64s-64 28.654-64 64zM448.002 64c0 0 0 0 0 0 0 35.346 28.654 64 64 64s64-28.654 64-64c0 0 0 0 0 0 0-35.346-28.654-64-64-64s-64 28.654-64 64zM176.472 176.47c0 0 0 0 0 0 0 35.346 28.654 64 64 64s64-28.654 64-64c0 0 0 0 0 0 0-35.346-28.654-64-64-64s-64 28.654-64 64zM144.472 719.53c0 0 0 0 0 0 0 53.019 42.981 96 96 96s96-42.981 96-96c0 0 0 0 0 0 0-53.019-42.981-96-96-96s-96 42.981-96 96zM56 448c0 39.765 32.235 72 72 72s72-32.235 72-72c0-39.765-32.235-72-72-72s-72 32.235-72 72z" />
<glyph unicode="&#xe986;" glyph-name="search" d="M992.262 88.604l-242.552 206.294c-25.074 22.566-51.89 32.926-73.552 31.926 57.256 67.068 91.842 154.078 91.842 249.176 0 212.078-171.922 384-384 384-212.076 0-384-171.922-384-384s171.922-384 384-384c95.098 0 182.108 34.586 249.176 91.844-1-21.662 9.36-48.478 31.926-73.552l206.294-242.552c35.322-39.246 93.022-42.554 128.22-7.356s31.892 92.898-7.354 128.22zM384 320c-141.384 0-256 114.616-256 256s114.616 256 256 256 256-114.616 256-256-114.614-256-256-256z" />
<glyph unicode="&#xe9ce;" glyph-name="eye" d="M512 768c-223.318 0-416.882-130.042-512-320 95.118-189.958 288.682-320 512-320 223.312 0 416.876 130.042 512 320-95.116 189.958-288.688 320-512 320zM764.45 598.296c60.162-38.374 111.142-89.774 149.434-150.296-38.292-60.522-89.274-111.922-149.436-150.296-75.594-48.218-162.89-73.704-252.448-73.704-89.56 0-176.858 25.486-252.452 73.704-60.158 38.372-111.138 89.772-149.432 150.296 38.292 60.524 89.274 111.924 149.434 150.296 3.918 2.5 7.876 4.922 11.86 7.3-9.96-27.328-15.41-56.822-15.41-87.596 0-141.382 114.616-256 256-256 141.382 0 256 114.618 256 256 0 30.774-5.452 60.268-15.408 87.598 3.978-2.378 7.938-4.802 11.858-7.302v0zM512 544c0-53.020-42.98-96-96-96s-96 42.98-96 96 42.98 96 96 96 96-42.982 96-96z" />
<glyph unicode="&#xe9d1;" glyph-name="eye-blocked" d="M945.942 945.942c-18.746 18.744-49.136 18.744-67.882 0l-202.164-202.164c-51.938 15.754-106.948 24.222-163.896 24.222-223.318 0-416.882-130.042-512-320 41.122-82.124 100.648-153.040 173.022-207.096l-158.962-158.962c-18.746-18.746-18.746-49.136 0-67.882 9.372-9.374 21.656-14.060 33.94-14.060s24.568 4.686 33.942 14.058l864 864c18.744 18.746 18.744 49.138 0 67.884zM416 640c42.24 0 78.082-27.294 90.92-65.196l-121.724-121.724c-37.902 12.838-65.196 48.68-65.196 90.92 0 53.020 42.98 96 96 96zM110.116 448c38.292 60.524 89.274 111.924 149.434 150.296 3.918 2.5 7.876 4.922 11.862 7.3-9.962-27.328-15.412-56.822-15.412-87.596 0-54.89 17.286-105.738 46.7-147.418l-60.924-60.924c-52.446 36.842-97.202 83.882-131.66 138.342zM768 518c0 27.166-4.256 53.334-12.102 77.898l-321.808-321.808c24.568-7.842 50.742-12.090 77.91-12.090 141.382 0 256 114.618 256 256zM830.026 670.026l-69.362-69.362c1.264-0.786 2.53-1.568 3.786-2.368 60.162-38.374 111.142-89.774 149.434-150.296-38.292-60.522-89.274-111.922-149.436-150.296-75.594-48.218-162.89-73.704-252.448-73.704-38.664 0-76.902 4.76-113.962 14.040l-76.894-76.894c59.718-21.462 123.95-33.146 190.856-33.146 223.31 0 416.876 130.042 512 320-45.022 89.916-112.118 166.396-193.974 222.026z" />
<glyph unicode="&#xe9d7;" glyph-name="star-empty" d="M1024 562.95l-353.78 51.408-158.22 320.582-158.216-320.582-353.784-51.408 256-249.538-60.432-352.352 316.432 166.358 316.432-166.358-60.434 352.352 256.002 249.538zM512 206.502l-223.462-117.48 42.676 248.83-180.786 176.222 249.84 36.304 111.732 226.396 111.736-226.396 249.836-36.304-180.788-176.222 42.678-248.83-223.462 117.48z" />
<glyph unicode="&#xe9d8;" glyph-name="star-half" d="M1024 562.95l-353.78 51.408-158.22 320.582-158.216-320.582-353.784-51.408 256-249.538-60.432-352.352 316.432 166.358 316.432-166.358-60.434 352.352 256.002 249.538zM512 206.502l-0.942-0.496 0.942 570.768 111.736-226.396 249.836-36.304-180.788-176.222 42.678-248.83-223.462 117.48z" />
<glyph unicode="&#xe9d9;" glyph-name="star-full" d="M1024 562.95l-353.78 51.408-158.22 320.582-158.216-320.582-353.784-51.408 256-249.538-60.432-352.352 316.432 166.358 316.432-166.358-60.434 352.352 256.002 249.538z" />

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 36 KiB

View file

@ -1,4 +1,4 @@
@import "../vendor/bulma/sass/utilities/initial-variables.sass";
@import "../vendor/bulma/sass/utilities/initial-variables";
/* Colors
******************************************************************************/
@ -16,7 +16,7 @@ $danger-light: #481922;
$light: #393939;
$red: #ffa1b4;
$black: #000;
$white-ter: hsl(0, 0%, 90%);
$white-ter: hsl(0deg, 0%, 90%);
/* book cover standins */
$no-cover-color: #002549;
@ -79,7 +79,7 @@ $info-dark: #72b6ee;
}
/* misc */
$shadow: 0 0.5em 0.5em -0.125em rgba($black, 0.2), 0 0px 0 1px rgba($black, 0.02);
$shadow: 0 0.5em 0.5em -0.125em rgba($black, 0.2), 0 0 0 1px rgba($black, 0.02);
$card-header-shadow: 0 0.125em 0.25em rgba($black, 0.1);
$invisible-overlay-background-color: rgba($black, 0.66);
$progress-value-background-color: $border-light;
@ -97,27 +97,23 @@ $family-secondary: $family-sans-serif;
color: $grey-light !important;
}
.tabs li:not(.is-active) a {
color: #2e7eb9 !important;
}
.tabs li:not(.is-active) a:hover {
.tabs li:not(.is-active) a:hover {
border-bottom-color: #2e7eb9 !important;
}
.tabs li:not(.is-active) a {
color: #2e7eb9 !important;
}
.tabs li.is-active a {
color: #e6e6e6 !important;
border-bottom-color: #e6e6e6 !important ;
border-bottom-color: #e6e6e6 !important;
}
#qrcode svg {
background-color: #a6a6a6;
}
@import "../bookwyrm.scss";
@import "../bookwyrm";
@import "../vendor/icons.css";
@import "../vendor/shepherd.scss";
@import "../vendor/shepherd";

View file

@ -1,4 +1,4 @@
@import "../vendor/bulma/sass/utilities/derived-variables.sass";
@import "../vendor/bulma/sass/utilities/derived-variables";
/* Colors
******************************************************************************/
@ -68,19 +68,16 @@ $family-secondary: $family-sans-serif;
.tabs li:not(.is-active) a {
color: #3273dc !important;
}
.tabs li:not(.is-active) a:hover {
border-bottom-color: #3273dc !important;
}
.tabs li:not(.is-active) a {
color: #3273dc !important;
.tabs li:not(.is-active) a:hover {
border-bottom-color: #3273dc !important;
}
.tabs li.is-active a {
color: #4a4a4a !important;
border-bottom-color: #4a4a4a !important ;
border-bottom-color: #4a4a4a !important;
}
@import "../bookwyrm.scss";
@import "../bookwyrm";
@import "../vendor/icons.css";
@import "../vendor/shepherd.scss";
@import "../vendor/shepherd";

View file

@ -155,3 +155,9 @@
.icon-barcode:before {
content: "\e937";
}
.icon-eye:before {
content: "\e9ce";
}
.icon-eye-blocked:before {
content: "\e9d1";
}

View file

@ -30,6 +30,12 @@ let BookWyrm = new (class {
.querySelectorAll("[data-back]")
.forEach((button) => button.addEventListener("click", this.back));
document
.querySelectorAll("[data-password-icon]")
.forEach((button) =>
button.addEventListener("click", this.togglePasswordVisibility.bind(this))
);
document
.querySelectorAll('input[type="file"]')
.forEach((node) => node.addEventListener("change", this.disableIfTooLarge.bind(this)));
@ -820,4 +826,24 @@ let BookWyrm = new (class {
form.querySelector('input[name="preferred_timezone"]').value = tz;
}
togglePasswordVisibility(event) {
const iconElement = event.currentTarget.getElementsByTagName("button")[0];
const passwordElementId = event.currentTarget.dataset.for;
const passwordInputElement = document.getElementById(passwordElementId);
if (!passwordInputElement) return;
if (passwordInputElement.type === "password") {
passwordInputElement.type = "text";
this.addRemoveClass(iconElement, "icon-eye-blocked");
this.addRemoveClass(iconElement, "icon-eye", true);
} else {
passwordInputElement.type = "password";
this.addRemoveClass(iconElement, "icon-eye");
this.addRemoveClass(iconElement, "icon-eye-blocked", true);
}
this.toggleFocus(passwordElementId);
}
})();

View file

@ -47,12 +47,11 @@
.querySelectorAll("[data-remove]")
.forEach((node) => node.addEventListener("click", removeInput));
// Get the element, add a keypress listener...
// Get element, add a keypress listener...
document.getElementById("subjects").addEventListener("keypress", function (e) {
// e.target is the element where it listens!
// if e.target is input field within the "subjects" div, do stuff
// Linstening to element e.target
// If e.target is an input field within "subjects" div preventDefault()
if (e.target && e.target.nodeName == "INPUT") {
// Item found, prevent default
if (event.keyCode == 13) {
event.preventDefault();
}

View file

@ -144,14 +144,6 @@
</a>
</div>
{% endif %}
{% if author.isfdb %}
<div>
<a itemprop="sameAs" href="https://www.isfdb.org/cgi-bin/ea.cgi?{{ author.isfdb }}" target="_blank" rel="nofollow noopener noreferrer">
{% trans "View ISFDB entry" %}
</a>
</div>
{% endif %}
</div>
</section>
{% endif %}

View file

@ -44,16 +44,18 @@
{% endif %}
{% if book.series %}
<meta itemprop="isPartOf" content="{{ book.series | escape }}">
<meta itemprop="volumeNumber" content="{{ book.series_number }}">
<meta itemprop="position" content="{{ book.series_number }}">
<span itemprop="isPartOf" itemscope itemtype="https://schema.org/BookSeries">
{% if book.authors.exists %}
<a href="{% url 'book-series-by' book.authors.first.id %}?series_name={{ book.series }}">
<a href="{% url 'book-series-by' book.authors.first.id %}?series_name={{ book.series | urlencode }}"
itemprop="url">
{% endif %}
{{ book.series }}{% if book.series_number %} #{{ book.series_number }}{% endif %}
<span itemprop="name">{{ book.series }}</span>
{% if book.series_number %} #{{ book.series_number }}{% endif %}
{% if book.authors.exists %}
</a>
{% endif %}
</span>
{% endif %}
</p>
{% endif %}
@ -186,8 +188,6 @@
itemtype="https://schema.org/AggregateRating"
>
<meta itemprop="ratingValue" content="{{ rating|floatformat }}">
{# @todo Is it possible to not hard-code the value? #}
<meta itemprop="bestRating" content="5">
<meta itemprop="reviewCount" content="{{ review_count }}">
<span>

View file

@ -40,16 +40,13 @@
</p>
{% endif %}
{% with date=book.published_date|naturalday publisher=book.publishers|join:', ' %}
{% if date or book.first_published_date or book.publishers %}
{% if date or book.first_published_date %}
{% if book.published_date or book.first_published_date %}
<meta
itemprop="datePublished"
content="{{ book.first_published_date|default:book.published_date|date:'Y-m-d' }}"
>
{% endif %}
<p>
{% comment %}
@todo The publisher property needs to be an Organization or a Person. Well be using Thing which is the more generic ancestor.
@see https://schema.org/Publisher
@ -60,14 +57,14 @@
{% endfor %}
{% endif %}
{% if date and publisher %}
{% with date=book.published_date|default:book.first_published_date|naturalday publisher=book.publishers|join:', ' %}
{% if book.published_date and publisher %}
{% blocktrans %}Published {{ date }} by {{ publisher }}.{% endblocktrans %}
{% elif date %}
{% blocktrans %}Published {{ date }}{% endblocktrans %}
{% elif publisher %}
{% blocktrans %}Published by {{ publisher }}.{% endblocktrans %}
{% elif date %}
{% blocktrans %}Published {{ date }}{% endblocktrans %}
{% endif %}
{% endwith %}
</p>
{% endif %}
{% endwith %}
{% endspaceless %}

View file

@ -5,13 +5,18 @@
{% include 'snippets/avatar.html' with user=user %}
</div>
<div class="media-content">
<div>
<a href="{{ user.local_path }}">{{ user.display_name }}</a>
<div class="media-content" itemprop="review" itemscope itemtype="https://schema.org/Review">
<div itemprop="author"
itemscope
itemtype="https://schema.org/Person"
>
<a href="{{ user.local_path }}" itemprop="url">
<span itemprop="name">{{ user.display_name }}</span>
</a>
</div>
<div class="is-flex">
<div class="is-flex" itemprop="reviewRating" itemscope itemtype="https://schema.org/Rating">
<meta itemprop="ratingValue" content="{{ rating.rating|floatformat }}">
<p class="mr-1">{% trans "rated it" %}</p>
{% include 'snippets/stars.html' with rating=rating.rating %}
</div>
<div>

View file

@ -5,15 +5,15 @@
{% block title %}{{ series_name }}{% endblock %}
{% block content %}
<div class="block">
<h1 class="title">{{ series_name }}</h1>
<div class="block" itemscope itemtype="https://schema.org/BookSeries">
<h1 class="title" itemprop="name">{{ series_name }}</h1>
<div class="subtitle" dir="auto">
{% trans "Series by" %} <a
href="{{ author.local_path }}"
class="author {{ link_class }}"
itemprop="author"
itemprop="creator"
itemscope
itemtype="https://schema.org/Thing"
itemtype="https://schema.org/Person"
><span
itemprop="name"
>{{ author.name }}</span></a>
@ -22,6 +22,7 @@
<div class="columns is-multiline is-mobile">
{% for book in books %}
{% with book=book %}
{# @todo Set `hasPart` property in some meaningful way #}
<div class="column is-one-fifth-tablet is-half-mobile is-flex is-flex-direction-column">
<div class="is-flex-grow-1 mb-3">
<span class="subtitle">{% if book.series_number %}{% blocktrans with series_number=book.series_number %}Book {{ series_number }}{% endblocktrans %}{% else %}{% trans 'Unsorted Book' %}{% endif %}</span>

View file

@ -12,6 +12,7 @@
<base target="_blank">
<link rel="shortcut icon" type="image/x-icon" href="{% if site.favicon %}{% get_media_prefix %}{{ site.favicon }}{% else %}{% static "images/favicon.ico" %}{% endif %}">
<link rel="manifest" href="/manifest.json" />
</head>
<body>

View file

@ -14,6 +14,7 @@
<link rel="shortcut icon" type="image/x-icon" href="{% if site.favicon %}{% get_media_prefix %}{{ site.favicon }}{% else %}{% static "images/favicon.ico" %}{% endif %}">
<link rel="apple-touch-icon" href="{% if site.logo %}{{ media_full_url }}{{ site.logo }}{% else %}{% static "images/logo.png" %}{% endif %}">
<link rel="manifest" href="/manifest.json" />
{% block opengraph %}
{% include 'snippets/opengraph.html' %}
@ -129,7 +130,12 @@
</div>
<div class="column">
<label class="is-sr-only" for="id_password">{% trans "Password:" %}</label>
<input type="password" name="password" maxlength="128" class="input" required="" id="id_password" placeholder="{% trans 'password' %}">
<div class="control has-icons-right">
<input type="password" name="password" maxlength="128" class="input" required="" id="id_password" placeholder="{% trans 'password' %}">
<span data-password-icon data-for="id_password" class="icon is-right is-clickable">
<button type="button" aria-controls="id_password" class="icon-eye-blocked" title="{% trans 'Show/Hide password' %}"></button>
</span>
</div>
<p class="help"><a href="{% url 'password-reset' %}">{% trans "Forgot your password?" %}</a></p>
</div>
<div class="column is-narrow">

View file

@ -0,0 +1,14 @@
{% load static %}
{
"name": "{{ site.name }}",
"description": "{{ site.description }}",
"icons": [
{
"src": "{% if site.logo %}{{ media_full_url }}{{ site.logo }}{% else %}{% static 'images/logo.png' %}{% endif %}",
"type": "image/png",
"sizes": "512x512"
}
],
"start_url": "/",
"display": "standalone"
}

View file

@ -6,14 +6,6 @@
{% load humanize %}
{% with status_type=status.status_type %}
<div
class="block"
{% if status_type == "Review" %}
itemprop="rating"
itemtype="https://schema.org/Rating"
{% endif %}
>
<div class="columns is-gapless">
{% if not hide_book %}
{% with book=status.book|default:status.mention_books.first %}
@ -58,9 +50,6 @@
{% endif %}
>
<meta itemprop="ratingValue" content="{{ status.rating|floatformat }}">
{# @todo Is it possible to not hard-code the value? #}
<meta itemprop="bestRating" content="5">
</span>
{% include 'snippets/stars.html' with rating=status.rating %}
</h4>
@ -154,6 +143,5 @@
</div>
</article>
</div>
</div>
{% endwith %}

View file

@ -16,9 +16,6 @@
>
<span class="is-hidden" {{ rating_type }}>
<meta itemprop="ratingValue" content="{{ status.rating|floatformat }}">
{# @todo Is it possible to not hard-code the value? #}
<meta itemprop="bestRating" content="5">
</span>
</span>
</span>

View file

@ -92,7 +92,23 @@ class Book(TestCase):
book.published_date = timezone.make_aware(parse("2020"))
book.save()
self.assertEqual(book.edition_info, "worm, Glorbish language, 2020")
self.assertEqual(book.alt_text, "Test Edition (worm, Glorbish language, 2020)")
def test_alt_text(self):
"""text slug used for cover images"""
book = models.Edition.objects.create(title="Test Edition")
author = models.Author.objects.create(name="Author Name")
self.assertEqual(book.alt_text, "Test Edition")
book.authors.set([author])
book.save()
self.assertEqual(book.alt_text, "Author Name: Test Edition")
book.physical_format = "worm"
book.published_date = timezone.make_aware(parse("2022"))
self.assertEqual(book.alt_text, "Author Name: Test Edition (worm, 2022)")
def test_get_rank(self):
"""sets the data quality index for the book"""

View file

@ -314,6 +314,29 @@ class Status(TestCase):
)
self.assertEqual(activity["attachment"][0]["name"], "Test Edition")
def test_quotation_with_author_to_pure_activity(self, *_):
"""serialization of quotation of a book with author and edition info"""
self.book.authors.set([models.Author.objects.create(name="Author Name")])
self.book.physical_format = "worm"
self.book.save()
status = models.Quotation.objects.create(
quote="quote",
content="",
user=self.local_user,
book=self.book,
)
activity = status.to_activity(pure=True)
self.assertEqual(
activity["content"],
(
f'quote <p>— Author Name: <a href="{self.book.remote_id}">'
"<i>Test Edition</i></a></p>"
),
)
self.assertEqual(
activity["attachment"][0]["name"], "Author Name: Test Edition (worm)"
)
def test_quotation_page_serialization(self, *_):
"""serialization of quotation page position"""
tests = [

View file

@ -7,6 +7,7 @@ from django.core.files.uploadedfile import SimpleUploadedFile
from django.template.response import TemplateResponse
from django.test import TestCase
from django.test.client import RequestFactory
from django.utils import timezone
from bookwyrm import forms, models, views
from bookwyrm.tests.validate_html import validate_html
@ -128,7 +129,7 @@ class ImportViews(TestCase):
def test_get_average_import_time_with_data(self):
"""Now, with data"""
now = datetime.datetime.now()
now = timezone.now()
two_hours_ago = now - datetime.timedelta(hours=2)
four_hours_ago = now - datetime.timedelta(hours=4)
models.ImportJob.objects.create(
@ -152,7 +153,7 @@ class ImportViews(TestCase):
def test_get_average_import_time_ignore_stopped(self):
"""Don't include stopped, do include no status"""
now = datetime.datetime.now()
now = timezone.now()
two_hours_ago = now - datetime.timedelta(hours=2)
four_hours_ago = now - datetime.timedelta(hours=4)
models.ImportJob.objects.create(

View file

@ -34,6 +34,12 @@ urlpatterns = [
"robots.txt",
TemplateView.as_view(template_name="robots.txt", content_type="text/plain"),
),
path(
"manifest.json",
TemplateView.as_view(
template_name="manifest.json", content_type="application/json"
),
),
# federation endpoints
re_path(r"^inbox/?$", views.Inbox.as_view(), name="inbox"),
re_path(rf"{LOCAL_USER_PATH}/inbox/?$", views.Inbox.as_view(), name="user_inbox"),

78
bump-version.sh Normal file
View file

@ -0,0 +1,78 @@
#!/bin/bash
NOW="$(date +'%B %d, %Y')"
RED="\033[1;31m"
GREEN="\033[0;32m"
YELLOW="\033[1;33m"
BLUE="\033[1;34m"
PURPLE="\033[1;35m"
CYAN="\033[1;36m"
WHITE="\033[1;37m"
RESET="\033[0m"
LATEST_HASH=`git log --pretty=format:'%h' -n 1`
QUESTION_FLAG="${GREEN}?"
WARNING_FLAG="${YELLOW}!"
NOTICE_FLAG="${CYAN}"
# ADJUSTMENTS_MSG="${QUESTION_FLAG} ${CYAN}Now you can make adjustments to ${WHITE}CHANGELOG.md${CYAN}. Then press enter to continue."
PUSHING_MSG="${NOTICE_FLAG} Pushing new version to the ${WHITE}origin${CYAN}..."
if [ -f VERSION ]; then
BASE_STRING=`cat VERSION`
BASE_LIST=(`echo $BASE_STRING | tr '.' ' '`)
V_MAJOR=${BASE_LIST[0]}
V_MINOR=${BASE_LIST[1]}
V_PATCH=${BASE_LIST[2]}
echo -e "${NOTICE_FLAG} Current version: ${WHITE}$BASE_STRING"
echo -e "${NOTICE_FLAG} Latest commit hash: ${WHITE}$LATEST_HASH"
V_MINOR=$((V_MINOR + 1))
V_PATCH=0
SUGGESTED_VERSION="$V_MAJOR.$V_MINOR.$V_PATCH"
echo -ne "${QUESTION_FLAG} ${CYAN}Enter a version number [${WHITE}$SUGGESTED_VERSION${CYAN}]: "
read INPUT_STRING
if [ "$INPUT_STRING" = "" ]; then
INPUT_STRING=$SUGGESTED_VERSION
fi
echo -e "${NOTICE_FLAG} Will set new version to be ${WHITE}$INPUT_STRING"
echo $INPUT_STRING > VERSION
# echo "## $INPUT_STRING ($NOW)" > tmpfile
# git log --pretty=format:" - %s" "v$BASE_STRING"...HEAD >> tmpfile
# echo "" >> tmpfile
# echo "" >> tmpfile
# cat CHANGELOG.md >> tmpfile
# mv tmpfile CHANGELOG.md
# echo -e "$ADJUSTMENTS_MSG"
# read
echo -e "$PUSHING_MSG"
# git add CHANGELOG.md VERSION
git commit -m "Bump version to ${INPUT_STRING}."
git tag -a -m "Tag version ${INPUT_STRING}." "v$INPUT_STRING"
git push origin --tags
else
echo -e "${WARNING_FLAG} Could not find a VERSION file."
echo -ne "${QUESTION_FLAG} ${CYAN}Do you want to create a version file and start from scratch? [${WHITE}y${CYAN}]: "
read RESPONSE
if [ "$RESPONSE" = "" ]; then RESPONSE="y"; fi
if [ "$RESPONSE" = "Y" ]; then RESPONSE="y"; fi
if [ "$RESPONSE" = "Yes" ]; then RESPONSE="y"; fi
if [ "$RESPONSE" = "yes" ]; then RESPONSE="y"; fi
if [ "$RESPONSE" = "YES" ]; then RESPONSE="y"; fi
if [ "$RESPONSE" = "y" ]; then
echo "0.1.0" > VERSION
echo "## 0.1.0 ($NOW)" > CHANGELOG.md
# git log --pretty=format:" - %s" >> CHANGELOG.md
# echo "" >> CHANGELOG.md
# echo "" >> CHANGELOG.md
# echo -e "$ADJUSTMENTS_MSG"
# read
echo -e "$PUSHING_MSG"
git add VERSION CHANGELOG.md
git commit -m "Add VERSION and CHANGELOG.md files, Bump version to v0.1.0."
git tag -a -m "Tag version 0.1.0." "v0.1.0"
git push origin --tags
fi
fi
echo -e "${NOTICE_FLAG} Finished."

18
bw-dev
View file

@ -188,27 +188,25 @@ case "$CMD" in
;;
prettier)
prod_error
$DOCKER_COMPOSE run --rm dev-tools npx prettier --write bookwyrm/static/js/*.js
$DOCKER_COMPOSE run --rm dev-tools prettier --write bookwyrm/static/js/*.js
;;
eslint)
prod_error
$DOCKER_COMPOSE run --rm dev-tools npx eslint bookwyrm/static --ext .js
$DOCKER_COMPOSE run --rm dev-tools eslint bookwyrm/static --ext .js
;;
stylelint)
prod_error
$DOCKER_COMPOSE run --rm dev-tools npx stylelint \
bookwyrm/static/css/bookwyrm.scss bookwyrm/static/css/bookwyrm/**/*.scss --fix \
--config dev-tools/.stylelintrc.js
$DOCKER_COMPOSE run --rm dev-tools stylelint --fix bookwyrm/static/css \
--config dev-tools/.stylelintrc.js --ignore-path dev-tools/.stylelintignore
;;
formatters)
prod_error
runweb pylint bookwyrm/
$DOCKER_COMPOSE run --rm dev-tools black celerywyrm bookwyrm
$DOCKER_COMPOSE run --rm dev-tools npx prettier --write bookwyrm/static/js/*.js
$DOCKER_COMPOSE run --rm dev-tools npx eslint bookwyrm/static --ext .js
$DOCKER_COMPOSE run --rm dev-tools npx stylelint \
bookwyrm/static/css/bookwyrm.scss bookwyrm/static/css/bookwyrm/**/*.scss --fix \
--config dev-tools/.stylelintrc.js
$DOCKER_COMPOSE run --rm dev-tools prettier --write bookwyrm/static/js/*.js
$DOCKER_COMPOSE run --rm dev-tools eslint bookwyrm/static --ext .js
$DOCKER_COMPOSE run --rm dev-tools stylelint --fix bookwyrm/static/css \
--config dev-tools/.stylelintrc.js --ignore-path dev-tools/.stylelintignore
;;
mypy)
prod_error

View file

@ -5,10 +5,30 @@ After=network.target postgresql.service redis.service
[Service]
User=bookwyrm
Group=bookwyrm
WorkingDirectory=/opt/bookwyrm/
WorkingDirectory=/opt/bookwyrm
ExecStart=/opt/bookwyrm/venv/bin/celery -A celerywyrm beat -l INFO --scheduler django_celery_beat.schedulers:DatabaseScheduler
StandardOutput=journal
StandardError=inherit
ProtectSystem=strict
ProtectHome=tmpfs
InaccessiblePaths=-/media -/mnt -/srv
PrivateTmp=yes
TemporaryFileSystem=/var /run /opt
PrivateUsers=true
PrivateDevices=true
BindReadOnlyPaths=/opt/bookwyrm
BindPaths=/opt/bookwyrm/images /opt/bookwyrm/static /var/run/postgresql
LockPersonality=yes
MemoryDenyWriteExecute=true
PrivateMounts=true
ProtectHostname=true
ProtectClock=true
ProtectKernelTunables=true
ProtectKernelModules=true
ProtectKernelLogs=true
ProtectControlGroups=true
RestrictRealtime=true
RestrictNamespaces=net
[Install]
WantedBy=multi-user.target

View file

@ -5,10 +5,30 @@ After=network.target postgresql.service redis.service
[Service]
User=bookwyrm
Group=bookwyrm
WorkingDirectory=/opt/bookwyrm/
WorkingDirectory=/opt/bookwyrm
ExecStart=/opt/bookwyrm/venv/bin/celery -A celerywyrm worker -l info -Q high_priority,medium_priority,low_priority,streams,images,suggested_users,email,connectors,lists,inbox,imports,import_triggered,broadcast,misc
StandardOutput=journal
StandardError=inherit
ProtectSystem=strict
ProtectHome=tmpfs
InaccessiblePaths=-/media -/mnt -/srv
PrivateTmp=yes
TemporaryFileSystem=/var /run /opt
PrivateUsers=true
PrivateDevices=true
BindReadOnlyPaths=/opt/bookwyrm
BindPaths=/opt/bookwyrm/images /opt/bookwyrm/static /var/run/postgresql
LockPersonality=yes
MemoryDenyWriteExecute=true
PrivateMounts=true
ProtectHostname=true
ProtectClock=true
ProtectKernelTunables=true
ProtectKernelModules=true
ProtectKernelLogs=true
ProtectControlGroups=true
RestrictRealtime=true
RestrictNamespaces=net
[Install]
WantedBy=multi-user.target

View file

@ -2,13 +2,33 @@
Description=BookWyrm
After=network.target postgresql.service redis.service
[Service]
[Service]P
User=bookwyrm
Group=bookwyrm
WorkingDirectory=/opt/bookwyrm/
ExecStart=/opt/bookwyrm/venv/bin/gunicorn bookwyrm.wsgi:application --threads=8 --bind 0.0.0.0:8000
WorkingDirectory=/opt/bookwyrm
ExecStart=/opt/bookwyrm/venv/bin/gunicorn bookwyrm.wsgi:application --bind 0.0.0.0:8000
StandardOutput=journal
StandardError=inherit
ProtectSystem=strict
ProtectHome=tmpfs
InaccessiblePaths=-/media -/mnt -/srv
PrivateTmp=yes
TemporaryFileSystem=/var /run /opt
PrivateUsers=true
PrivateDevices=true
BindReadOnlyPaths=/opt/bookwyrm
BindPaths=/opt/bookwyrm/images /opt/bookwyrm/static /var/run/postgresql
LockPersonality=yes
MemoryDenyWriteExecute=true
PrivateMounts=true
ProtectHostname=true
ProtectClock=true
ProtectKernelTunables=true
ProtectKernelModules=true
ProtectKernelLogs=true
ProtectControlGroups=true
RestrictRealtime=true
RestrictNamespaces=net
[Install]
WantedBy=multi-user.target

View file

@ -1 +1,2 @@
**/vendor/**
**/fonts/**

View file

@ -1,7 +1,7 @@
/* global module */
module.exports = {
"extends": "stylelint-config-standard",
"extends": "stylelint-config-standard-scss",
"plugins": [
"stylelint-order"
@ -18,5 +18,13 @@ module.exports = {
"declaration-block-no-redundant-longhand-properties": null,
"no-descending-specificity": null,
"alpha-value-notation": null
}
},
"overrides": [
{
"files": [ "../**/themes/bookwyrm-*.scss" ],
"rules": {
"no-invalid-position-at-import-rule": null
}
}
]
};

View file

@ -1,14 +1,17 @@
FROM python:3.9
WORKDIR /app/dev-tools
ENV PYTHONUNBUFFERED 1
ENV PATH="/app/dev-tools/node_modules/.bin:$PATH"
ENV PYTHONUNBUFFERED=1
ENV NPM_CONFIG_UPDATE_NOTIFIER=false
ENV PIP_ROOT_USER_ACTION=ignore PIP_DISABLE_PIP_VERSION_CHECK=1
COPY nodejs.sources /etc/apt/sources.list.d/
COPY package.json requirements.txt .stylelintrc.js .stylelintignore /app/dev-tools/
RUN apt-get update && \
apt-get install -y nodejs && \
pip install -r requirements.txt && \
npm install .
RUN mkdir /app
WORKDIR /app
COPY package.json requirements.txt .stylelintrc.js .stylelintignore /app/
RUN pip install -r requirements.txt
RUN apt-get update && apt-get install -y curl
RUN curl -sL https://deb.nodesource.com/setup_18.x | bash -
RUN apt-get install -y nodejs && apt-get clean
RUN npm install .

34
dev-tools/nodejs.sources Normal file
View file

@ -0,0 +1,34 @@
Types: deb
URIs: https://deb.nodesource.com/node_18.x
Suites: nodistro
Components: main
Signed-By:
-----BEGIN PGP PUBLIC KEY BLOCK-----
.
mQENBFdDN1ABCADaNd/I3j3tn40deQNgz7hB2NvT+syXe6k4ZmdiEcOfBvFrkS8B
hNS67t93etHsxEy7E0qwsZH32bKazMqe9zDwoa3aVImryjh6SHC9lMtW27JPHFeM
Srkt9YmH1WMwWcRO6eSY9B3PpazquhnvbammLuUojXRIxkDroy6Fw4UKmUNSRr32
9Ej87jRoR1B2/57Kfp2Y4+vFGGzSvh3AFQpBHq51qsNHALU6+8PjLfIt+5TPvaWR
TB+kAZnQZkaIQM2nr1n3oj6ak2RATY/+kjLizgFWzgEfbCrbsyq68UoY5FPBnu4Z
E3iDZpaIqwKr0seUC7iA1xM5eHi5kty1oB7HABEBAAG0Ik5Tb2xpZCA8bnNvbGlk
LWdwZ0Bub2Rlc291cmNlLmNvbT6JATgEEwECACIFAldDN1ACGwMGCwkIBwMCBhUI
AgkKCwQWAgMBAh4BAheAAAoJEC9ZtfmbG+C0y7wH/i4xnab36dtrYW7RZwL8i6Sc
NjMx4j9+U1kr/F6YtqWd+JwCbBdar5zRghxPcYEq/qf7MbgAYcs1eSOuTOb7n7+o
xUwdH2iCtHhKh3Jr2mRw1ks7BbFZPB5KmkxHaEBfLT4d+I91ZuUdPXJ+0SXs9gzk
Dbz65Uhoz3W03aiF8HeL5JNARZFMbHHNVL05U1sTGTCOtu+1c/33f3TulQ/XZ3Y4
hwGCpLe0Tv7g7Lp3iLMZMWYPEa0a7S4u8he5IEJQLd8bE8jltcQvrdr3Fm8kI2Jg
BJmUmX4PSfhuTCFaR/yeCt3UoW883bs9LfbTzIx9DJGpRIu8Y0IL3b4sj/GoZVq5
AQ0EV0M3UAEIAKrTaC62ayzqOIPa7nS90BHHck4Z33a2tZF/uof38xNOiyWGhT8u
JeFoTTHn5SQq5Ftyu4K3K2fbbpuu/APQF05AaljzVkDGNMW4pSkgOasdysj831cu
ssrHX2RYS22wg80k6C/Hwmh5F45faEuNxsV+bPx7oPUrt5n6GMx84vEP3i1+FDBi
0pt/B/QnDFBXki1BGvJ35f5NwDefK8VaInxXP3ZN/WIbtn5dqxppkV/YkO7GiJlp
Jlju9rf3kKUIQzKQWxFsbCAPIHoWv7rH9RSxgDithXtG6Yg5R1aeBbJaPNXL9wpJ
YBJbiMjkAFaz4B95FOqZm3r7oHugiCGsHX0AEQEAAYkBHwQYAQIACQUCV0M3UAIb
DAAKCRAvWbX5mxvgtE/OB/0VN88DR3Y3fuqy7lq/dthkn7Dqm9YXdorZl3L152eE
IF882aG8FE3qZdaLGjQO4oShAyNWmRfSGuoH0XERXAI9n0r8m4mDMxE6rtP7tHet
y/5M8x3CTyuMgx5GLDaEUvBusnTD+/v/fBMwRK/cZ9du5PSG4R50rtst+oYyC2ao
x4I2SgjtF/cY7bECsZDplzatN3gv34PkcdIg8SLHAVlL4N5tzumDeizRspcSyoy2
K2+hwKU4C4+dekLLTg8rjnRROvplV2KtaEk6rxKtIRFDCoQng8wfJuIMrDNKvqZw
FRGt7cbvW5MCnuH8MhItOl9Uxp1wHp6gtav/h8Gp6MBa
=MARt
-----END PGP PUBLIC KEY BLOCK-----

View file

@ -11,6 +11,7 @@
"stylelint-config-recommended": "^7.0.0",
"stylelint-config-standard": "^25.0.0",
"stylelint-order": "^5.0.0",
"stylelint-config-standard-scss": "^3.0.0",
"watch": "^0.13.0"
},
"dependencies": {

View file

@ -130,6 +130,7 @@ services:
build: dev-tools
env_file: .env
volumes:
- /app/dev-tools/
- .:/app
volumes:
pgdata:

788
poetry.lock generated

File diff suppressed because it is too large Load diff

View file

@ -25,7 +25,7 @@ gunicorn = "20.0.4"
libsass = "0.22.0"
Markdown = "3.4.1"
packaging = "21.3"
Pillow = "^9.4.0"
Pillow = "10.0.1"
psycopg2 = "2.9.5"
pycryptodome = "3.16.0"
python-dateutil = "2.8.2"