+
diff --git a/tests/e2e/Mobile/FlowTests.cs b/tests/e2e/Mobile/FlowTests.cs
index f452a9b..845b524 100644
--- a/tests/e2e/Mobile/FlowTests.cs
+++ b/tests/e2e/Mobile/FlowTests.cs
@@ -262,6 +262,21 @@ namespace decePubClient.E2E.Mobile
}
}
+ // a tap on a post (in its margin, not on a link or a control) opens its thread
+ [Fact]
+ public async Task A_tap_on_a_post_opens_its_thread()
+ {
+ await using var session = await SignIn.As(browser, Persona(), Phone);
+ await Flow(session, "thread", async page =>
+ {
+ var card = Card(page, "a[data-thread-link]");
+ var box = (await card.BoundingBoxAsync())!;
+ await card.TapAsync(new() { Position = new() { X = box.Width - 5, Y = box.Height * 0.35f } });
+ await page.WaitForURLAsync(url => new Uri(url).AbsolutePath.StartsWith("/expand/"), new() { Timeout = 15000 });
+ Assert.Empty(await page.Locator("[data-globe-pinned]").AllAsync());
+ });
+ }
+
static async Task Latest(Pasture.Persona persona) =>
(await PersonaApi.Call(persona, $"/api/v1/accounts/{persona.LocalId}/statuses?limit=1"))!.AsArray().FirstOrDefault();
}
diff --git a/wwwroot/js/globe.js b/wwwroot/js/globe.js
index 16a1f00..534d59d 100644
--- a/wwwroot/js/globe.js
+++ b/wwwroot/js/globe.js
@@ -21,6 +21,8 @@ const vendor = new URL("../vendor/globe/", import.meta.url).href
const textures = { day: vendor + "earth-blue-marble.jpg", night: vendor + "earth-night.jpg" }
const countriesUrl = vendor + "countries.json"
const reducedMotion = matchMedia("(prefers-reduced-motion: reduce)")
+// a touch screen: a tap on a card opens its thread there (main.js), so cards are pinned with a mouse only
+const canHover = matchMedia("(hover: hover)")
let loading = null
let state = null
@@ -597,10 +599,10 @@ function watchCards() {
hover(null)
})
// a click on a card (not on its links or controls) pins it: the globe keeps its moment and place until it is
- // clicked again or another card is pinned
+ // clicked again or another card is pinned. With a mouse only: on a touch screen the tap opens the post's thread
listen(scroller, "click", event => {
const card = event.target.closest?.("[data-post-id]")
- if (!card || event.target.closest("a, button, input, textarea, select, label, summary, video, audio, [role=button]"))
+ if (!canHover.matches || !card || event.target.closest("a, button, input, textarea, select, label, summary, video, audio, [role=button]"))
return
const key = card.dataset.postId
scroller.querySelectorAll("[data-globe-pinned]").forEach(c => c.removeAttribute("data-globe-pinned"))
diff --git a/wwwroot/main.js b/wwwroot/main.js
index 7db0b90..b09025d 100644
--- a/wwwroot/main.js
+++ b/wwwroot/main.js
@@ -123,6 +123,23 @@ window.clearAppBadge = () => {
window.logFromJs(e.message, "clearAppBadge")
}
}
+// On a touch screen a tap on a post (not on its links, controls or menus, and not ending a text selection) opens its
+// thread, through the card's own thread link (Content.razor, data-thread-link), which Blazor's router takes.
+document.addEventListener("click", event => {
+ try {
+ if (window.matchMedia("(hover: hover)").matches || !(event.target instanceof Element))
+ return
+ const card = event.target.closest("[data-thread]")
+ if (!card || event.target.closest("a, button, input, textarea, select, label, summary, video, audio, [role=button], .dropdown, .modal, [data-sheet]"))
+ return
+ if (!window.getSelection()?.isCollapsed)
+ return
+ card.querySelector("a[data-thread-link]")?.click()
+ } catch (e) {
+ console.error(e)
+ window.logFromJs(e.message, "openThread")
+ }
+})
window.canHover = () => {
try {
return window.matchMedia("(hover: hover)").matches