THE root cause of the entire 'still reloads / still not working' saga
across 2.7 -> 2.8 -> 2.8b: shop_tags.j2 (tag_bulk.js) and
product_edit.j2 (product_tags.js) loaded their <script> WITHOUT the
?v={{ request.git_hash }} cache-bust. routes.py serves /static with
cache_max_age=3600, so the operator's browser kept the STALE JS for up
to an hour after every deploy — the new SPA code never executed, forms
fell back to native submit = full page reload, every time. Server-side
functional tests passed throughout because they have no browser cache.
Fix: append ?v={{ request.git_hash }} to EVERY static <script> include
(the established base.j2 / offer.js / pay-countdown.js convention) —
not just the two at fault but the whole latent class: tag_bulk,
product_tags, tag_filter, auction, player, sandbox, watch, signals,
comments, shop-settings. request.git_hash shifts every deploy -> URL
changes -> fresh fetch, no hard-refresh ever needed again.
Gate (must be empty):
grep -rnE '<script src="/static/js/[^"?]+\.js"' make_post_sell/templates/
The 2.8/2.8b JS (onTagFormClick unified click handler, AJAX focus,
drag-to-reorder) stands — it just was never being fetched by the
browser. 1131 tests pass. Docs: mps-24.md Phase 2.8c, CLAUDE.md
(new mandatory cache-bust convention section).
211 lines
10 KiB
Django/Jinja
211 lines
10 KiB
Django/Jinja
{% extends "base.j2" -%}
|
|
|
|
{%- block append_to_head_tag_section %}
|
|
<link rel="canonical" href="{{ product.absolute_url(request) }}" />
|
|
<title>{{ product.title }}</title>
|
|
<meta property="og:title" content="{{ product.title }}" />
|
|
<meta name="description" content="{{ product.description|truncate(500) }}">
|
|
<meta property="og:description" content="{{ product.description|truncate(500) }}" />
|
|
<meta property="og:type" content="article" />
|
|
<meta property="og:url" content="{{ product.absolute_url(request) }}" />
|
|
<meta property="og:site_name" content="{{ request.shop.name }}" />
|
|
{%- if "thumbnail1" in product.extensions %}
|
|
<meta property="og:image" content="{{ request.shop_cdn_endpoint }}/{{ product.s3_path }}/thumbnail1?ts={{ product.updated_timestamp }}" />
|
|
<meta name="twitter:card" content="summary_large_image" />
|
|
<meta name="twitter:title" content="{{ product.title }}" />
|
|
<meta name="twitter:description" content="{{ product.description|truncate(500) }}" />
|
|
<meta name="twitter:image" content="{{ request.shop_cdn_endpoint }}/{{ product.s3_path }}/thumbnail1?ts={{ product.updated_timestamp }}" />
|
|
{%- endif %}
|
|
|
|
{# Removed auto-refresh timer - better UX to let download links expire than interrupt reading #}
|
|
{%- endblock append_to_head_tag_section -%}
|
|
|
|
|
|
{%- block call_to_action -%}
|
|
{% if request.user and request.user.can_edit_shop(request.shop) %}
|
|
<a href="{{ product.absolute_edit_url(request) }}" class="product-edit-button mps-button mps-button-small">✎ Edit</a>
|
|
{% endif %}
|
|
{%- endblock call_to_action -%}
|
|
|
|
|
|
{% block content -%}
|
|
|
|
<section class="two-column{% if request.shop.watch_mode_enabled %} watch-mode{% endif %}" data-signal-product-id="{{ product.id }}" data-signal-shop-id="{{ product.shop_id }}"{% if request.user and request.user.can_edit_shop(request.shop) %} data-signal-is-owner="1"{% endif %}>
|
|
|
|
{% if request.shop.watch_mode_enabled %}<div class="watch-left" data-watch-product-id="{{ product.id }}"{% if request.user and request.user.can_edit_shop(request.shop) %} data-watch-is-mod="1"{% endif %}>{% endif %}
|
|
|
|
<div class="product-images">
|
|
{% set video_extensions = ["mp4", "avi", "mov", "wmv", "flv", "mkv", "webm", "m4v"] %}
|
|
{% set audio_extensions = ["mp3", "wav", "ogg", "m4a", "flac", "aac", "opus"] %}
|
|
{% if request.shop.watch_mode_enabled and product.extensions.get("product") in video_extensions %}
|
|
{# Watch mode: direct video render with autoplay #}
|
|
{% set watch_video_url = request.shop_cdn_endpoint ~ "/" ~ product.s3_path ~ "/product" %}
|
|
<div id="watch-media-container"{% if instrumentals_url %} data-instrumentals-url="{{ instrumentals_url }}"{% endif %}{% if vocals_url %} data-vocals-url="{{ vocals_url }}"{% endif %}{% if karaoke_eligible %} data-karaoke-eligible="1"{% endif %}>
|
|
<div class="watch-video-container">
|
|
<video id="watch-video" src="{{ watch_video_url }}" autoplay controls playsinline class="product-main"></video>
|
|
<button class="unmute-overlay">Tap to unmute</button>
|
|
</div>
|
|
</div>
|
|
<div id="watch-countdown" class="watch-countdown js-only" style="display:none">
|
|
<img id="countdown-thumb" class="countdown-thumb" />
|
|
<div class="countdown-text">
|
|
<span id="countdown-title">Up next: ...</span>
|
|
<span id="countdown-number">7</span>
|
|
</div>
|
|
<button id="countdown-play-now" class="mps-button mps-button-blue">Play Now</button>
|
|
<button id="countdown-cancel" class="mps-button">Cancel</button>
|
|
</div>
|
|
<noscript>
|
|
<a href="{{ watch_video_url }}" target="_blank">Open video</a>
|
|
</noscript>
|
|
{% elif request.shop.watch_mode_enabled and product.extensions.get("product") in audio_extensions %}
|
|
{# Watch mode: audio with album art #}
|
|
{% set watch_audio_url = request.shop_cdn_endpoint ~ "/" ~ product.s3_path ~ "/product" %}
|
|
<div id="watch-media-container"{% if instrumentals_url %} data-instrumentals-url="{{ instrumentals_url }}"{% endif %}{% if vocals_url %} data-vocals-url="{{ vocals_url }}"{% endif %}{% if karaoke_eligible %} data-karaoke-eligible="1"{% endif %}>
|
|
<div class="watch-audio-container">
|
|
{% if "thumbnail1" in product.extensions %}
|
|
<img src="{{ request.shop_cdn_endpoint }}/{{ product.s3_path }}/thumbnail1?ts={{ product.updated_timestamp }}" class="product-main audio-cover" />
|
|
{% endif %}
|
|
<audio id="watch-audio" src="{{ watch_audio_url }}" autoplay controls></audio>
|
|
</div>
|
|
</div>
|
|
<div id="watch-countdown" class="watch-countdown js-only" style="display:none">
|
|
<img id="countdown-thumb" class="countdown-thumb" />
|
|
<div class="countdown-text">
|
|
<span id="countdown-title">Up next: ...</span>
|
|
<span id="countdown-number">7</span>
|
|
</div>
|
|
<button id="countdown-play-now" class="mps-button mps-button-blue">Play Now</button>
|
|
<button id="countdown-cancel" class="mps-button">Cancel</button>
|
|
</div>
|
|
<noscript>
|
|
<a href="{{ watch_audio_url }}" target="_blank">Listen</a>
|
|
</noscript>
|
|
{% elif "thumbnail1" in product.extensions %}
|
|
{% if product.extensions.get("product") in video_extensions %}
|
|
{# Video: play button overlay, click to play inline #}
|
|
{% set video_url = request.shop_cdn_endpoint ~ "/" ~ product.s3_path ~ "/product" %}
|
|
<div id="video-container-{{ product.id }}" class="video-thumbnail-container" onclick="playInline(this, '{{ video_url }}')">
|
|
<img src="{{ request.shop_cdn_endpoint }}/{{ product.s3_path }}/thumbnail1?ts={{ product.updated_timestamp }}" class="product-main" />
|
|
<div class="video-play-overlay"></div>
|
|
</div>
|
|
<noscript>
|
|
<a href="{{ video_url }}" target="_blank">Open video</a>
|
|
</noscript>
|
|
<div class="video-click-to-play">click ▶ to play</div>
|
|
{% else %}
|
|
{# Non-video: click to open in new window #}
|
|
<a target="_blank" href="{{ request.shop_cdn_endpoint }}/{{ product.s3_path }}/product?ts={{ product.updated_timestamp }}">
|
|
<img src="{{ request.shop_cdn_endpoint }}/{{ product.s3_path }}/thumbnail1?ts={{ product.updated_timestamp }}" class="product-main" />
|
|
</a>
|
|
{% endif %}
|
|
{% endif %}
|
|
{% if request.shop.watch_mode_enabled and product.extensions.get("product") not in video_extensions and product.extensions.get("product") not in audio_extensions %}
|
|
{# Countdown overlay for static content (PDFs, images) #}
|
|
<div id="watch-countdown" class="watch-countdown js-only" style="display:none">
|
|
<img id="countdown-thumb" class="countdown-thumb" />
|
|
<div class="countdown-text">
|
|
<span id="countdown-title">Up next: ...</span>
|
|
<span id="countdown-number">7</span>
|
|
</div>
|
|
<button id="countdown-play-now" class="mps-button mps-button-blue">Play Now</button>
|
|
<button id="countdown-cancel" class="mps-button">Cancel</button>
|
|
</div>
|
|
{% endif %}
|
|
<br/>
|
|
|
|
<h1>{{ product.title }}<br/><span class="subtitle-text">uploaded to <a href="{{ product.shop.absolute_about_url(request) }}" rel="nofollow" class="shop-theme-link-color">{{ product.shop.name }}</a></span></h1>
|
|
</div>
|
|
|
|
<div class="cinema-content-stack">
|
|
<div class="product-description">
|
|
<br/>
|
|
|
|
<b>Description</b>
|
|
<section>{{ product.description_html | safe }}</section>
|
|
|
|
<br/>
|
|
<br/>
|
|
|
|
{% if product.has_product_file %}
|
|
<b>File Type</b>
|
|
<br/>
|
|
{{ product.get_content_type("product") }} {{ product_size }}
|
|
<br/>
|
|
{% endif %}
|
|
|
|
{% if request.shop.show_dates %}
|
|
<div class="product-meta-dates">
|
|
Created {{ product.human_created_timestamp }}{% if product.human_created_timestamp != product.human_updated_timestamp %} · Updated {{ product.human_updated_timestamp }}{% endif %}{% if product.human_view_count %} · <span class="product-view-count">{{ product.human_view_count }}</span>{% endif %}
|
|
</div>
|
|
{% endif %}
|
|
|
|
{% set cs = product.checksums.get("product", {}) %}
|
|
{% if cs.get("sha256") or cs.get("md5") %}
|
|
<details class="product-checksums">
|
|
<summary>Checksums</summary>
|
|
<table class="checksum-table">
|
|
{% if cs.get("sha256") %}<tr><td>SHA-256</td><td><code>{{ cs["sha256"] }}</code></td></tr>{% endif %}
|
|
{% if cs.get("md5") %}<tr><td>MD5</td><td><code>{{ cs["md5"] }}</code></td></tr>{% endif %}
|
|
</table>
|
|
</details>
|
|
{% endif %}
|
|
</div>
|
|
|
|
<div class="product-comments">
|
|
<br/>
|
|
<br/>
|
|
|
|
<!-- Comments Section -->
|
|
{% include 'snippets/comments.j2' %}
|
|
</div>
|
|
</div>
|
|
|
|
{% if request.shop.watch_mode_enabled %}</div>{% endif %}
|
|
|
|
<section class="product-right">
|
|
{% if product.has_product_file %}
|
|
<div class="well">
|
|
{% if signed_get_object_url is not none %}
|
|
<a href="{{ signed_get_object_url }}" class="product-download-button mps-button" target="_blank" download>⭳ Download</a>
|
|
{% endif %}
|
|
{% if torrent_magnet_link %}
|
|
<a href="{{ torrent_magnet_link }}" class="mps-button" style="margin-left:.5rem">◡ Magnet</a>
|
|
{% endif %}
|
|
{% if torrent_file_url %}
|
|
<a href="{{ torrent_file_url }}" class="mps-button" style="margin-left:.5rem" download>↧ .torrent</a>
|
|
{% endif %}
|
|
</div>
|
|
{% endif %}
|
|
<div id="watch-queue" class="js-only" style="display:none">
|
|
<h4>Queue</h4>
|
|
<div id="queue-items"></div>
|
|
</div>
|
|
{% include 'snippets/related_content.j2' %}
|
|
</section>
|
|
|
|
</section>
|
|
|
|
<script>
|
|
// Replace thumbnail with inline video player
|
|
function playInline(container, videoUrl) {
|
|
// Create video element
|
|
const video = document.createElement('video');
|
|
video.src = videoUrl;
|
|
video.controls = true;
|
|
video.autoplay = true;
|
|
video.className = 'product-main';
|
|
video.style.maxWidth = '100%';
|
|
|
|
// Replace container content with video
|
|
container.innerHTML = '';
|
|
container.onclick = null;
|
|
container.appendChild(video);
|
|
}
|
|
</script>
|
|
{% if request.shop.watch_mode_enabled %}
|
|
<script src="/static/js/watch.js?v={{ request.git_hash }}"></script>
|
|
{% endif %}
|
|
<script src="/static/js/signals.js?v={{ request.git_hash }}"></script>
|
|
|
|
{%- endblock -%}
|