fix(nav): a linked player, file and search survive what the app does by itself (review round 2)
- Playlists: a link over a playlist the account does not hold plays alone; the rail's own fallback no longer counts as a pick that gives the player up. - The linked opener opens or clears nothing once the address names another player, and stops looking (and fetching its row) once it settled. - Files: a list the server's cap cut — the sorted or searched one too — keeps the link, with a notice of its own (EN/HU). - History: an in-place view rewrite (search refine, count, channel to channel, sub-view) drops a reloaded entry's overlay marks (replaceView); a landing on a channel opened from a search keeps the search; the Feed press on a search opens the plain feed. - Files/Playlists: an emptied search is written at once. - Client log: plf/cf (column text filters) are typed keys; the shell has none of its own. - Contract: writers outside lib/ take a literal or a lib/ function's result; every URL key is sorted typed or not; the writer regexes are held by a fixture; sub-views named; the bookmark net reads page tags. - Feed defaults in one constant; one live-search queue helper; Scheduler writes through the Activity codec; notRun copy (EN/HU) names Forward and a plain "Search YouTube" button.
This commit is contained in:
@@ -17,6 +17,7 @@ import {
|
||||
nextNavId,
|
||||
notifyNav,
|
||||
rememberNavPosition,
|
||||
replaceView,
|
||||
} from "../../lib/history";
|
||||
import { foreignUrlKeys, moduleUrlKeys, pageTitleKey, reachablePage } from "./moduleRegistry";
|
||||
import { CHANNEL_PAGE_KEYS } from "../../lib/channelPageUrlState";
|
||||
@@ -86,7 +87,7 @@ export function NavigationProvider({ children }: { children: ReactNode }) {
|
||||
// The live YouTube search: named in the address (`yt`, and `ytn` for its count, lib/ytSearchUrlState)
|
||||
// so a reload or a link opens it again; its own history entry, so Back steps player → search → feed.
|
||||
// Whether it RUNS by itself is not the address's to say: `ytAsked` is the term this document was
|
||||
// asked to search for (sent from the search box, or "Search YouTube again" pressed), on the entries
|
||||
// asked to search for (sent from the search box, or "Search YouTube" pressed), on the entries
|
||||
// it has stayed on since — leaving the search ends it. Anything else — a reload, a link, a Back or
|
||||
// a Forward onto a search — runs only when it costs no quota (Feed decides).
|
||||
const [ytSearch, setYtSearch] = useState<string | null>(() =>
|
||||
@@ -136,7 +137,8 @@ export function NavigationProvider({ children }: { children: ReactNode }) {
|
||||
closeOverlaysThenNavigate(() => {
|
||||
// A channel page overlays the content column, so a nav click must close it even when the
|
||||
// underlying page is unchanged (e.g. "Feed" while a channel opened from the feed).
|
||||
if (next === pageRef.current && !channelViewRef.current) {
|
||||
// A live search on it is one too: the press opens the page's root, as over a channel.
|
||||
if (next === pageRef.current && !channelViewRef.current && ytSearchRef.current == null) {
|
||||
then?.();
|
||||
return;
|
||||
}
|
||||
@@ -161,7 +163,7 @@ export function NavigationProvider({ children }: { children: ReactNode }) {
|
||||
notifyNav();
|
||||
then?.();
|
||||
}),
|
||||
[pageRef, channelViewRef, endYtSearch],
|
||||
[pageRef, channelViewRef, ytSearchRef, endYtSearch],
|
||||
);
|
||||
|
||||
// A sub-view (channel / YT search) is an OVERLAY, not a page change. Like setPage it never lands
|
||||
@@ -174,7 +176,7 @@ export function NavigationProvider({ children }: { children: ReactNode }) {
|
||||
const url = channelUrl(id);
|
||||
if (window.history.state?._chan) {
|
||||
const st = window.history.state;
|
||||
window.history.replaceState({ ...st, _chan: id, _chanName: name ?? null }, "", url);
|
||||
replaceView({ ...st, _chan: id, _chanName: name ?? null }, url);
|
||||
} else {
|
||||
const st = rememberNavPosition();
|
||||
window.history.pushState({ ...st, _chan: id, _chanName: name ?? null }, "", url);
|
||||
@@ -193,9 +195,8 @@ export function NavigationProvider({ children }: { children: ReactNode }) {
|
||||
// again: the channel's address carried none of it.
|
||||
setChannelView(null);
|
||||
const { _chan: _id, _chanName: _name, ...st } = window.history.state ?? {};
|
||||
window.history.replaceState(
|
||||
replaceView(
|
||||
st,
|
||||
"",
|
||||
urlWithParams({
|
||||
...closedChannelParams(),
|
||||
...ytSearchParams(ytSearchRef.current, ytCountRef.current),
|
||||
@@ -212,11 +213,7 @@ export function NavigationProvider({ children }: { children: ReactNode }) {
|
||||
if (st._yt || ytSearchFrom(here) != null) {
|
||||
// Refine the search on screen, in place, at the count it shows (one a link opened too: it
|
||||
// is the first entry of this document, so its new `_yt` mark has nothing of ours below it).
|
||||
window.history.replaceState(
|
||||
{ ...st, _yt: q },
|
||||
"",
|
||||
urlWithParams(ytSearchParams(q, ytCountFrom(here))),
|
||||
);
|
||||
replaceView({ ...st, _yt: q }, urlWithParams(ytSearchParams(q, ytCountFrom(here))));
|
||||
} else {
|
||||
// New sub-view entry, at the default count.
|
||||
setYtCountState(DEFAULT_YT_COUNT);
|
||||
@@ -235,9 +232,8 @@ export function NavigationProvider({ children }: { children: ReactNode }) {
|
||||
const setYtCount = useCallback(
|
||||
(n: number) => {
|
||||
setYtCountState(n);
|
||||
window.history.replaceState(
|
||||
window.history.state,
|
||||
"",
|
||||
replaceView(
|
||||
window.history.state ?? {},
|
||||
urlWithParams(ytSearchParams(ytSearchRef.current, n)),
|
||||
);
|
||||
},
|
||||
@@ -251,7 +247,7 @@ export function NavigationProvider({ children }: { children: ReactNode }) {
|
||||
else {
|
||||
endYtSearch();
|
||||
const { _yt: _q, ...st } = window.history.state ?? {};
|
||||
window.history.replaceState(st, "", urlWithParams(ytSearchParams(null, DEFAULT_YT_COUNT)));
|
||||
replaceView(st, urlWithParams(ytSearchParams(null, DEFAULT_YT_COUNT)));
|
||||
}
|
||||
}, [endYtSearch]);
|
||||
|
||||
@@ -263,10 +259,7 @@ export function NavigationProvider({ children }: { children: ReactNode }) {
|
||||
const [pageSearchWasOn, setPageSearchWasOn] = useState(page);
|
||||
if (pageSearchWasOn !== page) {
|
||||
setPageSearchWasOn(page);
|
||||
if (page !== "feed") {
|
||||
setYtSearch(null);
|
||||
setYtAsked(null);
|
||||
}
|
||||
if (page !== "feed") endYtSearch();
|
||||
}
|
||||
|
||||
// The browser tab names the place and the object in it (DESIGN § Composition, "Where am I"):
|
||||
@@ -349,8 +342,12 @@ export function NavigationProvider({ children }: { children: ReactNode }) {
|
||||
// The YouTube search is read from the address the browser restored, so Back/Forward opens or
|
||||
// clears it to match the entry we landed on. One landed on without it has ended: a Forward back
|
||||
// onto it is not asked for, and an API search waits for a press.
|
||||
// A channel page opened from its results stands over it: its entry carries the search's mark
|
||||
// (its address does not), and the search stays under it — closing a player over that channel
|
||||
// lands there, and is no leaving of the search.
|
||||
const here = new URLSearchParams(window.location.search);
|
||||
const landed = p === "feed" ? ytSearchFrom(here) : null;
|
||||
const under = e.state?._chan && typeof e.state._yt === "string" ? e.state._yt : null;
|
||||
const landed = p === "feed" ? (ytSearchFrom(here) ?? under) : null;
|
||||
setYtSearch(landed);
|
||||
if (landed == null) setYtAsked(null);
|
||||
setYtCountState(ytCountFrom(here));
|
||||
|
||||
Reference in New Issue
Block a user