diff --git a/.gitignore b/.gitignore
index 6f3e4ffe..b91a4948 100644
--- a/.gitignore
+++ b/.gitignore
@@ -15,6 +15,11 @@ staticfiles/*
customizations/**/*.css
customizations/**/*.js
+### Vite's temporary compiled copy of vite.config.mjs, created on every
+### `vite build`/`vite dev` run and normally deleted on clean exit. A build
+### that errors out (missing dep, etc.) can leave one behind.
+vite.config.mjs.timestamp-*.mjs
+
### python gitignores auto-generated by github
# Byte-compiled / optimized / DLL files
diff --git a/apps/cms/wagtail_hooks.py b/apps/cms/wagtail_hooks.py
index 26be90f0..4d4b1a83 100644
--- a/apps/cms/wagtail_hooks.py
+++ b/apps/cms/wagtail_hooks.py
@@ -8,5 +8,10 @@
# Register a custom css file for the wagtail admin.
@hooks.register("insert_global_admin_css", order=100)
def global_admin_css():
- """Add /static/css/wagtail.css."""
- return format_html('', static("css/wagtail.css"))
+ """Add /static/css/wagtail_admin.css.
+
+ Named wagtail_admin.css (not wagtail.css) to avoid confusion with the
+ unrelated components/wagtail.scss, which styles rich-text embeds on the
+ public-facing site rather than admin widgets.
+ """
+ return format_html('', static("css/wagtail_admin.css"))
diff --git a/apps/readux/views.py b/apps/readux/views.py
index 47653aa7..224c754c 100644
--- a/apps/readux/views.py
+++ b/apps/readux/views.py
@@ -224,20 +224,17 @@ def get_context_data(self, **kwargs):
.count()
)
- user_annotation_index = UserAnnotation.objects.all()
-
- user_annotation_index = user_annotation_index.filter(
- canvas__manifest__label=manifest.label
- )
-
- user_annotation_index = user_annotation_index.filter(
- owner_id=self.request.user.id
- ).distinct()
-
+ # Filter by manifest id (not label — labels aren't guaranteed unique,
+ # so filtering by label risks mixing in annotations from a different
+ # manifest that happens to share a title). Grouping happens via
+ # .values().annotate(Count(...)); an upstream .distinct() on the
+ # un-grouped queryset is unnecessary here (the GROUP BY already
+ # collapses to one row per canvas__position) and was masking/dropping
+ # canvases from the index in practice, so it's been removed.
user_annotation_index = (
- user_annotation_index.values(
- "canvas__position", "canvas__manifest__label", "canvas__pid"
- )
+ UserAnnotation.objects.filter(canvas__manifest__id=manifest.id)
+ .filter(owner_id=self.request.user.id)
+ .values("canvas__position", "canvas__manifest__label", "canvas__pid")
.annotate(Count("canvas__position"))
.order_by("canvas__position")
)
diff --git a/apps/static/css/components/search.scss b/apps/static/css/components/search.scss
index 2a85b6e2..14efe98e 100644
--- a/apps/static/css/components/search.scss
+++ b/apps/static/css/components/search.scss
@@ -236,6 +236,17 @@ form#search-form {
background-color: $rx-color-faded-mint !important;
}
+// Scoped to the date range filter only (its selectize dropdowns render inline
+// within #date-range-filter, since no dropdownParent is set in search.js) —
+// the start/end year selects are single-select, so "selected" here means the
+// currently chosen year, not a multi-select tag like the collection/author/
+// language facets above. Use the same dark blue as those facet chips instead
+// of selectize's default light blue, to match the rest of the design.
+#date-range-filter .selectize-dropdown .option.selected {
+ background-color: $rx-color-midnight-blue !important;
+ color: $color-white !important;
+}
+
.selectize-control.plugin-clear_button .clear {
height: 85%;
top: -3px !important;
diff --git a/apps/static/css/readux.scss b/apps/static/css/readux.scss
index f27a845b..91e54b65 100644
--- a/apps/static/css/readux.scss
+++ b/apps/static/css/readux.scss
@@ -65,7 +65,7 @@ main { flex-grow: 1; }
width: 100%;
height: 80vh;
min-height: 500px;
- background-color: rgba($rx-color-midnight-blue, 0.8);
+ background-color: rgba($rx-color-midnight-blue, 0.85);
}
.content {
@@ -82,7 +82,11 @@ main { flex-grow: 1; }
/* Text & headings */
.paragraph { color: $rx-color-midnight-blue; line-height: normal; }
.title { color: $rx-color-midnight-blue; font-size: x-large; font-weight: bold; }
-.hero { padding-top: 15vh; padding-bottom: 2rem; color: $color-white; background-color: rgba($rx-color-midnight-blue, 0.85); }
+/* No background-color here: .hero sits on top of .overlay (see above), which
+ already provides the scrim over the hero image. Giving .hero its own
+ background would double-stack with .overlay and make the hero area visibly
+ more opaque than the surrounding .home-nav / .overlay-only regions. */
+.hero { padding-top: 15vh; padding-bottom: 2rem; color: $color-white; }
.uk-container h2 { margin-bottom: 0.35rem;}
/* Utilities */
diff --git a/apps/static/css/wagtail.css b/apps/static/css/wagtail_admin.css
similarity index 100%
rename from apps/static/css/wagtail.css
rename to apps/static/css/wagtail_admin.css
diff --git a/apps/static/js/components/VolumeAnnotations.vue b/apps/static/js/components/VolumeAnnotations.vue
index 9e499f9d..f16f49bd 100644
--- a/apps/static/js/components/VolumeAnnotations.vue
+++ b/apps/static/js/components/VolumeAnnotations.vue
@@ -69,13 +69,34 @@ export default {
// navigation, where added/deleted can be spuriously true because the
// counts of two different pages get compared. Track the current canvas
// and only treat add/delete as real when the canvas hasn't changed.
+ //
+ // The annotator (ecds-annotator) also dispatches a synthetic RESET
+ // "canvasswitch" — {canvas: "all", annotationsOnPage: 0, ...} — before
+ // the real per-canvas event for whatever canvas is loading. That's not
+ // a timing race to be debounced away; it's a deliberate, deterministic
+ // placeholder event that never represents a real page's annotation
+ // count. Treating it like any other canvas (as the old code did) let it
+ // poison _prevPageCount with 0, so the next real event for the actual
+ // canvas got diffed against that bogus 0 and double-counted an
+ // annotation that was already included in the server-rendered total.
+ // Fix: ignore canvas === "all" outright rather than guess a settle time.
+ const RESET_CANVAS = "all";
this._currentCanvas = null;
this._prevPageCount = null;
+
this._onCanvasSwitch = (event) => {
const detail = event && event.detail ? event.detail : {};
- const sameCanvas = detail.canvas && detail.canvas === this._currentCanvas;
const newPageCount = typeof detail.annotationsOnPage === "number" ? detail.annotationsOnPage : null;
+ if (!detail.canvas || detail.canvas === RESET_CANVAS) {
+ // Synthetic reset event: carries no real per-canvas count. Ignore
+ // it entirely — don't touch localPageCount, _currentCanvas, or
+ // _prevPageCount from it.
+ return;
+ }
+
+ const sameCanvas = detail.canvas === this._currentCanvas;
+
if (sameCanvas && newPageCount !== null && this._prevPageCount !== null) {
const delta = newPageCount - this._prevPageCount;
if (delta > 0) {
@@ -117,12 +138,23 @@ export default {
}
}
- if (detail.canvas) {
- if (detail.canvas !== this._currentCanvas) {
- this._prevPageCount = newPageCount;
- }
- this._currentCanvas = detail.canvas;
+ if (detail.canvas !== this._currentCanvas) {
+ // First sighting of this canvas (or a switch to it). Don't trust
+ // this event's own annotationsOnPage as the baseline — it can be a
+ // transient/incomplete read fired before the annotator's async
+ // fetch for this canvas has resolved, and a later event for the
+ // SAME canvas with the real count would then get diffed against
+ // that bogus baseline and double-count an annotation that's already
+ // included in the server-rendered totals. annotationData (seeded
+ // from the server-rendered json_data) already has the authoritative
+ // per-canvas count for anything with existing annotations, so use
+ // that as the baseline instead. Only fall back to this event's
+ // value for a canvas annotationData has never heard of (a page with
+ // zero annotations, where 0 is correct either way).
+ const known = this.annotationData.find((a) => a.canvas__pid === detail.canvas);
+ this._prevPageCount = known ? known.canvas__position__count : newPageCount;
}
+ this._currentCanvas = detail.canvas;
};
window.addEventListener("canvasswitch", this._onCanvasSwitch);
diff --git a/apps/templates/base.html b/apps/templates/base.html
index 36976e0f..12bc2897 100644
--- a/apps/templates/base.html
+++ b/apps/templates/base.html
@@ -82,6 +82,31 @@
+
{% block css %}
diff --git a/apps/templates/page.html b/apps/templates/page.html
index 0785ac7b..9d02581b 100644
--- a/apps/templates/page.html
+++ b/apps/templates/page.html
@@ -13,7 +13,6 @@
{% endblock nav %}
{% block content %}
-{{ json_data|json_script:"context" }}
{% block inner %}
@@ -76,6 +75,21 @@
{% endif %}
{% endblock viewer %}
+{% block javascript %}
+{% comment %}
+Rendered here (outside #v-readux, before main.js loads) rather than inside
+the content block: Vue 3 strips