diff --git a/web/tests/integration/BanDrawerDemoDownloadTest.php b/web/tests/integration/BanDrawerDemoDownloadTest.php
new file mode 100644
index 000000000..583b37f24
--- /dev/null
+++ b/web/tests/integration/BanDrawerDemoDownloadTest.php
@@ -0,0 +1,36 @@
+assertStringContainsString('data.demo_count', $src);
+ $this->assertStringContainsString('getdemo.php?type=B&id=', $src);
+ $this->assertStringContainsString('data-testid="drawer-demo-download"', $src);
+ }
+
+ public function testBansDetailApiExposesDemoCount(): void
+ {
+ $src = (string) file_get_contents(self::panelRoot() . '/api/handlers/bans.php');
+ $this->assertStringContainsString("'demo_count'", $src);
+ }
+}
diff --git a/web/themes/default/js/theme.js b/web/themes/default/js/theme.js
index 4324beb85..df9bb91f7 100644
--- a/web/themes/default/js/theme.js
+++ b/web/themes/default/js/theme.js
@@ -778,6 +778,22 @@
).join('')
+ '';
+ // #1554 — surface the demo download affordance the way SB 1.x's
+ // sliding ban panel did. `bans.detail` carries `demo_count`; when a
+ // ban has an uploaded demo the file is served by `getdemo.php?type=B`
+ // (see web/getdemo.php). Comm-blocks have no demo column, so this is
+ // ban-focal only.
+ let demoHtml = '';
+ if (!isComm && Number((data && data.demo_count) || 0) > 0) {
+ const demoUrl = 'getdemo.php?type=B&id=' + encodeURIComponent(String(data.bid));
+ demoHtml = '';
+ }
+
let commentsHtml = '';
if (commentsVisible) {
commentsHtml = ''
@@ -798,7 +814,7 @@
+ '';
}
- return idHtml + focalHtml + commentsHtml;
+ return idHtml + focalHtml + demoHtml + commentsHtml;
}
/**