Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Implement the document search page redesign (#1614) #1618

Merged
merged 4 commits into from
Jul 31, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 20 additions & 22 deletions geniza/corpus/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ class DocumentSearchForm(RangeForm):
widget=forms.TextInput(
attrs={
# Translators: placeholder for keyword search input
"placeholder": _("search by keyword"),
"placeholder": _("Search all fields by keyword"),
# Translators: accessible label for keyword search input
"aria-label": _("Keyword or Phrase"),
"type": "search",
Expand Down Expand Up @@ -207,9 +207,9 @@ class DocumentSearchForm(RangeForm):
required=False,
widget=SelectWithDisabled,
)
# Translators: label for filter documents by date range
docdate = RangeField(
label=_("Document Dates (CE)"),
# Translators: label for filter documents by date range
label=_("Dates"),
required=False,
widget=YearRangeWidget(
attrs={"size": 4, "data-action": "input->search#update"},
Expand All @@ -218,23 +218,23 @@ class DocumentSearchForm(RangeForm):

doctype = FacetChoiceField(
# Translators: label for document type search form filter
label=_("Document Type"),
label=_("Document type"),
)
has_image = BooleanFacetField(
# Translators: label for "has image" search form filter
label=_("Has Image"),
label=_("Image"),
)
has_transcription = BooleanFacetField(
# Translators: label for "has transcription" search form filter
label=_("Has Transcription"),
label=_("Transcription"),
)
has_translation = BooleanFacetField(
# Translators: label for "has translation" search form filter
label=_("Has Translation"),
label=_("Translation"),
)
has_discussion = BooleanFacetField(
# Translators: label for "has discussion" search form filter
label=_("Has Discussion"),
label=_("Discussion"),
)

# mapping of solr facet fields to form input
Expand All @@ -258,6 +258,14 @@ def __init__(self, data=None, *args, **kwargs):
{"label": self.SORT_CHOICES[0][1], "disabled": True},
)

def get_translated_label(self, field, label):
"""Lookup translated label via db model object when applicable;
handle Person.gender as a special case; and otherwise just return the label"""
if field == "type" or field == "doctype":
# for doctype, label should be translated, so use doctype object
return DocumentType.objects_by_label.get(label, _("Unknown type"))
return label

def filters_active(self):
"""Check if any filters are active; returns true if form fields other than sort or q are set"""
if self.is_valid():
Expand All @@ -276,20 +284,10 @@ def set_choices_from_facets(self, facets):
# populate facet field choices from current facets
for key, facet_dict in facets.items():
# restructure dict to set values of each key to tuples of (label, count)
if key == "type":
# for doctype, label should be translated, so use doctype object
facet_dict = {
label: (
DocumentType.objects_by_label.get(label, _("Unknown type")),
count,
)
for (label, count) in facet_dict.items()
}
else:
# for other formfields, label == facet name
facet_dict = {
label: (label, count) for (label, count) in facet_dict.items()
}
facet_dict = {
label: (self.get_translated_label(key, label), count)
for (label, count) in facet_dict.items()
}
# use field from facet fields map or else field name as is
formfield = self.solr_facet_fields.get(key, key)
# for each facet, set the corresponding choice field
Expand Down
11 changes: 9 additions & 2 deletions geniza/corpus/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -1159,6 +1159,8 @@ def prep_index_chunk(cls, chunk):
"languages",
"log_entries",
"dating_set",
"persondocumentrelation_set",
"documentplacerelation_set",
Prefetch(
"textblock_set",
queryset=TextBlock.objects.select_related(
Expand Down Expand Up @@ -1237,6 +1239,7 @@ def index_data(self):
"old_pgpids_is": self.old_pgpids,
"language_code_s": self.primary_lang_code,
"language_script_s": self.primary_script,
"language_name_ss": [str(l) for l in self.languages.all()],
# use image info link without trailing info.json to easily convert back to iiif image client
# NOTE: if/when piffle supports initializing from full image uris, we could simplify this
# code to index the full image url, with rotation overrides applied
Expand All @@ -1247,6 +1250,8 @@ def index_data(self):
"iiif_labels_ss": [img["label"] for img in images],
"iiif_rotations_is": [img["rotation"] for img in images],
"has_image_b": len(images) > 0,
"people_count_i": self.persondocumentrelation_set.count(),
"places_count_i": self.documentplacerelation_set.count(),
}
)

Expand Down Expand Up @@ -1283,11 +1288,13 @@ def index_data(self):
fn.doc_relation
)

# make sure digital editions are also counted as editions,
# whether or not there is a separate edition footnote
# make sure digital editions/translations are also counted,
# whether or not there is a separate edition/translation footnote
for source, doc_relations in source_relations.items():
if Footnote.DIGITAL_EDITION in doc_relations:
source_relations[source].add(Footnote.EDITION)
if Footnote.DIGITAL_TRANSLATION in doc_relations:
source_relations[source].add(Footnote.TRANSLATION)

# flatten sets of relations by source into a list of relations
for relation in list(chain(*source_relations.values())):
Expand Down
16 changes: 16 additions & 0 deletions geniza/corpus/solr_queryset.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ class DocumentSolrQuerySet(AliasedSolrQuerySet):
"type": "type_s",
"status": "status_s",
"shelfmark": "shelfmark_s", # string version for display
"shelfmarks": "fragment_shelfmark_ss",
"document_date": "document_date_t", # text version for search & display
"original_date_t": "original_date",
"collection": "collection_ss",
Expand All @@ -65,6 +66,7 @@ class DocumentSolrQuerySet(AliasedSolrQuerySet):
"transcription": "text_transcription",
"language_code": "language_code_s",
"language_script": "language_script_s",
"languages": "language_name_ss",
"translation": "text_translation",
"translation_language_code": "translation_language_code_s",
"translation_language_direction": "translation_language_direction_s",
Expand All @@ -79,6 +81,8 @@ class DocumentSolrQuerySet(AliasedSolrQuerySet):
"old_shelfmark_t": "old_shelfmark_t",
"transcription_nostem": "transcription_nostem",
"description_nostem": "description_nostem",
"related_people": "people_count_i",
"related_places": "places_count_i",
}

# regex to convert field aliases used in search to actual solr fields
Expand Down Expand Up @@ -254,6 +258,18 @@ def get_result_document(self, doc):
_("Unknown type"),
)

if doc.get("shelfmarks"):
doc["related_documents"] = (
DocumentSolrQuerySet()
.filter("NOT pgpid_i:%d" % doc["pgpid"])
.filter(
fragment_shelfmark_ss__in=[
'"%s"' % shelfmark for shelfmark in doc["shelfmarks"]
]
)
.count()
)

return doc

def get_highlighting(self):
Expand Down
172 changes: 99 additions & 73 deletions geniza/corpus/templates/corpus/document_list.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
{% block meta_description %}{{ page_description }}{% endblock meta_description %}

{% block main %}
<h1 class="sr-only">{{ page_title }}</h1>
<h1>{{ page_title }}</h1>
<form data-controller="search" data-turbo-frame="main" data-turbo-action="advance" data-page="document">
<fieldset id="query">
{% render_field form.q data-search-target="query" data-action="input->search#autoUpdateSort change->search#update" %}
Expand All @@ -14,60 +14,82 @@ <h1 class="sr-only">{{ page_title }}</h1>
{% translate 'Submit search' as search_label %}
<button type="submit" aria-label="{{ search_label }}" />
</fieldset>
<a href="#filters" role="button" id="filters-button" data-action="click->search#openFilters"{% if form.filters_active %} class="active"{% endif %}>
<svg><use xlink:href="{% static 'img/ui/all/all/search-filter-icon.svg' %}#filter-icon" /></svg>
{% translate "Filters" %}
</a>

<div id="filters-header">
<a href="#filters" role="button" id="filters-button" data-search-target="filtersButton" data-action="click->search#toggleFiltersOpen">
<svg><use xlink:href="{% static 'img/ui/all/all/search-filter-icon.svg' %}#filter-icon" /></svg>
<span>{% translate "Filters" %}</span>
{% if applied_filters %}
<span class="filter-count">{{ applied_filters|length }}</span>
{% endif %}
</a>
{% if applied_filters %}
{# convenient unapply filter buttons; aria role set to presentation as functionality duplicated below #}
<div id="applied-filters" role="presentation">
{% for filter in applied_filters %}
<button
data-field="{{ filter.field }}"
value="{{ filter.value }}"
data-action="click->search#unapplyFilter"
>
{{ filter.label }}
<i class="ph-x"></i>
</button>
{% endfor %}
</div>
<button id="clear-filters" data-action="click->search#clearFilters">
{# Translators: label for button to clear all applied filters #}
{% translate "Clear all" %}
</button>
{% endif %}
</div>
<div class="modal-backdrop" aria-hidden="true" data-action="click->search#closeFilters"></div>
<fieldset id="filters" aria-expanded="false" data-search-target="filterModal">
<legend>{% translate "Filters" %}</legend>
<a href="#" role="button" id="close-filters-modal" data-action="click->search#closeFilters">
{# Translators: label for 'filters' close button for mobile navigation #}
{% translate "Close filter options" as close_button %}
<span class="sr-only">{{ close_button }}</span>
</a>
<a href="#" role="button" id="close-filters-button" data-action="click->search#closeFilters"{% if form.filters_active %} class="active"{% endif %}>
<svg><use xlink:href="{% static 'img/ui/all/all/search-filter-icon.svg' %}#filter-icon" /></svg>
{% translate "Filters" %}
</a>
<label for="{{ form.docdate.auto_id }}" class="date-range-label">
<span>{{ form.docdate.label }}</span>
{# NOTE: stimulus action is configured via django widget attrs #}
{{ form.docdate }}
</label>
<label for="{{ form.has_image.auto_id }}">
{% render_field form.has_image data-action="search#update" %}
{{ form.has_image.label }}
</label>
<label for="{{ form.has_transcription.auto_id }}">
{% render_field form.has_transcription data-action="search#update" %}
{{ form.has_transcription.label }}
</label>
<label for="{{ form.has_translation.auto_id }}">
{% render_field form.has_translation data-action="search#update" %}
{{ form.has_translation.label }}
</label>
<label for="{{ form.has_discussion.auto_id }}">
{% render_field form.has_discussion data-action="search#update" %}
{{ form.has_discussion.label }}
</label>
<details class="doctype-filter" data-search-target="doctypeFilter">
<summary data-action="click->search#toggleDoctypeFilter">
{{ form.doctype.label }}
</summary>
<div class="fieldset-left-column">
<label for="{{ form.docdate.auto_id }}" class="date-range-label">
<span class="fieldname">{{ form.docdate.label }}</span>
{# NOTE: stimulus action is configured via django widget attrs #}
{{ form.docdate }}
</label>
</div>
<fieldset class="includes-fields">
<legend><span class="fieldname">{% translate "Includes" %}</span></legend>
<ul>
<li>
<label for="{{ form.has_image.auto_id }}">
{% render_field form.has_image data-action="search#update" %}
{{ form.has_image.label }}
</label>
</li>
<li>
<label for="{{ form.has_transcription.auto_id }}">
{% render_field form.has_transcription data-action="search#update" %}
{{ form.has_transcription.label }}
</label>
</li>
<li>
<label for="{{ form.has_translation.auto_id }}">
{% render_field form.has_translation data-action="search#update" %}
{{ form.has_translation.label }}
</label>
</li>
<li>
<label for="{{ form.has_discussion.auto_id }}">
{% render_field form.has_discussion data-action="search#update" %}
{{ form.has_discussion.label }}
</label>
</li>
</ul>
</fieldset>
<label for="{{ form.doctype.auto_id }}">
<span class="fieldname">{{ form.doctype.label }}</span>
{% render_field form.doctype data-action="search#update" %}
</details>
<button type="submit" class="primary" data-action="click->search#applyFilters">
{% translate "Apply" %}
</button>
</fieldset>

<fieldset id="sort-fieldset">
<label for="{{ form.sort.html_name }}">
{{ form.sort.label }}
</label>
{% render_field form.sort id="sort" data-search-target="sort" data-action="input->search#update" %}
{# caret icon for <select>; since we also have the select element, role=presentation #}
<i class="ph-caret-down" role="presentation"></i>
</fieldset>

{% if form.errors %}
Expand All @@ -80,31 +102,35 @@ <h1 class="sr-only">{{ page_title }}</h1>
{% endfor %}
</ul>
{% endif %}
</form>
<section id="document-list">
<h1>
{% comment %}Translators: number of search results{% endcomment %}
{% blocktranslate with count_humanized=paginator.count|intcomma count counter=paginator.count trimmed %}
1 result
{% plural %}
{{ count_humanized }} total results
{% endblocktranslate %}
</h1>
{% if is_paginated %}
{% include "corpus/snippets/pagination.html" %}
{% endif %}
<ol>
{% for document in documents %}
{% include "corpus/snippets/document_result.html" %}
{% endfor %}
</ol>
{% if is_paginated %}
{# Translators: screen reader label for pagination navigation displayed after search results #}
{% translate "secondary pagination" as pagination_label %}
{# don't include footer pagination on random sort, since it's disabled #}
{% if form.sort.value != 'random' %}
{% include "corpus/snippets/pagination.html" with aria_label=pagination_label %}
<section id="document-list">
<div class="header-row">
<h2>
{# Translators: search results section header #}
{% translate "Results" %}
</h2>
<span class="result-count">
{% comment %}Translators: number of search results{% endcomment %}
{% blocktranslate with count_humanized=paginator.count|intcomma count counter=paginator.count trimmed %}
1 result
{% plural %}
{{ count_humanized }} results
{% endblocktranslate %}
</span>
<fieldset id="sort-field">
<label for="{{ form.sort.html_name }}">{{ form.sort.label }}</label>
{% render_field form.sort id="sort" data-search-target="sort" data-action="input->search#update" %}
{# caret icon for <select>; since we also have the select element, role=presentation #}
<i class="ph-caret-down" role="presentation"></i>
</fieldset>
</div>
<ol>
{% for document in documents %}
{% include "corpus/snippets/document_result.html" %}
{% endfor %}
</ol>
{% if is_paginated %}
{% include "corpus/snippets/pagination.html" %}
{% endif %}
{% endif %}
</section>
</section>
</form>
{% endblock main %}
Loading
Loading