diff --git a/apps/cms/templatetags/url_replace.py b/apps/cms/templatetags/url_replace.py deleted file mode 100644 index 53ff7ceca..000000000 --- a/apps/cms/templatetags/url_replace.py +++ /dev/null @@ -1,19 +0,0 @@ -from urllib.parse import urlencode -from django import template -from django.utils.html import mark_safe - -register = template.Library() - -@register.simple_tag(takes_context=True) -def url_replace(context, **kwargs): - query = context['request'].GET.copy() - - for kwarg in kwargs: - try: - query.pop(kwarg) - except KeyError: - pass - - query.update(kwargs) - - return mark_safe(query.urlencode()) \ No newline at end of file diff --git a/apps/iiif/manifests/documents.py b/apps/iiif/manifests/documents.py index b56d221d9..e5c07e360 100644 --- a/apps/iiif/manifests/documents.py +++ b/apps/iiif/manifests/documents.py @@ -56,7 +56,7 @@ class ManifestDocument(Document): class Index: """Settings for Elasticsearch""" - name = f"{settings.INDEX_PREFIX}_manifests" + name = f"{settings.INDEX_PREFIX}_manifests" if settings.INDEX_PREFIX else "manifests" class Django: """Settings for automatically pulling data from Django""" diff --git a/apps/readux/annotations.py b/apps/readux/annotations.py index a3f13bd00..12237f79c 100644 --- a/apps/readux/annotations.py +++ b/apps/readux/annotations.py @@ -22,7 +22,7 @@ class Annotations(View): """ def get_queryset(self): - return Canvas.objects.filter(pid=self.kwargs["canvas"]) + return Canvas.objects.filter(pid=self.kwargs["canvas"], manifest__pid=self.kwargs["vol"]) def get(self, request, *args, **kwargs): username = kwargs["username"] @@ -54,19 +54,20 @@ def get(self, request, *args, **kwargs): ) if "3" in kwargs["version"]: + canvas = queryset.first() annotations = [] if username == "ocr": - annotations = queryset.first().annotation_set.all() + annotations = canvas.annotation_set.all() elif owner.username == username: - annotations = queryset.first().userannotation_set.filter( - owner=owner - ) + annotations = canvas.userannotation_set.filter(owner=owner) return JsonResponse( json.loads( serialize( - "annotation_page_v3", queryset, annotations=annotations + "annotation_page_v3", + Canvas.objects.filter(pk=canvas.pk), + annotations=annotations, ) ) ) diff --git a/apps/readux/documents.py b/apps/readux/documents.py index 8ae06a51d..1bd139e52 100644 --- a/apps/readux/documents.py +++ b/apps/readux/documents.py @@ -26,7 +26,7 @@ class UserAnnotationDocument(Document): class Index: """Settings for Elasticsearch""" - name = f"{settings.INDEX_PREFIX}_annotations" + name = f"{settings.INDEX_PREFIX}_annotations" if settings.INDEX_PREFIX else "annotations" class Django: """Settings for automatically pulling data from Django""" diff --git a/apps/readux/search.py b/apps/readux/search.py index b3d46dd3c..871c836d6 100644 --- a/apps/readux/search.py +++ b/apps/readux/search.py @@ -142,7 +142,15 @@ def get_queryresults(self): anno_queries_exact.append({"bool": {"should": [nested_exact]}}) # combine exact and partial with bool: { should, must } - q = Q("bool", should=anno_queries, must=anno_queries_exact) + # NOTE: because we apply `.filter()` (owner + manifest), the bool query has a + # filter context. When there is no `must` clause, Elasticsearch defaults + # minimum_should_match to 0, which would return *every* annotation in the volume + # regardless of the keyword. Require at least one `should` match when there are no + # exact (must) queries to enforce the keyword. + bool_kwargs = {"should": anno_queries, "must": anno_queries_exact} + if not anno_queries_exact: + bool_kwargs["minimum_should_match"] = 1 + q = Q("bool", **bool_kwargs) annotations = annotations.query(q) annotations = annotations.highlight("content") @@ -154,11 +162,16 @@ def get_queryresults(self): annotation_matches = [] if annotation_match_count: for anno in anno_response.hits: + # skip hits without a highlight (no actual keyword match); accessing + # missing highlight data would otherwise raise an AttributeError + highlight = getattr(anno.meta, "highlight", None) + if not highlight or "content" not in highlight: + continue annotation_matches.append( { "canvas_index": anno["canvas_index"], "canvas_pid": anno["canvas_pid"], - "context": list(anno.meta.highlight.content), + "context": list(highlight.content), } ) @@ -171,10 +184,11 @@ def get_queryresults(self): annotation_matches_on_canvas = 0 annotation_matches_in_volume = 0 for k, v in groupby(annotation_matches, key=group_key): - canvas_matches = list(flatten([match["context"] for match in v])) + group = list(v) + canvas_matches = list(flatten([match["context"] for match in group])) annotation_matches_grouped.append( { - "canvas_index": anno["canvas_index"], + "canvas_index": group[0]["canvas_index"], "canvas_match_count": len(canvas_matches), "canvas_pid": k, "context": canvas_matches, diff --git a/apps/readux/tests/test_search.py b/apps/readux/tests/test_search.py index 4d8c8d9c5..eff489d85 100644 --- a/apps/readux/tests/test_search.py +++ b/apps/readux/tests/test_search.py @@ -147,6 +147,21 @@ def test_manifest_canvas_user_annotation_partial_search(self): # should be in canvas indices 1 and 2 assert match["canvas_index"] in [1, 2] + def test_manifest_canvas_user_annotation_partial_search_no_results(self): + # a partial (non-exact) keyword that matches none of the user's annotations should + # return zero matches. Regression test: a filter context with only `should` clauses + # caused Elasticsearch to default minimum_should_match to 0 and return every + # annotation in the volume regardless of the keyword. + query_params = {'volume_id': self.volume.pid, 'keyword': 'nonexistentword'} + request = self.request.get( + self.url, query_params + ) + request.user = self.user + response = self.search_manifest_view(request) + search_results = self.load_results(response) + assert search_results['matches_in_annotations']['total_matches_in_volume'] == 0 + assert len(search_results['matches_in_annotations']['volume_matches']) == 0 + def test_manifest_canvas_user_annotation_exact_search(self): query_params = {'volume_id': self.volume.pid, 'keyword': '"outcasts"'} request = self.request.get( diff --git a/apps/static/css/components/collection.scss b/apps/static/css/components/collection.scss index 87819d532..4ff930e18 100644 --- a/apps/static/css/components/collection.scss +++ b/apps/static/css/components/collection.scss @@ -94,7 +94,18 @@ .description-text { display: block; } - + + /* Links need a light color for contrast against the dark hero background */ + a { + color: $rx-color-sky-blue; + text-decoration: underline; + + &:hover, + &:focus { + color: $rx-color-faded-mint; + } + } + /* When description is truncated */ &.description-truncated .description-text::after { content: ''; diff --git a/apps/static/css/components/reader.scss b/apps/static/css/components/reader.scss index a95919787..f6b41ac11 100644 --- a/apps/static/css/components/reader.scss +++ b/apps/static/css/components/reader.scss @@ -86,6 +86,39 @@ right: 240px; } +/* .ecds-annotator sets color:white globally; reset text color for annotation + content popups and the Jodit editor which both have white backgrounds. */ +.rdx-annotation-content, +.jodit-container { + color: $rx-color-dark-charcoal; +} + .uk-search { padding-right: 10px; +} + +.rx-annotation-index-row { + display: flex; + align-items: center; + gap: 0.4rem; + + a { + display: flex; + align-items: center; + line-height: 1; + } + + .rx-label-copy { + vertical-align: middle; + } +} + +.rx-search-result-list { + margin-top: 0.25rem !important; + margin-bottom: 0 !important; + padding-left: 1.25rem !important; + + .rx-search-result-item { + margin-top: 0.2rem; + } } \ No newline at end of file diff --git a/apps/static/css/readux.scss b/apps/static/css/readux.scss index 4b50b4529..d3571bd05 100644 --- a/apps/static/css/readux.scss +++ b/apps/static/css/readux.scss @@ -40,8 +40,14 @@ html, body { main { flex-grow: 1; } -/* Home page navigation transparent */ -.home-nav { nav { background-color: transparent !important; } } +/* Home page navigation transparent, light text/icons over dark hero */ +.home-nav { + nav { background-color: transparent !important; } + a, .brand-logo, .brand-tagline { color: $color-white !important; } + svg line, svg path, svg circle, svg polygon, svg polyline, svg rect { + stroke: $color-white !important; + } +} /* Overlay & content shell */ .overlay { @@ -185,8 +191,9 @@ em { color: inherit !important; } /* Buttons */ .uk-button-default { @include interactive-border($rx-color-midnight-blue); } .uk-button-primary { - background-color: var(--link-color); - &:hover, &:active { background-color: var(--link-color); filter: contrast(var(--contrast)); } + background-color: var(--link-color) !important; + color: #fff !important; + &:hover, &:active { background-color: var(--link-color) !important; color: #fff !important; filter: contrast(var(--contrast)); } } /* Close icon */ @@ -342,9 +349,6 @@ button.google, button.github { color: $color-white; } padding: 0; margin: 0; user-select: auto; } .rx-action-btn { letter-spacing: 0.1em; font-size: small; } -.rx-action-btn:hover { - .rx-label-copy { background-color: $rx-color-midnight-blue !important; color: $color-white !important; } -} .rx-breadcrumb-item > a, .rx-action-btn, .rx-icon-btn, .uk-dropdown-nav > li > a:hover { color: var(--link-color) !important; opacity: 0.75; transition: all 100ms ease-in-out; @@ -356,12 +360,26 @@ button.google, button.github { color: $color-white; } .rx-anchor { color: var(--link-color) !important; opacity: 0.75; word-break: break-all; transition: all 100ms; &:hover { color: var(--link-color); opacity: 1; } } .rx-label-copy { - background: $color-white; - color: var(--link-color) !important; - border: var(--link-color) 1px solid; - font-size: 0.75rem; letter-spacing: 0.1rem; cursor: pointer; transition: all 100ms ease-in-out; font-weight: 600; font-family: 'Consolas','Monaco','Andale Mono','Ubuntu Mono','monospace' !important; - &:active { background-color: $rx-color-midnight-blue !important; border: 1px solid $rx-color-midnight-blue !important; opacity: 1; } - &:hover { background: $rx-color-midnight-blue; border-color: $rx-color-midnight-blue; color: $color-white !important; } + background: $rx-color-midnight-blue !important; + color: $color-white !important; + border: none !important; + font-size: 0.75rem; + letter-spacing: 0.1rem; + cursor: pointer; + transition: background 150ms ease-in-out, opacity 150ms ease-in-out; + font-weight: 600; + font-family: 'Consolas', 'Monaco', 'Andale Mono', 'Ubuntu Mono', monospace !important; + + &:hover { + background: darken($rx-color-midnight-blue, 12%) !important; + color: $color-white !important; + opacity: 0.9; + } + + &:active { + background: darken($rx-color-midnight-blue, 20%) !important; + opacity: 1; + } } /* Navigation & bars */ diff --git a/apps/static/js/components/OcrInspector.vue b/apps/static/js/components/OcrInspector.vue index fc153f24a..9cef69fec 100644 --- a/apps/static/js/components/OcrInspector.vue +++ b/apps/static/js/components/OcrInspector.vue @@ -5,9 +5,8 @@ This page does not have any usable OCR. -
-
+ +
+

Or sign in with your local account:

+ +
+ {% csrf_token %} + +
+ + +
+
+ + +
+
+ + Forgot password? +
+ +
Need help? Contact us diff --git a/apps/templates/_page/_exports.html b/apps/templates/_page/_exports.html index 0be45aa44..99c2bf6e6 100644 --- a/apps/templates/_page/_exports.html +++ b/apps/templates/_page/_exports.html @@ -3,18 +3,7 @@

Download and Expor {% if volume.pdf %} - + {% endif %} diff --git a/apps/templates/account/base.html b/apps/templates/account/base.html new file mode 100644 index 000000000..62fb03ce2 --- /dev/null +++ b/apps/templates/account/base.html @@ -0,0 +1,3 @@ +{% extends "allauth/layouts/base.html" %} +{% block title %}{% block head_title %}{% endblock head_title %} – {{ request.site.name }}{% endblock title %} +{% block breadcrumb %}{% block account_breadcrumb %}{% endblock account_breadcrumb %}{% endblock breadcrumb %} diff --git a/apps/templates/account/login.html b/apps/templates/account/login.html new file mode 100644 index 000000000..23623ac40 --- /dev/null +++ b/apps/templates/account/login.html @@ -0,0 +1,89 @@ +{% extends "account/base.html" %} +{% load i18n account socialaccount %} + +{% block head_title %}Sign In{% endblock %} +{% block account_breadcrumb %}Sign In{% endblock %} + +{% block content %} +
+
+ +

Log in to {{ request.site.name }}

+ + {% get_providers as socialaccount_providers %} + {% if socialaccount_providers %} +

+ Continue with one of the following services: +

+ +
+ {% for provider in socialaccount_providers %} + {% if provider.id not in user.socialaccount_list or user.is_anonymous %} +
+ {% csrf_token %} + +
+ {% endif %} + {% endfor %} +
+ +
+ +

+ Or sign in with your local account: +

+ {% endif %} + +
+ {% csrf_token %} + + {% for field in form %} +
+ {% if field.field.widget.input_type != 'checkbox' %} + + {% endif %} +
+ {% if field.field.widget.input_type == 'checkbox' %} + + {% else %} + + {% endif %} + {% if field.errors %} +

{{ field.errors|join:", " }}

+ {% endif %} +
+
+ {% endfor %} + + {% if redirect_field_value %} + + {% endif %} + +
+ Forgot Password? + +
+
+ +
+

+ Don't have an account? Sign up +

+ +
+
+{% endblock content %} diff --git a/apps/templates/base.html b/apps/templates/base.html index 419dd4943..46b4073eb 100644 --- a/apps/templates/base.html +++ b/apps/templates/base.html @@ -136,12 +136,14 @@

+ {% if MATOMO_ID %} + {% endif %} {% block javascript %} diff --git a/apps/templates/cms/collections_page.html b/apps/templates/cms/collections_page.html index dad5730be..2e9d59c39 100644 --- a/apps/templates/cms/collections_page.html +++ b/apps/templates/cms/collections_page.html @@ -26,7 +26,7 @@

{{ full_label }}

{{ collection.manifests.all|length }} volume{{ collection.manifests.all|length|pluralize }} {% endif %} -

{{ collection.summary|striptags|truncatewords:70 }}

+

{{ collection.summary|striptags|truncatewords:70|safe }}

{% empty %}
There are no collections available to browse.
@@ -56,7 +56,7 @@

{{ full_label }}

{{ collection.manifests.all|length }} volume{{ collection.manifests.all|length|pluralize }} -
{{ collection.summary|striptags|truncatewords:70 }}
+
{{ collection.summary|striptags|truncatewords:70|safe }}
{% empty %} diff --git a/apps/templates/collection.html b/apps/templates/collection.html index 5e20f4e9d..41452b096 100644 --- a/apps/templates/collection.html +++ b/apps/templates/collection.html @@ -29,15 +29,15 @@

{{ collection.label }}

{{ volumes.paginator.count }} volume{{ volumes.paginator.count|pluralize }}
- {% with summary_plain=collection.summary|striptags|unescape_html %} + {% with summary_plain=collection.summary|striptags|unescape_html summary_html=collection.summary|unescape_html %} {% if summary_plain %} -

+

{% if summary_plain|length > 300 %} - {{ summary_plain|truncatechars:300 }} + {{ summary_html|truncatechars_html:300|safe }} {% else %} - {{ summary_plain }} + {{ summary_html|safe }} {% endif %} -

+
{% endif %} {% endwith %} @@ -47,14 +47,16 @@

- + {% if collection.metadata %} + + {% endif %}

@@ -70,31 +72,13 @@