feat(shorts): /shorts/{id} deep-link + URL sync + persistent mute + wheel nav
- New route /shorts/{video} opens the feed scrolled to that short.
Controller aborts 404 when the id doesn't resolve to a short or
isn't viewable by the current viewer, so bad links land on the
branded 404 (added in the next commit) rather than loading an
empty feed.
- The IntersectionObserver that detects the active short now
history.replaceState()s the URL to /shorts/{id} so copy-paste of
the address bar works, without pushing a navigation entry — the
back button still exits the feed in one step.
- Mute state is persisted in localStorage (shortsMuted = '0' | '1')
so unmuting once sticks across sessions. Default remains muted
to satisfy autoplay policies on first load.
- Mouse-wheel navigation moves one short per gesture with a 450 ms
debounce. Trackpad flicks no longer skip five shorts at once.
Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
This commit is contained in:
1 parent
646d3e9d2a
commit
9e613c05d2
3 files changed
+67
-6
No files matched your search
@@ -26,7 +26,7 @@ class VideoController extends Controller
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
$this->middleware('auth')->except(['index', 'show', 'search', 'stream', 'hls', 'trending', 'shorts', 'download', 'downloadMp3', 'recordShare', 'ogImage', 'vsPreview', 'accessShare', 'showByToken', 'recommendations', 'slideshowProgress', 'playerData', 'streamAudioTrack', 'lyricsProgress', 'translateDescription', 'feedMore']);
|
||||
$this->middleware('auth')->except(['index', 'show', 'search', 'stream', 'hls', 'trending', 'shorts', 'shortsFocused', 'download', 'downloadMp3', 'recordShare', 'ogImage', 'vsPreview', 'accessShare', 'showByToken', 'recommendations', 'slideshowProgress', 'playerData', 'streamAudioTrack', 'lyricsProgress', 'translateDescription', 'feedMore']);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2190,7 +2190,7 @@ class VideoController extends Controller
|
||||
]);
|
||||
}
|
||||
|
||||
public function shorts(Request $request)
|
||||
public function shorts(Request $request, ?int $startId = null)
|
||||
{
|
||||
$videos = Video::public()
|
||||
->where('is_shorts', true)
|
||||
@@ -2199,7 +2199,23 @@ class VideoController extends Controller
|
||||
->latest()
|
||||
->get();
|
||||
|
||||
return view('videos.shorts', compact('videos'));
|
||||
return view('videos.shorts', [
|
||||
'videos' => $videos,
|
||||
'startId' => $startId, // the id to auto-scroll to, if any
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* /shorts/{video}: deep-link to a specific short in the vertical feed.
|
||||
* The feed itself is the same ordered list; the view uses ?start= / the
|
||||
* $startId prop to scroll into position on load.
|
||||
*/
|
||||
public function shortsFocused(Video $video)
|
||||
{
|
||||
if (! $video->is_shorts || ! $video->canView(Auth::user())) {
|
||||
abort(404);
|
||||
}
|
||||
return $this->shorts(request(), $video->id);
|
||||
}
|
||||
|
||||
public function stream(Video $video)
|
||||
|
||||
Reference in new issue
Block a user