Merge branch 'main' into user-migration

This commit is contained in:
Hugh Rundle 2023-10-22 18:47:41 +11:00 committed by GitHub
commit 8477d0b89d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
91 changed files with 6697 additions and 3892 deletions

1
.prettierrc Normal file
View file

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

1
VERSION Normal file
View file

@ -0,0 +1 @@
0.7.0

View file

@ -4,7 +4,11 @@ import sys
from .base_activity import ActivityEncoder, Signature, naive_parse from .base_activity import ActivityEncoder, Signature, naive_parse
from .base_activity import Link, Mention, Hashtag from .base_activity import Link, Mention, Hashtag
from .base_activity import ActivitySerializerError, resolve_remote_id from .base_activity import (
ActivitySerializerError,
resolve_remote_id,
get_representative,
)
from .image import Document, Image from .image import Document, Image
from .note import Note, GeneratedNote, Article, Comment, Quotation from .note import Note, GeneratedNote, Article, Comment, Quotation
from .note import Review, Rating from .note import Review, Rating

View file

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

View file

@ -1,5 +1,6 @@
""" activitypub-aware django model fields """ """ activitypub-aware django model fields """
from dataclasses import MISSING from dataclasses import MISSING
from datetime import datetime
import re import re
from uuid import uuid4 from uuid import uuid4
from urllib.parse import urljoin from urllib.parse import urljoin
@ -534,8 +535,10 @@ class DateTimeField(ActivitypubFieldMixin, models.DateTimeField):
return value.isoformat() return value.isoformat()
def field_from_activity(self, value, allow_external_connections=True): def field_from_activity(self, value, allow_external_connections=True):
missing_fields = datetime(1970, 1, 1) # "2022-10" => "2022-10-01"
try: 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: try:
return timezone.make_aware(date_value) return timezone.make_aware(date_value)
except ValueError: except ValueError:

View file

@ -1,4 +1,5 @@
""" track progress of goodreads imports """ """ track progress of goodreads imports """
from datetime import datetime
import math import math
import re import re
import dateutil.parser import dateutil.parser
@ -259,38 +260,30 @@ class ImportItem(models.Model):
except ValueError: except ValueError:
return None 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 @property
def date_added(self): def date_added(self):
"""when the book was added to this dataset""" """when the book was added to this dataset"""
if self.normalized_data.get("date_added"): return self._parse_datefield("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
@property @property
def date_started(self): def date_started(self):
"""when the book was started""" """when the book was started"""
if self.normalized_data.get("date_started"): return self._parse_datefield("date_started")
return timezone.make_aware(
dateutil.parser.parse(self.normalized_data.get("date_started"))
)
return None
@property @property
def date_read(self): def date_read(self):
"""the date a book was completed""" """the date a book was completed"""
if self.normalized_data.get("date_finished"): return self._parse_datefield("date_finished")
return timezone.make_aware(
dateutil.parser.parse(self.normalized_data.get("date_finished"))
)
return None
@property @property
def reads(self): 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>"', self.quote)
quote = re.sub(r"</p>$", '"</p>', quote) quote = re.sub(r"</p>$", '"</p>', quote)
title, href = self.book.title, self.book.remote_id 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(): if position := self._format_position():
citation += f", {position}" citation += f", {position}"
return f"{quote} <p>{citation}</p>{self.content}" return f"{quote} <p>{citation}</p>{self.content}"

View file

@ -4,6 +4,7 @@ from typing import AnyStr
from environs import Env from environs import Env
import requests import requests
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from django.core.exceptions import ImproperlyConfigured from django.core.exceptions import ImproperlyConfigured
@ -14,7 +15,13 @@ from django.core.exceptions import ImproperlyConfigured
env = Env() env = Env()
env.read_env() env.read_env()
DOMAIN = env("DOMAIN") DOMAIN = env("DOMAIN")
VERSION = "0.6.5"
with open("VERSION", encoding="utf-8") as f:
version = f.read()
version = version.replace("\n", "")
f.close()
VERSION = version
RELEASE_API = env( RELEASE_API = env(
"RELEASE_API", "RELEASE_API",
@ -24,7 +31,7 @@ RELEASE_API = env(
PAGE_LENGTH = env.int("PAGE_LENGTH", 15) PAGE_LENGTH = env.int("PAGE_LENGTH", 15)
DEFAULT_LANGUAGE = env("DEFAULT_LANGUAGE", "English") DEFAULT_LANGUAGE = env("DEFAULT_LANGUAGE", "English")
JS_CACHE = "b972a43c" JS_CACHE = "ac315a3b"
# email # email
EMAIL_BACKEND = env("EMAIL_BACKEND", "django.core.mail.backends.smtp.EmailBackend") EMAIL_BACKEND = env("EMAIL_BACKEND", "django.core.mail.backends.smtp.EmailBackend")
@ -317,6 +324,7 @@ LANGUAGES = [
LANGUAGE_ARTICLES = { LANGUAGE_ARTICLES = {
"English": {"the", "a", "an"}, "English": {"the", "a", "an"},
"Español (Spanish)": {"un", "una", "unos", "unas", "el", "la", "los", "las"},
} }
TIME_ZONE = "UTC" TIME_ZONE = "UTC"

View file

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

View file

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

View file

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

View file

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

View file

@ -44,12 +44,12 @@
.bw-tabs a:hover { .bw-tabs a:hover {
border-bottom-color: transparent; border-bottom-color: transparent;
color: $text color: $text;
} }
.bw-tabs a.is-active { .bw-tabs a.is-active {
border-bottom-color: transparent; border-bottom-color: transparent;
color: $link color: $link;
} }
.bw-tabs.is-left { .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="&#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="&#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="&#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="&#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="&#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" /> <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 /* Colors
******************************************************************************/ ******************************************************************************/
@ -16,7 +16,7 @@ $danger-light: #481922;
$light: #393939; $light: #393939;
$red: #ffa1b4; $red: #ffa1b4;
$black: #000; $black: #000;
$white-ter: hsl(0, 0%, 90%); $white-ter: hsl(0deg, 0%, 90%);
/* book cover standins */ /* book cover standins */
$no-cover-color: #002549; $no-cover-color: #002549;
@ -79,7 +79,7 @@ $info-dark: #72b6ee;
} }
/* misc */ /* 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); $card-header-shadow: 0 0.125em 0.25em rgba($black, 0.1);
$invisible-overlay-background-color: rgba($black, 0.66); $invisible-overlay-background-color: rgba($black, 0.66);
$progress-value-background-color: $border-light; $progress-value-background-color: $border-light;
@ -97,27 +97,23 @@ $family-secondary: $family-sans-serif;
color: $grey-light !important; color: $grey-light !important;
} }
.tabs li:not(.is-active) a { .tabs li:not(.is-active) a {
color: #2e7eb9 !important; color: #2e7eb9 !important;
} }
.tabs li:not(.is-active) a:hover {
.tabs li:not(.is-active) a:hover {
border-bottom-color: #2e7eb9 !important; border-bottom-color: #2e7eb9 !important;
}
.tabs li:not(.is-active) a {
color: #2e7eb9 !important;
} }
.tabs li.is-active a { .tabs li.is-active a {
color: #e6e6e6 !important; color: #e6e6e6 !important;
border-bottom-color: #e6e6e6 !important ; border-bottom-color: #e6e6e6 !important;
} }
#qrcode svg { #qrcode svg {
background-color: #a6a6a6; background-color: #a6a6a6;
} }
@import "../bookwyrm.scss"; @import "../bookwyrm";
@import "../vendor/icons.css"; @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 /* Colors
******************************************************************************/ ******************************************************************************/
@ -68,19 +68,16 @@ $family-secondary: $family-sans-serif;
.tabs li:not(.is-active) a { .tabs li:not(.is-active) a {
color: #3273dc !important; color: #3273dc !important;
} }
.tabs li:not(.is-active) a:hover {
border-bottom-color: #3273dc !important;
}
.tabs li:not(.is-active) a { .tabs li:not(.is-active) a:hover {
color: #3273dc !important; border-bottom-color: #3273dc !important;
} }
.tabs li.is-active a { .tabs li.is-active a {
color: #4a4a4a !important; color: #4a4a4a !important;
border-bottom-color: #4a4a4a !important ; border-bottom-color: #4a4a4a !important;
} }
@import "../bookwyrm";
@import "../bookwyrm.scss";
@import "../vendor/icons.css"; @import "../vendor/icons.css";
@import "../vendor/shepherd.scss"; @import "../vendor/shepherd";

View file

@ -155,3 +155,9 @@
.icon-barcode:before { .icon-barcode:before {
content: "\e937"; 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]") .querySelectorAll("[data-back]")
.forEach((button) => button.addEventListener("click", this.back)); .forEach((button) => button.addEventListener("click", this.back));
document
.querySelectorAll("[data-password-icon]")
.forEach((button) =>
button.addEventListener("click", this.togglePasswordVisibility.bind(this))
);
document document
.querySelectorAll('input[type="file"]') .querySelectorAll('input[type="file"]')
.forEach((node) => node.addEventListener("change", this.disableIfTooLarge.bind(this))); .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; 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]") .querySelectorAll("[data-remove]")
.forEach((node) => node.addEventListener("click", removeInput)); .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) { document.getElementById("subjects").addEventListener("keypress", function (e) {
// e.target is the element where it listens! // Linstening to element e.target
// if e.target is input field within the "subjects" div, do stuff // If e.target is an input field within "subjects" div preventDefault()
if (e.target && e.target.nodeName == "INPUT") { if (e.target && e.target.nodeName == "INPUT") {
// Item found, prevent default
if (event.keyCode == 13) { if (event.keyCode == 13) {
event.preventDefault(); event.preventDefault();
} }

View file

@ -144,14 +144,6 @@
</a> </a>
</div> </div>
{% endif %} {% 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> </div>
</section> </section>
{% endif %} {% endif %}

View file

@ -44,16 +44,18 @@
{% endif %} {% endif %}
{% if book.series %} {% if book.series %}
<meta itemprop="isPartOf" content="{{ book.series | escape }}"> <meta itemprop="position" content="{{ book.series_number }}">
<meta itemprop="volumeNumber" content="{{ book.series_number }}"> <span itemprop="isPartOf" itemscope itemtype="https://schema.org/BookSeries">
{% if book.authors.exists %} {% 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 %} {% 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 %} {% if book.authors.exists %}
</a> </a>
{% endif %} {% endif %}
</span>
{% endif %} {% endif %}
</p> </p>
{% endif %} {% endif %}
@ -186,8 +188,6 @@
itemtype="https://schema.org/AggregateRating" itemtype="https://schema.org/AggregateRating"
> >
<meta itemprop="ratingValue" content="{{ rating|floatformat }}"> <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 }}"> <meta itemprop="reviewCount" content="{{ review_count }}">
<span> <span>

View file

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

View file

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

View file

@ -5,15 +5,15 @@
{% block title %}{{ series_name }}{% endblock %} {% block title %}{{ series_name }}{% endblock %}
{% block content %} {% block content %}
<div class="block"> <div class="block" itemscope itemtype="https://schema.org/BookSeries">
<h1 class="title">{{ series_name }}</h1> <h1 class="title" itemprop="name">{{ series_name }}</h1>
<div class="subtitle" dir="auto"> <div class="subtitle" dir="auto">
{% trans "Series by" %} <a {% trans "Series by" %} <a
href="{{ author.local_path }}" href="{{ author.local_path }}"
class="author {{ link_class }}" class="author {{ link_class }}"
itemprop="author" itemprop="creator"
itemscope itemscope
itemtype="https://schema.org/Thing" itemtype="https://schema.org/Person"
><span ><span
itemprop="name" itemprop="name"
>{{ author.name }}</span></a> >{{ author.name }}</span></a>
@ -22,6 +22,7 @@
<div class="columns is-multiline is-mobile"> <div class="columns is-multiline is-mobile">
{% for book in books %} {% for book in books %}
{% with book=book %} {% 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="column is-one-fifth-tablet is-half-mobile is-flex is-flex-direction-column">
<div class="is-flex-grow-1 mb-3"> <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> <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"> <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="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> </head>
<body> <body>

View file

@ -18,7 +18,7 @@
{% if import_size_limit and import_limit_reset %} {% if import_size_limit and import_limit_reset %}
<div class="notification"> <div class="notification">
<p> <p>
{% blocktrans count days=import_limit_reset with display_size=import_size_limit|intcomma %} {% blocktrans trimmed count days=import_limit_reset with display_size=import_size_limit|intcomma %}
Currently, you are allowed to import {{ display_size }} books every {{ import_limit_reset }} day. Currently, you are allowed to import {{ display_size }} books every {{ import_limit_reset }} day.
{% plural %} {% plural %}
Currently, you are allowed to import {{ import_size_limit }} books every {{ import_limit_reset }} days. Currently, you are allowed to import {{ import_size_limit }} books every {{ import_limit_reset }} days.

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="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="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 %} {% block opengraph %}
{% include 'snippets/opengraph.html' %} {% include 'snippets/opengraph.html' %}
@ -129,7 +130,12 @@
</div> </div>
<div class="column"> <div class="column">
<label class="is-sr-only" for="id_password">{% trans "Password:" %}</label> <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> <p class="help"><a href="{% url 'password-reset' %}">{% trans "Forgot your password?" %}</a></p>
</div> </div>
<div class="column is-narrow"> <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

@ -75,13 +75,13 @@
{% include 'snippets/form_errors.html' with errors_list=form.invite_request_text.errors id="desc_invite_request_text" %} {% include 'snippets/form_errors.html' with errors_list=form.invite_request_text.errors id="desc_invite_request_text" %}
</div> </div>
<div class="field"> <div class="field">
<label class="label" for="id_invite_requests_question"> <label class="label">
{{ form.invite_request_question }} {{ form.invite_request_question }}
{% trans "Set a question for invite requests" %} {% trans "Set a question for invite requests" %}
</label> </label>
</div> </div>
<div class="field"> <div class="field">
<label class="label" for="id_invite_question_text"> <label class="label">
{% trans "Question:" %} {% trans "Question:" %}
{{ form.invite_question_text }} {{ form.invite_question_text }}
</label> </label>

View file

@ -45,7 +45,7 @@
{% include 'snippets/form_errors.html' with errors_list=form.invite_request_text.errors id="desc_invite_request_text" %} {% include 'snippets/form_errors.html' with errors_list=form.invite_request_text.errors id="desc_invite_request_text" %}
</div> </div>
<div class="field"> <div class="field">
<label class="label" for="id_invite_requests_question"> <label class="label">
{{ form.invite_request_question }} {{ form.invite_request_question }}
{% trans "Set a question for invite requests" %} {% trans "Set a question for invite requests" %}
</label> </label>

View file

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

View file

@ -16,9 +16,6 @@
> >
<span class="is-hidden" {{ rating_type }}> <span class="is-hidden" {{ rating_type }}>
<meta itemprop="ratingValue" content="{{ status.rating|floatformat }}"> <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> </span>
</span> </span>

View file

@ -92,7 +92,23 @@ class Book(TestCase):
book.published_date = timezone.make_aware(parse("2020")) book.published_date = timezone.make_aware(parse("2020"))
book.save() book.save()
self.assertEqual(book.edition_info, "worm, Glorbish language, 2020") 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): def test_get_rank(self):
"""sets the data quality index for the book""" """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") 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, *_): def test_quotation_page_serialization(self, *_):
"""serialization of quotation page position""" """serialization of quotation page position"""
tests = [ tests = [

View file

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

View file

@ -156,7 +156,7 @@ class Views(TestCase):
response = view(request) response = view(request)
validate_html(response.render()) validate_html(response.render())
self.assertFalse("results" in response.context_data) self.assertTrue("results" in response.context_data)
def test_search_lists(self): def test_search_lists(self):
"""searches remote connectors""" """searches remote connectors"""

View file

@ -72,7 +72,7 @@ class SetupViews(TestCase):
self.site.refresh_from_db() self.site.refresh_from_db()
self.assertFalse(self.site.install_mode) self.assertFalse(self.site.install_mode)
user = models.User.objects.get() user = models.User.objects.first()
self.assertTrue(user.is_active) self.assertTrue(user.is_active)
self.assertTrue(user.is_superuser) self.assertTrue(user.is_superuser)
self.assertTrue(user.is_staff) self.assertTrue(user.is_staff)

View file

@ -34,6 +34,12 @@ urlpatterns = [
"robots.txt", "robots.txt",
TemplateView.as_view(template_name="robots.txt", content_type="text/plain"), 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 # federation endpoints
re_path(r"^inbox/?$", views.Inbox.as_view(), name="inbox"), re_path(r"^inbox/?$", views.Inbox.as_view(), name="inbox"),
re_path(rf"{LOCAL_USER_PATH}/inbox/?$", views.Inbox.as_view(), name="user_inbox"), re_path(rf"{LOCAL_USER_PATH}/inbox/?$", views.Inbox.as_view(), name="user_inbox"),

View file

@ -91,18 +91,15 @@ def book_search(request):
def user_search(request): def user_search(request):
"""cool kids members only user search""" """user search: search for a user"""
viewer = request.user viewer = request.user
query = request.GET.get("q") query = request.GET.get("q")
query = query.strip() query = query.strip()
data = {"type": "user", "query": query} data = {"type": "user", "query": query}
# logged out viewers can't search users
if not viewer.is_authenticated:
return TemplateResponse(request, "search/user.html", data)
# use webfinger for mastodon style account@domain.com username to load the user if # use webfinger for mastodon style account@domain.com username to load the user if
# they don't exist locally (handle_remote_webfinger will check the db) # they don't exist locally (handle_remote_webfinger will check the db)
if re.match(regex.FULL_USERNAME, query): if re.match(regex.FULL_USERNAME, query) and viewer.is_authenticated:
handle_remote_webfinger(query) handle_remote_webfinger(query)
results = ( results = (
@ -118,6 +115,11 @@ def user_search(request):
) )
.order_by("-similarity") .order_by("-similarity")
) )
# don't expose remote users
if not viewer.is_authenticated:
results = results.filter(local=True)
paginated = Paginator(results, PAGE_LENGTH) paginated = Paginator(results, PAGE_LENGTH)
page = paginated.get_page(request.GET.get("page")) page = paginated.get_page(request.GET.get("page"))
data["results"] = page data["results"] = page

View file

@ -9,6 +9,7 @@ from django.shortcuts import redirect
from django.template.response import TemplateResponse from django.template.response import TemplateResponse
from django.views import View from django.views import View
from bookwyrm.activitypub import get_representative
from bookwyrm import forms, models from bookwyrm import forms, models
from bookwyrm import settings from bookwyrm import settings
from bookwyrm.utils import regex from bookwyrm.utils import regex
@ -96,4 +97,5 @@ class CreateAdmin(View):
login(request, user) login(request, user)
site.install_mode = False site.install_mode = False
site.save() site.save()
get_representative() # create the instance user
return redirect("settings-site") return redirect("settings-site")

80
bump-version.sh Executable file
View file

@ -0,0 +1,80 @@
#!/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
git add 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 CHANGELOG.md
git add VERSION
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) prettier)
prod_error 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) eslint)
prod_error 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) stylelint)
prod_error prod_error
$DOCKER_COMPOSE run --rm dev-tools npx stylelint \ $DOCKER_COMPOSE run --rm dev-tools stylelint --fix bookwyrm/static/css \
bookwyrm/static/css/bookwyrm.scss bookwyrm/static/css/bookwyrm/**/*.scss --fix \ --config dev-tools/.stylelintrc.js --ignore-path dev-tools/.stylelintignore
--config dev-tools/.stylelintrc.js
;; ;;
formatters) formatters)
prod_error prod_error
runweb pylint bookwyrm/ runweb pylint bookwyrm/
$DOCKER_COMPOSE run --rm dev-tools black celerywyrm 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 prettier --write bookwyrm/static/js/*.js
$DOCKER_COMPOSE run --rm dev-tools npx eslint bookwyrm/static --ext .js $DOCKER_COMPOSE run --rm dev-tools eslint bookwyrm/static --ext .js
$DOCKER_COMPOSE run --rm dev-tools npx stylelint \ $DOCKER_COMPOSE run --rm dev-tools stylelint --fix bookwyrm/static/css \
bookwyrm/static/css/bookwyrm.scss bookwyrm/static/css/bookwyrm/**/*.scss --fix \ --config dev-tools/.stylelintrc.js --ignore-path dev-tools/.stylelintignore
--config dev-tools/.stylelintrc.js
;; ;;
mypy) mypy)
prod_error prod_error

View file

@ -5,10 +5,30 @@ After=network.target postgresql.service redis.service
[Service] [Service]
User=bookwyrm User=bookwyrm
Group=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 ExecStart=/opt/bookwyrm/venv/bin/celery -A celerywyrm beat -l INFO --scheduler django_celery_beat.schedulers:DatabaseScheduler
StandardOutput=journal StandardOutput=journal
StandardError=inherit 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] [Install]
WantedBy=multi-user.target WantedBy=multi-user.target

View file

@ -5,10 +5,30 @@ After=network.target postgresql.service redis.service
[Service] [Service]
User=bookwyrm User=bookwyrm
Group=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 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 StandardOutput=journal
StandardError=inherit 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] [Install]
WantedBy=multi-user.target WantedBy=multi-user.target

View file

@ -5,10 +5,30 @@ After=network.target postgresql.service redis.service
[Service] [Service]
User=bookwyrm User=bookwyrm
Group=bookwyrm Group=bookwyrm
WorkingDirectory=/opt/bookwyrm/ WorkingDirectory=/opt/bookwyrm
ExecStart=/opt/bookwyrm/venv/bin/gunicorn bookwyrm.wsgi:application --bind 0.0.0.0:8000 ExecStart=/opt/bookwyrm/venv/bin/gunicorn bookwyrm.wsgi:application --bind 0.0.0.0:8000
StandardOutput=journal StandardOutput=journal
StandardError=inherit 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] [Install]
WantedBy=multi-user.target WantedBy=multi-user.target

View file

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

View file

@ -1,7 +1,7 @@
/* global module */ /* global module */
module.exports = { module.exports = {
"extends": "stylelint-config-standard", "extends": "stylelint-config-standard-scss",
"plugins": [ "plugins": [
"stylelint-order" "stylelint-order"
@ -18,5 +18,13 @@ module.exports = {
"declaration-block-no-redundant-longhand-properties": null, "declaration-block-no-redundant-longhand-properties": null,
"no-descending-specificity": null, "no-descending-specificity": null,
"alpha-value-notation": 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 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 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-recommended": "^7.0.0",
"stylelint-config-standard": "^25.0.0", "stylelint-config-standard": "^25.0.0",
"stylelint-order": "^5.0.0", "stylelint-order": "^5.0.0",
"stylelint-config-standard-scss": "^3.0.0",
"watch": "^0.13.0" "watch": "^0.13.0"
}, },
"dependencies": { "dependencies": {

View file

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

Binary file not shown.

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: 0.0.1\n" "Project-Id-Version: 0.0.1\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-09-27 01:11+0000\n" "POT-Creation-Date: 2023-10-02 16:40+0000\n"
"PO-Revision-Date: 2021-02-28 17:19-0800\n" "PO-Revision-Date: 2021-02-28 17:19-0800\n"
"Last-Translator: Mouse Reeve <mousereeve@riseup.net>\n" "Last-Translator: Mouse Reeve <mousereeve@riseup.net>\n"
"Language-Team: English <LL@li.org>\n" "Language-Team: English <LL@li.org>\n"
@ -1373,7 +1373,7 @@ msgstr ""
#: bookwyrm/templates/book/editions/editions.html:8 #: bookwyrm/templates/book/editions/editions.html:8
#, python-format #, python-format
msgid "Editions of <a href=\"%(work_path)s\">\"%(work_title)s\"</a>" msgid "Editions of <a href=\"%(work_path)s\"><i>%(work_title)s</i></a>"
msgstr "" msgstr ""
#: bookwyrm/templates/book/editions/editions.html:55 #: bookwyrm/templates/book/editions/editions.html:55
@ -2806,14 +2806,8 @@ msgstr ""
#: bookwyrm/templates/import/import.html:21 #: bookwyrm/templates/import/import.html:21
#, python-format #, python-format
msgid "" msgid "Currently, you are allowed to import %(display_size)s books every %(import_limit_reset)s day."
"\n" msgid_plural "Currently, you are allowed to import %(import_size_limit)s books every %(import_limit_reset)s days."
" Currently, you are allowed to import %(display_size)s books every %(import_limit_reset)s day.\n"
" "
msgid_plural ""
"\n"
" Currently, you are allowed to import %(import_size_limit)s books every %(import_limit_reset)s days.\n"
" "
msgstr[0] "" msgstr[0] ""
msgstr[1] "" msgstr[1] ""

Binary file not shown.

File diff suppressed because it is too large Load diff

Binary file not shown.

File diff suppressed because it is too large Load diff

Binary file not shown.

File diff suppressed because it is too large Load diff

Binary file not shown.

File diff suppressed because it is too large Load diff

Binary file not shown.

File diff suppressed because it is too large Load diff

Binary file not shown.

File diff suppressed because it is too large Load diff

Binary file not shown.

File diff suppressed because it is too large Load diff

Binary file not shown.

File diff suppressed because it is too large Load diff

Binary file not shown.

File diff suppressed because it is too large Load diff

Binary file not shown.

File diff suppressed because it is too large Load diff

Binary file not shown.

File diff suppressed because it is too large Load diff

Binary file not shown.

File diff suppressed because it is too large Load diff

Binary file not shown.

File diff suppressed because it is too large Load diff

Binary file not shown.

File diff suppressed because it is too large Load diff

Binary file not shown.

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -13,7 +13,7 @@ environs==9.5.0
flower==1.2.0 flower==1.2.0
libsass==0.22.0 libsass==0.22.0
Markdown==3.4.1 Markdown==3.4.1
Pillow==9.4.0 Pillow==10.0.1
psycopg2==2.9.5 psycopg2==2.9.5
pycryptodome==3.16.0 pycryptodome==3.16.0
python-dateutil==2.8.2 python-dateutil==2.8.2