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