mirror of
https://github.com/iv-org/invidious.git
synced 2025-06-28 01:28:30 +00:00
Updated styling, formatting, structure of frontend
This commit is contained in:
parent
18ce902aa0
commit
71a30512c9
9
Makefile
9
Makefile
@ -53,7 +53,6 @@ get-libs:
|
|||||||
invidious: get-libs
|
invidious: get-libs
|
||||||
crystal build src/invidious.cr $(FLAGS) --progress --stats --error-trace
|
crystal build src/invidious.cr $(FLAGS) --progress --stats --error-trace
|
||||||
|
|
||||||
|
|
||||||
run: invidious
|
run: invidious
|
||||||
./invidious
|
./invidious
|
||||||
|
|
||||||
@ -73,6 +72,12 @@ verify:
|
|||||||
crystal build src/invidious.cr -Dskip_videojs_download \
|
crystal build src/invidious.cr -Dskip_videojs_download \
|
||||||
--no-codegen --progress --stats --error-trace
|
--no-codegen --progress --stats --error-trace
|
||||||
|
|
||||||
|
dev:
|
||||||
|
crystal build src/invidious.cr -Dskip_videojs_download \
|
||||||
|
--no-codegen --progress --stats --error-trace -- --disable-static-cache
|
||||||
|
|
||||||
|
dev-reload:
|
||||||
|
tree -fiA --prune --noreport src | entr -rd make dev
|
||||||
|
|
||||||
# -----------------------
|
# -----------------------
|
||||||
# (Un)Install
|
# (Un)Install
|
||||||
@ -125,4 +130,4 @@ help:
|
|||||||
|
|
||||||
# No targets generates an output named after themselves
|
# No targets generates an output named after themselves
|
||||||
.PHONY: all get-libs build amd64 run
|
.PHONY: all get-libs build amd64 run
|
||||||
.PHONY: format test verify clean distclean help
|
.PHONY: format test verify dev dev-reload clean distclean help
|
||||||
|
9
assets/css/animation.css
Normal file
9
assets/css/animation.css
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
@keyframes spin {
|
||||||
|
0% {
|
||||||
|
transform: rotate(0deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
@ -10,18 +10,18 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.watch-on-invidious {
|
.watch-on-invidious {
|
||||||
font-size: 1.3em !important;
|
font-size: 1.3em;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
margin: 0 1em 0 1em !important;
|
margin: 0 1em;
|
||||||
order: 3;
|
order: 3;
|
||||||
}
|
}
|
||||||
|
/**/
|
||||||
|
/* .watch-on-invidious > a { */
|
||||||
|
/* color: white; */
|
||||||
|
/* } */
|
||||||
|
|
||||||
.watch-on-invidious > a {
|
/* .watch-on-invidious > a:hover, */
|
||||||
color: white;
|
/* .watch-on-invidious > a:focus { */
|
||||||
}
|
/* color: ; */
|
||||||
|
/* } */
|
||||||
.watch-on-invidious > a:hover,
|
|
||||||
.watch-on-invidious > a:focus {
|
|
||||||
color: rgba(0, 182, 240, 1);;
|
|
||||||
}
|
|
||||||
|
@ -1,16 +1,32 @@
|
|||||||
|
|
||||||
|
/* Widget covers the whole page */
|
||||||
#search-widget {
|
#search-widget {
|
||||||
text-align: center;
|
position: absolute;
|
||||||
margin: 20vh 0 50px 0;
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
display: grid;
|
||||||
|
justify-content: center;
|
||||||
|
grid-gap: var(--gap);
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
#logo > h1 {
|
.search-homepage {
|
||||||
font-size: 3.5em;
|
display: grid;
|
||||||
margin: 0;
|
grid-template-rows: 1fr 1fr;
|
||||||
padding: 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (max-width: 1500px) and (max-height: 1000px) {
|
#search-widget > h1 {
|
||||||
#logo > h1 {
|
align-self: flex-end;
|
||||||
font-size: 10vmin;
|
font-size: 3em;
|
||||||
}
|
text-transform: uppercase;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.searchbar {
|
||||||
|
/* reset pointer events for interactive components */
|
||||||
|
pointer-events: initial;
|
||||||
}
|
}
|
||||||
|
@ -1,92 +1,106 @@
|
|||||||
|
.video-js {
|
||||||
|
font-family: inherit;
|
||||||
|
font-size: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
/* Youtube player style */
|
/* Youtube player style */
|
||||||
.video-js.player-style-youtube .vjs-progress-control {
|
/* .video-js.player-style-youtube .vjs-progress-control { */
|
||||||
height: 0;
|
/* height: 0; */
|
||||||
}
|
/* } */
|
||||||
|
/**/
|
||||||
|
/* .video-js.player-style-youtube .vjs-progress-control .vjs-progress-holder, */
|
||||||
|
/* .video-js.player-style-youtube .vjs-progress-control { */
|
||||||
|
/* position: absolute; */
|
||||||
|
/* right: 0; */
|
||||||
|
/* left: 0; */
|
||||||
|
/* width: 100%; */
|
||||||
|
/* margin: 0; */
|
||||||
|
/* } */
|
||||||
|
/**/
|
||||||
|
/* .video-js.player-style-youtube .vjs-control-bar { */
|
||||||
|
/* background: linear-gradient(rgb(0 0 0 / 10%), rgba(0 0 0 / 50%)); */
|
||||||
|
/* } */
|
||||||
|
/**/
|
||||||
|
/* .video-js.player-style-youtube .vjs-slider { */
|
||||||
|
/* background-color: rgb(255 255 255 / 20%); */
|
||||||
|
/* } */
|
||||||
|
/**/
|
||||||
|
/* .video-js.player-style-youtube .vjs-load-progress > div { */
|
||||||
|
/* background-color: rgb(255 255 255 / 20%); */
|
||||||
|
/* } */
|
||||||
|
/**/
|
||||||
|
/* .video-js.player-style-youtube .vjs-play-progress { */
|
||||||
|
/* background-color: var(--accent-bg-color); */
|
||||||
|
/* } */
|
||||||
|
/**/
|
||||||
|
/* .video-js.player-style-youtube */
|
||||||
|
/* .vjs-progress-control:hover */
|
||||||
|
/* .vjs-progress-holder { */
|
||||||
|
/* font-size: 1em; */
|
||||||
|
/* } */
|
||||||
|
/**/
|
||||||
|
/* .video-js.player-style-youtube .vjs-control-bar > .vjs-spacer { */
|
||||||
|
/* flex: 1; */
|
||||||
|
/* order: 2; */
|
||||||
|
/* } */
|
||||||
|
/**/
|
||||||
|
/* .video-js.player-style-youtube .vjs-play-progress .vjs-time-tooltip { */
|
||||||
|
/* display: none; */
|
||||||
|
/* } */
|
||||||
|
/**/
|
||||||
|
/* .video-js.player-style-youtube .vjs-play-progress::before { */
|
||||||
|
/* color: var(--accent-bg-color); */
|
||||||
|
/* font-size: 0.85em; */
|
||||||
|
/* display: none; */
|
||||||
|
/* } */
|
||||||
|
/**/
|
||||||
|
/* .video-js.player-style-youtube */
|
||||||
|
/* .vjs-progress-holder:hover */
|
||||||
|
/* .vjs-play-progress::before { */
|
||||||
|
/* display: unset; */
|
||||||
|
/* } */
|
||||||
|
/**/
|
||||||
|
/* .video-js.player-style-youtube .vjs-control-bar { */
|
||||||
|
/* display: flex; */
|
||||||
|
/* flex-direction: row; */
|
||||||
|
/* } */
|
||||||
|
/**/
|
||||||
|
/* .video-js.player-style-youtube .vjs-big-play-button { */
|
||||||
|
/* /* */
|
||||||
|
/* Styles copied from video-js.min.css, definition of */
|
||||||
|
/* .vjs-big-play-centered .vjs-big-play-button */
|
||||||
|
/* */
|
||||||
|
*/
|
||||||
|
/* top: 50%; */
|
||||||
|
/* left: 50%; */
|
||||||
|
/* margin-top: -0.8167em; */
|
||||||
|
/* margin-left: -1.5em; */
|
||||||
|
/* } */
|
||||||
|
|
||||||
.video-js.player-style-youtube .vjs-progress-control .vjs-progress-holder, .video-js.player-style-youtube .vjs-progress-control {
|
/* .video-js.player-style-youtube .vjs-menu-button-popup .vjs-menu { */
|
||||||
position: absolute;
|
/* margin-bottom: 2em; */
|
||||||
right: 0;
|
/* padding-top: 2em; */
|
||||||
left: 0;
|
/* } */
|
||||||
width: 100%;
|
/**/
|
||||||
margin: 0;
|
/* .video-js.player-style-youtube .vjs-progress-control .vjs-progress-holder, */
|
||||||
}
|
/* .video-js.player-style-youtube .vjs-progress-control { */
|
||||||
|
/* height: 0.3em; */
|
||||||
|
/* margin-bottom: 0.6em; */
|
||||||
|
/* } */
|
||||||
|
/**/
|
||||||
|
/* ul.vjs-menu-content::-webkit-scrollbar { */
|
||||||
|
/* display: none; */
|
||||||
|
/* } */
|
||||||
|
/**/
|
||||||
|
/* .vjs-user-inactive { */
|
||||||
|
/* cursor: none; */
|
||||||
|
/* } */
|
||||||
|
|
||||||
.video-js.player-style-youtube .vjs-control-bar {
|
/* .video-js .vjs-text-track-display > div > div > div { */
|
||||||
background: linear-gradient(rgba(0,0,0,0.1), rgba(0, 0, 0,0.5));
|
/* background-color: rgb(0 0 0 / 75%); */
|
||||||
}
|
/* border-radius: 0.5em; */
|
||||||
|
/* padding: 0.3em; */
|
||||||
.video-js.player-style-youtube .vjs-slider {
|
/* } */
|
||||||
background-color: rgba(255,255,255,0.2);
|
|
||||||
}
|
|
||||||
|
|
||||||
.video-js.player-style-youtube .vjs-load-progress > div {
|
|
||||||
background-color: rgba(255,255,255,0.5);
|
|
||||||
}
|
|
||||||
|
|
||||||
.video-js.player-style-youtube .vjs-play-progress {
|
|
||||||
background-color: red;
|
|
||||||
}
|
|
||||||
|
|
||||||
.video-js.player-style-youtube .vjs-progress-control:hover .vjs-progress-holder {
|
|
||||||
font-size: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.video-js.player-style-youtube .vjs-control-bar > .vjs-spacer {
|
|
||||||
flex: 1;
|
|
||||||
order: 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
.video-js.player-style-youtube .vjs-play-progress .vjs-time-tooltip {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.video-js.player-style-youtube .vjs-play-progress::before {
|
|
||||||
color: red;
|
|
||||||
font-size: 0.85em;
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.video-js.player-style-youtube .vjs-progress-holder:hover .vjs-play-progress::before {
|
|
||||||
display: unset;
|
|
||||||
}
|
|
||||||
|
|
||||||
.video-js.player-style-youtube .vjs-control-bar {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
}
|
|
||||||
|
|
||||||
.video-js.player-style-youtube .vjs-big-play-button {
|
|
||||||
/*
|
|
||||||
Styles copied from video-js.min.css, definition of
|
|
||||||
.vjs-big-play-centered .vjs-big-play-button
|
|
||||||
*/
|
|
||||||
top: 50%;
|
|
||||||
left: 50%;
|
|
||||||
margin-top: -0.81666em;
|
|
||||||
margin-left: -1.5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.video-js.player-style-youtube .vjs-menu-button-popup .vjs-menu {
|
|
||||||
margin-bottom: 2em;
|
|
||||||
padding-top: 2em
|
|
||||||
}
|
|
||||||
|
|
||||||
.video-js.player-style-youtube .vjs-progress-control .vjs-progress-holder, .video-js.player-style-youtube .vjs-progress-control {height: 5px;
|
|
||||||
margin-bottom: 10px;}
|
|
||||||
|
|
||||||
ul.vjs-menu-content::-webkit-scrollbar {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.vjs-user-inactive {
|
|
||||||
cursor: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.video-js .vjs-text-track-display > div > div > div {
|
|
||||||
background-color: rgba(0, 0, 0, 0.75) !important;
|
|
||||||
border-radius: 9px !important;
|
|
||||||
padding: 5px !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.vjs-play-control,
|
.vjs-play-control,
|
||||||
.vjs-volume-panel,
|
.vjs-volume-panel,
|
||||||
@ -123,143 +137,252 @@ ul.vjs-menu-content::-webkit-scrollbar {
|
|||||||
order: 7;
|
order: 7;
|
||||||
}
|
}
|
||||||
|
|
||||||
.vjs-playback-rate > .vjs-menu {
|
/* .vjs-playback-rate > .vjs-menu { */
|
||||||
width: 50px;
|
/* width: 3.2em; */
|
||||||
|
/* } */
|
||||||
|
/**/
|
||||||
|
|
||||||
|
/* Make the video relative, instead of absolute, so that
|
||||||
|
the parent container will size based on the video. Also,
|
||||||
|
note the max-height rule. Note the line-height 0 is to prevent
|
||||||
|
a small artifact on the bottom of the video.
|
||||||
|
https://stackoverflow.com/questions/46747320/limit-the-height-in-videojs-in-fluid-mode/47039499#47039499
|
||||||
|
*/
|
||||||
|
.video-js.vjs-fluid,
|
||||||
|
.video-js.vjs-16-9,
|
||||||
|
.video-js.vjs-4-3,
|
||||||
|
video.video-js,
|
||||||
|
video.vjs-tech {
|
||||||
|
max-height: 85vh;
|
||||||
|
position: relative !important;
|
||||||
|
width: 100%;
|
||||||
|
height: auto;
|
||||||
|
max-width: 100% !important;
|
||||||
|
padding-top: 0 !important;
|
||||||
|
line-height: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.vjs-control-bar {
|
.video-js.vjs-16-9 {
|
||||||
|
/* Keep the video spaced to fit */
|
||||||
|
aspect-ratio: 16 / 9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-js.vjs-4-3 {
|
||||||
|
/* Keep the video spaced to fit */
|
||||||
|
aspect-ratio: 4 / 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
#player {
|
||||||
|
/* Default to 16/9 video spacing */
|
||||||
|
aspect-ratio: 16 / 9;
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vjs-error .vjs-error-display:before,
|
||||||
|
.video-js .vjs-volume-tooltip,
|
||||||
|
.video-js .vjs-time-tooltip,
|
||||||
|
.vjs-menu .vjs-menu-content {
|
||||||
|
font-family: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vjs-menu-button-popup .vjs-menu .vjs-menu-content {
|
||||||
|
background-color: var(--secondary-bg-color-dark);
|
||||||
|
}
|
||||||
|
|
||||||
|
.vjs-menu li.vjs-menu-item:focus,
|
||||||
|
.vjs-menu li.vjs-menu-item:hover,
|
||||||
|
.js-focus-visible .vjs-menu li.vjs-menu-item:hover {
|
||||||
|
background-color: var(--accent-bg-color-dark);
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-js .vjs-slider {
|
||||||
|
background-color: rgb(166 166 166 / 50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-js .vjs-load-progress {
|
||||||
|
background-color: rgb(227 227 227 / 75%);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 480px) {
|
||||||
|
#player {
|
||||||
|
/* Default to 16/9 video spacing */
|
||||||
|
aspect-ratio: unset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.vjs-fullscreen video {
|
||||||
|
max-height: 100vh !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-js .vjs-control-bar {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
scrollbar-width: none;
|
scrollbar-width: none;
|
||||||
|
/* Fix the control bar due to us resetting the line-height on the video-js */
|
||||||
|
line-height: 1;
|
||||||
|
background: linear-gradient(
|
||||||
|
to bottom,
|
||||||
|
transparent 0%,
|
||||||
|
var(--secondary-bg-color-dark) 50%
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
.vjs-control-bar button:hover,
|
||||||
|
.vjs-control-bar button:focus {
|
||||||
|
outline-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.vjs-control-bar::-webkit-scrollbar {
|
.vjs-control-bar::-webkit-scrollbar {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.video-js .vjs-icon-cog {
|
.vjs-playback-rate .vjs-playback-rate-value {
|
||||||
font-size: 18px;
|
font-size: 1em;
|
||||||
|
line-height: 3rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.video-js .vjs-control-bar,
|
.vjs-button > .vjs-icon-placeholder::before {
|
||||||
.vjs-menu-button-popup .vjs-menu .vjs-menu-content {
|
font-size: 1.25em;
|
||||||
background-color: rgba(35, 35, 35, 0.75);
|
line-height: 3rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.vjs-menu li.vjs-menu-item:focus,
|
.vjs-menu li {
|
||||||
.vjs-menu li.vjs-menu-item:hover {
|
font-size: 1em;
|
||||||
background-color: rgba(255, 255, 255, 0.75);
|
|
||||||
color: rgba(49, 49, 51, 0.75);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.vjs-menu li.vjs-selected,
|
.video-js .vjs-control {
|
||||||
.vjs-menu li.vjs-selected:focus,
|
width: 3em;
|
||||||
.vjs-menu li.vjs-selected:hover {
|
|
||||||
background-color: rgba(0, 182, 240, 0.75);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Progress Bar */
|
.video-js .vjs-time-control {
|
||||||
.video-js .vjs-slider {
|
width: auto;
|
||||||
background-color: rgba(15, 15, 15, 0.5);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.video-js .vjs-load-progress,
|
.vjs-poster {
|
||||||
.video-js .vjs-load-progress div {
|
background-size: cover;
|
||||||
background: rgba(87, 87, 88, 1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.video-js .vjs-share__short-link-wrapper {
|
||||||
|
color: var(--fg-color-dark);
|
||||||
|
background-color: var(--secondary-bg-color-dark);
|
||||||
|
height: 2em;
|
||||||
|
margin: 0 auto;
|
||||||
|
margin-bottom: var(--secondary-gap);
|
||||||
|
border-radius: var(--radius);
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-js .vjs-share__short-link {
|
||||||
|
padding: var(--secondary-gap);
|
||||||
|
background-color: var(--secondary-bg-color-dark);
|
||||||
|
color: var(--fg-color-dark);
|
||||||
|
font-family: var(--monospace);
|
||||||
|
min-width: 15em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-js .vjs-share__btn {
|
||||||
|
background-color: var(--secondary-bg-color-dark);
|
||||||
|
color: var(--fg-color-dark);
|
||||||
|
height: 2em;
|
||||||
|
width: 2.25em;
|
||||||
|
padding: var(--secondary-gap);
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-js .vjs-share__title {
|
||||||
|
font-size: 1.25em;
|
||||||
|
color: var(--fg-color-dark);
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-js .vjs-share__subtitle {
|
||||||
|
font-size: 1em;
|
||||||
|
color: var(--fg-color-dark);
|
||||||
|
margin: 0 auto var(--secondary-gap);
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-js.vjs-videojs-share_open .vjs-modal-dialog .vjs-close-button {
|
||||||
|
height: var(--gap);
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-js.vjs-videojs-share_open .vjs-modal-dialog .vjs-close-button::before {
|
||||||
|
content: "";
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-js .vjs-control.vjs-close-button .vjs-icon-placeholder::before {
|
||||||
|
line-height: 1.67;
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-js .vjs-share__middle {
|
||||||
|
padding: 0 var(--secondary-gap);
|
||||||
|
}
|
||||||
|
|
||||||
|
.vjs-modal-dialog .vjs-modal-dialog-content {
|
||||||
|
font-size: 1em;
|
||||||
|
line-height: 1.1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**/
|
||||||
|
/* .video-js .vjs-icon-cog { */
|
||||||
|
/* font-size: 1em; */
|
||||||
|
/* } */
|
||||||
|
/**/
|
||||||
|
|
||||||
|
/* .video-js .vjs-control-bar, */
|
||||||
|
/* .vjs-menu-button-popup .vjs-menu .vjs-menu-content { */
|
||||||
|
/* background-color: rgb(35 35 35 / 75%); */
|
||||||
|
/* } */
|
||||||
|
/**/
|
||||||
|
/* .vjs-menu li.vjs-menu-item:focus, */
|
||||||
|
/* .vjs-menu li.vjs-menu-item:hover { */
|
||||||
|
/* background-color: rgb(255 255 255 / 75%); */
|
||||||
|
/* color: rgb(49 49 51 / 75%); */
|
||||||
|
/* } */
|
||||||
|
/**/
|
||||||
|
/* .vjs-menu li.vjs-selected, */
|
||||||
|
/* .vjs-menu li.vjs-selected:focus, */
|
||||||
|
/* .vjs-menu li.vjs-selected:hover { */
|
||||||
|
/* background-color: rgb(0 182 240 / 75%); */
|
||||||
|
/* } */
|
||||||
|
/**/
|
||||||
|
/* /* Progress Bar */
|
||||||
|
*/
|
||||||
|
/* .video-js .vjs-slider { */
|
||||||
|
/* background-color: rgb(15 15 15 / 50%); */
|
||||||
|
/* } */
|
||||||
|
/**/
|
||||||
|
/* .video-js .vjs-load-progress, */
|
||||||
|
/* .video-js .vjs-load-progress div { */
|
||||||
|
/* background: rgb(87 87 88); */
|
||||||
|
/* } */
|
||||||
|
/**/
|
||||||
.video-js .vjs-slider:hover,
|
.video-js .vjs-slider:hover,
|
||||||
.video-js button:hover {
|
.video-js button:hover {
|
||||||
color: rgba(0, 182, 240, 1);
|
background-color: var(--accent-bg-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.video-js.player-style-invidious .vjs-play-progress {
|
.video-js.player-style-invidious .vjs-play-progress {
|
||||||
background-color: rgba(0, 182, 240, 1);
|
background-color: var(--accent-bg-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Overlay */
|
.vjs-modal-dialog .vjs-modal-dialog-content {
|
||||||
.video-js .vjs-overlay {
|
padding: var(--gap);
|
||||||
background-color: rgba(35, 35, 35, 0.75) !important;
|
|
||||||
}
|
|
||||||
.video-js .vjs-overlay * {
|
|
||||||
color: rgba(255, 255, 255, 1) !important;
|
|
||||||
text-align: center;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ProgressBar marker */
|
|
||||||
.vjs-marker {
|
|
||||||
background-color: rgba(255, 255, 255, 1);
|
|
||||||
z-index: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Big "Play" Button */
|
|
||||||
.video-js .vjs-big-play-button {
|
.video-js .vjs-big-play-button {
|
||||||
background-color: rgba(35, 35, 35, 0.5);
|
font-size: var(--gap);
|
||||||
|
line-height: var(--gap);
|
||||||
|
height: var(--gap);
|
||||||
|
width: calc(var(--gap) * 2);
|
||||||
|
top: var(--secondary-gap);
|
||||||
|
left: var(--secondary-gap);
|
||||||
|
background-color: var(--secondary-bg-color);
|
||||||
|
border: none;
|
||||||
|
opacity: 0.7;
|
||||||
|
transition: opacity 240ms;
|
||||||
}
|
}
|
||||||
|
|
||||||
.video-js:hover .vjs-big-play-button {
|
.video-js .vjs-big-play-button:hover {
|
||||||
background-color: rgba(35, 35, 35, 0.75);
|
opacity: 0.9;
|
||||||
}
|
}
|
||||||
|
|
||||||
.video-js .vjs-current-time,
|
.vjs-control-bar {
|
||||||
.video-js .vjs-time-divider,
|
background-color: var(--secondary-bg-color-dark);
|
||||||
.video-js .vjs-duration {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.video-js .vjs-time-divider {
|
|
||||||
min-width: 0px;
|
|
||||||
padding-left: 0px;
|
|
||||||
padding-right: 0px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.video-js .vjs-poster {
|
|
||||||
background-size: cover;
|
|
||||||
object-fit: cover;
|
|
||||||
}
|
|
||||||
|
|
||||||
.player-dimensions.vjs-fluid {
|
|
||||||
padding-top: 82vh;
|
|
||||||
}
|
|
||||||
|
|
||||||
video.video-js {
|
|
||||||
position: absolute;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
#player-container {
|
|
||||||
position: relative;
|
|
||||||
padding-left: 0;
|
|
||||||
padding-right: 0;
|
|
||||||
margin-left: 1em;
|
|
||||||
margin-right: 1em;
|
|
||||||
padding-bottom: 82vh;
|
|
||||||
height: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mobile-operations-bar {
|
|
||||||
display: flex;
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
right: 1px !important;
|
|
||||||
left: initial !important;
|
|
||||||
width: initial !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mobile-operations-bar ul {
|
|
||||||
position: absolute !important;
|
|
||||||
bottom: unset !important;
|
|
||||||
top: 1.5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media screen and (max-width: 700px) {
|
|
||||||
.video-js .vjs-share {
|
|
||||||
justify-content: unset;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media screen and (max-width: 650px) {
|
|
||||||
.vjs-modal-dialog-content {
|
|
||||||
overflow-x: hidden;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
73
assets/css/pure-fix.css
Normal file
73
assets/css/pure-fix.css
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
/** fixes for pure to support our colors */
|
||||||
|
.pure-form input[type="color"],
|
||||||
|
.pure-form input[type="date"],
|
||||||
|
.pure-form input[type="datetime-local"],
|
||||||
|
.pure-form input[type="datetime"],
|
||||||
|
.pure-form input[type="email"],
|
||||||
|
.pure-form input[type="month"],
|
||||||
|
.pure-form input[type="number"],
|
||||||
|
.pure-form input[type="password"],
|
||||||
|
.pure-form input[type="search"],
|
||||||
|
.pure-form input[type="tel"],
|
||||||
|
.pure-form input[type="text"],
|
||||||
|
.pure-form input[type="time"],
|
||||||
|
.pure-form input[type="url"],
|
||||||
|
.pure-form input[type="week"],
|
||||||
|
.pure-form select,
|
||||||
|
.pure-form textarea {
|
||||||
|
font-size: inherit;
|
||||||
|
padding: var(--secondary-gap);
|
||||||
|
color: var(--fg-color);
|
||||||
|
background-color: var(--secondary-bg-color);
|
||||||
|
border: 1px solid var(--secondary-bg-color);
|
||||||
|
box-shadow: unset;
|
||||||
|
border-radius: var(--radius);
|
||||||
|
}
|
||||||
|
|
||||||
|
.pure-menu-heading,
|
||||||
|
.pure-g,
|
||||||
|
.pure-g [class*="pure-u"] {
|
||||||
|
font-family: inherit;
|
||||||
|
letter-spacing: initial;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pure-form legend {
|
||||||
|
color: var(--fg-color);
|
||||||
|
border-bottom: 1px solid var(--accent-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
legend {
|
||||||
|
border: initial;
|
||||||
|
padding: initial;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pure-button {
|
||||||
|
font-family: inherit;
|
||||||
|
font-size: inherit;
|
||||||
|
padding: 0;
|
||||||
|
color: currentcolor;
|
||||||
|
border: none;
|
||||||
|
border: none transparent;
|
||||||
|
background-color: transparent;
|
||||||
|
text-decoration: none;
|
||||||
|
border-radius: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pure-button-hover,
|
||||||
|
.pure-button:focus,
|
||||||
|
.pure-button:hover {
|
||||||
|
background-image: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Wider settings name to less word wrap */
|
||||||
|
.pure-form-aligned .pure-control-group label {
|
||||||
|
width: 19em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pure-menu-heading {
|
||||||
|
color: var(--accent-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
a:active, a:hover {
|
||||||
|
outline: inherit;
|
||||||
|
}
|
@ -1,121 +1,111 @@
|
|||||||
summary {
|
summary {
|
||||||
/* This should hide the marker */
|
display: block;
|
||||||
display: block;
|
font-size: 1.17em;
|
||||||
|
margin: 0 auto 0.625em;
|
||||||
font-size: 1.17em;
|
cursor: pointer;
|
||||||
font-weight: bold;
|
|
||||||
margin: 0 auto 10px auto;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
summary::-webkit-details-marker,
|
summary::-webkit-details-marker,
|
||||||
summary::marker { display: none; }
|
summary::marker {
|
||||||
|
display: none;
|
||||||
summary:before {
|
|
||||||
border-radius: 5px;
|
|
||||||
content: "[ + ]";
|
|
||||||
margin: -2px 10px 0 10px;
|
|
||||||
padding: 1px 0 3px 0;
|
|
||||||
text-align: center;
|
|
||||||
width: 40px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
details[open] > summary:before { content: "[ − ]"; }
|
summary::before {
|
||||||
|
content: "+ ";
|
||||||
|
text-align: center;
|
||||||
#filters-box {
|
|
||||||
padding: 10px 20px 20px 10px;
|
|
||||||
margin: 10px 15px;
|
|
||||||
}
|
}
|
||||||
#filters-flex {
|
|
||||||
|
details[open] > summary::before {
|
||||||
|
content: "− ";
|
||||||
|
}
|
||||||
|
|
||||||
|
.filters {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(12em, 1fr));
|
||||||
|
grid-gap: var(--secondary-gap);
|
||||||
|
margin: var(--gap) 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filters fieldset {
|
||||||
|
display: grid;
|
||||||
|
grid-gap: var(--secondary-gap);
|
||||||
|
align-content: baseline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filters fieldset div {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
gap: var(--secondary-gap);
|
||||||
flex-direction: row;
|
|
||||||
align-items: flex-start;
|
|
||||||
align-content: flex-start;
|
|
||||||
justify-content: flex-start;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* #filters-box { */
|
||||||
fieldset, legend {
|
/* background-color: var(--secondary-bg-color); */
|
||||||
display: contents !important;
|
/* } */
|
||||||
border: none !important;
|
/**/
|
||||||
margin: 0 !important;
|
/* #filters-flex { */
|
||||||
padding: 0 !important;
|
/* display: flex; */
|
||||||
}
|
/* flex-flow: row wrap; */
|
||||||
|
/* align-items: flex-start; */
|
||||||
|
/* place-content: flex-start flex-start; */
|
||||||
.filter-column {
|
/* } */
|
||||||
display: inline-block;
|
/**/
|
||||||
display: inline-flex;
|
/* .filter-column { */
|
||||||
width: max-content;
|
/* display: inline-block; */
|
||||||
min-width: max-content;
|
/* display: inline-flex; */
|
||||||
max-width: 16em;
|
/* width: max-content; */
|
||||||
margin: 15px;
|
/* min-width: max-content; */
|
||||||
flex-grow: 2;
|
/* max-width: 16em; */
|
||||||
flex-basis: auto;
|
/* margin: 0.9375rem; */
|
||||||
flex-direction: column;
|
/* flex-grow: 2; */
|
||||||
}
|
/* flex-basis: auto; */
|
||||||
.filter-name, .filter-options {
|
/* flex-direction: column; */
|
||||||
display: block;
|
/* } */
|
||||||
padding: 5px 10px;
|
/**/
|
||||||
margin: 0;
|
/* .filter-name, */
|
||||||
text-align: start;
|
/* .filter-options { */
|
||||||
}
|
/* display: block; */
|
||||||
|
/* padding: 0.3125rem 0.625rem; */
|
||||||
.filter-options div { margin: 6px 0; }
|
/* margin: 0; */
|
||||||
.filter-options div * { vertical-align: middle; }
|
/* text-align: start; */
|
||||||
.filter-options label { margin: 0 10px; }
|
/* } */
|
||||||
|
/**/
|
||||||
|
/* .filter-options div { */
|
||||||
#filters-apply {
|
/* margin: 0.375rem 0; */
|
||||||
text-align: right; /* IE11 only */
|
/* } */
|
||||||
text-align: end; /* Override for compatible browsers */
|
/**/
|
||||||
}
|
/* .filter-options div * { */
|
||||||
|
/* vertical-align: middle; */
|
||||||
|
/* } */
|
||||||
|
/**/
|
||||||
|
/* .filter-options label { */
|
||||||
|
/* margin: 0 0.625rem; */
|
||||||
|
/* } */
|
||||||
|
/**/
|
||||||
|
/* #filters-apply { */
|
||||||
|
/* text-align: right; /* IE11 only */ */
|
||||||
|
/* text-align: end; /* Override for compatible browsers */ */
|
||||||
|
/* } */
|
||||||
|
|
||||||
/* Error message */
|
/* Error message */
|
||||||
|
|
||||||
.no-results-error {
|
.no-results-error {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
line-height: 180%;
|
font-size: 1.1em;
|
||||||
font-size: 110%;
|
padding: 1em 1em 8em;
|
||||||
padding: 15px 15px 125px 15px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Responsive rules */
|
/* @media only screen and (max-width: 50px) { */
|
||||||
|
/* summary { */
|
||||||
@media only screen and (max-width: 800px) {
|
/* font-size: 1.3em; */
|
||||||
summary { font-size: 1.30em; }
|
/* } */
|
||||||
#filters-box {
|
/**/
|
||||||
margin: 10px 0 0 0;
|
/* #filters-box { */
|
||||||
padding: 0;
|
/* margin: 0.6em 0 0; */
|
||||||
}
|
/* padding: 0; */
|
||||||
#filters-apply {
|
/* } */
|
||||||
text-align: center;
|
/**/
|
||||||
padding: 15px;
|
/* #filters-apply { */
|
||||||
}
|
/* text-align: center; */
|
||||||
}
|
/* padding: 1em; */
|
||||||
|
/* } */
|
||||||
/* Light theme */
|
/* } */
|
||||||
|
/**/
|
||||||
.light-theme #filters-box {
|
|
||||||
background: #dfdfdf;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (prefers-color-scheme: light) {
|
|
||||||
.no-theme #filters-box {
|
|
||||||
background: #dfdfdf;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Dark theme */
|
|
||||||
|
|
||||||
.dark-theme #filters-box {
|
|
||||||
background: #373737;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (prefers-color-scheme: dark) {
|
|
||||||
.no-theme #filters-box {
|
|
||||||
background: #373737;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
16
assets/css/theme-catppuccin-latte.css
Normal file
16
assets/css/theme-catppuccin-latte.css
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
:root {
|
||||||
|
--fg-color-dark: #f8f8f2;
|
||||||
|
--bg-color-dark: #eff1f5;
|
||||||
|
--accent-color-dark: #fff;
|
||||||
|
--accent-bg-color-dark: #181926;
|
||||||
|
--secondary-color-dark: #e3e3e3;
|
||||||
|
--secondary-bg-color-dark: #494d64;
|
||||||
|
|
||||||
|
/* light theme colors */
|
||||||
|
--fg-color-light: #4c4f69;
|
||||||
|
--bg-color-light: #eff1f5;
|
||||||
|
--accent-color-light: #bcc0cc;
|
||||||
|
--accent-bg-color-light: #7287fd;
|
||||||
|
--secondary-color-light: #5c5f77;
|
||||||
|
--secondary-bg-color-light: #e6e9ef;
|
||||||
|
}
|
16
assets/css/theme-catppuccin-macchiato.css
Normal file
16
assets/css/theme-catppuccin-macchiato.css
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
:root {
|
||||||
|
--fg-color-dark: #f8f8f2;
|
||||||
|
--bg-color-dark: #24273a;
|
||||||
|
--accent-color-dark: #fff;
|
||||||
|
--accent-bg-color-dark: #181926;
|
||||||
|
--secondary-color-dark: #e3e3e3;
|
||||||
|
--secondary-bg-color-dark: #494d64;
|
||||||
|
|
||||||
|
/* light theme colors */
|
||||||
|
--fg-color-light: black;
|
||||||
|
--bg-color-light: #eee;
|
||||||
|
--accent-color-light: #3a3a3a;
|
||||||
|
--accent-bg-color-light: #008bec;
|
||||||
|
--secondary-color-light: #424242;
|
||||||
|
--secondary-bg-color-light: #d9d9d9;
|
||||||
|
}
|
16
assets/css/theme-dracula.css
Normal file
16
assets/css/theme-dracula.css
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
:root {
|
||||||
|
--fg-color-dark: #f8f8f2;
|
||||||
|
--bg-color-dark: #282a36;
|
||||||
|
--accent-color-dark: #fff;
|
||||||
|
--accent-bg-color-dark: #44475a;
|
||||||
|
--secondary-color-dark: #e3e3e3;
|
||||||
|
--secondary-bg-color-dark: #21222C;
|
||||||
|
|
||||||
|
/* light theme colors */
|
||||||
|
--fg-color-light: black;
|
||||||
|
--bg-color-light: #eee;
|
||||||
|
--accent-color-light: #3a3a3a;
|
||||||
|
--accent-bg-color-light: #008bec;
|
||||||
|
--secondary-color-light: #424242;
|
||||||
|
--secondary-bg-color-light: #d9d9d9;
|
||||||
|
}
|
16
assets/css/theme-tokyonight.css
Normal file
16
assets/css/theme-tokyonight.css
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
:root {
|
||||||
|
--fg-color-dark: #f8f8f2;
|
||||||
|
--bg-color-dark: #1a1b26;
|
||||||
|
--accent-color-dark: #fff;
|
||||||
|
--accent-bg-color-dark: #3e4f80;
|
||||||
|
--secondary-color-dark: #e3e3e3;
|
||||||
|
--secondary-bg-color-dark: #2e2e3e;
|
||||||
|
|
||||||
|
/* light theme colors */
|
||||||
|
--fg-color-light: black;
|
||||||
|
--bg-color-light: #eee;
|
||||||
|
--accent-color-light: #3a3a3a;
|
||||||
|
--accent-bg-color-light: #008bec;
|
||||||
|
--secondary-color-light: #424242;
|
||||||
|
--secondary-bg-color-light: #d9d9d9;
|
||||||
|
}
|
59
assets/css/theme.css
Normal file
59
assets/css/theme.css
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
:root {
|
||||||
|
--fg-color-dark: #f0f0f0;
|
||||||
|
--bg-color-dark: #131313;
|
||||||
|
--accent-color-dark: #27a6ff;
|
||||||
|
--accent-bg-color-dark: #004a7e;
|
||||||
|
--secondary-color-dark: #e3e3e3;
|
||||||
|
--secondary-bg-color-dark: #313131;
|
||||||
|
--watched-overlay-color-dark: rgb(0 0 0 / 40%);
|
||||||
|
|
||||||
|
/* light theme colors */
|
||||||
|
--fg-color-light: black;
|
||||||
|
--bg-color-light: #eee;
|
||||||
|
--accent-color-light: #044c99;
|
||||||
|
--accent-bg-color-light: #3eaefd;
|
||||||
|
--secondary-color-light: #404040;
|
||||||
|
--secondary-bg-color-light: #dbdbdb;
|
||||||
|
--watched-overlay-color-light: rgb(255 255 255 / 40%);
|
||||||
|
|
||||||
|
/** apply default colors to dark */
|
||||||
|
--fg-color: var(--fg-color-dark);
|
||||||
|
--bg-color: var(--bg-color-dark);
|
||||||
|
--accent-color: var(--accent-color-dark);
|
||||||
|
--accent-bg-color: var(--accent-bg-color-dark);
|
||||||
|
--secondary-color: var(--secondary-color-dark);
|
||||||
|
--secondary-bg-color: var(--secondary-bg-color-dark);
|
||||||
|
--watched-overlay-color: var(--watched-overlay-color-dark);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: light) {
|
||||||
|
:root {
|
||||||
|
--fg-color: var(--fg-color-light);
|
||||||
|
--bg-color: var(--bg-color-light);
|
||||||
|
--accent-color: var(--accent-color-light);
|
||||||
|
--accent-bg-color: var(--accent-bg-color-light);
|
||||||
|
--secondary-color: var(--secondary-color-light);
|
||||||
|
--secondary-bg-color: var(--secondary-bg-color-light);
|
||||||
|
--watched-overlay-color: var(--watched-overlay-color-dark);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.light-theme {
|
||||||
|
--fg-color: var(--fg-color-light);
|
||||||
|
--bg-color: var(--bg-color-light);
|
||||||
|
--accent-color: var(--accent-color-light);
|
||||||
|
--accent-bg-color: var(--accent-bg-color-light);
|
||||||
|
--secondary-color: var(--secondary-color-light);
|
||||||
|
--secondary-bg-color: var(--secondary-bg-color-light);
|
||||||
|
--watched-overlay-color: var(--watched-overlay-color-light);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark-theme {
|
||||||
|
--fg-color: var(--fg-color-dark);
|
||||||
|
--bg-color: var(--bg-color-dark);
|
||||||
|
--accent-color: var(--accent-color-dark);
|
||||||
|
--accent-bg-color: var(--accent-bg-color-dark);
|
||||||
|
--secondary-color: var(--secondary-color-dark);
|
||||||
|
--secondary-bg-color: var(--secondary-bg-color-dark);
|
||||||
|
--watched-overlay-color: var(--watched-overlay-color-dark);
|
||||||
|
}
|
@ -1,6 +1,6 @@
|
|||||||
var video_data = JSON.parse(document.getElementById('video_data').textContent);
|
var video_data = JSON.parse(document.getElementById('video_data').textContent);
|
||||||
|
|
||||||
var spinnerHTML = '<h3 style="text-align:center"><div class="loading"><i class="icon ion-ios-refresh"></i></div></h3>';
|
var spinnerHTML = '<div class="loading"><i class="icon ion-ios-refresh"></i></div>';
|
||||||
var spinnerHTMLwithHR = spinnerHTML + '<hr>';
|
var spinnerHTMLwithHR = spinnerHTML + '<hr>';
|
||||||
|
|
||||||
String.prototype.supplant = function (o) {
|
String.prototype.supplant = function (o) {
|
||||||
@ -11,14 +11,14 @@ String.prototype.supplant = function (o) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
function toggle_comments(event) {
|
function toggle_comments(event) {
|
||||||
var target = event.target;
|
const target = event.target;
|
||||||
var body = target.parentNode.parentNode.parentNode.children[1];
|
const comments = document.querySelector(".comments");
|
||||||
if (body.style.display === 'none') {
|
if (comments.style.display === 'none') {
|
||||||
target.textContent = '[ − ]';
|
target.textContent = '−';
|
||||||
body.style.display = '';
|
comments.style.display = '';
|
||||||
} else {
|
} else {
|
||||||
target.textContent = '[ + ]';
|
target.textContent = '+';
|
||||||
body.style.display = 'none';
|
comments.style.display = 'none';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -39,6 +39,7 @@ function hide_youtube_replies(event) {
|
|||||||
|
|
||||||
function show_youtube_replies(event) {
|
function show_youtube_replies(event) {
|
||||||
var target = event.target;
|
var target = event.target;
|
||||||
|
console.log(target);
|
||||||
|
|
||||||
var sub_text = target.getAttribute('data-inner-text');
|
var sub_text = target.getAttribute('data-inner-text');
|
||||||
var inner_text = target.getAttribute('data-sub-text');
|
var inner_text = target.getAttribute('data-sub-text');
|
||||||
@ -75,23 +76,24 @@ function get_youtube_comments() {
|
|||||||
helpers.xhr('GET', url, {retries: 5, entity_name: 'comments'}, {
|
helpers.xhr('GET', url, {retries: 5, entity_name: 'comments'}, {
|
||||||
on200: function (response) {
|
on200: function (response) {
|
||||||
var commentInnerHtml = ' \
|
var commentInnerHtml = ' \
|
||||||
<div> \
|
<nav class="comments-header"> \
|
||||||
<h3> \
|
<ul> \
|
||||||
<a href="javascript:void(0)">[ − ]</a> \
|
<li> \
|
||||||
|
<button class="secondary" id="toggle-comments">−</button> \
|
||||||
{commentsText} \
|
{commentsText} \
|
||||||
</h3> \
|
</li> \
|
||||||
<b> \
|
\
|
||||||
'
|
<li>'
|
||||||
if (video_data.support_reddit) {
|
if (video_data.support_reddit) {
|
||||||
commentInnerHtml += ' <a href="javascript:void(0)" data-comments="reddit"> \
|
commentInnerHtml += ' <button data-comments="reddit"> \
|
||||||
{redditComments} \
|
{redditComments} \
|
||||||
</a> \
|
</button> \
|
||||||
'
|
'
|
||||||
}
|
}
|
||||||
commentInnerHtml += ' </b> \
|
commentInnerHtml += ' </li> \
|
||||||
</div> \
|
</ul> \
|
||||||
<div>{contentHtml}</div> \
|
</nav> \
|
||||||
<hr>'
|
<div class="comments">{contentHtml}</div>'
|
||||||
commentInnerHtml = commentInnerHtml.supplant({
|
commentInnerHtml = commentInnerHtml.supplant({
|
||||||
contentHtml: response.contentHtml,
|
contentHtml: response.contentHtml,
|
||||||
redditComments: video_data.reddit_comments_text,
|
redditComments: video_data.reddit_comments_text,
|
||||||
@ -104,9 +106,9 @@ function get_youtube_comments() {
|
|||||||
})
|
})
|
||||||
});
|
});
|
||||||
comments.innerHTML = commentInnerHtml;
|
comments.innerHTML = commentInnerHtml;
|
||||||
comments.children[0].children[0].children[0].onclick = toggle_comments;
|
document.getElementById("toggle-comments").onclick = toggle_comments;
|
||||||
if (video_data.support_reddit) {
|
if (video_data.support_reddit) {
|
||||||
comments.children[0].children[1].children[0].onclick = swap_comments;
|
comments.children[1].children[1].onclick = swap_comments;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onNon200: onNon200, // declared above
|
onNon200: onNon200, // declared above
|
||||||
@ -122,7 +124,7 @@ function get_youtube_comments() {
|
|||||||
function get_youtube_replies(target, load_more, load_replies) {
|
function get_youtube_replies(target, load_more, load_replies) {
|
||||||
var continuation = target.getAttribute('data-continuation');
|
var continuation = target.getAttribute('data-continuation');
|
||||||
|
|
||||||
var body = target.parentNode.parentNode;
|
var body = target.parentNode;
|
||||||
var fallback = body.innerHTML;
|
var fallback = body.innerHTML;
|
||||||
body.innerHTML = spinnerHTML;
|
body.innerHTML = spinnerHTML;
|
||||||
var baseUrl = video_data.base_url || '/api/v1/comments/'+ video_data.id
|
var baseUrl = video_data.base_url || '/api/v1/comments/'+ video_data.id
|
||||||
@ -140,26 +142,24 @@ function get_youtube_replies(target, load_more, load_replies) {
|
|||||||
helpers.xhr('GET', url, {}, {
|
helpers.xhr('GET', url, {}, {
|
||||||
on200: function (response) {
|
on200: function (response) {
|
||||||
if (load_more) {
|
if (load_more) {
|
||||||
body = body.parentNode.parentNode;
|
body = body.parentNode;
|
||||||
body.removeChild(body.lastElementChild);
|
body.removeChild(body.lastElementChild);
|
||||||
body.insertAdjacentHTML('beforeend', response.contentHtml);
|
body.insertAdjacentHTML('beforeend', response.contentHtml);
|
||||||
} else {
|
} else {
|
||||||
body.removeChild(body.lastElementChild);
|
body.removeChild(body.lastElementChild);
|
||||||
|
|
||||||
var p = document.createElement('p');
|
var div = document.createElement('div');
|
||||||
var a = document.createElement('a');
|
var button = document.createElement('button');
|
||||||
p.appendChild(a);
|
div.appendChild(button);
|
||||||
|
|
||||||
a.href = 'javascript:void(0)';
|
button.onclick = hide_youtube_replies;
|
||||||
a.onclick = hide_youtube_replies;
|
button.setAttribute('data-sub-text', video_data.hide_replies_text);
|
||||||
a.setAttribute('data-sub-text', video_data.hide_replies_text);
|
button.setAttribute('data-inner-text', video_data.show_replies_text);
|
||||||
a.setAttribute('data-inner-text', video_data.show_replies_text);
|
button.textContent = video_data.hide_replies_text;
|
||||||
a.textContent = video_data.hide_replies_text;
|
|
||||||
|
|
||||||
var div = document.createElement('div');
|
var div = document.createElement('div');
|
||||||
div.innerHTML = response.contentHtml;
|
div.innerHTML = response.contentHtml;
|
||||||
|
|
||||||
body.appendChild(p);
|
|
||||||
body.appendChild(div);
|
body.appendChild(div);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -171,4 +171,4 @@ function get_youtube_replies(target, load_more, load_replies) {
|
|||||||
body.innerHTML = fallback;
|
body.innerHTML = fallback;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -58,13 +58,13 @@
|
|||||||
el.onclick = function () { mark_unwatched(el); };
|
el.onclick = function () { mark_unwatched(el); };
|
||||||
});
|
});
|
||||||
document.querySelectorAll('[data-onclick="add_playlist_video"]').forEach(function (el) {
|
document.querySelectorAll('[data-onclick="add_playlist_video"]').forEach(function (el) {
|
||||||
el.onclick = function () { add_playlist_video(el); };
|
el.onclick = function (e) { add_playlist_video(e); };
|
||||||
});
|
});
|
||||||
document.querySelectorAll('[data-onclick="add_playlist_item"]').forEach(function (el) {
|
document.querySelectorAll('[data-onclick="add_playlist_item"]').forEach(function (el) {
|
||||||
el.onclick = function () { add_playlist_item(el); };
|
el.onclick = function (e) { add_playlist_item(e); };
|
||||||
});
|
});
|
||||||
document.querySelectorAll('[data-onclick="remove_playlist_item"]').forEach(function (el) {
|
document.querySelectorAll('[data-onclick="remove_playlist_item"]').forEach(function (el) {
|
||||||
el.onclick = function () { remove_playlist_item(el); };
|
el.onclick = function (e) { remove_playlist_item(e); };
|
||||||
});
|
});
|
||||||
document.querySelectorAll('[data-onclick="revoke_token"]').forEach(function (el) {
|
document.querySelectorAll('[data-onclick="revoke_token"]').forEach(function (el) {
|
||||||
el.onclick = function () { revoke_token(el); };
|
el.onclick = function () { revoke_token(el); };
|
||||||
|
@ -1,131 +1,197 @@
|
|||||||
'use strict';
|
"use strict";
|
||||||
var notification_data = JSON.parse(document.getElementById('notification_data').textContent);
|
var notification_data = JSON.parse(
|
||||||
|
document.getElementById("notification_data").textContent,
|
||||||
|
);
|
||||||
|
|
||||||
/** Boolean meaning 'some tab have stream' */
|
/** Boolean meaning 'some tab have stream' */
|
||||||
const STORAGE_KEY_STREAM = 'stream';
|
const STORAGE_KEY_STREAM = "stream";
|
||||||
/** Number of notifications. May be increased or reset */
|
/** Number of notifications. May be increased or reset */
|
||||||
const STORAGE_KEY_NOTIF_COUNT = 'notification_count';
|
const STORAGE_KEY_NOTIF_COUNT = "notification_count";
|
||||||
|
|
||||||
var notifications, delivered;
|
var notifications, delivered;
|
||||||
var notifications_mock = { close: function () { } };
|
var notifications_mock = { close: function () {} };
|
||||||
|
|
||||||
function get_subscriptions() {
|
async function get_subscriptions_call() {
|
||||||
helpers.xhr('GET', '/api/v1/auth/subscriptions', {
|
return new Promise((resolve) => {
|
||||||
|
helpers.xhr(
|
||||||
|
"GET",
|
||||||
|
"/api/v1/auth/subscriptions",
|
||||||
|
{
|
||||||
retries: 5,
|
retries: 5,
|
||||||
entity_name: 'subscriptions'
|
entity_name: "subscriptions",
|
||||||
}, {
|
},
|
||||||
on200: create_notification_stream
|
{
|
||||||
});
|
on200: function (subscriptions) {
|
||||||
|
create_notification_stream(subscriptions);
|
||||||
|
resolve(subscriptions);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Start the retry mechanism
|
||||||
|
const get_subscriptions = exponential_backoff(get_subscriptions_call, 100, 1000);
|
||||||
|
|
||||||
function create_notification_stream(subscriptions) {
|
function create_notification_stream(subscriptions) {
|
||||||
// sse.js can't be replaced to EventSource in place as it lack support of payload and headers
|
// sse.js can't be replaced to EventSource in place as it lack support of payload and headers
|
||||||
// see https://developer.mozilla.org/en-US/docs/Web/API/EventSource/EventSource
|
// see https://developer.mozilla.org/en-US/docs/Web/API/EventSource/EventSource
|
||||||
notifications = new SSE(
|
notifications = new SSE("/api/v1/auth/notifications", {
|
||||||
'/api/v1/auth/notifications', {
|
withCredentials: true,
|
||||||
withCredentials: true,
|
payload:
|
||||||
payload: 'topics=' + subscriptions.map(function (subscription) { return subscription.authorId; }).join(','),
|
"topics=" +
|
||||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
|
subscriptions
|
||||||
});
|
.map(function (subscription) {
|
||||||
delivered = [];
|
return subscription.authorId;
|
||||||
|
})
|
||||||
|
.join(","),
|
||||||
|
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
||||||
|
});
|
||||||
|
delivered = [];
|
||||||
|
|
||||||
var start_time = Math.round(new Date() / 1000);
|
var start_time = Math.round(new Date() / 1000);
|
||||||
|
|
||||||
notifications.onmessage = function (event) {
|
notifications.onmessage = function (event) {
|
||||||
if (!event.id) return;
|
if (!event.id) return;
|
||||||
|
|
||||||
var notification = JSON.parse(event.data);
|
var notification = JSON.parse(event.data);
|
||||||
console.info('Got notification:', notification);
|
console.info("Got notification:", notification);
|
||||||
|
|
||||||
// Ignore not actual and delivered notifications
|
// Ignore not actual and delivered notifications
|
||||||
if (start_time > notification.published || delivered.includes(notification.videoId)) return;
|
if (
|
||||||
|
start_time > notification.published ||
|
||||||
|
delivered.includes(notification.videoId)
|
||||||
|
)
|
||||||
|
return;
|
||||||
|
|
||||||
delivered.push(notification.videoId);
|
delivered.push(notification.videoId);
|
||||||
|
|
||||||
let notification_count = helpers.storage.get(STORAGE_KEY_NOTIF_COUNT) || 0;
|
let notification_count = helpers.storage.get(STORAGE_KEY_NOTIF_COUNT) || 0;
|
||||||
notification_count++;
|
notification_count++;
|
||||||
helpers.storage.set(STORAGE_KEY_NOTIF_COUNT, notification_count);
|
helpers.storage.set(STORAGE_KEY_NOTIF_COUNT, notification_count);
|
||||||
|
|
||||||
update_ticker_count();
|
update_ticker_count();
|
||||||
|
|
||||||
// permission for notifications handled on settings page. JS handler is in handlers.js
|
// permission for notifications handled on settings page. JS handler is in handlers.js
|
||||||
if (window.Notification && Notification.permission === 'granted') {
|
if (window.Notification && Notification.permission === "granted") {
|
||||||
var notification_text = notification.liveNow ? notification_data.live_now_text : notification_data.upload_text;
|
var notification_text = notification.liveNow
|
||||||
notification_text = notification_text.replace('`x`', notification.author);
|
? notification_data.live_now_text
|
||||||
|
: notification_data.upload_text;
|
||||||
|
notification_text = notification_text.replace("`x`", notification.author);
|
||||||
|
|
||||||
var system_notification = new Notification(notification_text, {
|
var system_notification = new Notification(notification_text, {
|
||||||
body: notification.title,
|
body: notification.title,
|
||||||
icon: '/ggpht' + new URL(notification.authorThumbnails[2].url).pathname,
|
icon: "/ggpht" + new URL(notification.authorThumbnails[2].url).pathname,
|
||||||
img: '/ggpht' + new URL(notification.authorThumbnails[4].url).pathname
|
img: "/ggpht" + new URL(notification.authorThumbnails[4].url).pathname,
|
||||||
});
|
});
|
||||||
|
|
||||||
system_notification.onclick = function (e) {
|
system_notification.onclick = function (e) {
|
||||||
open('/watch?v=' + notification.videoId, '_blank');
|
open("/watch?v=" + notification.videoId, "_blank");
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
notifications.addEventListener('error', function (e) {
|
notifications.addEventListener("error", function (e) {
|
||||||
console.warn('Something went wrong with notifications, trying to reconnect...');
|
console.warn(
|
||||||
notifications = notifications_mock;
|
"Something went wrong with notifications, trying to reconnect...",
|
||||||
setTimeout(get_subscriptions, 1000);
|
);
|
||||||
});
|
notifications = notifications_mock;
|
||||||
|
|
||||||
notifications.stream();
|
});
|
||||||
|
|
||||||
|
notifications.stream();
|
||||||
}
|
}
|
||||||
|
|
||||||
function update_ticker_count() {
|
function update_ticker_count() {
|
||||||
var notification_ticker = document.getElementById('notification_ticker');
|
var notification_ticker = document.getElementById("notification_ticker");
|
||||||
|
|
||||||
const notification_count = helpers.storage.get(STORAGE_KEY_STREAM);
|
const notification_count = helpers.storage.get(STORAGE_KEY_STREAM);
|
||||||
if (notification_count > 0) {
|
if (notification_count > 0) {
|
||||||
notification_ticker.innerHTML =
|
notification_ticker.innerHTML =
|
||||||
'<span id="notification_count">' + notification_count + '</span> <i class="icon ion-ios-notifications"></i>';
|
'<span id="notification_count">' +
|
||||||
} else {
|
notification_count +
|
||||||
notification_ticker.innerHTML =
|
'</span> <i class="icon ion-ios-notifications"></i>';
|
||||||
'<i class="icon ion-ios-notifications-outline"></i>';
|
} else {
|
||||||
}
|
notification_ticker.innerHTML =
|
||||||
|
'<i class="icon ion-ios-notifications-outline"></i>';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function start_stream_if_needed() {
|
function start_stream_if_needed() {
|
||||||
// random wait for other tabs set 'stream' flag
|
// random wait for other tabs set 'stream' flag
|
||||||
setTimeout(function () {
|
setTimeout(
|
||||||
if (!helpers.storage.get(STORAGE_KEY_STREAM)) {
|
function () {
|
||||||
// if no one set 'stream', set it by yourself and start stream
|
if (!helpers.storage.get(STORAGE_KEY_STREAM)) {
|
||||||
helpers.storage.set(STORAGE_KEY_STREAM, true);
|
// if no one set 'stream', set it by yourself and start stream
|
||||||
notifications = notifications_mock;
|
helpers.storage.set(STORAGE_KEY_STREAM, true);
|
||||||
get_subscriptions();
|
notifications = notifications_mock;
|
||||||
}
|
get_subscriptions();
|
||||||
}, Math.random() * 1000 + 50); // [0.050 .. 1.050) second
|
}
|
||||||
|
},
|
||||||
|
Math.random() * 1000 + 50,
|
||||||
|
); // [0.050 .. 1.050) second
|
||||||
}
|
}
|
||||||
|
|
||||||
|
addEventListener("storage", function (e) {
|
||||||
|
if (e.key === STORAGE_KEY_NOTIF_COUNT) update_ticker_count();
|
||||||
|
|
||||||
addEventListener('storage', function (e) {
|
// if 'stream' key was removed
|
||||||
if (e.key === STORAGE_KEY_NOTIF_COUNT)
|
if (
|
||||||
update_ticker_count();
|
e.key === STORAGE_KEY_STREAM &&
|
||||||
|
!helpers.storage.get(STORAGE_KEY_STREAM)
|
||||||
// if 'stream' key was removed
|
) {
|
||||||
if (e.key === STORAGE_KEY_STREAM && !helpers.storage.get(STORAGE_KEY_STREAM)) {
|
if (notifications) {
|
||||||
if (notifications) {
|
// restore it if we have active stream
|
||||||
// restore it if we have active stream
|
helpers.storage.set(STORAGE_KEY_STREAM, true);
|
||||||
helpers.storage.set(STORAGE_KEY_STREAM, true);
|
} else {
|
||||||
} else {
|
start_stream_if_needed();
|
||||||
start_stream_if_needed();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
addEventListener('load', function () {
|
addEventListener("load", function () {
|
||||||
var notification_count_el = document.getElementById('notification_count');
|
var notification_count_el = document.getElementById("notification_count");
|
||||||
var notification_count = notification_count_el ? parseInt(notification_count_el.textContent) : 0;
|
var notification_count = notification_count_el
|
||||||
helpers.storage.set(STORAGE_KEY_NOTIF_COUNT, notification_count);
|
? parseInt(notification_count_el.textContent)
|
||||||
|
: 0;
|
||||||
|
helpers.storage.set(STORAGE_KEY_NOTIF_COUNT, notification_count);
|
||||||
|
|
||||||
if (helpers.storage.get(STORAGE_KEY_STREAM))
|
if (helpers.storage.get(STORAGE_KEY_STREAM))
|
||||||
helpers.storage.remove(STORAGE_KEY_STREAM);
|
helpers.storage.remove(STORAGE_KEY_STREAM);
|
||||||
start_stream_if_needed();
|
start_stream_if_needed();
|
||||||
});
|
});
|
||||||
|
|
||||||
addEventListener('unload', function () {
|
addEventListener("unload", function () {
|
||||||
// let chance to other tabs to be a streamer via firing 'storage' event
|
// let chance to other tabs to be a streamer via firing 'storage' event
|
||||||
if (notifications) helpers.storage.remove(STORAGE_KEY_STREAM);
|
if (notifications) helpers.storage.remove(STORAGE_KEY_STREAM);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function exponential_backoff(
|
||||||
|
fn,
|
||||||
|
maxRetries = 5,
|
||||||
|
initialDelay = 1000,
|
||||||
|
randomnessFactor = 0.5,
|
||||||
|
) {
|
||||||
|
let attempt = 0;
|
||||||
|
|
||||||
|
return function tryFunction() {
|
||||||
|
fn()
|
||||||
|
.then((response) => {
|
||||||
|
attempt = 0;
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
if (attempt < maxRetries) {
|
||||||
|
attempt++;
|
||||||
|
let delay = initialDelay * Math.pow(2, attempt); // Exponential backoff
|
||||||
|
let randomMultiplier = 1 + Math.random() * randomnessFactor;
|
||||||
|
delay = delay * randomMultiplier;
|
||||||
|
console.log(
|
||||||
|
`Attempt ${attempt} failed. Retrying in ${(delay / 1000).toPrecision(2)} seconds...`,
|
||||||
|
);
|
||||||
|
setTimeout(tryFunction, delay); // Retry after delay
|
||||||
|
} else {
|
||||||
|
console.log("Max retries reached. Operation failed:", error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -2,8 +2,9 @@
|
|||||||
var playlist_data = JSON.parse(document.getElementById('playlist_data').textContent);
|
var playlist_data = JSON.parse(document.getElementById('playlist_data').textContent);
|
||||||
var payload = 'csrf_token=' + playlist_data.csrf_token;
|
var payload = 'csrf_token=' + playlist_data.csrf_token;
|
||||||
|
|
||||||
function add_playlist_video(target) {
|
function add_playlist_video(event) {
|
||||||
var select = target.parentNode.children[0].children[1];
|
const target = event.target;
|
||||||
|
var select = document.querySelector("#playlists");
|
||||||
var option = select.children[select.selectedIndex];
|
var option = select.children[select.selectedIndex];
|
||||||
|
|
||||||
var url = '/playlist_ajax?action_add_video=1&redirect=false' +
|
var url = '/playlist_ajax?action_add_video=1&redirect=false' +
|
||||||
@ -12,37 +13,43 @@ function add_playlist_video(target) {
|
|||||||
|
|
||||||
helpers.xhr('POST', url, {payload: payload}, {
|
helpers.xhr('POST', url, {payload: payload}, {
|
||||||
on200: function (response) {
|
on200: function (response) {
|
||||||
option.textContent = '✓' + option.textContent;
|
option.textContent = '✓ ' + option.textContent;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function add_playlist_item(target) {
|
function add_playlist_item(event) {
|
||||||
var tile = target.parentNode.parentNode.parentNode.parentNode.parentNode;
|
event.preventDefault();
|
||||||
tile.style.display = 'none';
|
const target = event.target;
|
||||||
|
const video_id = target.getAttribute('data-id');
|
||||||
|
var card = document.querySelector(`#video-card-${video_id}`);
|
||||||
|
card.classList.add("hide");
|
||||||
|
|
||||||
var url = '/playlist_ajax?action_add_video=1&redirect=false' +
|
var url = '/playlist_ajax?action_add_video=1&redirect=false' +
|
||||||
'&video_id=' + target.getAttribute('data-id') +
|
'&video_id=' + video_id +
|
||||||
'&playlist_id=' + target.getAttribute('data-plid');
|
'&playlist_id=' + target.getAttribute('data-plid');
|
||||||
|
|
||||||
helpers.xhr('POST', url, {payload: payload}, {
|
helpers.xhr('POST', url, {payload: payload}, {
|
||||||
onNon200: function (xhr) {
|
onNon200: function (xhr) {
|
||||||
tile.style.display = '';
|
card.classList.remove("hide");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function remove_playlist_item(target) {
|
function remove_playlist_item(event) {
|
||||||
var tile = target.parentNode.parentNode.parentNode.parentNode.parentNode;
|
event.preventDefault();
|
||||||
tile.style.display = 'none';
|
const target = event.target;
|
||||||
|
const video_index = target.getAttribute('data-index');
|
||||||
|
const card = document.querySelector(`.video-card [data-index="${video_index}"]`)
|
||||||
|
card.classList.add("hide");
|
||||||
|
|
||||||
var url = '/playlist_ajax?action_remove_video=1&redirect=false' +
|
var url = '/playlist_ajax?action_remove_video=1&redirect=false' +
|
||||||
'&set_video_id=' + target.getAttribute('data-index') +
|
'&set_video_id=' + video_index +
|
||||||
'&playlist_id=' + target.getAttribute('data-plid');
|
'&playlist_id=' + target.getAttribute('data-plid');
|
||||||
|
|
||||||
helpers.xhr('POST', url, {payload: payload}, {
|
helpers.xhr('POST', url, {payload: payload}, {
|
||||||
onNon200: function (xhr) {
|
onNon200: function (xhr) {
|
||||||
tile.style.display = '';
|
card.classList.remove("hide");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,6 @@ var subscribe_data = JSON.parse(document.getElementById('subscribe_data').textCo
|
|||||||
var payload = 'csrf_token=' + subscribe_data.csrf_token;
|
var payload = 'csrf_token=' + subscribe_data.csrf_token;
|
||||||
|
|
||||||
var subscribe_button = document.getElementById('subscribe');
|
var subscribe_button = document.getElementById('subscribe');
|
||||||
subscribe_button.parentNode.action = 'javascript:void(0)';
|
|
||||||
|
|
||||||
if (subscribe_button.getAttribute('data-type') === 'subscribe') {
|
if (subscribe_button.getAttribute('data-type') === 'subscribe') {
|
||||||
subscribe_button.onclick = subscribe;
|
subscribe_button.onclick = subscribe;
|
||||||
@ -11,10 +10,29 @@ if (subscribe_button.getAttribute('data-type') === 'subscribe') {
|
|||||||
subscribe_button.onclick = unsubscribe;
|
subscribe_button.onclick = unsubscribe;
|
||||||
}
|
}
|
||||||
|
|
||||||
function subscribe() {
|
function toggleSubscribeButton() {
|
||||||
var fallback = subscribe_button.innerHTML;
|
subscribe_button.classList.remove("primary");
|
||||||
subscribe_button.onclick = unsubscribe;
|
subscribe_button.classList.remove("secondary");
|
||||||
subscribe_button.innerHTML = '<b>' + subscribe_data.unsubscribe_text + ' | ' + subscribe_data.sub_count_text + '</b>';
|
subscribe_button.classList.remove("unsubscribe");
|
||||||
|
subscribe_button.classList.remove("subscribe");
|
||||||
|
|
||||||
|
if (subscribe_button.getAttribute('data-type') === 'subscribe') {
|
||||||
|
subscribe_button.textContent = subscribe_data.unsubscribe_text + ' | ' + subscribe_data.sub_count_text;
|
||||||
|
subscribe_button.onclick = unsubscribe;
|
||||||
|
subscribe_button.classList.add("secondary");
|
||||||
|
subscribe_button.classList.add("unsubscribe");
|
||||||
|
} else {
|
||||||
|
subscribe_button.textContent = subscribe_data.subscribe_text + ' | ' + subscribe_data.sub_count_text;
|
||||||
|
subscribe_button.onclick = subscribe;
|
||||||
|
subscribe_button.classList.add("primary");
|
||||||
|
subscribe_button.classList.add("subscribe");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function subscribe(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
var fallback = subscribe_button.textContent;
|
||||||
|
toggleSubscribeButton();
|
||||||
|
|
||||||
var url = '/subscription_ajax?action_create_subscription_to_channel=1&redirect=false' +
|
var url = '/subscription_ajax?action_create_subscription_to_channel=1&redirect=false' +
|
||||||
'&c=' + subscribe_data.ucid;
|
'&c=' + subscribe_data.ucid;
|
||||||
@ -22,15 +40,15 @@ function subscribe() {
|
|||||||
helpers.xhr('POST', url, {payload: payload, retries: 5, entity_name: 'subscribe request'}, {
|
helpers.xhr('POST', url, {payload: payload, retries: 5, entity_name: 'subscribe request'}, {
|
||||||
onNon200: function (xhr) {
|
onNon200: function (xhr) {
|
||||||
subscribe_button.onclick = subscribe;
|
subscribe_button.onclick = subscribe;
|
||||||
subscribe_button.innerHTML = fallback;
|
subscribe_button.textContent = fallback;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function unsubscribe() {
|
function unsubscribe(e) {
|
||||||
var fallback = subscribe_button.innerHTML;
|
e.preventDefault();
|
||||||
subscribe_button.onclick = subscribe;
|
var fallback = subscribe_button.textContent;
|
||||||
subscribe_button.innerHTML = '<b>' + subscribe_data.subscribe_text + ' | ' + subscribe_data.sub_count_text + '</b>';
|
toggleSubscribeButton();
|
||||||
|
|
||||||
var url = '/subscription_ajax?action_remove_subscriptions=1&redirect=false' +
|
var url = '/subscription_ajax?action_remove_subscriptions=1&redirect=false' +
|
||||||
'&c=' + subscribe_data.ucid;
|
'&c=' + subscribe_data.ucid;
|
||||||
@ -38,7 +56,7 @@ function unsubscribe() {
|
|||||||
helpers.xhr('POST', url, {payload: payload, retries: 5, entity_name: 'unsubscribe request'}, {
|
helpers.xhr('POST', url, {payload: payload, retries: 5, entity_name: 'unsubscribe request'}, {
|
||||||
onNon200: function (xhr) {
|
onNon200: function (xhr) {
|
||||||
subscribe_button.onclick = unsubscribe;
|
subscribe_button.onclick = unsubscribe;
|
||||||
subscribe_button.innerHTML = fallback;
|
subscribe_button.textContent = fallback;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
44
assets/js/theme.js
Normal file
44
assets/js/theme.js
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
const themeSelector = document.querySelector("#theme-selector");
|
||||||
|
themeSelector.addEventListener("change", (event) => {
|
||||||
|
const select = event.target;
|
||||||
|
const selected = select.options[select.selectedIndex].text;
|
||||||
|
applyTheme(selected);
|
||||||
|
});
|
||||||
|
|
||||||
|
const colorSchemeSelector = document.querySelector("#color-scheme");
|
||||||
|
colorSchemeSelector.addEventListener("change", (event) => {
|
||||||
|
const select = event.target;
|
||||||
|
const selected = select.options[select.selectedIndex].text;
|
||||||
|
applyColorScheme(selected);
|
||||||
|
});
|
||||||
|
|
||||||
|
function applyTheme(theme) {
|
||||||
|
const link = document.createElement("link");
|
||||||
|
link.rel = "stylesheet";
|
||||||
|
link.href = `/css/theme-${theme}.css`;
|
||||||
|
link.id = "theme-css";
|
||||||
|
|
||||||
|
const themeCss = document.querySelector("#theme-css");
|
||||||
|
if (themeCss) {
|
||||||
|
themeCss.parentNode.removeChild(themeCss);
|
||||||
|
}
|
||||||
|
|
||||||
|
const head = document.getElementsByTagName("head")[0];
|
||||||
|
head.appendChild(link);
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyColorScheme(colorScheme) {
|
||||||
|
document.body.classList.remove("dark-theme");
|
||||||
|
document.body.classList.remove("light-theme");
|
||||||
|
|
||||||
|
if (colorScheme === "dark" || colorScheme === "light") {
|
||||||
|
document.body.classList.add(`${colorScheme}-theme`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
applyTheme(themeSelector.options[themeSelector.selectedIndex].text);
|
||||||
|
applyColorScheme("dark");
|
||||||
|
|
||||||
|
// <link rel="stylesheet" href="/css/theme-dracula.css" />
|
||||||
|
// <link rel="stylesheet" href="/css/theme-catppuccin-latte.css" />
|
||||||
|
// <link rel="stylesheet" href="/css/ionicons.min.css" />
|
@ -1,13 +1,13 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
var toggle_theme = document.getElementById('toggle_theme');
|
var toggle_theme = document.getElementById('toggle_theme');
|
||||||
toggle_theme.href = 'javascript:void(0)';
|
|
||||||
|
|
||||||
const STORAGE_KEY_THEME = 'dark_mode';
|
const STORAGE_KEY_THEME = 'dark_mode';
|
||||||
const THEME_DARK = 'dark';
|
const THEME_DARK = 'dark';
|
||||||
const THEME_LIGHT = 'light';
|
const THEME_LIGHT = 'light';
|
||||||
|
|
||||||
// TODO: theme state controlled by system
|
// TODO: theme state controlled by system
|
||||||
toggle_theme.addEventListener('click', function () {
|
toggle_theme.addEventListener('click', function (e) {
|
||||||
|
e.preventDefault();
|
||||||
const isDarkTheme = helpers.storage.get(STORAGE_KEY_THEME) === THEME_DARK;
|
const isDarkTheme = helpers.storage.get(STORAGE_KEY_THEME) === THEME_DARK;
|
||||||
const newTheme = isDarkTheme ? THEME_LIGHT : THEME_DARK;
|
const newTheme = isDarkTheme ? THEME_LIGHT : THEME_DARK;
|
||||||
setTheme(newTheme);
|
setTheme(newTheme);
|
||||||
|
@ -69,6 +69,8 @@ function get_playlist(plid) {
|
|||||||
|
|
||||||
helpers.xhr('GET', plid_url, {retries: 5, entity_name: 'playlist'}, {
|
helpers.xhr('GET', plid_url, {retries: 5, entity_name: 'playlist'}, {
|
||||||
on200: function (response) {
|
on200: function (response) {
|
||||||
|
if (response === null) return;
|
||||||
|
|
||||||
playlist.innerHTML = response.playlistHtml;
|
playlist.innerHTML = response.playlistHtml;
|
||||||
|
|
||||||
if (!response.nextVideo) return;
|
if (!response.nextVideo) return;
|
||||||
|
@ -1,22 +1,13 @@
|
|||||||
# Warning: This docker-compose file is made for development purposes.
|
|
||||||
# Using it will build an image from the locally cloned repository.
|
|
||||||
#
|
|
||||||
# If you want to use Invidious in production, see the docker-compose.yml file provided
|
|
||||||
# in the installation documentation: https://docs.invidious.io/installation/
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
version: "3"
|
version: "3"
|
||||||
services:
|
services:
|
||||||
|
|
||||||
invidious:
|
invidious:
|
||||||
build:
|
image: quay.io/invidious/invidious:master
|
||||||
context: .
|
# image: quay.io/invidious/invidious:master-arm64 # ARM64/AArch64 devices
|
||||||
dockerfile: docker/Dockerfile
|
|
||||||
image: rockerboo/invidious
|
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
# Remove "127.0.0.1:" if used from an external IP
|
||||||
ports:
|
ports:
|
||||||
- "127.0.0.1:9999:3000"
|
- "127.0.0.1:3000:3000"
|
||||||
environment:
|
environment:
|
||||||
# Please read the following file for a comprehensive list of all available
|
# Please read the following file for a comprehensive list of all available
|
||||||
# configuration options and their associated syntax:
|
# configuration options and their associated syntax:
|
||||||
@ -29,22 +20,63 @@ services:
|
|||||||
host: invidious-db
|
host: invidious-db
|
||||||
port: 5432
|
port: 5432
|
||||||
check_tables: true
|
check_tables: true
|
||||||
|
invidious_companion:
|
||||||
|
# URL used for the internal communication between invidious and invidious companion
|
||||||
|
# There is no need to change that except if Invidious companion does not run on the same docker compose file.
|
||||||
|
- private_url: "http://companion:8282"
|
||||||
|
# (public) URL used for the communication between your browser and invidious companion
|
||||||
|
# IF you are using a reverse proxy OR accessing invidious from an external IP then you NEED to change this value
|
||||||
|
# Please consult for more doc: https://github.com/unixfox/invidious/blob/invidious-companion/config/config.example.yml#L57-L88
|
||||||
|
# And make sure to add the routes from the post install when using a reverse proxy!
|
||||||
|
public_url: "http://localhost:8282"
|
||||||
|
# IT is NOT recommended to use the same key as HMAC KEY. Generate a new key!
|
||||||
|
# Use the key generated in the 2nd step
|
||||||
|
invidious_companion_key: "foo0te5naijooTho"
|
||||||
# external_port:
|
# external_port:
|
||||||
# domain:
|
# domain:
|
||||||
# https_only: false
|
# https_only: false
|
||||||
# statistics_enabled: false
|
# statistics_enabled: false
|
||||||
hmac_key: "CHANGE_ME!!"
|
# Use the key generated in the 2nd step
|
||||||
|
hmac_key: "foo0te5naijooTho"
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: wget -nv --tries=1 --spider http://127.0.0.1:3000/api/v1/trending || exit 1
|
test: wget -nv --tries=1 --spider http://127.0.0.1:3000/api/v1/trending || exit 1
|
||||||
interval: 30s
|
interval: 30s
|
||||||
timeout: 5s
|
timeout: 5s
|
||||||
retries: 2
|
retries: 2
|
||||||
|
logging:
|
||||||
|
options:
|
||||||
|
max-size: "1G"
|
||||||
|
max-file: "4"
|
||||||
|
depends_on:
|
||||||
|
- invidious-db
|
||||||
|
|
||||||
|
companion:
|
||||||
|
image: quay.io/invidious/invidious-companion:latest
|
||||||
|
environment:
|
||||||
|
# Use the key generated in the 2nd step
|
||||||
|
- SERVER_SECRET_KEY=foo0te5naijooTho
|
||||||
|
restart: unless-stopped
|
||||||
|
# Remove "127.0.0.1:" if used from an external IP
|
||||||
|
ports:
|
||||||
|
- "127.0.0.1:8282:8282"
|
||||||
|
logging:
|
||||||
|
options:
|
||||||
|
max-size: "1G"
|
||||||
|
max-file: "4"
|
||||||
|
cap_drop:
|
||||||
|
- ALL
|
||||||
|
read_only: true
|
||||||
|
# cache for youtube library
|
||||||
|
volumes:
|
||||||
|
- companioncache:/var/tmp/youtubei.js:rw
|
||||||
|
security_opt:
|
||||||
|
- no-new-privileges:true
|
||||||
|
|
||||||
invidious-db:
|
invidious-db:
|
||||||
image: docker.io/library/postgres:14
|
image: docker.io/library/postgres:14
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
volumes:
|
volumes:
|
||||||
- postgresdata:/var/lib/postgresql/data
|
- postgresdata-companion:/var/lib/postgresql/data
|
||||||
- ./config/sql:/config/sql
|
- ./config/sql:/config/sql
|
||||||
- ./docker/init-invidious-db.sh:/docker-entrypoint-initdb.d/init-invidious-db.sh
|
- ./docker/init-invidious-db.sh:/docker-entrypoint-initdb.d/init-invidious-db.sh
|
||||||
environment:
|
environment:
|
||||||
@ -55,4 +87,5 @@ services:
|
|||||||
test: ["CMD-SHELL", "pg_isready -U $$POSTGRES_USER -d $$POSTGRES_DB"]
|
test: ["CMD-SHELL", "pg_isready -U $$POSTGRES_USER -d $$POSTGRES_DB"]
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
postgresdata:
|
postgresdata-companion:
|
||||||
|
companioncache:
|
||||||
|
@ -463,6 +463,7 @@
|
|||||||
"next_steps_error_message": "After which you should try to: ",
|
"next_steps_error_message": "After which you should try to: ",
|
||||||
"next_steps_error_message_refresh": "Refresh",
|
"next_steps_error_message_refresh": "Refresh",
|
||||||
"next_steps_error_message_go_to_youtube": "Go to YouTube",
|
"next_steps_error_message_go_to_youtube": "Go to YouTube",
|
||||||
|
"invidious_footer_description": "A free and open source frontend for Youtube that that respects your privacy! Now you can watch videos (ad-free), subscribe to channels, create playlist and much more all without the prying eyes of Google!",
|
||||||
"footer_donate_page": "Donate",
|
"footer_donate_page": "Donate",
|
||||||
"footer_documentation": "Documentation",
|
"footer_documentation": "Documentation",
|
||||||
"footer_source_code": "Source code",
|
"footer_source_code": "Source code",
|
||||||
|
@ -133,6 +133,9 @@ Kemal.config.extra_options do |parser|
|
|||||||
Invidious::Database::Migrator.new(PG_DB).migrate
|
Invidious::Database::Migrator.new(PG_DB).migrate
|
||||||
exit
|
exit
|
||||||
end
|
end
|
||||||
|
parser.on("--disable-static-cache", "Disable static file handler cache") do
|
||||||
|
CONFIG.disable_static_cache = true
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
Kemal::CLI.new ARGV
|
Kemal::CLI.new ARGV
|
||||||
|
@ -158,6 +158,9 @@ class Config
|
|||||||
# Playlist length limit
|
# Playlist length limit
|
||||||
property playlist_length_limit : Int32 = 500
|
property playlist_length_limit : Int32 = 500
|
||||||
|
|
||||||
|
# Disable static file handle cache. Prefer only for development. --disable_static_cache also available on build.
|
||||||
|
property disable_static_cache : Bool = false
|
||||||
|
|
||||||
def disabled?(option)
|
def disabled?(option)
|
||||||
case disabled = CONFIG.disable_proxy
|
case disabled = CONFIG.disable_proxy
|
||||||
when Bool
|
when Bool
|
||||||
|
@ -23,22 +23,15 @@ module Invidious::Frontend::ChannelPage
|
|||||||
tab_name = tab.to_s.downcase
|
tab_name = tab.to_s.downcase
|
||||||
|
|
||||||
if channel.tabs.includes? tab_name
|
if channel.tabs.includes? tab_name
|
||||||
str << %(<div class="pure-u-1 pure-md-1-3">\n)
|
# Video tab doesn't have the last path component
|
||||||
|
url = tab.videos? ? base_url : "#{base_url}/#{tab_name}"
|
||||||
|
selected_class = tab == selected_tab ? "selected" : ""
|
||||||
|
|
||||||
if tab == selected_tab
|
str << %(<li class=") << selected_class << %(">\n)
|
||||||
str << "\t<b>"
|
str << %(\t<a href=") << url << %(">)
|
||||||
str << translate(locale, "channel_tab_#{tab_name}_label")
|
str << translate(locale, "channel_tab_#{tab_name}_label")
|
||||||
str << "</b>\n"
|
str << "</a>\n"
|
||||||
else
|
str << "</li>"
|
||||||
# Video tab doesn't have the last path component
|
|
||||||
url = tab.videos? ? base_url : "#{base_url}/#{tab_name}"
|
|
||||||
|
|
||||||
str << %(\t<a href=") << url << %(">)
|
|
||||||
str << translate(locale, "channel_tab_#{tab_name}_label")
|
|
||||||
str << "</a>\n"
|
|
||||||
end
|
|
||||||
|
|
||||||
str << "</div>"
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -13,14 +13,9 @@ module Invidious::Frontend::Comments
|
|||||||
)
|
)
|
||||||
|
|
||||||
replies_html = <<-END_HTML
|
replies_html = <<-END_HTML
|
||||||
<div id="replies" class="pure-g">
|
<div class="replies">
|
||||||
<div class="pure-u-1-24"></div>
|
<button data-continuation="#{child["replies"]["continuation"]}"
|
||||||
<div class="pure-u-23-24">
|
data-onclick="get_youtube_replies" data-load-replies>#{replies_count_text}</button>
|
||||||
<p>
|
|
||||||
<a href="javascript:void(0)" data-continuation="#{child["replies"]["continuation"]}"
|
|
||||||
data-onclick="get_youtube_replies" data-load-replies>#{replies_count_text}</a>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
END_HTML
|
END_HTML
|
||||||
elsif comments["authorId"]? && !comments["singlePost"]?
|
elsif comments["authorId"]? && !comments["singlePost"]?
|
||||||
@ -32,21 +27,20 @@ module Invidious::Frontend::Comments
|
|||||||
)
|
)
|
||||||
|
|
||||||
replies_html = <<-END_HTML
|
replies_html = <<-END_HTML
|
||||||
<div class="pure-g">
|
<div class="reply-count">
|
||||||
<div class="pure-u-1-24"></div>
|
<a role="button" href="/post/#{child["commentId"]}?ucid=#{comments["authorId"]}">#{replies_count_text}</a>
|
||||||
<div class="pure-u-23-24">
|
|
||||||
<p>
|
|
||||||
<a href="/post/#{child["commentId"]}?ucid=#{comments["authorId"]}">#{replies_count_text}</a>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
END_HTML
|
END_HTML
|
||||||
end
|
end
|
||||||
|
|
||||||
if !thin_mode
|
if thin_mode
|
||||||
author_thumbnail = "/ggpht#{URI.parse(child["authorThumbnails"][-1]["url"].as_s).request_target}"
|
|
||||||
else
|
|
||||||
author_thumbnail = ""
|
author_thumbnail = ""
|
||||||
|
else
|
||||||
|
author_thumbnail_url = "/ggpht#{URI.parse(child["authorThumbnails"][-1]["url"].as_s).request_target}"
|
||||||
|
|
||||||
|
author_thumbnail = <<-THUMBNAIL
|
||||||
|
<img loading="lazy" src="#{author_thumbnail_url}" class="profile-pic" alt="" />
|
||||||
|
THUMBNAIL
|
||||||
end
|
end
|
||||||
|
|
||||||
author_name = HTML.escape(child["author"].as_s)
|
author_name = HTML.escape(child["author"].as_s)
|
||||||
@ -65,18 +59,76 @@ module Invidious::Frontend::Comments
|
|||||||
str << %(width="16" height="16" />)
|
str << %(width="16" height="16" />)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
html << <<-END_HTML
|
html << <<-END_HTML
|
||||||
<div class="pure-g" style="width:100%">
|
<div class="comment" id="comment_#{child["commentId"]}">
|
||||||
<div class="channel-profile pure-u-4-24 pure-u-md-2-24">
|
<div class="comment-header">
|
||||||
<img loading="lazy" style="margin-right:1em;margin-top:1em;width:90%" src="#{author_thumbnail}" alt="" />
|
<div class="comment-temporal">
|
||||||
|
<div class="channel-profile">
|
||||||
|
#{author_thumbnail}
|
||||||
|
<h4>
|
||||||
|
<a class="#{child["authorIsChannelOwner"] == true ? "channel-owner" : ""}" href="#{child["authorUrl"]}">#{author_name}</a>
|
||||||
|
#{sponsor_icon}
|
||||||
|
</h4>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<time datetime="#{Time.unix(child["published"].as_i64).to_s("%Y-%m-%d")}" title="#{Time.unix(child["published"].as_i64).to_s(translate(locale, "%A %B %-d, %Y"))}">#{translate(locale, "`x` ago", recode_date(Time.unix(child["published"].as_i64), locale))} #{child["isEdited"] == true ? translate(locale, "(edited)") : ""}</time>
|
||||||
</div>
|
</div>
|
||||||
<div class="pure-u-20-24 pure-u-md-22-24">
|
END_HTML
|
||||||
<p>
|
|
||||||
<b>
|
html << <<-END_HTML
|
||||||
<a class="#{child["authorIsChannelOwner"] == true ? "channel-owner" : ""}" href="#{child["authorUrl"]}">#{author_name}</a>
|
<ul class="comment-meta-sub">
|
||||||
</b>
|
END_HTML
|
||||||
#{sponsor_icon}
|
|
||||||
<p style="white-space:pre-wrap">#{child["contentHtml"]}</p>
|
|
||||||
|
if child["likeCount"]?
|
||||||
|
html << <<-END_HTML
|
||||||
|
<li>
|
||||||
|
<i class="icon ion-ios-thumbs-up"></i> #{number_with_separator(child["likeCount"])}
|
||||||
|
</li>
|
||||||
|
END_HTML
|
||||||
|
end
|
||||||
|
|
||||||
|
if child["creatorHeart"]?
|
||||||
|
if !thin_mode
|
||||||
|
creator_thumbnail = "/ggpht#{URI.parse(child["creatorHeart"]["creatorThumbnail"].as_s).request_target}"
|
||||||
|
else
|
||||||
|
creator_thumbnail = ""
|
||||||
|
end
|
||||||
|
|
||||||
|
html << <<-END_HTML
|
||||||
|
<li>
|
||||||
|
<span class="creator-heart-container" title="#{translate(locale, "`x` marked it with a ❤", child["creatorHeart"]["creatorName"].as_s)}">
|
||||||
|
<span class="creator-heart">
|
||||||
|
<img loading="lazy" class="creator-heart-background-hearted" src="#{creator_thumbnail}" alt="" />
|
||||||
|
<span class="creator-heart-small-hearted">
|
||||||
|
<span class="icon ion-ios-heart creator-heart-small-container"></span>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</li>
|
||||||
|
END_HTML
|
||||||
|
end
|
||||||
|
|
||||||
|
if comments["videoId"]?
|
||||||
|
html << <<-END_HTML
|
||||||
|
<li>
|
||||||
|
<a rel="noreferrer noopener" href="https://www.youtube.com/watch?v=#{comments["videoId"]}&lc=#{child["commentId"]}" title="#{translate(locale, "YouTube comment permalink")}" title="View on YouTube">YT</a>
|
||||||
|
</li>
|
||||||
|
END_HTML
|
||||||
|
elsif comments["authorId"]?
|
||||||
|
html << <<-END_HTML
|
||||||
|
<li>
|
||||||
|
<a rel="noreferrer noopener" href="https://www.youtube.com/channel/#{comments["authorId"]}/community?lb=#{child["commentId"]}" title="#{translate(locale, "YouTube comment permalink")}" title="View on YouTube">YT</a>
|
||||||
|
</li>
|
||||||
|
END_HTML
|
||||||
|
end
|
||||||
|
|
||||||
|
html << <<-END_HTML
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="_comment">
|
||||||
|
<p class="raw-text">#{child["contentHtml"]}</p>
|
||||||
END_HTML
|
END_HTML
|
||||||
|
|
||||||
if child["attachment"]?
|
if child["attachment"]?
|
||||||
@ -87,10 +139,8 @@ module Invidious::Frontend::Comments
|
|||||||
attachment = attachment["imageThumbnails"][1]
|
attachment = attachment["imageThumbnails"][1]
|
||||||
|
|
||||||
html << <<-END_HTML
|
html << <<-END_HTML
|
||||||
<div class="pure-g">
|
<div>
|
||||||
<div class="pure-u-1 pure-u-md-1-2">
|
<img loading="lazy" src="/ggpht#{URI.parse(attachment["url"].as_s).request_target}" alt="" />
|
||||||
<img loading="lazy" style="width:100%" src="/ggpht#{URI.parse(attachment["url"].as_s).request_target}" alt="" />
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
END_HTML
|
END_HTML
|
||||||
when "video"
|
when "video"
|
||||||
@ -141,50 +191,8 @@ module Invidious::Frontend::Comments
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
html << <<-END_HTML
|
|
||||||
<p>
|
|
||||||
<span title="#{Time.unix(child["published"].as_i64).to_s(translate(locale, "%A %B %-d, %Y"))}">#{translate(locale, "`x` ago", recode_date(Time.unix(child["published"].as_i64), locale))} #{child["isEdited"] == true ? translate(locale, "(edited)") : ""}</span>
|
|
||||||
|
|
|
||||||
END_HTML
|
|
||||||
|
|
||||||
if comments["videoId"]?
|
|
||||||
html << <<-END_HTML
|
|
||||||
<a rel="noreferrer noopener" href="https://www.youtube.com/watch?v=#{comments["videoId"]}&lc=#{child["commentId"]}" title="#{translate(locale, "YouTube comment permalink")}">[YT]</a>
|
|
||||||
|
|
|
||||||
END_HTML
|
|
||||||
elsif comments["authorId"]?
|
|
||||||
html << <<-END_HTML
|
|
||||||
<a rel="noreferrer noopener" href="https://www.youtube.com/channel/#{comments["authorId"]}/community?lb=#{child["commentId"]}" title="#{translate(locale, "YouTube comment permalink")}">[YT]</a>
|
|
||||||
|
|
|
||||||
END_HTML
|
|
||||||
end
|
|
||||||
|
|
||||||
html << <<-END_HTML
|
html << <<-END_HTML
|
||||||
<i class="icon ion-ios-thumbs-up"></i> #{number_with_separator(child["likeCount"])}
|
|
||||||
END_HTML
|
|
||||||
|
|
||||||
if child["creatorHeart"]?
|
|
||||||
if !thin_mode
|
|
||||||
creator_thumbnail = "/ggpht#{URI.parse(child["creatorHeart"]["creatorThumbnail"].as_s).request_target}"
|
|
||||||
else
|
|
||||||
creator_thumbnail = ""
|
|
||||||
end
|
|
||||||
|
|
||||||
html << <<-END_HTML
|
|
||||||
|
|
||||||
<span class="creator-heart-container" title="#{translate(locale, "`x` marked it with a ❤", child["creatorHeart"]["creatorName"].as_s)}">
|
|
||||||
<span class="creator-heart">
|
|
||||||
<img loading="lazy" class="creator-heart-background-hearted" src="#{creator_thumbnail}" alt="" />
|
|
||||||
<span class="creator-heart-small-hearted">
|
|
||||||
<span class="icon ion-ios-heart creator-heart-small-container"></span>
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
END_HTML
|
|
||||||
end
|
|
||||||
|
|
||||||
html << <<-END_HTML
|
|
||||||
</p>
|
|
||||||
#{replies_html}
|
#{replies_html}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -193,16 +201,17 @@ module Invidious::Frontend::Comments
|
|||||||
|
|
||||||
if comments["continuation"]?
|
if comments["continuation"]?
|
||||||
html << <<-END_HTML
|
html << <<-END_HTML
|
||||||
<div class="pure-g">
|
<div>
|
||||||
<div class="pure-u-1">
|
<button data-continuation="#{comments["continuation"]}"
|
||||||
<p>
|
data-onclick="get_youtube_replies" data-load-more #{"data-load-replies" if is_replies}>#{translate(locale, "Load more")}</button>
|
||||||
<a href="javascript:void(0)" data-continuation="#{comments["continuation"]}"
|
|
||||||
data-onclick="get_youtube_replies" data-load-more #{"data-load-replies" if is_replies}>#{translate(locale, "Load more")}</a>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
END_HTML
|
END_HTML
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Closes comment box
|
||||||
|
html << <<-END_HTML
|
||||||
|
</div>
|
||||||
|
END_HTML
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -5,7 +5,7 @@ module Invidious::Frontend::Pagination
|
|||||||
|
|
||||||
private def previous_page(str : String::Builder, locale : String?, url : String)
|
private def previous_page(str : String::Builder, locale : String?, url : String)
|
||||||
# Link
|
# Link
|
||||||
str << %(<a href=") << url << %(" class="pure-button pure-button-secondary">)
|
str << %(<a href=") << url << %(" class="secondary" role="button">)
|
||||||
|
|
||||||
if locale_is_rtl?(locale)
|
if locale_is_rtl?(locale)
|
||||||
# Inverted arrow ("previous" points to the right)
|
# Inverted arrow ("previous" points to the right)
|
||||||
@ -24,12 +24,12 @@ module Invidious::Frontend::Pagination
|
|||||||
|
|
||||||
private def next_page(str : String::Builder, locale : String?, url : String)
|
private def next_page(str : String::Builder, locale : String?, url : String)
|
||||||
# Link
|
# Link
|
||||||
str << %(<a href=") << url << %(" class="pure-button pure-button-secondary">)
|
str << %(<a href=") << url << %(" class="secondary" role="button">)
|
||||||
|
|
||||||
if locale_is_rtl?(locale)
|
if locale_is_rtl?(locale)
|
||||||
# Inverted arrow ("next" points to the left)
|
# Inverted arrow ("next" points to the left)
|
||||||
str << %(<i class="icon ion-ios-arrow-back"></i>)
|
str << %(<i class="icon ion-ios-arrow-back"></i>)
|
||||||
str << " "
|
str << " "
|
||||||
str << translate(locale, "Next page")
|
str << translate(locale, "Next page")
|
||||||
else
|
else
|
||||||
# Regular arrow ("next" points to the right)
|
# Regular arrow ("next" points to the right)
|
||||||
@ -43,55 +43,49 @@ module Invidious::Frontend::Pagination
|
|||||||
|
|
||||||
def nav_numeric(locale : String?, *, base_url : String | URI, current_page : Int, show_next : Bool = true)
|
def nav_numeric(locale : String?, *, base_url : String | URI, current_page : Int, show_next : Bool = true)
|
||||||
return String.build do |str|
|
return String.build do |str|
|
||||||
str << %(<div class="h-box">\n)
|
str << %(<nav class="pagination">\n<ul>\n)
|
||||||
str << %(<div class="page-nav-container flexible">\n)
|
|
||||||
|
|
||||||
str << %(<div class="page-prev-container flex-left">)
|
|
||||||
|
|
||||||
if current_page > 1
|
if current_page > 1
|
||||||
|
str << %(<li>)
|
||||||
|
|
||||||
params_prev = URI::Params{"page" => (current_page - 1).to_s}
|
params_prev = URI::Params{"page" => (current_page - 1).to_s}
|
||||||
url_prev = HttpServer::Utils.add_params_to_url(base_url, params_prev)
|
url_prev = HttpServer::Utils.add_params_to_url(base_url, params_prev)
|
||||||
|
|
||||||
self.previous_page(str, locale, url_prev.to_s)
|
self.previous_page(str, locale, url_prev.to_s)
|
||||||
|
|
||||||
|
str << %(</li>\n)
|
||||||
end
|
end
|
||||||
|
|
||||||
str << %(</div>\n)
|
|
||||||
str << %(<div class="page-next-container flex-right">)
|
|
||||||
|
|
||||||
if show_next
|
if show_next
|
||||||
|
str << %(<li>)
|
||||||
params_next = URI::Params{"page" => (current_page + 1).to_s}
|
params_next = URI::Params{"page" => (current_page + 1).to_s}
|
||||||
url_next = HttpServer::Utils.add_params_to_url(base_url, params_next)
|
url_next = HttpServer::Utils.add_params_to_url(base_url, params_next)
|
||||||
|
|
||||||
self.next_page(str, locale, url_next.to_s)
|
self.next_page(str, locale, url_next.to_s)
|
||||||
|
str << %(</li>\n)
|
||||||
end
|
end
|
||||||
|
|
||||||
str << %(</div>\n)
|
str << %(</ul>\n</nav>\n)
|
||||||
|
|
||||||
str << %(</div>\n)
|
|
||||||
str << %(</div>\n\n)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def nav_ctoken(locale : String?, *, base_url : String | URI, ctoken : String?)
|
def nav_ctoken(locale : String?, *, base_url : String | URI, ctoken : String?)
|
||||||
|
puts ctoken
|
||||||
return String.build do |str|
|
return String.build do |str|
|
||||||
str << %(<div class="h-box">\n)
|
|
||||||
str << %(<div class="page-nav-container flexible">\n)
|
|
||||||
|
|
||||||
str << %(<div class="page-prev-container flex-left"></div>\n)
|
|
||||||
|
|
||||||
str << %(<div class="page-next-container flex-right">)
|
|
||||||
|
|
||||||
if !ctoken.nil?
|
if !ctoken.nil?
|
||||||
|
str << %(<nav class="pagination">\n<ul>\n)
|
||||||
|
|
||||||
|
str << %(<li>)
|
||||||
params_next = URI::Params{"continuation" => ctoken}
|
params_next = URI::Params{"continuation" => ctoken}
|
||||||
url_next = HttpServer::Utils.add_params_to_url(base_url, params_next)
|
url_next = HttpServer::Utils.add_params_to_url(base_url, params_next)
|
||||||
|
|
||||||
self.next_page(str, locale, url_next.to_s)
|
self.next_page(str, locale, url_next.to_s)
|
||||||
|
|
||||||
|
str << %(</li>\n)
|
||||||
|
|
||||||
|
str << %(</ul>\n</nav>\n)
|
||||||
end
|
end
|
||||||
|
|
||||||
str << %(</div>\n)
|
|
||||||
|
|
||||||
str << %(</div>\n)
|
|
||||||
str << %(</div>\n\n)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -5,15 +5,15 @@ module Invidious::Frontend::SearchFilters
|
|||||||
def generate(filters : Search::Filters, query : String, page : Int, locale : String) : String
|
def generate(filters : Search::Filters, query : String, page : Int, locale : String) : String
|
||||||
return String.build(8000) do |str|
|
return String.build(8000) do |str|
|
||||||
str << "<div id='filters'>\n"
|
str << "<div id='filters'>\n"
|
||||||
str << "\t<details id='filters-collapse'>"
|
str << "\t<details id='filters'>"
|
||||||
str << "\t\t<summary>" << translate(locale, "search_filters_title") << "</summary>\n"
|
str << "\t\t<summary>" << translate(locale, "search_filters_title") << "</summary>\n"
|
||||||
|
|
||||||
str << "\t\t<div id='filters-box'><form action='/search' method='get'>\n"
|
str << "\t\t<form action='/search' method='get'>\n"
|
||||||
|
|
||||||
str << "\t\t\t<input type='hidden' name='q' value='" << HTML.escape(query) << "'>\n"
|
str << "\t\t\t<input type='hidden' name='q' value='" << HTML.escape(query) << "'>\n"
|
||||||
str << "\t\t\t<input type='hidden' name='page' value='" << page << "'>\n"
|
str << "\t\t\t<input type='hidden' name='page' value='" << page << "'>\n"
|
||||||
|
|
||||||
str << "\t\t\t<div id='filters-flex'>"
|
str << "\t\t\t<div class='filters'>"
|
||||||
|
|
||||||
filter_wrapper(date)
|
filter_wrapper(date)
|
||||||
filter_wrapper(type)
|
filter_wrapper(type)
|
||||||
@ -23,32 +23,32 @@ module Invidious::Frontend::SearchFilters
|
|||||||
|
|
||||||
str << "\t\t\t</div>\n"
|
str << "\t\t\t</div>\n"
|
||||||
|
|
||||||
str << "\t\t\t<div id='filters-apply'>"
|
str << "\t\t\t<div class='action-controls'>"
|
||||||
str << "<button type='submit' class=\"pure-button pure-button-primary\">"
|
str << "<button type='submit' class=\"primary\">"
|
||||||
str << translate(locale, "search_filters_apply_button")
|
str << translate(locale, "search_filters_apply_button")
|
||||||
str << "</button></div>\n"
|
str << "</button></div>\n"
|
||||||
|
|
||||||
str << "\t\t</form></div>\n"
|
str << "\t\t</form></div>\n"
|
||||||
|
|
||||||
str << "\t</details>\n"
|
str << "\t</details>\n"
|
||||||
str << "</div>\n"
|
# str << "</div>\n"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Generate wrapper HTML (`<div>`, filter name, etc...) around the
|
# Generate wrapper HTML (`<div>`, filter name, etc...) around the
|
||||||
# `<input>` elements of a search filter
|
# `<input>` elements of a search filter
|
||||||
macro filter_wrapper(name)
|
macro filter_wrapper(name)
|
||||||
str << "\t\t\t\t<div class=\"filter-column\"><fieldset>\n"
|
str << "\t\t\t\t<fieldset class='form'>\n"
|
||||||
|
|
||||||
str << "\t\t\t\t\t<legend><div class=\"filter-name underlined\">"
|
str << "\t\t\t\t\t<legend>"
|
||||||
str << translate(locale, "search_filters_{{name}}_label")
|
str << translate(locale, "search_filters_{{name}}_label")
|
||||||
str << "</div></legend>\n"
|
str << "</legend>\n"
|
||||||
|
|
||||||
str << "\t\t\t\t\t<div class=\"filter-options\">\n"
|
str << "\t\t\t\t\t\n"
|
||||||
make_{{name}}_filter_options(str, filters.{{name}}, locale)
|
make_{{name}}_filter_options(str, filters.{{name}}, locale)
|
||||||
str << "\t\t\t\t\t</div>"
|
str << "\t\t\t\t\t"
|
||||||
|
|
||||||
str << "\t\t\t\t</fieldset></div>\n"
|
str << "\t\t\t\t</fieldset>\n"
|
||||||
end
|
end
|
||||||
|
|
||||||
# Generates the HTML for the list of radio buttons of the "date" search filter
|
# Generates the HTML for the list of radio buttons of the "date" search filter
|
||||||
|
@ -25,7 +25,7 @@ module Invidious::Frontend::WatchPage
|
|||||||
|
|
||||||
return String.build(4000) do |str|
|
return String.build(4000) do |str|
|
||||||
str << "<form"
|
str << "<form"
|
||||||
str << " class=\"pure-form pure-form-stacked\""
|
str << " class=\"watch-action-group\""
|
||||||
str << " action='/download'"
|
str << " action='/download'"
|
||||||
str << " method='post'"
|
str << " method='post'"
|
||||||
str << " rel='noopener'"
|
str << " rel='noopener'"
|
||||||
@ -36,7 +36,7 @@ module Invidious::Frontend::WatchPage
|
|||||||
str << "<input type='hidden' name='id' value='" << video.id << "'/>\n"
|
str << "<input type='hidden' name='id' value='" << video.id << "'/>\n"
|
||||||
str << "<input type='hidden' name='title' value='" << HTML.escape(video.title) << "'/>\n"
|
str << "<input type='hidden' name='title' value='" << HTML.escape(video.title) << "'/>\n"
|
||||||
|
|
||||||
str << "\t<div class=\"pure-control-group\">\n"
|
# str << "\t<div class=\"control-group\">\n"
|
||||||
|
|
||||||
str << "\t\t<label for='download_widget'>"
|
str << "\t\t<label for='download_widget'>"
|
||||||
str << translate(locale, "Download as: ")
|
str << translate(locale, "Download as: ")
|
||||||
@ -92,14 +92,14 @@ module Invidious::Frontend::WatchPage
|
|||||||
str << "</option>\n"
|
str << "</option>\n"
|
||||||
end
|
end
|
||||||
|
|
||||||
# End of form
|
|
||||||
|
|
||||||
str << "\t\t</select>\n"
|
str << "\t\t</select>\n"
|
||||||
str << "\t</div>\n"
|
# str << "\t</div>\n"
|
||||||
|
|
||||||
str << "\t<button type=\"submit\" class=\"pure-button pure-button-primary\">\n"
|
# str << "\t<div class=\"control-actions\">"
|
||||||
str << "\t\t<b>" << translate(locale, "Download") << "</b>\n"
|
str << "\t<button type=\"submit\" class=\"secondary\">\n"
|
||||||
|
str << "\t\t" << translate(locale, "Download") << "\n"
|
||||||
str << "\t</button>\n"
|
str << "\t</button>\n"
|
||||||
|
# str << "\t</div>"
|
||||||
|
|
||||||
str << "</form>\n"
|
str << "</form>\n"
|
||||||
end
|
end
|
||||||
|
@ -98,11 +98,11 @@ def template_mix(mix)
|
|||||||
<a href="/watch?v=#{video["videoId"]}&list=#{mix["mixId"]}">
|
<a href="/watch?v=#{video["videoId"]}&list=#{mix["mixId"]}">
|
||||||
<div class="thumbnail">
|
<div class="thumbnail">
|
||||||
<img loading="lazy" class="thumbnail" src="/vi/#{video["videoId"]}/mqdefault.jpg" alt="" />
|
<img loading="lazy" class="thumbnail" src="/vi/#{video["videoId"]}/mqdefault.jpg" alt="" />
|
||||||
<p class="length">#{recode_length_seconds(video["lengthSeconds"].as_i)}</p>
|
<div class="length">#{recode_length_seconds(video["lengthSeconds"].as_i)}</div>
|
||||||
</div>
|
</div>
|
||||||
<p style="width:100%">#{video["title"]}</p>
|
<p>#{video["title"]}</p>
|
||||||
<p>
|
<p>
|
||||||
<b style="width:100%">#{video["author"]}</b>
|
#{video["author"]}
|
||||||
</p>
|
</p>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
@ -450,6 +450,8 @@ end
|
|||||||
def extract_playlist_videos(initial_data : Hash(String, JSON::Any))
|
def extract_playlist_videos(initial_data : Hash(String, JSON::Any))
|
||||||
videos = [] of PlaylistVideo
|
videos = [] of PlaylistVideo
|
||||||
|
|
||||||
|
puts initial_data.inspect
|
||||||
|
|
||||||
if initial_data["contents"]?
|
if initial_data["contents"]?
|
||||||
tabs = initial_data["contents"]["twoColumnBrowseResultsRenderer"]["tabs"]
|
tabs = initial_data["contents"]["twoColumnBrowseResultsRenderer"]["tabs"]
|
||||||
tabs_renderer = tabs.as_a.select(&.["tabRenderer"]["selected"]?.try &.as_bool)[0]["tabRenderer"]
|
tabs_renderer = tabs.as_a.select(&.["tabRenderer"]["selected"]?.try &.as_bool)[0]["tabRenderer"]
|
||||||
@ -480,6 +482,9 @@ def extract_playlist_videos(initial_data : Hash(String, JSON::Any))
|
|||||||
title = i["title"].try { |t| t["simpleText"]? || t["runs"]?.try &.[0]["text"]? }.try &.as_s || ""
|
title = i["title"].try { |t| t["simpleText"]? || t["runs"]?.try &.[0]["text"]? }.try &.as_s || ""
|
||||||
author = i["shortBylineText"]?.try &.["runs"][0]["text"].as_s || ""
|
author = i["shortBylineText"]?.try &.["runs"][0]["text"].as_s || ""
|
||||||
ucid = i["shortBylineText"]?.try &.["runs"][0]["navigationEndpoint"]["browseEndpoint"]["browseId"].as_s || ""
|
ucid = i["shortBylineText"]?.try &.["runs"][0]["navigationEndpoint"]["browseEndpoint"]["browseId"].as_s || ""
|
||||||
|
|
||||||
|
# TODO: Possibly add author_thumbnail support
|
||||||
|
# author_thumbnail = i["playlistSidebarSecondaryInfoRenderer"]?.try &.["videoOwner"]["videoOwnerRenderer"]["thumbnail"]["thumbnails"][0]["url"].as_s || ""
|
||||||
length_seconds = i["lengthSeconds"]?.try &.as_s.to_i
|
length_seconds = i["lengthSeconds"]?.try &.as_s.to_i
|
||||||
live = false
|
live = false
|
||||||
|
|
||||||
@ -512,23 +517,29 @@ def template_playlist(playlist)
|
|||||||
#{playlist["title"]}
|
#{playlist["title"]}
|
||||||
</a>
|
</a>
|
||||||
</h3>
|
</h3>
|
||||||
<div class="pure-menu pure-menu-scrollable playlist-restricted">
|
<div class="playlist-restricted">
|
||||||
<ol class="pure-menu-list">
|
<ol>
|
||||||
END_HTML
|
END_HTML
|
||||||
|
|
||||||
playlist["videos"].as_a.each do |video|
|
playlist["videos"].as_a.each do |video|
|
||||||
html += <<-END_HTML
|
html += <<-END_HTML
|
||||||
<li class="pure-menu-item" id="#{video["videoId"]}">
|
<li id="#{video["videoId"]}">
|
||||||
<a href="/watch?v=#{video["videoId"]}&list=#{playlist["playlistId"]}&index=#{video["index"]}">
|
|
||||||
<div class="thumbnail">
|
<div class="thumbnail">
|
||||||
|
<a href="/watch?v=#{video["videoId"]}&list=#{playlist["playlistId"]}&index=#{video["index"]}">
|
||||||
<img loading="lazy" class="thumbnail" src="/vi/#{video["videoId"]}/mqdefault.jpg" alt="" />
|
<img loading="lazy" class="thumbnail" src="/vi/#{video["videoId"]}/mqdefault.jpg" alt="" />
|
||||||
<p class="length">#{recode_length_seconds(video["lengthSeconds"].as_i)}</p>
|
<div class="length">#{recode_length_seconds(video["lengthSeconds"].as_i)}</div>
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<p style="width:100%">#{video["title"]}</p>
|
<h3>
|
||||||
<p>
|
<a href="/watch?v=#{video["videoId"]}&list=#{playlist["playlistId"]}&index=#{video["index"]}">
|
||||||
<b style="width:100%">#{video["author"]}</b>
|
#{video["title"]}
|
||||||
</p>
|
</a>
|
||||||
</a>
|
</h3>
|
||||||
|
<h4>
|
||||||
|
<a href="/channel/#{video["ucid"]}">
|
||||||
|
#{video["author"]}
|
||||||
|
</a>
|
||||||
|
</h4>
|
||||||
</li>
|
</li>
|
||||||
END_HTML
|
END_HTML
|
||||||
end
|
end
|
||||||
@ -536,7 +547,6 @@ def template_playlist(playlist)
|
|||||||
html += <<-END_HTML
|
html += <<-END_HTML
|
||||||
</ol>
|
</ol>
|
||||||
</div>
|
</div>
|
||||||
<hr>
|
|
||||||
END_HTML
|
END_HTML
|
||||||
|
|
||||||
html
|
html
|
||||||
|
@ -267,6 +267,8 @@ module Invidious::Routes::Playlists
|
|||||||
show_next: (items.size >= 20)
|
show_next: (items.size >= 20)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
puts items
|
||||||
|
|
||||||
env.set "add_playlist_items", plid
|
env.set "add_playlist_items", plid
|
||||||
templated "add_playlist_items"
|
templated "add_playlist_items"
|
||||||
end
|
end
|
||||||
|
@ -67,6 +67,10 @@ module Invidious::Routes::Search
|
|||||||
|
|
||||||
redirect_url = Invidious::Frontend::Misc.redirect_url(env)
|
redirect_url = Invidious::Frontend::Misc.redirect_url(env)
|
||||||
|
|
||||||
|
puts items.size
|
||||||
|
puts (items.size >= 20)
|
||||||
|
puts query.page
|
||||||
|
|
||||||
# Pagination
|
# Pagination
|
||||||
page_nav_html = Frontend::Pagination.nav_numeric(locale,
|
page_nav_html = Frontend::Pagination.nav_numeric(locale,
|
||||||
base_url: "/search?#{query.to_http_params}",
|
base_url: "/search?#{query.to_http_params}",
|
||||||
|
@ -3,25 +3,38 @@
|
|||||||
<link rel="alternate" type="application/rss+xml" title="RSS" href="/feed/playlist/<%= plid %>" />
|
<link rel="alternate" type="application/rss+xml" title="RSS" href="/feed/playlist/<%= plid %>" />
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<div class="pure-g">
|
<div class="playlist-header">
|
||||||
<div class="pure-u-1 pure-u-lg-1-5"></div>
|
<h1><%= translate(locale, "Editing playlist") %></h1>
|
||||||
<div class="pure-u-1 pure-u-lg-3-5">
|
<nav>
|
||||||
<div class="h-box">
|
<ul>
|
||||||
<form class="pure-form pure-form-aligned" action="/add_playlist_items" method="get">
|
<li><a href="/playlist?list=<%= playlist.id %>"><%= translate(locale, "View playlist") %></a></li>
|
||||||
<legend><a href="/playlist?list=<%= playlist.id %>"><%= translate(locale, "Editing playlist `x`", %|"#{HTML.escape(playlist.title)}"|) %></a></legend>
|
</ul>
|
||||||
|
</nav>
|
||||||
<fieldset>
|
|
||||||
<input class="pure-input-1" type="search" name="q"
|
|
||||||
<% if query %>value="<%= HTML.escape(query.text) %>"<% end %>
|
|
||||||
placeholder="<%= translate(locale, "Search for videos") %>">
|
|
||||||
<input type="hidden" name="list" value="<%= plid %>">
|
|
||||||
</fieldset>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="pure-u-1 pure-u-lg-1-5"></div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<h2><%= HTML.escape(playlist.title) %></h2>
|
||||||
|
|
||||||
|
<form action="/add_playlist_items" method="get">
|
||||||
|
<fieldset>
|
||||||
|
<legend><%= translate(locale, "Search for videos") %></legend>
|
||||||
|
|
||||||
|
<div class="search-box">
|
||||||
|
<input type="search" class="search" autocorrect="off"
|
||||||
|
autocapitalize="none" spellcheck="false" autofocus
|
||||||
|
name="q"
|
||||||
|
id="playlist-video-search"
|
||||||
|
<% if query %>value="<%= HTML.escape(query.text) %>"<% end %>
|
||||||
|
placeholder="<%= translate(locale, "Search for videos") %>"
|
||||||
|
title="<%= translate(locale, "search") %>">
|
||||||
|
|
||||||
|
<button type="submit" class="search-action" aria-label="<%= translate(locale, "search") %>">
|
||||||
|
<i class="icon ion-ios-search"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<input type="hidden" name="list" value="<%= plid %>">
|
||||||
|
</fieldset>
|
||||||
|
</form>
|
||||||
|
|
||||||
<script id="playlist_data" type="application/json">
|
<script id="playlist_data" type="application/json">
|
||||||
<%=
|
<%=
|
||||||
{
|
{
|
||||||
@ -31,5 +44,4 @@
|
|||||||
</script>
|
</script>
|
||||||
<script src="/js/playlist_widget.js?v=<%= ASSET_COMMIT %>"></script>
|
<script src="/js/playlist_widget.js?v=<%= ASSET_COMMIT %>"></script>
|
||||||
|
|
||||||
|
|
||||||
<%= rendered "components/items_paginated" %>
|
<%= rendered "components/items_paginated" %>
|
||||||
|
@ -46,9 +46,4 @@
|
|||||||
|
|
||||||
<%= rendered "components/channel_info" %>
|
<%= rendered "components/channel_info" %>
|
||||||
|
|
||||||
<div class="h-box">
|
|
||||||
<hr>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
<%= rendered "components/items_paginated" %>
|
<%= rendered "components/items_paginated" %>
|
||||||
|
@ -17,18 +17,12 @@
|
|||||||
|
|
||||||
<%= rendered "components/channel_info" %>
|
<%= rendered "components/channel_info" %>
|
||||||
|
|
||||||
<div class="h-box">
|
|
||||||
<hr>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<% if error_message %>
|
<% if error_message %>
|
||||||
<div class="h-box">
|
<p><%= error_message %></p>
|
||||||
<p><%= error_message %></p>
|
|
||||||
</div>
|
|
||||||
<% else %>
|
<% else %>
|
||||||
<div class="h-box pure-g comments" id="comments">
|
<div class="comments community" id="comments">
|
||||||
<%= IV::Frontend::Comments.template_youtube(items.not_nil!, locale, thin_mode) %>
|
<%= IV::Frontend::Comments.template_youtube(items.not_nil!, locale, thin_mode) %>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<script id="community_data" type="application/json">
|
<script id="community_data" type="application/json">
|
||||||
|
@ -1,61 +1,49 @@
|
|||||||
<% if channel.banner %>
|
<% if channel.banner %>
|
||||||
<div class="h-box">
|
<img src="/ggpht<%= URI.parse(channel.banner.not_nil!.gsub("=w1060-", "=w1280-")).request_target %>" alt="" />
|
||||||
<img style="width:100%" src="/ggpht<%= URI.parse(channel.banner.not_nil!.gsub("=w1060-", "=w1280-")).request_target %>" alt="" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="h-box">
|
|
||||||
<hr>
|
|
||||||
</div>
|
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<div class="pure-g h-box flexible title">
|
<div class="title" dir="auto">
|
||||||
<div class="pure-u-1-2 flex-left flexible">
|
<div class="channel-profile">
|
||||||
<div class="channel-profile">
|
<img src="/ggpht<%= channel_profile_pic %>" alt="" />
|
||||||
<img src="/ggpht<%= channel_profile_pic %>" alt="" />
|
<h1><%= author %><% if !channel.verified.nil? && channel.verified %> <i class="icon ion ion-md-checkmark-circle"></i><% end %></h1>
|
||||||
<span><%= author %></span><% if !channel.verified.nil? && channel.verified %> <i class="icon ion ion-md-checkmark-circle"></i><% end %>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="pure-u-1-2 flex-right flexible button-container">
|
<div class="button-container">
|
||||||
<div class="pure-u">
|
<% sub_count_text = number_to_short_text(channel.sub_count) %>
|
||||||
<% sub_count_text = number_to_short_text(channel.sub_count) %>
|
<%= rendered "components/subscribe_widget" %>
|
||||||
<%= rendered "components/subscribe_widget" %>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="pure-u">
|
<a role="button" class="secondary" href="/feed/channel/<%= ucid %>">
|
||||||
<a class="pure-button pure-button-secondary" dir="auto" href="/feed/channel/<%= ucid %>">
|
<i class="icon ion-logo-rss"></i> <%= translate(locale, "generic_button_rss") %>
|
||||||
<i class="icon ion-logo-rss"></i> <%= translate(locale, "generic_button_rss") %>
|
</a>
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="h-box">
|
<div class="watch-context">
|
||||||
<div id="descriptionWrapper"><p><span style="white-space:pre-wrap"><%= channel.description_html %></span></p></div>
|
<div id="description" dir="auto">
|
||||||
|
<p class="raw-text"><%= channel.description_html %></p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<nav dir="auto">
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<a href="<%= youtube_url %>"><%= translate(locale, "View channel on YouTube") %></a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="<%= redirect_url %>"><%= translate(locale, "Switch Invidious Instance") %></a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="pure-g h-box">
|
<nav class="menu" dir="auto">
|
||||||
<div class="pure-u-1-2">
|
<ul>
|
||||||
<div class="pure-u-1 pure-md-1-3">
|
|
||||||
<a href="<%= youtube_url %>"><%= translate(locale, "View channel on YouTube") %></a>
|
|
||||||
</div>
|
|
||||||
<div class="pure-u-1 pure-md-1-3">
|
|
||||||
<a href="<%= redirect_url %>"><%= translate(locale, "Switch Invidious Instance") %></a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<%= Invidious::Frontend::ChannelPage.generate_tabs_links(locale, channel, selected_tab) %>
|
<%= Invidious::Frontend::ChannelPage.generate_tabs_links(locale, channel, selected_tab) %>
|
||||||
</div>
|
</ul>
|
||||||
<div class="pure-u-1-2">
|
<ul>
|
||||||
<div class="pure-g" style="text-align:end">
|
<% sort_options.each do |sort| %>
|
||||||
<% sort_options.each do |sort| %>
|
<li <% if sort_by == sort %>class="selected"<% end %>>
|
||||||
<div class="pure-u-1 pure-md-1-3">
|
<a href="<%= relative_url %>?sort_by=<%= sort %>"><%= translate(locale, sort) %></a>
|
||||||
<% if sort_by == sort %>
|
</li>
|
||||||
<b><%= translate(locale, sort) %></b>
|
<% end %>
|
||||||
<% else %>
|
</ul>
|
||||||
<a href="<%= relative_url %>?sort_by=<%= sort %>"><%= translate(locale, sort) %></a>
|
</nav>
|
||||||
<% end %>
|
|
||||||
</div>
|
|
||||||
<% end %>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
@ -1,11 +1,15 @@
|
|||||||
<div class="feed-menu">
|
<nav class="feed-menu">
|
||||||
|
<ul>
|
||||||
<% feed_menu = env.get("preferences").as(Preferences).feed_menu.dup %>
|
<% feed_menu = env.get("preferences").as(Preferences).feed_menu.dup %>
|
||||||
<% if !env.get?("user") %>
|
<% if !env.get?("user") %>
|
||||||
<% feed_menu.reject! {|item| {"Subscriptions", "Playlists"}.includes? item} %>
|
<% feed_menu.reject! {|item| {"Subscriptions", "Playlists"}.includes? item} %>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% feed_menu.each do |feed| %>
|
<% feed_menu.each do |feed| %>
|
||||||
<a href="/feed/<%= feed.downcase %>" class="feed-menu-item pure-menu-heading">
|
<li>
|
||||||
|
<a href="/feed/<%= feed.downcase %>">
|
||||||
<%= translate(locale, feed) %>
|
<%= translate(locale, feed) %>
|
||||||
</a>
|
</a>
|
||||||
|
</li>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</ul>
|
||||||
|
</nav>
|
||||||
|
@ -3,58 +3,53 @@
|
|||||||
item_watched = !item.is_a?(SearchChannel | SearchHashtag | SearchPlaylist | InvidiousPlaylist | Category) && env.get?("user").try &.as(User).watched.index(item.id) != nil
|
item_watched = !item.is_a?(SearchChannel | SearchHashtag | SearchPlaylist | InvidiousPlaylist | Category) && env.get?("user").try &.as(User).watched.index(item.id) != nil
|
||||||
author_verified = item.responds_to?(:author_verified) && item.author_verified
|
author_verified = item.responds_to?(:author_verified) && item.author_verified
|
||||||
-%>
|
-%>
|
||||||
|
|
||||||
<div class="pure-u-1 pure-u-md-1-4">
|
|
||||||
<div class="h-box">
|
|
||||||
<% case item when %>
|
<% case item when %>
|
||||||
<% when SearchChannel %>
|
<% when SearchChannel %>
|
||||||
<% if !thin_mode %>
|
<article class="video-card" dir="auto">
|
||||||
<a tabindex="-1" href="/channel/<%= item.ucid %>">
|
<% if thin_mode %>
|
||||||
<center>
|
<div class="thumbnail-placeholder"></div>
|
||||||
<img loading="lazy" style="width:56.25%" src="/ggpht<%= URI.parse(item.author_thumbnail).request_target.gsub(/=s\d+/, "=s176") %>" alt="" />
|
|
||||||
</center>
|
|
||||||
</a>
|
|
||||||
<%- else -%>
|
<%- else -%>
|
||||||
<div class="thumbnail-placeholder" style="width:56.25%"></div>
|
<a tabindex="-1" href="/channel/<%= item.ucid %>" title="<%= HTML.escape(item.author) %> channel">
|
||||||
|
<img loading="lazy" class="profile-pic" src="/ggpht<%= URI.parse(item.author_thumbnail).request_target.gsub(/=s\d+/, "=s176") %>" alt="<%= HTML.escape(item.author) %> profile picture" />
|
||||||
|
</a>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<div class="video-card-row flexible">
|
<h3 class="channel-name">
|
||||||
<div class="flex-left"><a href="/channel/<%= item.ucid %>">
|
<a href="/channel/<%= item.ucid %>" title="<%= HTML.escape(item.author) %> channel">
|
||||||
<p class="channel-name" dir="auto"><%= HTML.escape(item.author) %>
|
<%= HTML.escape(item.author) %>
|
||||||
<%- if author_verified %> <i class="icon ion ion-md-checkmark-circle"></i><% end -%>
|
<%- if author_verified %> <i class="icon ion ion-md-checkmark-circle"></i><% end -%>
|
||||||
</p>
|
</a>
|
||||||
</a></div>
|
</h3>
|
||||||
</div>
|
|
||||||
|
|
||||||
<% if !item.channel_handle.nil? %><p class="channel-name" dir="auto"><%= item.channel_handle %></p><% end %>
|
<% if !item.channel_handle.nil? %><h3 class="channel-name"><%= item.channel_handle %></h3><% end %>
|
||||||
<p><%= translate_count(locale, "generic_subscribers_count", item.subscriber_count, NumberFormatting::Separator) %></p>
|
<div><%= translate_count(locale, "generic_subscribers_count", item.subscriber_count, NumberFormatting::Separator) %></div>
|
||||||
<% if !item.auto_generated && item.channel_handle.nil? %><p><%= translate_count(locale, "generic_videos_count", item.video_count, NumberFormatting::Separator) %></p><% end %>
|
<% if !item.auto_generated && item.channel_handle.nil? %><div><%= translate_count(locale, "generic_videos_count", item.video_count, NumberFormatting::Separator) %></div><% end %>
|
||||||
<h5><%= item.description_html %></h5>
|
<p><%= item.description_html %></p>
|
||||||
<% when SearchHashtag %>
|
<% when SearchHashtag %>
|
||||||
<% if !thin_mode %>
|
<article class="video-card" dir="auto">
|
||||||
<a tabindex="-1" href="<%= item.url %>">
|
<% if thin_mode %>
|
||||||
<center><img style="width:56.25%" src="/hashtag.svg" alt="" /></center>
|
<div class="thumbnail"></div>
|
||||||
</a>
|
|
||||||
<%- else -%>
|
<%- else -%>
|
||||||
<div class="thumbnail-placeholder" style="width:56.25%"></div>
|
<a tabindex="-1" href="<%= item.url %>">
|
||||||
|
<img src="/hashtag.svg" alt="" />
|
||||||
|
</a>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<div class="video-card-row">
|
<h3><a href="<%= item.url %>" title="<%= HTML.escape(item.title) %>"><%= HTML.escape(item.title) %></a></h3>
|
||||||
<div class="flex-left"><a href="<%= item.url %>"><%= HTML.escape(item.title) %></a></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="video-card-row">
|
<%- if item.video_count != 0 -%>
|
||||||
<%- if item.video_count != 0 -%>
|
<div>
|
||||||
<p><%= translate_count(locale, "generic_videos_count", item.video_count, NumberFormatting::Separator) %></p>
|
<%= translate_count(locale, "generic_videos_count", item.video_count, NumberFormatting::Separator) %>
|
||||||
<%- end -%>
|
</div>
|
||||||
</div>
|
<%- end -%>
|
||||||
|
|
||||||
<div class="video-card-row">
|
<div>
|
||||||
<%- if item.channel_count != 0 -%>
|
<%- if item.channel_count != 0 -%>
|
||||||
<p><%= translate_count(locale, "generic_channels_count", item.channel_count, NumberFormatting::Separator) %></p>
|
<div><%= translate_count(locale, "generic_channels_count", item.channel_count, NumberFormatting::Separator) %></div>
|
||||||
<%- end -%>
|
<%- end -%>
|
||||||
</div>
|
</div>
|
||||||
<% when SearchPlaylist, InvidiousPlaylist %>
|
<% when SearchPlaylist, InvidiousPlaylist %>
|
||||||
|
<article class="video-card" dir="auto">
|
||||||
<%-
|
<%-
|
||||||
if item.id.starts_with? "RD"
|
if item.id.starts_with? "RD"
|
||||||
link_url = "/mix?list=#{item.id}&continuation=#{URI.parse(item.thumbnail || "/vi/-----------").request_target.split("/")[2]}"
|
link_url = "/mix?list=#{item.id}&continuation=#{URI.parse(item.thumbnail || "/vi/-----------").request_target.split("/")[2]}"
|
||||||
@ -65,39 +60,37 @@
|
|||||||
|
|
||||||
<div class="thumbnail">
|
<div class="thumbnail">
|
||||||
<%- if !thin_mode %>
|
<%- if !thin_mode %>
|
||||||
<a tabindex="-1" href="<%= link_url %>">
|
<a tabindex="-1" href="<%= link_url %>" title="<%= HTML.escape(item.title) %>">
|
||||||
<img loading="lazy" class="thumbnail" src="<%= URI.parse(item.thumbnail || "/").request_target %>" alt="" />
|
<img loading="lazy" class="thumbnail" src="<%= URI.parse(item.thumbnail || "/").request_target %>" alt="" />
|
||||||
</a>
|
</a>
|
||||||
<%- else -%>
|
|
||||||
<div class="thumbnail-placeholder"></div>
|
|
||||||
<%- end -%>
|
<%- end -%>
|
||||||
|
<div class="length">
|
||||||
<div class="bottom-right-overlay">
|
<%= translate_count(locale, "generic_videos_count", item.video_count, NumberFormatting::Separator) %>
|
||||||
<p class="length"><%= translate_count(locale, "generic_videos_count", item.video_count, NumberFormatting::Separator) %></p>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="video-card-row">
|
<h3><a href="<%= link_url %>" title="<%= HTML.escape(item.title) %>"><%= HTML.escape(item.title) %></a></h3>
|
||||||
<a href="<%= link_url %>"><p dir="auto"><%= HTML.escape(item.title) %></p></a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="video-card-row flexible">
|
<div class="video-meta-sub">
|
||||||
<div class="flex-left">
|
<h3 class="channel-name">
|
||||||
<% if !item.ucid.to_s.empty? %>
|
<% if !item.ucid.to_s.empty? %>
|
||||||
<a href="/channel/<%= item.ucid %>">
|
<a href="/channel/<%= item.ucid %>" title="<%= HTML.escape(item.author) %> channel">
|
||||||
<p class="channel-name" dir="auto"><%= HTML.escape(item.author) %>
|
<% end %>
|
||||||
<%- if author_verified %> <i class="icon ion ion-md-checkmark-circle"></i><% end -%>
|
<%= HTML.escape(item.author) %>
|
||||||
</p>
|
<%- if author_verified %> <i class="icon ion ion-md-checkmark-circle"></i><% end -%>
|
||||||
</a>
|
<% if !item.ucid.to_s.empty? %>
|
||||||
<% else %>
|
</a>
|
||||||
<p class="channel-name" dir="auto"><%= HTML.escape(item.author) %>
|
<% end %>
|
||||||
<%- if author_verified %> <i class="icon ion ion-md-checkmark-circle"></i><% end -%>
|
|
||||||
</p>
|
|
||||||
<% end %>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<% when Category %>
|
<% when Category %>
|
||||||
<% else %>
|
<% else %>
|
||||||
|
<%- if item.is_a?(PlaylistVideo) && (plid_form = env.get?("remove_playlist_items"))
|
||||||
|
index_attribute = "data-index='#{item.index}'"
|
||||||
|
else
|
||||||
|
index_attribute = ""
|
||||||
|
end
|
||||||
|
-%>
|
||||||
|
<article class="video-card" dir="auto" id="video-card-<%= item.id %>" data-id="<%= item.id %>" <%= index_attribute %>>
|
||||||
<%-
|
<%-
|
||||||
# `endpoint_params` is used for the "video-context-buttons" component
|
# `endpoint_params` is used for the "video-context-buttons" component
|
||||||
if item.is_a?(PlaylistVideo)
|
if item.is_a?(PlaylistVideo)
|
||||||
@ -111,10 +104,13 @@
|
|||||||
endpoint_params = "?v=#{item.id}"
|
endpoint_params = "?v=#{item.id}"
|
||||||
end
|
end
|
||||||
-%>
|
-%>
|
||||||
|
|
||||||
<div class="thumbnail">
|
<div class="thumbnail">
|
||||||
<%- if !thin_mode -%>
|
<%- if thin_mode -%>
|
||||||
<a tabindex="-1" href="<%= link_url %>">
|
<a tabindex="-1" href="<%= link_url %>" title="<%= HTML.escape(item.title) %>">
|
||||||
|
<div class="thumbnail-placeholder"></div>
|
||||||
|
</a>
|
||||||
|
<%- else -%>
|
||||||
|
<a tabindex="-1" href="<%= link_url %>" title="<%= HTML.escape(item.title) %>">
|
||||||
<img loading="lazy" class="thumbnail" src="/vi/<%= item.id %>/mqdefault.jpg" alt="" />
|
<img loading="lazy" class="thumbnail" src="/vi/<%= item.id %>/mqdefault.jpg" alt="" />
|
||||||
|
|
||||||
<% if item_watched %>
|
<% if item_watched %>
|
||||||
@ -122,15 +118,13 @@
|
|||||||
<div class="watched-indicator" data-length="<%= item.length_seconds %>" data-id="<%= item.id %>"></div>
|
<div class="watched-indicator" data-length="<%= item.length_seconds %>" data-id="<%= item.id %>"></div>
|
||||||
<% end %>
|
<% end %>
|
||||||
</a>
|
</a>
|
||||||
<%- else -%>
|
|
||||||
<div class="thumbnail-placeholder"></div>
|
|
||||||
<%- end -%>
|
<%- end -%>
|
||||||
|
|
||||||
<div class="top-left-overlay">
|
<div class="top-left-overlay">
|
||||||
<%- if env.get? "show_watched" -%>
|
<%- if env.get? "show_watched" -%>
|
||||||
<form data-onsubmit="return_false" action="/watch_ajax?action_mark_watched=1&id=<%= item.id %>&referer=<%= env.get("current_page") %>" method="post">
|
<form data-onsubmit="return_false" action="/watch_ajax?action_mark_watched=1&id=<%= item.id %>&referer=<%= env.get("current_page") %>" method="post">
|
||||||
<input type="hidden" name="csrf_token" value="<%= HTML.escape(env.get?("csrf_token").try &.as(String) || "") %>">
|
<input type="hidden" name="csrf_token" value="<%= HTML.escape(env.get?("csrf_token").try &.as(String) || "") %>">
|
||||||
<button type="submit" class="pure-button pure-button-secondary low-profile"
|
<button type="submit" class="secondary"
|
||||||
data-onclick="mark_watched" data-id="<%= item.id %>">
|
data-onclick="mark_watched" data-id="<%= item.id %>">
|
||||||
<i data-mouse="switch_classes" data-switch-classes="ion-ios-eye-off,ion-ios-eye" class="icon ion-ios-eye"></i>
|
<i data-mouse="switch_classes" data-switch-classes="ion-ios-eye-off,ion-ios-eye" class="icon ion-ios-eye"></i>
|
||||||
</button>
|
</button>
|
||||||
@ -141,65 +135,60 @@
|
|||||||
<%- form_parameters = "action_add_video=1&video_id=#{item.id}&playlist_id=#{plid_form}&referer=#{env.get("current_page")}" -%>
|
<%- form_parameters = "action_add_video=1&video_id=#{item.id}&playlist_id=#{plid_form}&referer=#{env.get("current_page")}" -%>
|
||||||
<form data-onsubmit="return_false" action="/playlist_ajax?<%= form_parameters %>" method="post">
|
<form data-onsubmit="return_false" action="/playlist_ajax?<%= form_parameters %>" method="post">
|
||||||
<input type="hidden" name="csrf_token" value="<%= HTML.escape(env.get?("csrf_token").try &.as(String) || "") %>">
|
<input type="hidden" name="csrf_token" value="<%= HTML.escape(env.get?("csrf_token").try &.as(String) || "") %>">
|
||||||
<button type="submit" class="pure-button pure-button-secondary low-profile"
|
<button type="submit" class="secondary"
|
||||||
data-onclick="add_playlist_item" data-id="<%= item.id %>" data-plid="<%= plid_form %>"><i class="icon ion-md-add"></i></button>
|
data-onclick="add_playlist_item" data-id="<%= item.id %>" data-plid="<%= plid_form %>"><i class="icon ion-md-add"></i></button>
|
||||||
</form>
|
</form>
|
||||||
<%- elsif item.is_a?(PlaylistVideo) && (plid_form = env.get?("remove_playlist_items")) -%>
|
<%- elsif item.is_a?(PlaylistVideo) && (plid_form = env.get?("remove_playlist_items")) -%>
|
||||||
<%- form_parameters = "action_remove_video=1&set_video_id=#{item.index}&playlist_id=#{plid_form}&referer=#{env.get("current_page")}" -%>
|
<%- form_parameters = "action_remove_video=1&set_video_id=#{item.index}&playlist_id=#{plid_form}&referer=#{env.get("current_page")}" -%>
|
||||||
<form data-onsubmit="return_false" action="/playlist_ajax?<%= form_parameters %>" method="post">
|
<form data-onsubmit="return_false" action="/playlist_ajax?<%= form_parameters %>" method="post">
|
||||||
<input type="hidden" name="csrf_token" value="<%= HTML.escape(env.get?("csrf_token").try &.as(String) || "") %>">
|
<input type="hidden" name="csrf_token" value="<%= HTML.escape(env.get?("csrf_token").try &.as(String) || "") %>">
|
||||||
<button type="submit" class="pure-button pure-button-secondary low-profile"
|
<button type="submit" class="secondary"
|
||||||
data-onclick="remove_playlist_item" data-index="<%= item.index %>" data-plid="<%= plid_form %>"><i class="icon ion-md-trash"></i></button>
|
data-onclick="remove_playlist_item" data-index="<%= item.index %>" data-plid="<%= plid_form %>"><i class="icon ion-md-trash"></i></button>
|
||||||
</form>
|
</form>
|
||||||
<%- end -%>
|
<%- end -%>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="bottom-right-overlay">
|
<%- if item.responds_to?(:live_now) && item.live_now -%>
|
||||||
<%- if item.responds_to?(:live_now) && item.live_now -%>
|
<div class="length"><i class="icon ion-ios-play-circle"></i> <%= translate(locale, "LIVE") %></div>
|
||||||
<p class="length" dir="auto"><i class="icon ion-ios-play-circle"></i> <%= translate(locale, "LIVE") %></p>
|
<%- elsif item.length_seconds != 0 -%>
|
||||||
<%- elsif item.length_seconds != 0 -%>
|
<div class="length"><%= recode_length_seconds(item.length_seconds) %></div>
|
||||||
<p class="length"><%= recode_length_seconds(item.length_seconds) %></p>
|
<%- end -%>
|
||||||
<%- end -%>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="video-card-row">
|
<h3><a href="<%= link_url %>" title="<%= HTML.escape(item.title) %>"><%= HTML.escape(item.title) %></a></h3>
|
||||||
<a href="<%= link_url %>"><p dir="auto"><%= HTML.escape(item.title) %></p></a>
|
|
||||||
|
<div class="video-meta-sub">
|
||||||
|
<h3 class="channel-name">
|
||||||
|
<% if !item.ucid.to_s.empty? %>
|
||||||
|
<a href="/channel/<%= item.ucid %>" title="<%= HTML.escape(item.author) %>">
|
||||||
|
<% end %>
|
||||||
|
<%= HTML.escape(item.author) %>
|
||||||
|
<%- if author_verified %> <i class="icon ion ion-md-checkmark-circle"></i><% end -%>
|
||||||
|
<% if !item.ucid.to_s.empty? %>
|
||||||
|
</a>
|
||||||
|
<% end %>
|
||||||
|
</h3>
|
||||||
|
<%= rendered "components/video-context-buttons" %>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="video-card-row flexible">
|
<div class="video-meta-sub">
|
||||||
<div class="flex-left">
|
<% if item.responds_to?(:premiere_timestamp) && item.premiere_timestamp.try &.> Time.utc %>
|
||||||
<% if !item.ucid.to_s.empty? %>
|
<div class="video-data">
|
||||||
<a href="/channel/<%= item.ucid %>">
|
<time datetime="<%= item.premiere_timestamp.try { |t| t.to_s("%Y-%m-%d") } %>" title="<%= item.premiere_timestamp.try { |t| t.to_s(translate(locale, "%A %B %-d, %Y")) } %>">
|
||||||
<p class="channel-name" dir="auto"><%= HTML.escape(item.author) %>
|
<%= translate(locale, "Premieres in `x`", recode_date((item.premiere_timestamp.as(Time) - Time.utc).ago, locale)) %>
|
||||||
<%- if author_verified %> <i class="icon ion ion-md-checkmark-circle"></i><% end -%>
|
</time>
|
||||||
</p>
|
</div>
|
||||||
</a>
|
<% elsif item.responds_to?(:published) && (Time.utc - item.published) > 1.minute %>
|
||||||
<% else %>
|
<div class="video-data">
|
||||||
<p class="channel-name" dir="auto"><%= HTML.escape(item.author) %>
|
<time datetime="<%= item.published.try { |t| t.to_s("%Y-%m-%d") } %>" title="<%= item.published.try { |t| t.to_s(translate(locale, "%A %B %-d, %Y")) } %>">
|
||||||
<%- if author_verified %> <i class="icon ion ion-md-checkmark-circle"></i><% end -%>
|
<%= translate(locale, "Shared `x` ago", recode_date(item.published, locale)) %>
|
||||||
</p>
|
</time>
|
||||||
<% end %>
|
</div>
|
||||||
</div>
|
<% end %>
|
||||||
|
|
||||||
<%= rendered "components/video-context-buttons" %>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="video-card-row flexible">
|
|
||||||
<div class="flex-left">
|
|
||||||
<% if item.responds_to?(:premiere_timestamp) && item.premiere_timestamp.try &.> Time.utc %>
|
|
||||||
<p class="video-data" dir="auto"><%= translate(locale, "Premieres in `x`", recode_date((item.premiere_timestamp.as(Time) - Time.utc).ago, locale)) %></p>
|
|
||||||
<% elsif item.responds_to?(:published) && (Time.utc - item.published) > 1.minute %>
|
|
||||||
<p class="video-data" dir="auto"><%= translate(locale, "Shared `x` ago", recode_date(item.published, locale)) %></p>
|
|
||||||
<% end %>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<% if item.responds_to?(:views) && item.views %>
|
<% if item.responds_to?(:views) && item.views %>
|
||||||
<div class="flex-right">
|
<div class="video-data"><%= translate_count(locale, "generic_views_count", item.views || 0, NumberFormatting::Short) %></div>
|
||||||
<p class="video-data" dir="auto"><%= translate_count(locale, "generic_views_count", item.views || 0, NumberFormatting::Short) %></p>
|
|
||||||
</div>
|
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</article>
|
||||||
</div>
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<%= page_nav_html %>
|
<%= page_nav_html %>
|
||||||
|
|
||||||
<div class="pure-g">
|
<div class="videos">
|
||||||
<%- items.each do |item| -%>
|
<%- items.each do |item| -%>
|
||||||
<%= rendered "components/item" %>
|
<%= rendered "components/item" %>
|
||||||
<%- end -%>
|
<%- end -%>
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
<form class="pure-form" action="/search" method="get">
|
<form action="/search" method="get" id="site-search">
|
||||||
<fieldset>
|
<div class="search-box">
|
||||||
<input type="search" id="searchbox" autocorrect="off"
|
<input type="search" class="search" autocorrect="off"
|
||||||
autocapitalize="none" spellcheck="false" <% if autofocus %>autofocus<% end %>
|
autocapitalize="none" spellcheck="false" <% if autofocus %>autofocus<% end %>
|
||||||
name="q" placeholder="<%= translate(locale, "search") %>"
|
name="q" placeholder="<%= translate(locale, "search") %>"
|
||||||
title="<%= translate(locale, "search") %>"
|
title="<%= translate(locale, "search") %>"
|
||||||
value="<%= env.get?("search").try {|x| HTML.escape(x.as(String)) } %>">
|
value="<%= env.get?("search").try {|x| HTML.escape(x.as(String)) } %>">
|
||||||
</fieldset>
|
<button type="submit" class="search-action" aria-label="<%= translate(locale, "search") %>">
|
||||||
<button type="submit" id="searchbutton" aria-label="<%= translate(locale, "search") %>">
|
<i class="icon ion-ios-search"></i>
|
||||||
<i class="icon ion-ios-search"></i>
|
</button>
|
||||||
</button>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
@ -2,16 +2,12 @@
|
|||||||
<% if subscriptions.includes? ucid %>
|
<% if subscriptions.includes? ucid %>
|
||||||
<form action="/subscription_ajax?action_remove_subscriptions=1&c=<%= ucid %>&referer=<%= env.get("current_page") %>" method="post">
|
<form action="/subscription_ajax?action_remove_subscriptions=1&c=<%= ucid %>&referer=<%= env.get("current_page") %>" method="post">
|
||||||
<input type="hidden" name="csrf_token" value="<%= HTML.escape(env.get?("csrf_token").try &.as(String) || "") %>">
|
<input type="hidden" name="csrf_token" value="<%= HTML.escape(env.get?("csrf_token").try &.as(String) || "") %>">
|
||||||
<button data-type="unsubscribe" id="subscribe" class="pure-button pure-button-primary">
|
<input data-type="unsubscribe" id="subscribe" class="secondary" type="submit" value="<%= translate(locale, "Unsubscribe") %> | <%= sub_count_text %>">
|
||||||
<b><input style="all:unset" type="submit" value="<%= translate(locale, "Unsubscribe") %> | <%= sub_count_text %>"></b>
|
|
||||||
</button>
|
|
||||||
</form>
|
</form>
|
||||||
<% else %>
|
<% else %>
|
||||||
<form action="/subscription_ajax?action_create_subscription_to_channel=1&c=<%= ucid %>&referer=<%= env.get("current_page") %>" method="post">
|
<form action="/subscription_ajax?action_create_subscription_to_channel=1&c=<%= ucid %>&referer=<%= env.get("current_page") %>" method="post">
|
||||||
<input type="hidden" name="csrf_token" value="<%= HTML.escape(env.get?("csrf_token").try &.as(String) || "") %>">
|
<input type="hidden" name="csrf_token" value="<%= HTML.escape(env.get?("csrf_token").try &.as(String) || "") %>">
|
||||||
<button data-type="subscribe" id="subscribe" class="pure-button pure-button-primary">
|
<input class="primary" data-type="subscribe" id="subscribe" type="submit" value="<%= translate(locale, "Subscribe") %> | <%= sub_count_text %>">
|
||||||
<b><input style="all:unset" type="submit" value="<%= translate(locale, "Subscribe") %> | <%= sub_count_text %>"></b>
|
|
||||||
</button>
|
|
||||||
</form>
|
</form>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
@ -29,8 +25,8 @@
|
|||||||
</script>
|
</script>
|
||||||
<script src="/js/subscribe_widget.js?v=<%= ASSET_COMMIT %>"></script>
|
<script src="/js/subscribe_widget.js?v=<%= ASSET_COMMIT %>"></script>
|
||||||
<% else %>
|
<% else %>
|
||||||
<a id="subscribe" class="pure-button pure-button-primary"
|
<a id="subscribe" class="primary" role="button"
|
||||||
href="/login?referer=<%= env.get("current_page") %>">
|
href="/login?referer=<%= env.get("current_page") %>">
|
||||||
<b><%= translate(locale, "Subscribe") %> | <%= sub_count_text %></b>
|
<%= translate(locale, "Subscribe") %> | <%= sub_count_text %>
|
||||||
</a>
|
</a>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
@ -1,21 +1,29 @@
|
|||||||
<div class="flex-right flexible">
|
|
||||||
<div class="icon-buttons">
|
<div class="icon-buttons">
|
||||||
|
<nav>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
<a title="<%=translate(locale, "videoinfo_watch_on_youTube")%>" rel="noreferrer noopener" href="https://www.youtube.com/watch<%=endpoint_params%>">
|
<a title="<%=translate(locale, "videoinfo_watch_on_youTube")%>" rel="noreferrer noopener" href="https://www.youtube.com/watch<%=endpoint_params%>">
|
||||||
<i class="icon ion-logo-youtube"></i>
|
<i class="icon ion-logo-youtube"></i>
|
||||||
</a>
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
<a title="<%=translate(locale, "Audio mode")%>" href="/watch<%=endpoint_params%>&listen=1">
|
<a title="<%=translate(locale, "Audio mode")%>" href="/watch<%=endpoint_params%>&listen=1">
|
||||||
<i class="icon ion-md-headset"></i>
|
<i class="icon ion-md-headset"></i>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
<% if env.get("preferences").as(Preferences).automatic_instance_redirect%>
|
<% if env.get("preferences").as(Preferences).automatic_instance_redirect%>
|
||||||
<a title="<%=translate(locale, "Switch Invidious Instance")%>" href="/redirect?referer=%2Fwatch<%=URI.encode_www_form(endpoint_params)%>">
|
<a title="<%=translate(locale, "Switch Invidious Instance")%>" href="/redirect?referer=%2Fwatch<%=URI.encode_www_form(endpoint_params)%>" rel="noreferrer noopener">
|
||||||
<i class="icon ion-md-jet"></i>
|
<i class="icon ion-md-jet"></i>
|
||||||
</a>
|
</a>
|
||||||
<% else %>
|
<% else %>
|
||||||
<a title="<%=translate(locale, "Switch Invidious Instance")%>" href="https://redirect.invidious.io/watch<%=endpoint_params%>">
|
<a title="<%=translate(locale, "Switch Invidious Instance")%>" href="https://redirect.invidious.io/watch<%=endpoint_params%>" rel="noreferrer noopener" >
|
||||||
<i class="icon ion-md-jet"></i>
|
<i class="icon ion-md-jet"></i>
|
||||||
</a>
|
</a>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
@ -2,38 +2,31 @@
|
|||||||
<title><%= translate(locale, "Create playlist") %> - Invidious</title>
|
<title><%= translate(locale, "Create playlist") %> - Invidious</title>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<div class="pure-g">
|
<form action="/create_playlist?referer=<%= URI.encode_www_form(referer) %>" method="post">
|
||||||
<div class="pure-u-1 pure-u-lg-1-5"></div>
|
<fieldset class="form">
|
||||||
<div class="pure-u-1 pure-u-lg-3-5">
|
<legend><%= translate(locale, "Create playlist") %></legend>
|
||||||
<div class="h-box">
|
|
||||||
<form class="pure-form pure-form-aligned" action="/create_playlist?referer=<%= URI.encode_www_form(referer) %>" method="post">
|
|
||||||
<fieldset>
|
|
||||||
<legend><%= translate(locale, "Create playlist") %></legend>
|
|
||||||
|
|
||||||
<div class="pure-control-group">
|
<div class="control-group">
|
||||||
<label for="title"><%= translate(locale, "Title") %> :</label>
|
<label for="title"><%= translate(locale, "Title") %> :</label>
|
||||||
<input required name="title" type="text" placeholder="<%= translate(locale, "Title") %>">
|
<input required name="title" type="text" placeholder="<%= translate(locale, "Title") %>">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="pure-control-group">
|
<div class="control-group">
|
||||||
<label for="privacy"><%= translate(locale, "Playlist privacy") %> :</label>
|
<label for="privacy"><%= translate(locale, "Playlist privacy") %> :</label>
|
||||||
<select name="privacy" id="privacy">
|
<select name="privacy" id="privacy">
|
||||||
<% PlaylistPrivacy.names.each do |option| %>
|
<% PlaylistPrivacy.names.each do |option| %>
|
||||||
<option value="<%= option %>" <% if option == "Public" %> selected <% end %>><%= translate(locale, option) %></option>
|
<option value="<%= option %>" <% if option == "Public" %> selected <% end %>><%= translate(locale, option) %></option>
|
||||||
<% end %>
|
<% end %>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="pure-controls">
|
|
||||||
<button type="submit" name="action" value="create_playlist" class="pure-button pure-button-primary">
|
|
||||||
<%= translate(locale, "Create playlist") %>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<input type="hidden" name="csrf_token" value="<%= HTML.escape(csrf_token) %>">
|
<input type="hidden" name="csrf_token" value="<%= HTML.escape(csrf_token) %>">
|
||||||
</fieldset>
|
</fieldset>
|
||||||
</form>
|
|
||||||
</div>
|
<div class="action-controls">
|
||||||
</div>
|
<button type="submit" name="action" value="create_playlist" class="pure-button pure-button-primary">
|
||||||
<div class="pure-u-1 pure-u-lg-1-5"></div>
|
<%= translate(locale, "Create playlist") %>
|
||||||
</div>
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
@ -5,56 +5,62 @@
|
|||||||
<link rel="alternate" type="application/rss+xml" title="RSS" href="/feed/playlist/<%= plid %>" />
|
<link rel="alternate" type="application/rss+xml" title="RSS" href="/feed/playlist/<%= plid %>" />
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<form class="pure-form" action="/edit_playlist?list=<%= plid %>" method="post">
|
<form action="/edit_playlist?list=<%= plid %>" method="post">
|
||||||
<div class="h-box flexible">
|
|
||||||
<div class="flex-right button-container">
|
<div class="header-group">
|
||||||
<div class="pure-u">
|
<nav class="menu">
|
||||||
<a class="pure-button pure-button-secondary low-profile" dir="auto" href="/playlist?list=<%= plid %>">
|
<ul>
|
||||||
|
<li>
|
||||||
|
|
||||||
|
<a class="secondary" role="button" dir="auto" href="/playlist?list=<%= plid %>">
|
||||||
<i class="icon ion-md-close"></i> <%= translate(locale, "generic_button_cancel") %>
|
<i class="icon ion-md-close"></i> <%= translate(locale, "generic_button_cancel") %>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</li>
|
||||||
<div class="pure-u">
|
<li>
|
||||||
<button class="pure-button pure-button-secondary low-profile" dir="auto" type="submit">
|
<a class="secondary" role="button" dir="auto" href="/delete_playlist?list=<%= plid %>">
|
||||||
<i class="icon ion-md-save"></i> <%= translate(locale, "generic_button_save") %>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div class="pure-u">
|
|
||||||
<a class="pure-button pure-button-secondary low-profile" dir="auto" href="/delete_playlist?list=<%= plid %>">
|
|
||||||
<i class="icon ion-md-trash"></i> <%= translate(locale, "generic_button_delete") %>
|
<i class="icon ion-md-trash"></i> <%= translate(locale, "generic_button_delete") %>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</li>
|
||||||
</div>
|
</ul>
|
||||||
</div>
|
</nav>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<div class="h-box flexible title">
|
<ul>
|
||||||
<div>
|
<li>
|
||||||
<h3><input class="pure-input-1" maxlength="150" name="title" type="text" value="<%= title %>"></h3>
|
<%= HTML.escape(playlist.author) %>
|
||||||
</div>
|
</li>
|
||||||
</div>
|
<li>
|
||||||
|
<%= translate_count(locale, "generic_videos_count", playlist.video_count) %>
|
||||||
<div class="h-box">
|
</li>
|
||||||
<div class="pure-u-1-1">
|
<li>
|
||||||
<b>
|
|
||||||
<%= HTML.escape(playlist.author) %> |
|
|
||||||
<%= translate_count(locale, "generic_videos_count", playlist.video_count) %> |
|
|
||||||
</b>
|
|
||||||
<select name="privacy">
|
<select name="privacy">
|
||||||
<%- {"Public", "Unlisted", "Private"}.each do |option| -%>
|
<%- {"Public", "Unlisted", "Private"}.each do |option| -%>
|
||||||
<option value="<%= option %>" <% if option == playlist.privacy.to_s %>selected<% end %>><%= translate(locale, option) %></option>
|
<option value="<%= option %>" <% if option == playlist.privacy.to_s %>selected<% end %>><%= translate(locale, option) %></option>
|
||||||
<%- end -%>
|
<%- end -%>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</li>
|
||||||
</div>
|
</ul>
|
||||||
|
|
||||||
<div class="h-box">
|
<fieldset class="form">
|
||||||
<textarea maxlength="5000" name="description" style="margin-top:10px;max-width:100%;height:20vh" class="pure-input-1"><%= playlist.description %></textarea>
|
<legend>Edit playlist</legend>
|
||||||
</div>
|
<div class="control-group">
|
||||||
|
<label for="title">Title</label>
|
||||||
|
<input id="title" maxlength="150" name="title" type="text" value="<%= title %>">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="control-group">
|
||||||
|
<label for="description">Description</label>
|
||||||
|
<textarea maxlength="5000" name="description"><%= playlist.description %></textarea>
|
||||||
|
</div>
|
||||||
<input type="hidden" name="csrf_token" value="<%= HTML.escape(csrf_token) %>">
|
<input type="hidden" name="csrf_token" value="<%= HTML.escape(csrf_token) %>">
|
||||||
|
<div class="action-controls">
|
||||||
|
<button class="primary" dir="auto" type="submit">
|
||||||
|
<i class="icon ion-md-save"></i> <%= translate(locale, "generic_button_save") %>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<div class="h-box">
|
|
||||||
<hr>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
<%= rendered "components/items_paginated" %>
|
<%= rendered "components/items_paginated" %>
|
||||||
|
@ -2,20 +2,18 @@
|
|||||||
<title><%= translate(locale, "History") %> - Invidious</title>
|
<title><%= translate(locale, "History") %> - Invidious</title>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<div class="pure-g h-box">
|
<div class="history-header">
|
||||||
<div class="pure-u-1-3">
|
<h3><%= translate_count(locale, "generic_videos_count", user.watched.size, NumberFormatting::HtmlSpan) %></h3>
|
||||||
<h3><%= translate_count(locale, "generic_videos_count", user.watched.size, NumberFormatting::HtmlSpan) %></h3>
|
<nav>
|
||||||
</div>
|
<ul>
|
||||||
<div class="pure-u-1-3">
|
<li>
|
||||||
<h3 style="text-align:center">
|
<a href="/feed/subscriptions"><%= translate_count(locale, "generic_subscriptions_count", user.subscriptions.size, NumberFormatting::HtmlSpan) %></a>
|
||||||
<a href="/feed/subscriptions"><%= translate_count(locale, "generic_subscriptions_count", user.subscriptions.size, NumberFormatting::HtmlSpan) %></a>
|
</li>
|
||||||
</h3>
|
<li>
|
||||||
</div>
|
<a href="/clear_watch_history"><%= translate(locale, "Clear watch history") %></a>
|
||||||
<div class="pure-u-1-3">
|
</li>
|
||||||
<h3 style="text-align:right">
|
</ul>
|
||||||
<a href="/clear_watch_history"><%= translate(locale, "Clear watch history") %></a>
|
</nav>
|
||||||
</h3>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script id="watched_data" type="application/json">
|
<script id="watched_data" type="application/json">
|
||||||
@ -27,26 +25,23 @@
|
|||||||
</script>
|
</script>
|
||||||
<script src="/js/watched_widget.js"></script>
|
<script src="/js/watched_widget.js"></script>
|
||||||
|
|
||||||
<div class="pure-g">
|
<div class="videos">
|
||||||
<% watched.each do |item| %>
|
<% watched.each do |item| %>
|
||||||
<div class="pure-u-1 pure-u-md-1-4">
|
<div class="video-card">
|
||||||
<div class="h-box">
|
<div class="thumbnail">
|
||||||
<div class="thumbnail">
|
<a href="/watch?v=<%= item %>">
|
||||||
<a style="width:100%" href="/watch?v=<%= item %>">
|
<img class="thumbnail" src="/vi/<%= item %>/mqdefault.jpg" alt="" />
|
||||||
<img class="thumbnail" src="/vi/<%= item %>/mqdefault.jpg" alt="" />
|
</a>
|
||||||
</a>
|
|
||||||
|
|
||||||
<div class="top-left-overlay"><div class="watched">
|
<div class="top-left-overlay"><div class="watched">
|
||||||
<form data-onsubmit="return_false" action="/watch_ajax?action_mark_unwatched=1&id=<%= item %>&referer=<%= env.get("current_page") %>" method="post">
|
<form data-onsubmit="return_false" action="/watch_ajax?action_mark_unwatched=1&id=<%= item %>&referer=<%= env.get("current_page") %>" method="post">
|
||||||
<input type="hidden" name="csrf_token" value="<%= URI.encode_www_form(env.get?("csrf_token").try &.as(String) || "") %>">
|
<input type="hidden" name="csrf_token" value="<%= URI.encode_www_form(env.get?("csrf_token").try &.as(String) || "") %>">
|
||||||
<button type="submit" class="pure-button pure-button-secondary low-profile"
|
<button type="submit" class="pure-button pure-button-secondary low-profile"
|
||||||
data-onclick="mark_unwatched" data-id="<%= item %>"><i class="icon ion-md-trash"></i></button>
|
data-onclick="mark_unwatched" data-id="<%= item %>"><i class="icon ion-md-trash"></i></button>
|
||||||
</form>
|
</form>
|
||||||
</div></div>
|
</div></div>
|
||||||
</div>
|
</div>
|
||||||
<p></p>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -4,38 +4,33 @@
|
|||||||
|
|
||||||
<%= rendered "components/feed_menu" %>
|
<%= rendered "components/feed_menu" %>
|
||||||
|
|
||||||
<div class="pure-g h-box">
|
<div class="playlist-header">
|
||||||
<div class="pure-u-1-3">
|
<h3><%= translate(locale, "user_created_playlists", %(<span id="count">#{items_created.size}</span>)) %></h3>
|
||||||
<h3><%= translate(locale, "user_created_playlists", %(<span id="count">#{items_created.size}</span>)) %></h3>
|
<nav>
|
||||||
</div>
|
<ul>
|
||||||
<div class="pure-u-1-3">
|
<li>
|
||||||
<h3 style="text-align:center">
|
<a href="/create_playlist?referer=<%= URI.encode_www_form("/feed/playlists") %>"><%= translate(locale, "Create playlist") %></a>
|
||||||
<a href="/create_playlist?referer=<%= URI.encode_www_form("/feed/playlists") %>"><%= translate(locale, "Create playlist") %></a>
|
</li>
|
||||||
</h3>
|
<li>
|
||||||
</div>
|
<a href="/data_control?referer=<%= URI.encode_www_form("/feed/playlists") %>">
|
||||||
<div class="pure-u-1-3">
|
<%= translate(locale, "Import/export") %>
|
||||||
<h3 style="text-align:right">
|
</a>
|
||||||
<a href="/data_control?referer=<%= URI.encode_www_form("/feed/playlists") %>">
|
</li>
|
||||||
<%= translate(locale, "Import/export") %>
|
</ul>
|
||||||
</a>
|
</nav>
|
||||||
</h3>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="pure-g">
|
<div class="videos">
|
||||||
<% items_created.each do |item| %>
|
<% items_created.each do |item| %>
|
||||||
<%= rendered "components/item" %>
|
<%= rendered "components/item" %>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="pure-g h-box">
|
<h3><%= translate(locale, "user_saved_playlists", %(<span id="count">#{items_saved.size}</span>)) %></h3>
|
||||||
<div class="pure-u-1">
|
|
||||||
<h3><%= translate(locale, "user_saved_playlists", %(<span id="count">#{items_saved.size}</span>)) %></h3>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="pure-g">
|
<div class="videos">
|
||||||
<% items_saved.each do |item| %>
|
<% items_saved.each do |item| %>
|
||||||
|
<% puts item %>
|
||||||
<%= rendered "components/item" %>
|
<%= rendered "components/item" %>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
|
@ -11,10 +11,10 @@
|
|||||||
|
|
||||||
<%= rendered "components/feed_menu" %>
|
<%= rendered "components/feed_menu" %>
|
||||||
|
|
||||||
<div class="pure-g">
|
<main class="videos">
|
||||||
<% popular_videos.each do |item| %>
|
<% popular_videos.each do |item| %>
|
||||||
<%= rendered "components/item" %>
|
<%= rendered "components/item" %>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</main>
|
||||||
|
|
||||||
<script src="/js/watched_indicator.js"></script>
|
<script src="/js/watched_indicator.js"></script>
|
||||||
|
@ -5,63 +5,59 @@
|
|||||||
|
|
||||||
<%= rendered "components/feed_menu" %>
|
<%= rendered "components/feed_menu" %>
|
||||||
|
|
||||||
<div class="pure-g h-box">
|
<main>
|
||||||
<div class="pure-u-1-3">
|
<header class="subscription-menu">
|
||||||
<h3>
|
<% if CONFIG.enable_user_notifications %>
|
||||||
<a href="/subscription_manager"><%= translate(locale, "Manage subscriptions") %></a>
|
|
||||||
</h3>
|
|
||||||
</div>
|
|
||||||
<div class="pure-u-1-3">
|
|
||||||
<h3 style="text-align:center">
|
|
||||||
<a href="/feed/history"><%= translate(locale, "Watch history") %></a>
|
|
||||||
</h3>
|
|
||||||
</div>
|
|
||||||
<div class="pure-u-1-3">
|
|
||||||
<h3 style="text-align:right">
|
|
||||||
<a href="/feed/private?token=<%= token %>"><i class="icon ion-logo-rss"></i></a>
|
|
||||||
</h3>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<% if CONFIG.enable_user_notifications %>
|
<div>
|
||||||
|
<%= translate_count(locale, "subscriptions_unseen_notifs_count", notifications.size) %>
|
||||||
|
</div>
|
||||||
|
<% else %>
|
||||||
|
<div></div>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
<center>
|
<nav>
|
||||||
<%= translate_count(locale, "subscriptions_unseen_notifs_count", notifications.size) %>
|
<ul>
|
||||||
</center>
|
<li>
|
||||||
|
<a href="/subscription_manager"><%= translate(locale, "Manage subscriptions") %></a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="/feed/history"><%= translate(locale, "Watch history") %></a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="/feed/private?token=<%= token %>"><i class="icon ion-logo-rss"></i> <%= translate(locale, "Subscription RSS") %></a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
</header>
|
||||||
|
|
||||||
<% if !notifications.empty? %>
|
<% if CONFIG.enable_user_notifications %>
|
||||||
<div class="h-box">
|
<% if !notifications.empty? %>
|
||||||
<hr>
|
<div class="notifications videos">
|
||||||
</div>
|
<% notifications.each do |item| %>
|
||||||
<% end %>
|
<%= rendered "components/item" %>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
<div class="pure-g">
|
<% end %>
|
||||||
<% notifications.each do |item| %>
|
|
||||||
<%= rendered "components/item" %>
|
|
||||||
<% end %>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<% end %>
|
<script id="watched_data" type="application/json">
|
||||||
|
<%=
|
||||||
<div class="h-box">
|
{
|
||||||
<hr>
|
"csrf_token" => URI.encode_www_form(env.get?("csrf_token").try &.as(String) || "")
|
||||||
</div>
|
}.to_pretty_json
|
||||||
|
%>
|
||||||
<script id="watched_data" type="application/json">
|
</script>
|
||||||
<%=
|
<script src="/js/watched_widget.js"></script>
|
||||||
{
|
|
||||||
"csrf_token" => URI.encode_www_form(env.get?("csrf_token").try &.as(String) || "")
|
|
||||||
}.to_pretty_json
|
|
||||||
%>
|
|
||||||
</script>
|
|
||||||
<script src="/js/watched_widget.js"></script>
|
|
||||||
|
|
||||||
|
|
||||||
<div class="pure-g">
|
<div class="videos">
|
||||||
<% videos.each do |item| %>
|
<% videos.each do |item| %>
|
||||||
<%= rendered "components/item" %>
|
<%= rendered "components/item" %>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
|
</main>
|
||||||
|
|
||||||
<script src="/js/watched_indicator.js"></script>
|
<script src="/js/watched_indicator.js"></script>
|
||||||
|
|
||||||
|
@ -11,39 +11,31 @@
|
|||||||
|
|
||||||
<%= rendered "components/feed_menu" %>
|
<%= rendered "components/feed_menu" %>
|
||||||
|
|
||||||
<div class="pure-g h-box">
|
<nav class="menu filters">
|
||||||
<div style="align-self:flex-end" class="pure-u-2-3">
|
<ul>
|
||||||
<% if plid %>
|
<% if plid %>
|
||||||
|
<li>
|
||||||
<a href="/playlist?list=<%= plid %>">
|
<a href="/playlist?list=<%= plid %>">
|
||||||
<%= translate(locale, "View as playlist") %>
|
<%= translate(locale, "View as playlist") %>
|
||||||
</a>
|
</a>
|
||||||
|
</li>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</ul>
|
||||||
<div class="pure-u-1-3">
|
<ul>
|
||||||
<div class="pure-g" style="text-align:right">
|
<% {"Default", "Music", "Gaming", "Movies"}.each do |option| %>
|
||||||
<% {"Default", "Music", "Gaming", "Movies"}.each do |option| %>
|
<li <% if trending_type == option %>class="selected"<% end %>>
|
||||||
<div class="pure-u-1 pure-md-1-3">
|
<a href="/feed/trending?type=<%= option %>®ion=<%= region %>">
|
||||||
<% if trending_type == option %>
|
<%= translate(locale, option) %></b>
|
||||||
<b><%= translate(locale, option) %></b>
|
</a>
|
||||||
<% else %>
|
</li>
|
||||||
<a href="/feed/trending?type=<%= option %>®ion=<%= region %>">
|
<% end %>
|
||||||
<%= translate(locale, option) %>
|
</ul>
|
||||||
</a>
|
</nav>
|
||||||
<% end %>
|
|
||||||
</div>
|
|
||||||
<% end %>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="h-box">
|
<main class="videos">
|
||||||
<hr>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="pure-g">
|
|
||||||
<% trending.each do |item| %>
|
<% trending.each do |item| %>
|
||||||
<%= rendered "components/item" %>
|
<%= rendered "components/item" %>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</main>
|
||||||
|
|
||||||
<script src="/js/watched_indicator.js"></script>
|
<script src="/js/watched_indicator.js"></script>
|
||||||
|
@ -6,109 +6,141 @@
|
|||||||
<link rel="alternate" type="application/rss+xml" title="RSS" href="/feed/playlist/<%= plid %>" />
|
<link rel="alternate" type="application/rss+xml" title="RSS" href="/feed/playlist/<%= plid %>" />
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<div class="h-box flexible title">
|
<div class="playlist-header">
|
||||||
<div class="flex-left"><h3><%= title %></h3></div>
|
<h1><%= title %></h1>
|
||||||
|
|
||||||
<div class="flex-right button-container">
|
<nav class="menu">
|
||||||
|
<ul>
|
||||||
<%- if playlist.is_a?(InvidiousPlaylist) && playlist.author == user.try &.email -%>
|
<%- if playlist.is_a?(InvidiousPlaylist) && playlist.author == user.try &.email -%>
|
||||||
<div class="pure-u">
|
<li>
|
||||||
<a class="pure-button pure-button-secondary low-profile" dir="auto" href="/add_playlist_items?list=<%= plid %>">
|
<a class="secondary" role="button" dir="auto" href="/add_playlist_items?list=<%= plid %>">
|
||||||
<i class="icon ion-md-add"></i> <%= translate(locale, "playlist_button_add_items") %>
|
<i class="icon ion-md-add"></i> <%= translate(locale, "playlist_button_add_items") %>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</li>
|
||||||
<div class="pure-u">
|
<li>
|
||||||
<a class="pure-button pure-button-secondary low-profile" dir="auto" href="/edit_playlist?list=<%= plid %>">
|
<a class="secondary" role="button" dir="auto" href="/edit_playlist?list=<%= plid %>">
|
||||||
<i class="icon ion-md-create"></i> <%= translate(locale, "generic_button_edit") %>
|
<i class="icon ion-md-create"></i> <%= translate(locale, "generic_button_edit") %>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</li>
|
||||||
<div class="pure-u">
|
<li>
|
||||||
<a class="pure-button pure-button-secondary low-profile" dir="auto" href="/delete_playlist?list=<%= plid %>">
|
<a class="secondary" role="button" dir="auto" href="/delete_playlist?list=<%= plid %>">
|
||||||
<i class="icon ion-md-trash"></i> <%= translate(locale, "generic_button_delete") %>
|
<i class="icon ion-md-trash"></i> <%= translate(locale, "generic_button_delete") %>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</li>
|
||||||
<%- else -%>
|
<%- else -%>
|
||||||
<div class="pure-u">
|
<li>
|
||||||
<%- if IV::Database::Playlists.exists?(playlist.id) -%>
|
<%- if IV::Database::Playlists.exists?(playlist.id) -%>
|
||||||
<a class="pure-button pure-button-secondary low-profile" dir="auto" href="/subscribe_playlist?list=<%= plid %>">
|
<a class="secondary" role="button" dir="auto" href="/subscribe_playlist?list=<%= plid %>">
|
||||||
<i class="icon ion-md-add"></i> <%= translate(locale, "Subscribe") %>
|
<i class="icon ion-md-add"></i> <%= translate(locale, "Subscribe") %>
|
||||||
</a>
|
</a>
|
||||||
<%- else -%>
|
<%- else -%>
|
||||||
<a class="pure-button pure-button-secondary low-profile" dir="auto" href="/delete_playlist?list=<%= plid %>">
|
<a class="secondary" role="button" dir="auto" href="/delete_playlist?list=<%= plid %>">
|
||||||
<i class="icon ion-md-trash"></i> <%= translate(locale, "Unsubscribe") %>
|
<i class="icon ion-md-trash"></i> <%= translate(locale, "Unsubscribe") %>
|
||||||
</a>
|
</a>
|
||||||
<%- end -%>
|
<%- end -%>
|
||||||
</div>
|
</li>
|
||||||
<%- end -%>
|
<%- end -%>
|
||||||
|
|
||||||
<div class="pure-u">
|
<li>
|
||||||
<a class="pure-button pure-button-secondary low-profile" dir="auto" href="/feed/playlist/<%= plid %>">
|
<a class="secondary" role="button" dir="auto" href="/feed/playlist/<%= plid %>">
|
||||||
<i class="icon ion-logo-rss"></i> <%= translate(locale, "generic_button_rss") %>
|
<i class="icon ion-logo-rss"></i> <%= translate(locale, "generic_button_rss") %>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</li>
|
||||||
</div>
|
</ul>
|
||||||
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="h-box">
|
<nav>
|
||||||
<div class="pure-u-1-1">
|
<ul>
|
||||||
<% if playlist.is_a? InvidiousPlaylist %>
|
<% if playlist.is_a? InvidiousPlaylist %>
|
||||||
<b>
|
<li>
|
||||||
<% if playlist.author == user.try &.email %>
|
<% if playlist.author == user.try &.email %>
|
||||||
<a href="/feed/playlists"><%= author %></a> |
|
<a href="/feed/playlists"><%= author %></a>
|
||||||
<% else %>
|
<% else %>
|
||||||
<%= author %> |
|
<%= author %>
|
||||||
<% end %>
|
<% end %>
|
||||||
<%= translate_count(locale, "generic_videos_count", playlist.video_count) %> |
|
</li>
|
||||||
<%= translate(locale, "Updated `x` ago", recode_date(playlist.updated, locale)) %> |
|
<% else %>
|
||||||
<% case playlist.as(InvidiousPlaylist).privacy when %>
|
<li>
|
||||||
<% when PlaylistPrivacy::Public %>
|
<% if !author.empty? %>
|
||||||
<i class="icon ion-md-globe"></i> <%= translate(locale, "Public") %>
|
<% if playlist.author_thumbnail %>
|
||||||
<% when PlaylistPrivacy::Unlisted %>
|
<% author_profile_pic = URI.parse(playlist.author_thumbnail).request_target %>
|
||||||
<i class="icon ion-ios-unlock"></i> <%= translate(locale, "Unlisted") %>
|
<div class="channel-profile">
|
||||||
<% when PlaylistPrivacy::Private %>
|
<img src="/ggpht<%= author_profile_pic %>" alt="<%= author %> Profile Picture" class="profile-pic" />
|
||||||
<i class="icon ion-ios-lock"></i> <%= translate(locale, "Private") %>
|
<a href="/channel/<%= playlist.ucid %>"><%= author %></a>
|
||||||
<% end %>
|
</div>
|
||||||
</b>
|
<% else %>
|
||||||
<% else %>
|
<a href="/channel/<%= playlist.ucid %>"><%= author %></a>
|
||||||
<b>
|
<% end %>
|
||||||
<% if !author.empty? %>
|
<% elsif !playlist.subtitle.nil? %>
|
||||||
<a href="/channel/<%= playlist.ucid %>"><%= author %></a> |
|
<% subtitle = playlist.subtitle || "" %>
|
||||||
<% elsif !playlist.subtitle.nil? %>
|
<span><%= HTML.escape(subtitle[0..subtitle.rindex(" • ") || subtitle.size]) %></span>
|
||||||
<% subtitle = playlist.subtitle || "" %>
|
<% end %>
|
||||||
<span><%= HTML.escape(subtitle[0..subtitle.rindex(" • ") || subtitle.size]) %></span> |
|
</li>
|
||||||
<% end %>
|
<li>
|
||||||
<%= translate_count(locale, "generic_videos_count", playlist.video_count) %> |
|
<a href="/channel/<%= playlist.ucid %>/playlists"><%= translate(locale, "Playlists") %></a>
|
||||||
<%= translate(locale, "Updated `x` ago", recode_date(playlist.updated, locale)) %>
|
</li>
|
||||||
</b>
|
<% end %>
|
||||||
<% end %>
|
</ul>
|
||||||
|
</nav>
|
||||||
|
|
||||||
<% if !playlist.is_a? InvidiousPlaylist %>
|
<div class="playlist-meta">
|
||||||
<div class="pure-u-2-3">
|
<ul>
|
||||||
<a rel="noreferrer noopener" href="https://www.youtube.com/playlist?list=<%= playlist.id %>">
|
<% if playlist.is_a? InvidiousPlaylist %>
|
||||||
<%= translate(locale, "View playlist on YouTube") %>
|
<li>
|
||||||
</a>
|
<%= translate_count(locale, "generic_videos_count", playlist.video_count) %></li>
|
||||||
<span> | </span>
|
</li>
|
||||||
|
<li>
|
||||||
|
<time datetime="<%= playlist.updated.try { |t| t.to_s("%Y-%m-%d") } %>" title="<%= playlist.updated.try { |t| t.to_s(translate(locale, "%A %B %-d, %Y")) } %>">
|
||||||
|
<%= translate(locale, "Updated `x` ago", recode_date(playlist.updated, locale)) %>
|
||||||
|
</time>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<% case playlist.as(InvidiousPlaylist).privacy when %>
|
||||||
|
<% when PlaylistPrivacy::Public %>
|
||||||
|
<i class="icon ion-md-globe"></i> <%= translate(locale, "Public") %>
|
||||||
|
<% when PlaylistPrivacy::Unlisted %>
|
||||||
|
<i class="icon ion-ios-unlock"></i> <%= translate(locale, "Unlisted") %>
|
||||||
|
<% when PlaylistPrivacy::Private %>
|
||||||
|
<i class="icon ion-ios-lock"></i> <%= translate(locale, "Private") %>
|
||||||
|
<% end %>
|
||||||
|
</li>
|
||||||
|
<% else %>
|
||||||
|
<li>
|
||||||
|
<%= translate_count(locale, "generic_videos_count", playlist.video_count) %>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<time datetime="<%= playlist.updated.try { |t| t.to_s("%Y-%m-%d") } %>" title="<%= playlist.updated.to_s(translate(locale, "%A %B %-d, %Y")) %>"><%= translate(locale, "Updated `x` ago", recode_date(playlist.updated, locale)) %></time>
|
||||||
|
</li>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
<% if env.get("preferences").as(Preferences).automatic_instance_redirect%>
|
</ul>
|
||||||
<a href="/redirect?referer=<%= env.get?("current_page") %>">
|
|
||||||
<%= translate(locale, "Switch Invidious Instance") %>
|
<nav>
|
||||||
</a>
|
<ul>
|
||||||
<% else %>
|
<% if !playlist.is_a? InvidiousPlaylist %>
|
||||||
<a href="https://redirect.invidious.io/playlist?list=<%= playlist.id %>">
|
<li>
|
||||||
<%= translate(locale, "Switch Invidious Instance") %>
|
<a rel="noreferrer noopener" href="https://www.youtube.com/playlist?list=<%= playlist.id %>">
|
||||||
</a>
|
<%= translate(locale, "View playlist on YouTube") %>
|
||||||
<% end %>
|
</a>
|
||||||
</div>
|
</li>
|
||||||
<% end %>
|
<li>
|
||||||
</div>
|
<% if env.get("preferences").as(Preferences).automatic_instance_redirect%>
|
||||||
|
<a href="/redirect?referer=<%= env.get?("current_page") %>">
|
||||||
|
<%= translate(locale, "Switch Invidious Instance") %>
|
||||||
|
</a>
|
||||||
|
<% else %>
|
||||||
|
<a href="https://redirect.invidious.io/playlist?list=<%= playlist.id %>">
|
||||||
|
<%= translate(locale, "Switch Invidious Instance") %>
|
||||||
|
</a>
|
||||||
|
<% end %>
|
||||||
|
</li>
|
||||||
|
<% end %>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="h-box">
|
<div id="description" class="raw-text"><%= playlist.description_html %></div>
|
||||||
<div id="descriptionWrapper"><%= playlist.description_html %></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="h-box">
|
|
||||||
<hr>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<% if playlist.is_a?(InvidiousPlaylist) && playlist.author == user.try &.email %>
|
<% if playlist.is_a?(InvidiousPlaylist) && playlist.author == user.try &.email %>
|
||||||
<script id="playlist_data" type="application/json">
|
<script id="playlist_data" type="application/json">
|
||||||
@ -121,5 +153,5 @@
|
|||||||
<script src="/js/playlist_widget.js?v=<%= ASSET_COMMIT %>"></script>
|
<script src="/js/playlist_widget.js?v=<%= ASSET_COMMIT %>"></script>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
|
<!-- pagination -->
|
||||||
<%= rendered "components/items_paginated" %>
|
<%= rendered "components/items_paginated" %>
|
||||||
|
@ -5,15 +5,17 @@
|
|||||||
|
|
||||||
<!-- Search redirection and filtering UI -->
|
<!-- Search redirection and filtering UI -->
|
||||||
<%= Invidious::Frontend::SearchFilters.generate(query.filters, query.text, query.page, locale) %>
|
<%= Invidious::Frontend::SearchFilters.generate(query.filters, query.text, query.page, locale) %>
|
||||||
<hr/>
|
|
||||||
|
|
||||||
|
|
||||||
<%- if items.empty? -%>
|
<%- if items.empty? -%>
|
||||||
<div class="h-box no-results-error">
|
<div class="no-results-error">
|
||||||
<div>
|
<div>
|
||||||
<%= translate(locale, "search_message_no_results") %><br/><br/>
|
<%= translate(locale, "search_message_no_results") %>
|
||||||
<%= translate(locale, "search_message_change_filters_or_query") %><br/><br/>
|
</div>
|
||||||
<%= translate(locale, "search_message_use_another_instance", redirect_url) %>
|
<div>
|
||||||
|
<%= translate(locale, "search_message_change_filters_or_query") %>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<%= translate(locale, "search_message_use_another_instance", redirect_url) %>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<%- else -%>
|
<%- else -%>
|
||||||
|
@ -6,15 +6,11 @@
|
|||||||
<link rel="stylesheet" href="/css/empty.css?v=<%= ASSET_COMMIT %>">
|
<link rel="stylesheet" href="/css/empty.css?v=<%= ASSET_COMMIT %>">
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<%= rendered "components/feed_menu" %>
|
<div class="search-homepage">
|
||||||
|
<%= rendered "components/feed_menu" %>
|
||||||
|
|
||||||
<div class="pure-g h-box" id="search-widget">
|
<div id="search-widget">
|
||||||
<div class="pure-u-1" id="logo">
|
<h1>Invidious</h1>
|
||||||
<h1 href="/" class="pure-menu-heading">Invidious</h1>
|
<% autofocus = true %><%= rendered "components/search_box" %>
|
||||||
</div>
|
</div>
|
||||||
<div class="pure-u-1-4"></div>
|
|
||||||
<div class="pure-u-1 pure-u-md-12-24 searchbar">
|
|
||||||
<% autofocus = true %><%= rendered "components/search_box" %>
|
|
||||||
</div>
|
|
||||||
<div class="pure-u-1-4"></div>
|
|
||||||
</div>
|
</div>
|
||||||
|
@ -18,8 +18,11 @@
|
|||||||
<meta name="theme-color" content="#575757">
|
<meta name="theme-color" content="#575757">
|
||||||
<link title="Invidious" type="application/opensearchdescription+xml" rel="search" href="/opensearch.xml">
|
<link title="Invidious" type="application/opensearchdescription+xml" rel="search" href="/opensearch.xml">
|
||||||
<link rel="stylesheet" href="/css/pure-min.css?v=<%= ASSET_COMMIT %>">
|
<link rel="stylesheet" href="/css/pure-min.css?v=<%= ASSET_COMMIT %>">
|
||||||
|
<link rel="stylesheet" href="/css/pure-fix.css?v=<%= ASSET_COMMIT %>">
|
||||||
<link rel="stylesheet" href="/css/grids-responsive-min.css?v=<%= ASSET_COMMIT %>">
|
<link rel="stylesheet" href="/css/grids-responsive-min.css?v=<%= ASSET_COMMIT %>">
|
||||||
<link rel="stylesheet" href="/css/ionicons.min.css?v=<%= ASSET_COMMIT %>">
|
<link rel="stylesheet" href="/css/ionicons.min.css?v=<%= ASSET_COMMIT %>">
|
||||||
|
<link rel="stylesheet" href="/css/theme.css?v=<%= ASSET_COMMIT %>">
|
||||||
|
<link rel="stylesheet" href="/css/animation.css?v=<%= ASSET_COMMIT %>">
|
||||||
<link rel="stylesheet" href="/css/default.css?v=<%= ASSET_COMMIT %>">
|
<link rel="stylesheet" href="/css/default.css?v=<%= ASSET_COMMIT %>">
|
||||||
<link rel="stylesheet" href="/css/carousel.css?v=<%= ASSET_COMMIT %>">
|
<link rel="stylesheet" href="/css/carousel.css?v=<%= ASSET_COMMIT %>">
|
||||||
<script src="/js/_helpers.js?v=<%= ASSET_COMMIT %>"></script>
|
<script src="/js/_helpers.js?v=<%= ASSET_COMMIT %>"></script>
|
||||||
@ -27,135 +30,209 @@
|
|||||||
|
|
||||||
<body class="<%= dark_mode.blank? ? "no" : dark_mode %>-theme">
|
<body class="<%= dark_mode.blank? ? "no" : dark_mode %>-theme">
|
||||||
<span style="display:none" id="dark_mode_pref"><%= dark_mode %></span>
|
<span style="display:none" id="dark_mode_pref"><%= dark_mode %></span>
|
||||||
<div class="pure-g">
|
|
||||||
<div class="pure-u-1 pure-u-xl-20-24" id="contents">
|
|
||||||
<div class="pure-g navbar h-box">
|
|
||||||
<% if navbar_search %>
|
|
||||||
<div class="pure-u-1 pure-u-md-4-24">
|
|
||||||
<a href="/" class="index-link pure-menu-heading">Invidious</a>
|
|
||||||
</div>
|
|
||||||
<div class="pure-u-1 pure-u-md-12-24 searchbar">
|
|
||||||
<% autofocus = false %><%= rendered "components/search_box" %>
|
|
||||||
</div>
|
|
||||||
<% end %>
|
|
||||||
|
|
||||||
<div class="pure-u-1 pure-u-md-8-24 user-field">
|
<header class="container">
|
||||||
<% if env.get? "user" %>
|
<nav class="navbar">
|
||||||
<div class="pure-u-1-4">
|
<% if navbar_search %>
|
||||||
<a id="toggle_theme" href="/toggle_theme?referer=<%= env.get?("current_page") %>" class="pure-menu-heading" title="<%= translate(locale, "toggle_theme") %>">
|
<div id="index-link">
|
||||||
<% if dark_mode == "dark" %>
|
<a href="/">Invidious</a>
|
||||||
<i class="icon ion-ios-sunny"></i>
|
</div>
|
||||||
<% else %>
|
<% autofocus = false %><%= rendered "components/search_box" %>
|
||||||
<i class="icon ion-ios-moon"></i>
|
<% end %>
|
||||||
<% end %>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<div class="pure-u-1-4">
|
|
||||||
<a id="notification_ticker" title="<%= translate(locale, "Subscriptions") %>" href="/feed/subscriptions" class="pure-menu-heading">
|
|
||||||
<% notification_count = env.get("user").as(Invidious::User).notifications.size %>
|
|
||||||
<% if CONFIG.enable_user_notifications && notification_count > 0 %>
|
|
||||||
<span id="notification_count"><%= notification_count %></span> <i class="icon ion-ios-notifications"></i>
|
|
||||||
<% else %>
|
|
||||||
<i class="icon ion-ios-notifications-outline"></i>
|
|
||||||
<% end %>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<div class="pure-u-1-4">
|
|
||||||
<a title="<%= translate(locale, "Preferences") %>" href="/preferences?referer=<%= env.get?("current_page") %>" class="pure-menu-heading">
|
|
||||||
<i class="icon ion-ios-cog"></i>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<% if env.get("preferences").as(Preferences).show_nick %>
|
|
||||||
<div class="pure-u-1-4" style="overflow: hidden; white-space: nowrap;">
|
|
||||||
<span id="user_name"><%= HTML.escape(env.get("user").as(Invidious::User).email) %></span>
|
|
||||||
</div>
|
|
||||||
<% end %>
|
|
||||||
<div class="pure-u-1-4">
|
|
||||||
<form action="/signout?referer=<%= env.get?("current_page") %>" method="post">
|
|
||||||
<input type="hidden" name="csrf_token" value="<%= HTML.escape(env.get?("csrf_token").try &.as(String) || "") %>">
|
|
||||||
<a class="pure-menu-heading" href="#">
|
|
||||||
<input style="all:unset" type="submit" value="<%= translate(locale, "Log out") %>">
|
|
||||||
</a>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
<% else %>
|
|
||||||
<div class="pure-u-1-3">
|
|
||||||
<a id="toggle_theme" href="/toggle_theme?referer=<%= env.get?("current_page") %>" class="pure-menu-heading" title="<%= translate(locale, "toggle_theme") %>">
|
|
||||||
<% if dark_mode == "dark" %>
|
|
||||||
<i class="icon ion-ios-sunny"></i>
|
|
||||||
<% else %>
|
|
||||||
<i class="icon ion-ios-moon"></i>
|
|
||||||
<% end %>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<div class="pure-u-1-3">
|
|
||||||
<a title="<%= translate(locale, "Preferences") %>" href="/preferences?referer=<%= env.get?("current_page") %>" class="pure-menu-heading">
|
|
||||||
<i class="icon ion-ios-cog"></i>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<% if CONFIG.login_enabled %>
|
|
||||||
<div class="pure-u-1-3">
|
|
||||||
<a href="/login?referer=<%= env.get?("current_page") %>" class="pure-menu-heading">
|
|
||||||
<%= translate(locale, "Log in") %>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<% end %>
|
|
||||||
<% end %>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<% if CONFIG.banner %>
|
<ul id="user-nav">
|
||||||
<div class="h-box">
|
<% if env.get? "user" %>
|
||||||
<h3><%= CONFIG.banner %></h3>
|
<li>
|
||||||
</div>
|
<a id="toggle_theme" href="/toggle_theme?referer=<%= env.get?("current_page") %>" title="<%= translate(locale, "toggle_theme") %>">
|
||||||
<% end %>
|
<% if dark_mode == "dark" %>
|
||||||
|
<i class="icon ion-ios-sunny"></i>
|
||||||
|
<% else %>
|
||||||
|
<i class="icon ion-ios-moon"></i>
|
||||||
|
<% end %>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a id="notification_ticker" title="<%= translate(locale, "Subscriptions") %>" href="/feed/subscriptions">
|
||||||
|
<% notification_count = env.get("user").as(Invidious::User).notifications.size %>
|
||||||
|
<% if CONFIG.enable_user_notifications && notification_count > 0 %>
|
||||||
|
<span id="notification_count"><%= notification_count %></span> <i class="icon ion-ios-notifications"></i>
|
||||||
|
<% else %>
|
||||||
|
<i class="icon ion-ios-notifications-outline"></i>
|
||||||
|
<% end %>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a title="<%= translate(locale, "Preferences") %>" href="/preferences?referer=<%= env.get?("current_page") %>">
|
||||||
|
<i class="icon ion-ios-cog"></i>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<% if env.get("preferences").as(Preferences).show_nick %>
|
||||||
|
<li class="nick">
|
||||||
|
<span id="user_name"><%= HTML.escape(env.get("user").as(Invidious::User).email) %></span>
|
||||||
|
</li>
|
||||||
|
<% end %>
|
||||||
|
<li>
|
||||||
|
<form action="/signout?referer=<%= env.get?("current_page") %>" method="post">
|
||||||
|
<input type="hidden" name="csrf_token" value="<%= HTML.escape(env.get?("csrf_token").try &.as(String) || "") %>">
|
||||||
|
<input class="logout" type="submit" value="<%= translate(locale, "Log out") %>">
|
||||||
|
</form>
|
||||||
|
</li>
|
||||||
|
<% else %>
|
||||||
|
<li>
|
||||||
|
<a id="toggle_theme" href="/toggle_theme?referer=<%= env.get?("current_page") %>" title="<%= translate(locale, "toggle_theme") %>">
|
||||||
|
<% if dark_mode == "dark" %>
|
||||||
|
<i class="icon ion-ios-sunny"></i>
|
||||||
|
<% else %>
|
||||||
|
<i class="icon ion-ios-moon"></i>
|
||||||
|
<% end %>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a title="<%= translate(locale, "Preferences") %>" href="/preferences?referer=<%= env.get?("current_page") %>">
|
||||||
|
<i class="icon ion-ios-cog"></i>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<% if CONFIG.login_enabled %>
|
||||||
|
<li>
|
||||||
|
<a href="/login?referer=<%= env.get?("current_page") %>">
|
||||||
|
<%= translate(locale, "Log in") %>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
<%= content %>
|
<% end %>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
</header>
|
||||||
|
|
||||||
<footer>
|
<% if CONFIG.banner %>
|
||||||
<div class="pure-g">
|
<h3 class="container"><%= CONFIG.banner %></h3>
|
||||||
<div class="pure-u-1 pure-u-md-1-3">
|
<% end %>
|
||||||
<span>
|
|
||||||
<i class="icon ion-logo-github"></i>
|
|
||||||
<% if CONFIG.modified_source_code_url %>
|
|
||||||
<a href="https://github.com/iv-org/invidious"><%= translate(locale, "footer_original_source_code") %></a> /
|
|
||||||
<a href="<%= CONFIG.modified_source_code_url %>"><%= translate(locale, "footer_modfied_source_code") %></a>
|
|
||||||
<% else %>
|
|
||||||
<a href="https://github.com/iv-org/invidious"><%= translate(locale, "footer_source_code") %></a>
|
|
||||||
<% end %>
|
|
||||||
</span>
|
|
||||||
<span>
|
|
||||||
<i class="icon ion-ios-paper"></i>
|
|
||||||
<a href="https://github.com/iv-org/documentation"><%= translate(locale, "footer_documentation") %></a>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="pure-u-1 pure-u-md-1-3">
|
<div id="contents" class="container">
|
||||||
<span>
|
<%= content %>
|
||||||
<a href="https://github.com/iv-org/invidious/blob/master/LICENSE"><%= translate(locale, "Released under the AGPLv3 on Github.") %></a>
|
</div>
|
||||||
</span>
|
|
||||||
<span>
|
|
||||||
<i class="icon ion-logo-javascript"></i>
|
|
||||||
<a rel="jslicense" href="/licenses"><%= translate(locale, "View JavaScript license information.") %></a>
|
|
||||||
</span>
|
|
||||||
<span>
|
|
||||||
<i class="icon ion-ios-paper"></i>
|
|
||||||
<a href="/privacy"><%= translate(locale, "View privacy policy.") %></a>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="pure-u-1 pure-u-md-1-3">
|
<footer class="container">
|
||||||
<span>
|
<div>
|
||||||
<i class="icon ion-ios-wallet"></i>
|
<h3>Invidious</h3>
|
||||||
<a href="https://invidious.io/donate/"><%= translate(locale, "footer_donate_page") %></a>
|
<p><%=translate(locale, "invidious_footer_description")%></p>
|
||||||
</span>
|
</div>
|
||||||
<span><%= translate(locale, "Current version: ") %> <%= CURRENT_VERSION %>-<%= CURRENT_COMMIT %> @ <%= CURRENT_BRANCH %></span>
|
<div>
|
||||||
</div>
|
<h3><%= translate(locale, "Navigation")%></h3>
|
||||||
</div>
|
<nav>
|
||||||
</footer>
|
<ul>
|
||||||
|
<li>
|
||||||
|
<a href="/" title="<%= translate(locale, "Home")%>">
|
||||||
|
<%= translate(locale, "Home") %>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="/feed/popular" title="<%= translate(locale, "Popular")%>">
|
||||||
|
<%= translate(locale, "Popular") %>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="/feed/trending" title="<%= translate(locale, "Trending")%>" style="">
|
||||||
|
<%= translate(locale, "Trending") %>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="/search" title="<%= translate(locale, "Search")%>">
|
||||||
|
<%= translate(locale, "Search") %>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="/preferences?referer=<%= env.get?("current_page") %>" title="<%= translate(locale, "Preferences")%>">
|
||||||
|
<%= translate(locale, "Preferences") %>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h3 class="footer-section-header">Invidious</h3>
|
||||||
|
<nav>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<a href="https://invidious.io" title="<%= translate(locale, "Project Homepage")%>" target="_blank" rel="nofollow">
|
||||||
|
<%= translate(locale, "Project homepage") %>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="https://github.com/iv-org/invidious" title="<%= translate(locale, "Source Code")%>" target="_blank" rel="nofollow">
|
||||||
|
<%= translate(locale, "Source code") %>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="https://github.com/iv-org/invidious/issues" title="<%= translate(locale, "Issue tracker")%>" target="_blank" rel="nofollow">
|
||||||
|
<%= translate(locale, "Issue tracker") %>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="https://instances.invidious.io/" title="<%= translate(locale, "Public instances")%>" target="_blank" rel="nofollow">
|
||||||
|
<%= translate(locale, "Public instances") %>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="https://invidious.io/donate" title="<%= translate(locale, "Donate")%>" target="_blank">
|
||||||
|
<%= translate(locale, "Donate") %>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="https://matrix.to/#/#invidious:matrix.org" title="<%= translate(locale, "Matrix")%>" target="_blank" rel="nofollow">
|
||||||
|
<%= translate(locale, "Matrix") %>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="https://social.tchncs.de/@invidious" title="<%= translate(locale, "Matrix")%>" target="_blank" rel="nofollow">
|
||||||
|
<%= translate(locale, "Fediverse") %>
|
||||||
|
</a>
|
||||||
|
|
||||||
</div>
|
</li>
|
||||||
</div>
|
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h3><%= translate(locale, "Support")%></h3>
|
||||||
|
<nav>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<a href="https://github.com/iv-org/invidious/issues/new" title="<%= translate(locale, "Report a bug")%>" target="_blank" rel="nofollow">
|
||||||
|
<%= translate(locale, "Report a bug") %>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="https://docs.invidious.io/faq/" title="<%= translate(locale, "FAQs")%>" target="_blank" rel="nofollow">
|
||||||
|
<%= translate(locale, "FAQs") %>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h3><%= translate(locale, "Legal")%></h3>
|
||||||
|
<nav>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<a href="/licenses" title="<%= translate(locale, "Licenses")%>">
|
||||||
|
<%= translate(locale, "Licences") %>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<!-- TODO -->
|
||||||
|
<li>
|
||||||
|
<a href="/privacy" title="<%= translate(locale, "Privacy")%>">
|
||||||
|
<%= translate(locale, "Privacy") %>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- <%= translate(locale, "Current version: ") %> <%= CURRENT_VERSION %>-<%= CURRENT_COMMIT %> @ <%= CURRENT_BRANCH %> -->
|
||||||
|
</footer>
|
||||||
<script src="/js/handlers.js?v=<%= ASSET_COMMIT %>"></script>
|
<script src="/js/handlers.js?v=<%= ASSET_COMMIT %>"></script>
|
||||||
<script src="/js/themes.js?v=<%= ASSET_COMMIT %>"></script>
|
<script src="/js/themes.js?v=<%= ASSET_COMMIT %>"></script>
|
||||||
<% if env.get? "user" %>
|
<% if env.get? "user" %>
|
||||||
|
@ -2,17 +2,18 @@
|
|||||||
<title><%= translate(locale, "Import and Export Data") %> - Invidious</title>
|
<title><%= translate(locale, "Import and Export Data") %> - Invidious</title>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<div class="h-box">
|
<h1>Import/export</h1>
|
||||||
<form class="pure-form pure-form-aligned" enctype="multipart/form-data" action="/data_control?referer=<%= URI.encode_www_form(referer) %>" method="post">
|
|
||||||
<fieldset>
|
<form enctype="multipart/form-data" action="/data_control?referer=<%= URI.encode_www_form(referer) %>" method="post">
|
||||||
|
<fieldset class="form">
|
||||||
<legend><%= translate(locale, "Import") %></legend>
|
<legend><%= translate(locale, "Import") %></legend>
|
||||||
|
|
||||||
<div class="pure-control-group">
|
<div class="control-group">
|
||||||
<label for="import_invidious"><%= translate(locale, "Import Invidious data") %></label>
|
<label for="import_invidious"><%= translate(locale, "Import Invidious data") %></label>
|
||||||
<input type="file" id="import_invidious" name="import_invidious">
|
<input type="file" id="import_invidious" name="import_invidious">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="pure-control-group">
|
<div class="control-group">
|
||||||
<label for="import_youtube">
|
<label for="import_youtube">
|
||||||
<a rel="noopener" target="_blank" href="https://github.com/iv-org/documentation/blob/master/docs/export-youtube-subscriptions.md">
|
<a rel="noopener" target="_blank" href="https://github.com/iv-org/documentation/blob/master/docs/export-youtube-subscriptions.md">
|
||||||
<%= translate(locale, "Import YouTube subscriptions") %>
|
<%= translate(locale, "Import YouTube subscriptions") %>
|
||||||
@ -21,46 +22,49 @@
|
|||||||
<input type="file" id="import_youtube" name="import_youtube">
|
<input type="file" id="import_youtube" name="import_youtube">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="pure-control-group">
|
<div class="control-group">
|
||||||
<label for="import_youtube_pl"><%= translate(locale, "Import YouTube playlist (.csv)") %></label>
|
<label for="import_youtube_pl"><%= translate(locale, "Import YouTube playlist (.csv)") %></label>
|
||||||
<input type="file" id="import_youtube_pl" name="import_youtube_pl">
|
<input type="file" id="import_youtube_pl" name="import_youtube_pl">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="pure-control-group">
|
<div class="control-group">
|
||||||
<label for="import_youtube_wh"><%= translate(locale, "Import YouTube watch history (.json)") %></label>
|
<label for="import_youtube_wh"><%= translate(locale, "Import YouTube watch history (.json)") %></label>
|
||||||
<input type="file" id="import_youtube_wh" name="import_youtube_wh">
|
<input type="file" id="import_youtube_wh" name="import_youtube_wh">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="pure-control-group">
|
<div class="control-group">
|
||||||
<label for="import_freetube"><%= translate(locale, "Import FreeTube subscriptions (.db)") %></label>
|
<label for="import_freetube"><%= translate(locale, "Import FreeTube subscriptions (.db)") %></label>
|
||||||
<input type="file" id="import_freetube" name="import_freetube">
|
<input type="file" id="import_freetube" name="import_freetube">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="pure-control-group">
|
<div class="control-group">
|
||||||
<label for="import_newpipe_subscriptions"><%= translate(locale, "Import NewPipe subscriptions (.json)") %></label>
|
<label for="import_newpipe_subscriptions"><%= translate(locale, "Import NewPipe subscriptions (.json)") %></label>
|
||||||
<input type="file" id="import_newpipe_subscriptions" name="import_newpipe_subscriptions">
|
<input type="file" id="import_newpipe_subscriptions" name="import_newpipe_subscriptions">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="pure-control-group">
|
<div class="control-group">
|
||||||
<label for="import_newpipe"><%= translate(locale, "Import NewPipe data (.zip)") %></label>
|
<label for="import_newpipe"><%= translate(locale, "Import NewPipe data (.zip)") %></label>
|
||||||
<input type="file" id="import_newpipe" name="import_newpipe">
|
<input type="file" id="import_newpipe" name="import_newpipe">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="pure-controls">
|
<div class="action-controls">
|
||||||
<button type="submit" class="pure-button pure-button-primary"><%= translate(locale, "Import") %></button>
|
<button type="submit" class="primary"><%= translate(locale, "Import") %></button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
</fieldset>
|
||||||
|
<fieldset class="form">
|
||||||
|
|
||||||
<legend><%= translate(locale, "Export") %></legend>
|
<legend><%= translate(locale, "Export") %></legend>
|
||||||
|
|
||||||
<div class="pure-control-group">
|
<div class="control-group">
|
||||||
<a href="/subscription_manager?action_takeout=1"><%= translate(locale, "Export subscriptions as OPML") %></a>
|
<a href="/subscription_manager?action_takeout=1"><%= translate(locale, "Export subscriptions as OPML") %></a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="pure-control-group">
|
<div class="control-group">
|
||||||
<a href="/subscription_manager?action_takeout=1&format=newpipe"><%= translate(locale, "Export subscriptions as OPML (for NewPipe & FreeTube)") %></a>
|
<a href="/subscription_manager?action_takeout=1&format=newpipe"><%= translate(locale, "Export subscriptions as OPML (for NewPipe & FreeTube)") %></a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="pure-control-group">
|
<div class="control-group">
|
||||||
<a href="/subscription_manager?action_takeout=1&format=json"><%= translate(locale, "Export data as JSON") %></a>
|
<a href="/subscription_manager?action_takeout=1&format=json"><%= translate(locale, "Export data as JSON") %></a>
|
||||||
</div>
|
</div>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
@ -2,76 +2,84 @@
|
|||||||
<title><%= translate(locale, "Log in") %> - Invidious</title>
|
<title><%= translate(locale, "Log in") %> - Invidious</title>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<div class="pure-g">
|
|
||||||
<div class="pure-u-1 pure-u-lg-1-5"></div>
|
|
||||||
<div class="pure-u-1 pure-u-lg-3-5">
|
|
||||||
<div class="h-box">
|
|
||||||
<% case account_type when %>
|
<% case account_type when %>
|
||||||
<% else # "invidious" %>
|
<% else # "invidious" %>
|
||||||
<form class="pure-form pure-form-stacked" action="/login?referer=<%= URI.encode_www_form(referer) %>&type=invidious" method="post">
|
<form action="/login?referer=<%= URI.encode_www_form(referer) %>&type=invidious" method="post">
|
||||||
<fieldset>
|
<fieldset class="form">
|
||||||
|
<legend>Log in</legend>
|
||||||
<% if email %>
|
<% if email %>
|
||||||
|
<div class="control-group">
|
||||||
<input name="email" type="hidden" value="<%= HTML.escape(email) %>">
|
<input name="email" type="hidden" value="<%= HTML.escape(email) %>">
|
||||||
|
</div>
|
||||||
<% else %>
|
<% else %>
|
||||||
<label for="email"><%= translate(locale, "User ID") %> :</label>
|
<div class="control-group">
|
||||||
<input required class="pure-input-1" name="email" type="text" placeholder="<%= translate(locale, "User ID") %>">
|
<label for="email"><%= translate(locale, "User ID") %> :</label>
|
||||||
|
<input required class="pure-input-1" name="email" type="text" placeholder="<%= translate(locale, "User ID") %>">
|
||||||
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<% if password %>
|
<% if password %>
|
||||||
<input name="password" type="hidden" value="<%= HTML.escape(password) %>">
|
<div class="control-group">
|
||||||
|
<input name="password" type="hidden" value="<%= HTML.escape(password) %>">
|
||||||
|
</div>
|
||||||
<% else %>
|
<% else %>
|
||||||
<label for="password"><%= translate(locale, "Password") %> :</label>
|
<div class="control-group">
|
||||||
<input required class="pure-input-1" name="password" type="password" placeholder="<%= translate(locale, "Password") %>">
|
<label for="password"><%= translate(locale, "Password") %> :</label>
|
||||||
|
<input required class="pure-input-1" name="password" type="password" placeholder="<%= translate(locale, "Password") %>">
|
||||||
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
|
</fieldset>
|
||||||
|
|
||||||
<% if captcha %>
|
<% if captcha %>
|
||||||
<% case captcha_type when %>
|
<% case captcha_type when %>
|
||||||
<% when "image" %>
|
<% when "image" %>
|
||||||
<% captcha = captcha.not_nil! %>
|
<% captcha = captcha.not_nil! %>
|
||||||
<img style="width:50%" src='<%= captcha[:question] %>'/>
|
<img class="captcha" src='<%= captcha[:question] %>'/>
|
||||||
<% captcha[:tokens].each_with_index do |token, i| %>
|
<% captcha[:tokens].each_with_index do |token, i| %>
|
||||||
<input type="hidden" name="token[<%= i %>]" value="<%= HTML.escape(token) %>">
|
<input type="hidden" name="token[<%= i %>]" value="<%= HTML.escape(token) %>">
|
||||||
<% end %>
|
<% end %>
|
||||||
<input type="hidden" name="captcha_type" value="image">
|
<input type="hidden" name="captcha_type" value="image">
|
||||||
|
<div class="control-group">
|
||||||
<label for="answer"><%= translate(locale, "Time (h:mm:ss):") %></label>
|
<label for="answer"><%= translate(locale, "Time (h:mm:ss):") %></label>
|
||||||
<input type="text" name="answer" type="text" placeholder="h:mm:ss">
|
<input type="text" name="answer" type="text" placeholder="h:mm:ss">
|
||||||
|
</div>
|
||||||
<% else # "text" %>
|
<% else # "text" %>
|
||||||
<% captcha = captcha.not_nil! %>
|
<% captcha = captcha.not_nil! %>
|
||||||
<% captcha[:tokens].each_with_index do |token, i| %>
|
<% captcha[:tokens].each_with_index do |token, i| %>
|
||||||
<input type="hidden" name="token[<%= i %>]" value="<%= HTML.escape(token) %>">
|
<input type="hidden" name="token[<%= i %>]" value="<%= HTML.escape(token) %>">
|
||||||
<% end %>
|
<% end %>
|
||||||
<input type="hidden" name="captcha_type" value="text">
|
<input type="hidden" name="captcha_type" value="text">
|
||||||
|
<div class="control-group">
|
||||||
<label for="answer"><%= captcha[:question] %></label>
|
<label for="answer"><%= captcha[:question] %></label>
|
||||||
<input type="text" name="answer" type="text" placeholder="<%= translate(locale, "Answer") %>">
|
<input type="text" name="answer" type="text" placeholder="<%= translate(locale, "Answer") %>">
|
||||||
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<button type="submit" name="action" value="signin" class="pure-button pure-button-primary">
|
<button type="submit" name="action" value="signin" class="primary">
|
||||||
<%= translate(locale, "Register") %>
|
<%= translate(locale, "Register") %>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<% case captcha_type when %>
|
<% case captcha_type when %>
|
||||||
<% when "image" %>
|
<% when "image" %>
|
||||||
<label>
|
<label>
|
||||||
<button type="submit" name="change_type" class="pure-button pure-button-primary" value="text">
|
<button type="submit" name="change_type" class="primary" value="text">
|
||||||
<%= translate(locale, "Text CAPTCHA") %>
|
<%= translate(locale, "Text CAPTCHA") %>
|
||||||
</button>
|
</button>
|
||||||
</label>
|
</label>
|
||||||
<% else # "text" %>
|
<% else # "text" %>
|
||||||
<label>
|
<label>
|
||||||
<button type="submit" name="change_type" class="pure-button pure-button-primary" value="image">
|
<button type="submit" name="change_type" class="primary" value="image">
|
||||||
<%= translate(locale, "Image CAPTCHA") %>
|
<%= translate(locale, "Image CAPTCHA") %>
|
||||||
</button>
|
</button>
|
||||||
</label>
|
</label>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% else %>
|
<% else %>
|
||||||
|
<div class="action-controls">
|
||||||
<button type="submit" name="action" value="signin" class="pure-button pure-button-primary">
|
<button type="submit" name="action" value="signin" class="pure-button pure-button-primary">
|
||||||
<%= translate(locale, "Sign In") %>/<%= translate(locale, "Register") %>
|
<%= translate(locale, "Sign In") %>/<%= translate(locale, "Register") %>
|
||||||
</button>
|
</button>
|
||||||
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
</fieldset>
|
|
||||||
</form>
|
</form>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="pure-u-1 pure-u-lg-1-5"></div>
|
|
||||||
</div>
|
|
||||||
|
@ -2,362 +2,382 @@
|
|||||||
<title><%= translate(locale, "Preferences") %> - Invidious</title>
|
<title><%= translate(locale, "Preferences") %> - Invidious</title>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<div class="h-box">
|
<h1>Preferences</h1>
|
||||||
<form class="pure-form pure-form-aligned" action="/preferences?referer=<%= URI.encode_www_form(referer) %>" method="post">
|
|
||||||
<fieldset>
|
|
||||||
<legend><%= translate(locale, "preferences_category_player") %></legend>
|
|
||||||
|
|
||||||
<div class="pure-control-group">
|
<form action="/preferences?referer=<%= URI.encode_www_form(referer) %>" method="post">
|
||||||
<label for="video_loop"><%= translate(locale, "preferences_video_loop_label") %></label>
|
<fieldset class="preferences">
|
||||||
<input name="video_loop" id="video_loop" type="checkbox" <% if preferences.video_loop %>checked<% end %>>
|
<legend><%= translate(locale, "preferences_category_player") %></legend>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="pure-control-group">
|
<div class="control-group">
|
||||||
<label for="preload"><%= translate(locale, "preferences_preload_label") %></label>
|
<label for="video_loop"><%= translate(locale, "preferences_video_loop_label") %></label>
|
||||||
<input name="preload" id="preload" type="checkbox" <% if preferences.preload %>checked<% end %>>
|
<input name="video_loop" id="video_loop" type="checkbox" <% if preferences.video_loop %>checked<% end %>>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="pure-control-group">
|
<div class="control-group">
|
||||||
<label for="autoplay"><%= translate(locale, "preferences_autoplay_label") %></label>
|
<label for="preload"><%= translate(locale, "preferences_preload_label") %></label>
|
||||||
<input name="autoplay" id="autoplay" type="checkbox" <% if preferences.autoplay %>checked<% end %>>
|
<input name="preload" id="preload" type="checkbox" <% if preferences.preload %>checked<% end %>>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="pure-control-group">
|
<div class="control-group">
|
||||||
<label for="continue"><%= translate(locale, "preferences_continue_label") %></label>
|
<label for="autoplay"><%= translate(locale, "preferences_autoplay_label") %></label>
|
||||||
<input name="continue" id="continue" type="checkbox" <% if preferences.continue %>checked<% end %>>
|
<input name="autoplay" id="autoplay" type="checkbox" <% if preferences.autoplay %>checked<% end %>>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="pure-control-group">
|
<div class="control-group">
|
||||||
<label for="continue_autoplay"><%= translate(locale, "preferences_continue_autoplay_label") %></label>
|
<label for="continue"><%= translate(locale, "preferences_continue_label") %></label>
|
||||||
<input name="continue_autoplay" id="continue_autoplay" type="checkbox" <% if preferences.continue_autoplay %>checked<% end %>>
|
<input name="continue" id="continue" type="checkbox" <% if preferences.continue %>checked<% end %>>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="pure-control-group">
|
<div class="control-group">
|
||||||
<label for="local"><%= translate(locale, "preferences_local_label") %></label>
|
<label for="continue_autoplay"><%= translate(locale, "preferences_continue_autoplay_label") %></label>
|
||||||
<input name="local" id="local" type="checkbox" <% if preferences.local && !CONFIG.disabled?("local") %>checked<% end %> <% if CONFIG.disabled?("local") %>disabled<% end %>>
|
<input name="continue_autoplay" id="continue_autoplay" type="checkbox" <% if preferences.continue_autoplay %>checked<% end %>>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="pure-control-group">
|
<div class="control-group">
|
||||||
<label for="listen"><%= translate(locale, "preferences_listen_label") %></label>
|
<label for="local"><%= translate(locale, "preferences_local_label") %></label>
|
||||||
<input name="listen" id="listen" type="checkbox" <% if preferences.listen %>checked<% end %>>
|
<input name="local" id="local" type="checkbox" <% if preferences.local && !CONFIG.disabled?("local") %>checked<% end %> <% if CONFIG.disabled?("local") %>disabled<% end %>>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="pure-control-group">
|
<div class="control-group">
|
||||||
<label for="speed"><%= translate(locale, "preferences_speed_label") %></label>
|
<label for="listen"><%= translate(locale, "preferences_listen_label") %></label>
|
||||||
<select name="speed" id="speed">
|
<input name="listen" id="listen" type="checkbox" <% if preferences.listen %>checked<% end %>>
|
||||||
<% {2.0, 1.75, 1.5, 1.25, 1.0, 0.75, 0.5, 0.25}.each do |option| %>
|
</div>
|
||||||
<option <% if preferences.speed == option %> selected <% end %>><%= option %></option>
|
|
||||||
<% end %>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="pure-control-group">
|
<div class="control-group">
|
||||||
<label for="quality"><%= translate(locale, "preferences_quality_label") %></label>
|
<label for="speed"><%= translate(locale, "preferences_speed_label") %></label>
|
||||||
<select name="quality" id="quality">
|
<select name="speed" id="speed">
|
||||||
<% {"dash", "hd720", "medium", "small"}.each do |option| %>
|
<% {2.0, 1.75, 1.5, 1.25, 1.0, 0.75, 0.5, 0.25}.each do |option| %>
|
||||||
<% if !(option == "dash" && CONFIG.disabled?("dash")) %>
|
<option <% if preferences.speed == option %> selected <% end %>><%= option %></option>
|
||||||
<option value="<%= option %>" <% if preferences.quality == option %> selected <% end %>><%= translate(locale, "preferences_quality_option_" + option) %></option>
|
<% end %>
|
||||||
<% end %>
|
</select>
|
||||||
<% end %>
|
</div>
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<% if !CONFIG.disabled?("dash") %>
|
<div class="control-group">
|
||||||
<div class="pure-control-group">
|
<label for="quality"><%= translate(locale, "preferences_quality_label") %></label>
|
||||||
<label for="quality_dash"><%= translate(locale, "preferences_quality_dash_label") %></label>
|
<select name="quality" id="quality">
|
||||||
<select name="quality_dash" id="quality_dash">
|
<% {"dash", "hd720", "medium", "small"}.each do |option| %>
|
||||||
<% {"auto", "best", "4320p", "2160p", "1440p", "1080p", "720p", "480p", "360p", "240p", "144p", "worst"}.each do |option| %>
|
<% if !(option == "dash" && CONFIG.disabled?("dash")) %>
|
||||||
<option value="<%= option %>" <% if preferences.quality_dash == option %> selected <% end %>><%= translate(locale, "preferences_quality_dash_option_" + option) %></option>
|
<option value="<%= option %>" <% if preferences.quality == option %> selected <% end %>><%= translate(locale, "preferences_quality_option_" + option) %></option>
|
||||||
<% end %>
|
<% end %>
|
||||||
</select>
|
<% end %>
|
||||||
</div>
|
</select>
|
||||||
<% end %>
|
</div>
|
||||||
|
|
||||||
<div class="pure-control-group">
|
<% if !CONFIG.disabled?("dash") %>
|
||||||
<label for="volume"><%= translate(locale, "preferences_volume_label") %></label>
|
<div class="control-group">
|
||||||
<input name="volume" id="volume" data-onrange="update_volume_value" type="range" min="0" max="100" step="5" value="<%= preferences.volume %>">
|
<label for="quality_dash"><%= translate(locale, "preferences_quality_dash_label") %></label>
|
||||||
<span class="pure-form-message-inline" id="volume-value"><%= preferences.volume %></span>
|
<select name="quality_dash" id="quality_dash">
|
||||||
</div>
|
<% {"auto", "best", "4320p", "2160p", "1440p", "1080p", "720p", "480p", "360p", "240p", "144p", "worst"}.each do |option| %>
|
||||||
|
<option value="<%= option %>" <% if preferences.quality_dash == option %> selected <% end %>><%= translate(locale, "preferences_quality_dash_option_" + option) %></option>
|
||||||
|
<% end %>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
<div class="pure-control-group">
|
<div class="control-group">
|
||||||
<label for="comments[0]"><%= translate(locale, "preferences_comments_label") %></label>
|
<label for="volume"><%= translate(locale, "preferences_volume_label") %></label>
|
||||||
<% preferences.comments.each_with_index do |comments, index| %>
|
<div class="control-message">
|
||||||
<select name="comments[<%= index %>]" id="comments[<%= index %>]">
|
<input name="volume" id="volume" data-onrange="update_volume_value" type="range" min="0" max="100" step="5" value="<%= preferences.volume %>">
|
||||||
<% {"", "youtube", "reddit"}.each do |option| %>
|
<span id="volume-value"><%= preferences.volume %></span>
|
||||||
<option value="<%= option %>" <% if preferences.comments[index] == option %> selected <% end %>><%= translate(locale, option.blank? ? "none" : option) %></option>
|
</div>
|
||||||
<% end %>
|
</div>
|
||||||
</select>
|
|
||||||
<% end %>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="pure-control-group">
|
<div class="control-group">
|
||||||
<label for="captions[0]"><%= translate(locale, "preferences_captions_label") %></label>
|
<label for="comments[0]"><%= translate(locale, "preferences_comments_label") %></label>
|
||||||
<% preferences.captions.each_with_index do |caption, index| %>
|
<div>
|
||||||
<select class="pure-u-1-6" name="captions[<%= index %>]" id="captions[<%= index %>]">
|
<% preferences.comments.each_with_index do |comments, index| %>
|
||||||
<% Invidious::Videos::Captions::LANGUAGES.each do |option| %>
|
<select name="comments[<%= index %>]" id="comments[<%= index %>]">
|
||||||
<option value="<%= option %>" <% if preferences.captions[index] == option %> selected <% end %>><%= translate(locale, option.blank? ? "none" : option) %></option>
|
<% {"", "youtube", "reddit"}.each do |option| %>
|
||||||
<% end %>
|
<option value="<%= option %>" <% if preferences.comments[index] == option %> selected <% end %>><%= translate(locale, option.blank? ? "none" : option) %></option>
|
||||||
</select>
|
<% end %>
|
||||||
<% end %>
|
</select>
|
||||||
</div>
|
<% end %>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="pure-control-group">
|
<div class="control-group">
|
||||||
<label for="related_videos"><%= translate(locale, "preferences_related_videos_label") %></label>
|
<label for="captions[0]"><%= translate(locale, "preferences_captions_label") %></label>
|
||||||
<input name="related_videos" id="related_videos" type="checkbox" <% if preferences.related_videos %>checked<% end %>>
|
<div>
|
||||||
</div>
|
<% preferences.captions.each_with_index do |caption, index| %>
|
||||||
|
<select class="pure-u-1-6" name="captions[<%= index %>]" id="captions[<%= index %>]">
|
||||||
|
<% Invidious::Videos::Captions::LANGUAGES.each do |option| %>
|
||||||
|
<option value="<%= option %>" <% if preferences.captions[index] == option %> selected <% end %>><%= translate(locale, option.blank? ? "none" : option) %></option>
|
||||||
|
<% end %>
|
||||||
|
</select>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="pure-control-group">
|
<div class="control-group">
|
||||||
<label for="annotations"><%= translate(locale, "preferences_annotations_label") %></label>
|
<label for="related_videos"><%= translate(locale, "preferences_related_videos_label") %></label>
|
||||||
<input name="annotations" id="annotations" type="checkbox" <% if preferences.annotations %>checked<% end %>>
|
<input name="related_videos" id="related_videos" type="checkbox" <% if preferences.related_videos %>checked<% end %>>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="pure-control-group">
|
<div class="control-group">
|
||||||
<label for="extend_desc"><%= translate(locale, "preferences_extend_desc_label") %></label>
|
<label for="annotations"><%= translate(locale, "preferences_annotations_label") %></label>
|
||||||
<input name="extend_desc" id="extend_desc" type="checkbox" <% if preferences.extend_desc %>checked<% end %>>
|
<input name="annotations" id="annotations" type="checkbox" <% if preferences.annotations %>checked<% end %>>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="pure-control-group">
|
<div class="control-group">
|
||||||
<label for="vr_mode"><%= translate(locale, "preferences_vr_mode_label") %></label>
|
<label for="extend_desc"><%= translate(locale, "preferences_extend_desc_label") %></label>
|
||||||
<input name="vr_mode" id="vr_mode" type="checkbox" <% if preferences.vr_mode %>checked<% end %>>
|
<input name="extend_desc" id="extend_desc" type="checkbox" <% if preferences.extend_desc %>checked<% end %>>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="pure-control-group">
|
<div class="control-group">
|
||||||
<label for="save_player_pos"><%= translate(locale, "preferences_save_player_pos_label") %></label>
|
<label for="vr_mode"><%= translate(locale, "preferences_vr_mode_label") %></label>
|
||||||
<input name="save_player_pos" id="save_player_pos" type="checkbox" <% if preferences.save_player_pos %>checked<% end %>>
|
<input name="vr_mode" id="vr_mode" type="checkbox" <% if preferences.vr_mode %>checked<% end %>>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<legend><%= translate(locale, "preferences_category_visual") %></legend>
|
<div class="control-group">
|
||||||
|
<label for="save_player_pos"><%= translate(locale, "preferences_save_player_pos_label") %></label>
|
||||||
|
<input name="save_player_pos" id="save_player_pos" type="checkbox" <% if preferences.save_player_pos %>checked<% end %>>
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
<fieldset class="preferences">
|
||||||
|
<legend><%= translate(locale, "preferences_category_visual") %></legend>
|
||||||
|
|
||||||
<div class="pure-control-group">
|
<div class="control-group">
|
||||||
<label for="locale"><%= translate(locale, "preferences_locale_label") %></label>
|
<label for="locale"><%= translate(locale, "preferences_locale_label") %></label>
|
||||||
<select name="locale" id="locale">
|
<select name="locale" id="locale">
|
||||||
<% LOCALES_LIST.each do |iso_name, full_name| %>
|
<% LOCALES_LIST.each do |iso_name, full_name| %>
|
||||||
<option value="<%= iso_name %>" <% if preferences.locale == iso_name %> selected <% end %>><%= HTML.escape(full_name) %></option>
|
<option value="<%= iso_name %>" <% if preferences.locale == iso_name %> selected <% end %>><%= HTML.escape(full_name) %></option>
|
||||||
<% end %>
|
<% end %>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="pure-control-group">
|
<div class="control-group">
|
||||||
<label for="region"><%= translate(locale, "preferences_region_label") %></label>
|
<label for="region"><%= translate(locale, "preferences_region_label") %></label>
|
||||||
<select name="region" id="region">
|
<select name="region" id="region">
|
||||||
<% CONTENT_REGIONS.each do |option| %>
|
<% CONTENT_REGIONS.each do |option| %>
|
||||||
<option value="<%= option %>" <% if preferences.region == option %> selected <% end %>><%= option %></option>
|
<option value="<%= option %>" <% if preferences.region == option %> selected <% end %>><%= option %></option>
|
||||||
<% end %>
|
<% end %>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="pure-control-group">
|
<div class="control-group">
|
||||||
<label for="player_style"><%= translate(locale, "preferences_player_style_label") %></label>
|
<label for="player_style"><%= translate(locale, "preferences_player_style_label") %></label>
|
||||||
<select name="player_style" id="player_style">
|
<select name="player_style" id="player_style">
|
||||||
<% {"invidious", "youtube"}.each do |option| %>
|
<% {"invidious", "youtube"}.each do |option| %>
|
||||||
<option value="<%= option %>" <% if preferences.player_style == option %> selected <% end %>><%= translate(locale, option) %></option>
|
<option value="<%= option %>" <% if preferences.player_style == option %> selected <% end %>><%= translate(locale, option) %></option>
|
||||||
<% end %>
|
<% end %>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="pure-control-group">
|
<div class="control-group">
|
||||||
<label for="dark_mode"><%= translate(locale, "preferences_dark_mode_label") %></label>
|
<label for="dark_mode"><%= translate(locale, "preferences_dark_mode_label") %></label>
|
||||||
<select name="dark_mode" id="dark_mode">
|
<select name="dark_mode" id="dark_mode">
|
||||||
<% {"", "light", "dark"}.each do |option| %>
|
<% {"", "light", "dark"}.each do |option| %>
|
||||||
<option value="<%= option %>" <% if preferences.dark_mode == option %> selected <% end %>><%= translate(locale, option.blank? ? "auto" : option) %></option>
|
<option value="<%= option %>" <% if preferences.dark_mode == option %> selected <% end %>><%= translate(locale, option.blank? ? "auto" : option) %></option>
|
||||||
<% end %>
|
<% end %>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="pure-control-group">
|
<div class="control-group">
|
||||||
<label for="thin_mode"><%= translate(locale, "preferences_thin_mode_label") %></label>
|
<label for="thin_mode"><%= translate(locale, "preferences_thin_mode_label") %></label>
|
||||||
<input name="thin_mode" id="thin_mode" type="checkbox" <% if preferences.thin_mode %>checked<% end %>>
|
<input name="thin_mode" id="thin_mode" type="checkbox" <% if preferences.thin_mode %>checked<% end %>>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<% if env.get?("user") %>
|
<% if env.get?("user") %>
|
||||||
<% feed_options = {"", "Popular", "Trending", "Subscriptions", "Playlists"} %>
|
<% feed_options = {"", "Popular", "Trending", "Subscriptions", "Playlists"} %>
|
||||||
<% else %>
|
<% else %>
|
||||||
<% feed_options = {"", "Popular", "Trending"} %>
|
<% feed_options = {"", "Popular", "Trending"} %>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<div class="pure-control-group">
|
<div class="control-group">
|
||||||
<label for="default_home"><%= translate(locale, "preferences_default_home_label") %></label>
|
<label for="default_home"><%= translate(locale, "preferences_default_home_label") %></label>
|
||||||
<select name="default_home" id="default_home">
|
<select name="default_home" id="default_home">
|
||||||
<% feed_options.each do |option| %>
|
<% feed_options.each do |option| %>
|
||||||
<option value="<%= option %>" <% if preferences.default_home == option %> selected <% end %>><%= translate(locale, option.blank? ? "Search" : option) %></option>
|
<option value="<%= option %>" <% if preferences.default_home == option %> selected <% end %>><%= translate(locale, option.blank? ? "Search" : option) %></option>
|
||||||
<% end %>
|
<% end %>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="pure-control-group">
|
<div class="control-group">
|
||||||
<label for="feed_menu"><%= translate(locale, "preferences_feed_menu_label") %></label>
|
<label for="feed_menu"><%= translate(locale, "preferences_feed_menu_label") %></label>
|
||||||
<% (feed_options.size - 1).times do |index| %>
|
<div>
|
||||||
<select name="feed_menu[<%= index %>]" id="feed_menu[<%= index %>]">
|
<% (feed_options.size - 1).times do |index| %>
|
||||||
<% feed_options.each do |option| %>
|
<select name="feed_menu[<%= index %>]" id="feed_menu[<%= index %>]">
|
||||||
<option value="<%= option %>" <% if preferences.feed_menu[index]? == option %> selected <% end %>><%= translate(locale, option.blank? ? "Search" : option) %></option>
|
<% feed_options.each do |option| %>
|
||||||
<% end %>
|
<option value="<%= option %>" <% if preferences.feed_menu[index]? == option %> selected <% end %>><%= translate(locale, option.blank? ? "Search" : option) %></option>
|
||||||
</select>
|
<% end %>
|
||||||
<% end %>
|
</select>
|
||||||
</div>
|
<% end %>
|
||||||
<% if env.get? "user" %>
|
</div>
|
||||||
<div class="pure-control-group">
|
</div>
|
||||||
<label for="show_nick"><%= translate(locale, "preferences_show_nick_label") %></label>
|
<% if env.get? "user" %>
|
||||||
<input name="show_nick" id="show_nick" type="checkbox" <% if preferences.show_nick %>checked<% end %>>
|
<div class="control-group">
|
||||||
</div>
|
<label for="show_nick"><%= translate(locale, "preferences_show_nick_label") %></label>
|
||||||
<% end %>
|
<input name="show_nick" id="show_nick" type="checkbox" <% if preferences.show_nick %>checked<% end %>>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
<legend><%= translate(locale, "preferences_category_misc") %></legend>
|
</fieldset>
|
||||||
|
<fieldset class="preferences">
|
||||||
|
|
||||||
<div class="pure-control-group">
|
<legend><%= translate(locale, "preferences_category_misc") %></legend>
|
||||||
<label for="automatic_instance_redirect"><%= translate(locale, "preferences_automatic_instance_redirect_label") %></label>
|
|
||||||
<input name="automatic_instance_redirect" id="automatic_instance_redirect" type="checkbox" <% if preferences.automatic_instance_redirect %>checked<% end %>>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<% if env.get? "user" %>
|
<div class="control-group">
|
||||||
<legend><%= translate(locale, "preferences_category_subscription") %></legend>
|
<label for="automatic_instance_redirect"><%= translate(locale, "preferences_automatic_instance_redirect_label") %></label>
|
||||||
|
<input name="automatic_instance_redirect" id="automatic_instance_redirect" type="checkbox" <% if preferences.automatic_instance_redirect %>checked<% end %>>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="pure-control-group">
|
</fieldset>
|
||||||
<label for="watch_history"><%= translate(locale, "preferences_watch_history_label") %></label>
|
|
||||||
<input name="watch_history" id="watch_history" type="checkbox" <% if preferences.watch_history %>checked<% end %>>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="pure-control-group">
|
<% if env.get? "user" %>
|
||||||
<label for="annotations_subscribed"><%= translate(locale, "preferences_annotations_subscribed_label") %></label>
|
<fieldset class="preferences">
|
||||||
<input name="annotations_subscribed" id="annotations_subscribed" type="checkbox" <% if preferences.annotations_subscribed %>checked<% end %>>
|
<legend><%= translate(locale, "preferences_category_subscription") %></legend>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="pure-control-group">
|
<div class="control-group">
|
||||||
<label for="max_results"><%= translate(locale, "preferences_max_results_label") %></label>
|
<label for="watch_history"><%= translate(locale, "preferences_watch_history_label") %></label>
|
||||||
<input name="max_results" id="max_results" type="number" value="<%= preferences.max_results %>">
|
<input name="watch_history" id="watch_history" type="checkbox" <% if preferences.watch_history %>checked<% end %>>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="pure-control-group">
|
<div class="control-group">
|
||||||
<label for="sort"><%= translate(locale, "preferences_sort_label") %></label>
|
<label for="annotations_subscribed"><%= translate(locale, "preferences_annotations_subscribed_label") %></label>
|
||||||
<select name="sort" id="sort">
|
<input name="annotations_subscribed" id="annotations_subscribed" type="checkbox" <% if preferences.annotations_subscribed %>checked<% end %>>
|
||||||
<% {"published", "published - reverse", "alphabetically", "alphabetically - reverse", "channel name", "channel name - reverse"}.each do |option| %>
|
</div>
|
||||||
<option value="<%= option %>" <% if preferences.sort == option %> selected <% end %>><%= translate(locale, option) %></option>
|
|
||||||
<% end %>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="pure-control-group">
|
<div class="control-group">
|
||||||
<% if preferences.unseen_only %>
|
<label for="max_results"><%= translate(locale, "preferences_max_results_label") %></label>
|
||||||
<label for="latest_only"><%= translate(locale, "Only show latest unwatched video from channel: ") %></label>
|
<input name="max_results" id="max_results" type="number" value="<%= preferences.max_results %>">
|
||||||
<% else %>
|
</div>
|
||||||
<label for="latest_only"><%= translate(locale, "Only show latest video from channel: ") %></label>
|
|
||||||
<% end %>
|
|
||||||
<input name="latest_only" id="latest_only" type="checkbox" <% if preferences.latest_only %>checked<% end %>>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="pure-control-group">
|
<div class="control-group">
|
||||||
<label for="unseen_only"><%= translate(locale, "preferences_unseen_only_label") %></label>
|
<label for="sort"><%= translate(locale, "preferences_sort_label") %></label>
|
||||||
<input name="unseen_only" id="unseen_only" type="checkbox" <% if preferences.unseen_only %>checked<% end %>>
|
<select name="sort" id="sort">
|
||||||
</div>
|
<% {"published", "published - reverse", "alphabetically", "alphabetically - reverse", "channel name", "channel name - reverse"}.each do |option| %>
|
||||||
|
<option value="<%= option %>" <% if preferences.sort == option %> selected <% end %>><%= translate(locale, option) %></option>
|
||||||
|
<% end %>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
<% if CONFIG.enable_user_notifications %>
|
<div class="control-group">
|
||||||
<div class="pure-control-group">
|
<% if preferences.unseen_only %>
|
||||||
<label for="notifications_only"><%= translate(locale, "preferences_notifications_only_label") %></label>
|
<label for="latest_only"><%= translate(locale, "Only show latest unwatched video from channel: ") %></label>
|
||||||
<input name="notifications_only" id="notifications_only" type="checkbox" <% if preferences.notifications_only %>checked<% end %>>
|
<% else %>
|
||||||
</div>
|
<label for="latest_only"><%= translate(locale, "Only show latest video from channel: ") %></label>
|
||||||
|
<% end %>
|
||||||
|
<input name="latest_only" id="latest_only" type="checkbox" <% if preferences.latest_only %>checked<% end %>>
|
||||||
|
</div>
|
||||||
|
|
||||||
<% # Web notifications are only supported over HTTPS %>
|
<div class="control-group">
|
||||||
<% if Kemal.config.ssl || CONFIG.https_only %>
|
<label for="unseen_only"><%= translate(locale, "preferences_unseen_only_label") %></label>
|
||||||
<div class="pure-control-group">
|
<input name="unseen_only" id="unseen_only" type="checkbox" <% if preferences.unseen_only %>checked<% end %>>
|
||||||
<a href="#" data-onclick="notification_requestPermission"><%= translate(locale, "Enable web notifications") %></a>
|
</div>
|
||||||
</div>
|
|
||||||
<% end %>
|
|
||||||
<% end %>
|
|
||||||
<% end %>
|
|
||||||
|
|
||||||
<% if env.get?("user") && CONFIG.admins.includes? env.get?("user").as(Invidious::User).email %>
|
<% if CONFIG.enable_user_notifications %>
|
||||||
<legend><%= translate(locale, "preferences_category_admin") %></legend>
|
<div class="control-group">
|
||||||
|
<label for="notifications_only"><%= translate(locale, "preferences_notifications_only_label") %></label>
|
||||||
|
<input name="notifications_only" id="notifications_only" type="checkbox" <% if preferences.notifications_only %>checked<% end %>>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="pure-control-group">
|
<% # Web notifications are only supported over HTTPS %>
|
||||||
<label for="admin_default_home"><%= translate(locale, "preferences_default_home_label") %></label>
|
<% if Kemal.config.ssl || CONFIG.https_only %>
|
||||||
<select name="admin_default_home" id="admin_default_home">
|
<div class="control-group">
|
||||||
<% feed_options.each do |option| %>
|
<a href="#" data-onclick="notification_requestPermission"><%= translate(locale, "Enable web notifications") %></a>
|
||||||
<option value="<%= option %>" <% if CONFIG.default_user_preferences.default_home == option %> selected <% end %>><%= translate(locale, option.blank? ? "none" : option) %></option>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
</select>
|
<% end %>
|
||||||
</div>
|
</fieldset>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
<div class="pure-control-group">
|
<% if env.get?("user") && CONFIG.admins.includes? env.get?("user").as(Invidious::User).email %>
|
||||||
<label for="admin_feed_menu"><%= translate(locale, "preferences_feed_menu_label") %></label>
|
<fieldset class="preferences">
|
||||||
<% (feed_options.size - 1).times do |index| %>
|
<legend><%= translate(locale, "preferences_category_admin") %></legend>
|
||||||
<select name="admin_feed_menu[<%= index %>]" id="admin_feed_menu[<%= index %>]">
|
|
||||||
<% feed_options.each do |option| %>
|
|
||||||
<option value="<%= option %>" <% if CONFIG.default_user_preferences.feed_menu[index]? == option %> selected <% end %>><%= translate(locale, option.blank? ? "none" : option) %></option>
|
|
||||||
<% end %>
|
|
||||||
</select>
|
|
||||||
<% end %>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="pure-control-group">
|
<div class="control-group">
|
||||||
<label for="popular_enabled"><%= translate(locale, "Popular enabled: ") %></label>
|
<label for="admin_default_home"><%= translate(locale, "preferences_default_home_label") %></label>
|
||||||
<input name="popular_enabled" id="popular_enabled" type="checkbox" <% if CONFIG.popular_enabled %>checked<% end %>>
|
<select name="admin_default_home" id="admin_default_home">
|
||||||
</div>
|
<% feed_options.each do |option| %>
|
||||||
|
<option value="<%= option %>" <% if CONFIG.default_user_preferences.default_home == option %> selected <% end %>><%= translate(locale, option.blank? ? "none" : option) %></option>
|
||||||
|
<% end %>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="control-group">
|
||||||
|
<label for="admin_feed_menu"><%= translate(locale, "preferences_feed_menu_label") %></label>
|
||||||
|
<% (feed_options.size - 1).times do |index| %>
|
||||||
|
<select name="admin_feed_menu[<%= index %>]" id="admin_feed_menu[<%= index %>]">
|
||||||
|
<% feed_options.each do |option| %>
|
||||||
|
<option value="<%= option %>" <% if CONFIG.default_user_preferences.feed_menu[index]? == option %> selected <% end %>><%= translate(locale, option.blank? ? "none" : option) %></option>
|
||||||
|
<% end %>
|
||||||
|
</select>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="control-group">
|
||||||
|
<label for="popular_enabled"><%= translate(locale, "Popular enabled: ") %></label>
|
||||||
|
<input name="popular_enabled" id="popular_enabled" type="checkbox" <% if CONFIG.popular_enabled %>checked<% end %>>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<div class="pure-control-group">
|
<div class="control-group">
|
||||||
<label for="captcha_enabled"><%= translate(locale, "CAPTCHA enabled: ") %></label>
|
<label for="captcha_enabled"><%= translate(locale, "CAPTCHA enabled: ") %></label>
|
||||||
<input name="captcha_enabled" id="captcha_enabled" type="checkbox" <% if CONFIG.captcha_enabled %>checked<% end %>>
|
<input name="captcha_enabled" id="captcha_enabled" type="checkbox" <% if CONFIG.captcha_enabled %>checked<% end %>>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="pure-control-group">
|
<div class="control-group">
|
||||||
<label for="login_enabled"><%= translate(locale, "Login enabled: ") %></label>
|
<label for="login_enabled"><%= translate(locale, "Login enabled: ") %></label>
|
||||||
<input name="login_enabled" id="login_enabled" type="checkbox" <% if CONFIG.login_enabled %>checked<% end %>>
|
<input name="login_enabled" id="login_enabled" type="checkbox" <% if CONFIG.login_enabled %>checked<% end %>>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="pure-control-group">
|
<div class="control-group">
|
||||||
<label for="registration_enabled"><%= translate(locale, "Registration enabled: ") %></label>
|
<label for="registration_enabled"><%= translate(locale, "Registration enabled: ") %></label>
|
||||||
<input name="registration_enabled" id="registration_enabled" type="checkbox" <% if CONFIG.registration_enabled %>checked<% end %>>
|
<input name="registration_enabled" id="registration_enabled" type="checkbox" <% if CONFIG.registration_enabled %>checked<% end %>>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="pure-control-group">
|
<div class="control-group">
|
||||||
<label for="statistics_enabled"><%= translate(locale, "Report statistics: ") %></label>
|
<label for="statistics_enabled"><%= translate(locale, "Report statistics: ") %></label>
|
||||||
<input name="statistics_enabled" id="statistics_enabled" type="checkbox" <% if CONFIG.statistics_enabled %>checked<% end %>>
|
<input name="statistics_enabled" id="statistics_enabled" type="checkbox" <% if CONFIG.statistics_enabled %>checked<% end %>>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="pure-control-group">
|
<div class="control-group">
|
||||||
<label for="modified_source_code_url"><%= translate(locale, "adminprefs_modified_source_code_url_label") %></label>
|
<label for="modified_source_code_url"><%= translate(locale, "adminprefs_modified_source_code_url_label") %></label>
|
||||||
<input name="modified_source_code_url" id="modified_source_code_url" type="url" value="<%= CONFIG.modified_source_code_url %>">
|
<input name="modified_source_code_url" id="modified_source_code_url" type="url" value="<%= CONFIG.modified_source_code_url %>">
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
</fieldset>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
<% if env.get? "user" %>
|
<% if env.get? "user" %>
|
||||||
<legend><%= translate(locale, "preferences_category_data") %></legend>
|
<fieldset class="preferences">
|
||||||
|
<legend><%= translate(locale, "preferences_category_data") %></legend>
|
||||||
|
|
||||||
<div class="pure-control-group">
|
<div class="control-group">
|
||||||
<a href="/clear_watch_history?referer=<%= URI.encode_www_form(referer) %>"><%= translate(locale, "Clear watch history") %></a>
|
<a href="/clear_watch_history?referer=<%= URI.encode_www_form(referer) %>"><%= translate(locale, "Clear watch history") %></a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="pure-control-group">
|
<div class="control-group">
|
||||||
<a href="/change_password?referer=<%= URI.encode_www_form(referer) %>"><%= translate(locale, "Change password") %></a>
|
<a href="/change_password?referer=<%= URI.encode_www_form(referer) %>"><%= translate(locale, "Change password") %></a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="pure-control-group">
|
<div class="control-group">
|
||||||
<a href="/data_control?referer=<%= URI.encode_www_form(referer) %>"><%= translate(locale, "Import/export data") %></a>
|
<a href="/data_control?referer=<%= URI.encode_www_form(referer) %>"><%= translate(locale, "Import/export data") %></a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="pure-control-group">
|
<div class="control-group">
|
||||||
<a href="/subscription_manager"><%= translate(locale, "Manage subscriptions") %></a>
|
<a href="/subscription_manager"><%= translate(locale, "Manage subscriptions") %></a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="pure-control-group">
|
<div class="control-group">
|
||||||
<a href="/token_manager"><%= translate(locale, "Manage tokens") %></a>
|
<a href="/token_manager"><%= translate(locale, "Manage tokens") %></a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="pure-control-group">
|
<div class="control-group">
|
||||||
<a href="/feed/playlists"><%= translate(locale, "View all playlists") %></a>
|
<a href="/feed/playlists"><%= translate(locale, "View all playlists") %></a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="pure-control-group">
|
<div class="control-group">
|
||||||
<a href="/feed/history"><%= translate(locale, "Watch history") %></a>
|
<a href="/feed/history"><%= translate(locale, "Watch history") %></a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="pure-control-group">
|
<div class="control-group">
|
||||||
<a href="/delete_account?referer=<%= URI.encode_www_form(referer) %>"><%= translate(locale, "Delete account") %></a>
|
<a href="/delete_account?referer=<%= URI.encode_www_form(referer) %>"><%= translate(locale, "Delete account") %></a>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
</fieldset>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
<div class="pure-controls">
|
|
||||||
<button type="submit" class="pure-button pure-button-primary"><%= translate(locale, "Save preferences") %></button>
|
<div class="action-controls">
|
||||||
</div>
|
<button type="submit" class="primary"><%= translate(locale, "Save preferences") %></button>
|
||||||
</fieldset>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
|
||||||
|
@ -2,51 +2,40 @@
|
|||||||
<title><%= translate(locale, "Subscription manager") %> - Invidious</title>
|
<title><%= translate(locale, "Subscription manager") %> - Invidious</title>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<div class="pure-g h-box">
|
<div class="subscription-header">
|
||||||
<div class="pure-u-1-3">
|
<h1><%= translate(locale, "Subscription manager") %></h1>
|
||||||
<h3>
|
<nav>
|
||||||
<a href="/feed/subscriptions">
|
<ul>
|
||||||
<%= translate_count(locale, "generic_subscriptions_count", subscriptions.size, NumberFormatting::HtmlSpan) %>
|
<li>
|
||||||
</a>
|
<a href="/feed/subscriptions">
|
||||||
</h3>
|
<%= translate_count(locale, "generic_subscriptions_count", subscriptions.size, NumberFormatting::HtmlSpan) %>
|
||||||
</div>
|
</a>
|
||||||
<div class="pure-u-1-3">
|
</li>
|
||||||
<h3 style="text-align:center">
|
<li>
|
||||||
<a href="/feed/history">
|
|
||||||
<%= translate(locale, "Watch history") %>
|
<a href="/feed/history">
|
||||||
</a>
|
<%= translate(locale, "Watch history") %>
|
||||||
</h3>
|
</a>
|
||||||
</div>
|
</li>
|
||||||
<div class="pure-u-1-3">
|
<li>
|
||||||
<h3 style="text-align:right">
|
<a href="/data_control?referer=<%= URI.encode_www_form(referer) %>">
|
||||||
<a href="/data_control?referer=<%= URI.encode_www_form(referer) %>">
|
<%= translate(locale, "Import/export") %>
|
||||||
<%= translate(locale, "Import/export") %>
|
</a>
|
||||||
</a>
|
</li>
|
||||||
</h3>
|
<ul>
|
||||||
</div>
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="subscriptions">
|
||||||
<% subscriptions.each do |channel| %>
|
<% subscriptions.each do |channel| %>
|
||||||
<div class="h-box">
|
<div class="<% if channel.deleted %> deleted <% end %>">
|
||||||
<div class="pure-g<% if channel.deleted %> deleted <% end %>">
|
<h3>
|
||||||
<div class="pure-u-2-5">
|
|
||||||
<h3 style="padding-left:0.5em">
|
|
||||||
<a href="/channel/<%= channel.id %>"><%= HTML.escape(channel.author) %></a>
|
<a href="/channel/<%= channel.id %>"><%= HTML.escape(channel.author) %></a>
|
||||||
</h3>
|
</h3>
|
||||||
</div>
|
<form data-onsubmit="return_false" action="/subscription_ajax?action_remove_subscriptions=1&c=<%= channel.id %>&referer=<%= env.get("current_page") %>" method="post">
|
||||||
<div class="pure-u-2-5"></div>
|
<input type="hidden" name="csrf_token" value="<%= HTML.escape(env.get?("csrf_token").try &.as(String) || "") %>">
|
||||||
<div class="pure-u-1-5" style="text-align:right">
|
<input class="secondary-button" type="submit" data-onclick="remove_subscription" data-ucid="<%= channel.id %>" value="<%= translate(locale, "unsubscribe") %>">
|
||||||
<h3 style="padding-right:0.5em">
|
</form>
|
||||||
<form data-onsubmit="return_false" action="/subscription_ajax?action_remove_subscriptions=1&c=<%= channel.id %>&referer=<%= env.get("current_page") %>" method="post">
|
</div>
|
||||||
<input type="hidden" name="csrf_token" value="<%= HTML.escape(env.get?("csrf_token").try &.as(String) || "") %>">
|
|
||||||
<input style="all:unset" type="submit" data-onclick="remove_subscription" data-ucid="<%= channel.id %>" value="<%= translate(locale, "unsubscribe") %>">
|
|
||||||
</form>
|
|
||||||
</h3>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<% if subscriptions[-1].author != channel.author %>
|
|
||||||
<hr>
|
|
||||||
<% end %>
|
|
||||||
</div>
|
|
||||||
<% end %>
|
<% end %>
|
||||||
|
</div>
|
||||||
|
@ -34,11 +34,11 @@
|
|||||||
we're going to need to do it here in order to allow for translations.
|
we're going to need to do it here in order to allow for translations.
|
||||||
-->
|
-->
|
||||||
<style>
|
<style>
|
||||||
#descexpansionbutton ~ label > a::after {
|
#description-expansion ~ label::after {
|
||||||
content: "<%= translate(locale, "Show more") %>"
|
content: "<%= translate(locale, "Show more") %>"
|
||||||
}
|
}
|
||||||
|
|
||||||
#descexpansionbutton:checked ~ label > a::after {
|
#description-expansion:checked ~ label::after {
|
||||||
content: "<%= translate(locale, "Show less") %>"
|
content: "<%= translate(locale, "Show less") %>"
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
@ -70,305 +70,317 @@ we're going to need to do it here in order to allow for translations.
|
|||||||
%>
|
%>
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div id="player-container" class="h-box">
|
<main>
|
||||||
<%= rendered "components/player" %>
|
<article>
|
||||||
</div>
|
<div id="player-container">
|
||||||
|
<%= rendered "components/player" %>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="h-box">
|
<header class="video-header">
|
||||||
<h1>
|
<h1>
|
||||||
<%= title %>
|
<%= title %>
|
||||||
<% if params.listen %>
|
</h1>
|
||||||
<a title="<%=translate(locale, "Video mode")%>" href="/watch?<%= env.params.query %>&listen=0">
|
<div class="listen">
|
||||||
<i class="icon ion-ios-videocam"></i>
|
<% if params.listen %>
|
||||||
</a>
|
<a title="<%=translate(locale, "Video mode")%>" href="/watch?<%= env.params.query %>&listen=0">
|
||||||
<% else %>
|
<i class="icon ion-ios-videocam"></i>
|
||||||
<a title="<%=translate(locale, "Audio mode")%>" href="/watch?<%= env.params.query %>&listen=1">
|
</a>
|
||||||
<i class="icon ion-md-headset"></i>
|
<% else %>
|
||||||
</a>
|
<a title="<%=translate(locale, "Audio mode")%>" href="/watch?<%= env.params.query %>&listen=1">
|
||||||
<% end %>
|
<i class="icon ion-md-headset"></i>
|
||||||
</h1>
|
</a>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
<% if !video.is_listed %>
|
<div class="watch">
|
||||||
<h3>
|
<aside class="watch-meta">
|
||||||
<i class="icon ion-ios-unlock"></i> <%= translate(locale, "Unlisted") %>
|
<% if !video.is_listed %>
|
||||||
</h3>
|
<h4>
|
||||||
<% end %>
|
<i class="icon ion-ios-unlock"></i> <%= translate(locale, "Unlisted") %>
|
||||||
|
</h4>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
<% if video.reason %>
|
<% if video.reason %>
|
||||||
<h3>
|
<h4>
|
||||||
<%= video.reason %>
|
<%= video.reason %>
|
||||||
</h3>
|
</h4>
|
||||||
<% elsif video.premiere_timestamp.try &.> Time.utc %>
|
<% elsif video.premiere_timestamp.try &.> Time.utc %>
|
||||||
<h3>
|
<h4>
|
||||||
<%= video.premiere_timestamp.try { |t| translate(locale, "Premieres in `x`", recode_date((t - Time.utc).ago, locale)) } %>
|
<time datetime="<%= video.premiere_timestamp.try { |t| t.to_s("%Y-%m-%d") } %>"><%= video.premiere_timestamp.try { |t| translate(locale, "Premieres in `x`", recode_date((t - Time.utc).ago, locale)) } %></time>
|
||||||
</h3>
|
</h4>
|
||||||
<% elsif video.live_now %>
|
<% elsif video.live_now %>
|
||||||
<h3>
|
<h4>
|
||||||
<%= video.premiere_timestamp.try { |t| translate(locale, "videoinfo_started_streaming_x_ago", recode_date((Time.utc - t).ago, locale)) } %>
|
<time datetime="<%= video.premiere_timestamp.try { |t| t.to_s("%Y-%m-%d") } %>"><%= video.premiere_timestamp.try { |t| translate(locale, "videoinfo_started_streaming_x_ago", recode_date((Time.utc - t).ago, locale)) } %></time>
|
||||||
</h3>
|
</h4>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
<nav>
|
||||||
|
<ul>
|
||||||
|
<li id="watch-on-youtube">
|
||||||
|
<%-
|
||||||
|
link_yt_watch = URI.new(scheme: "https", host: "www.youtube.com", path: "/watch", query: "v=#{video.id}")
|
||||||
|
link_yt_embed = URI.new(scheme: "https", host: "www.youtube.com", path: "/embed/#{video.id}")
|
||||||
|
|
||||||
<div class="pure-g">
|
if !plid.nil? && !continuation.nil?
|
||||||
<div class="pure-u-1 pure-u-lg-1-5">
|
link_yt_param = URI::Params{"list" => [plid], "index" => [continuation.to_s]}
|
||||||
<div class="h-box">
|
link_yt_watch = IV::HttpServer::Utils.add_params_to_url(link_yt_watch, link_yt_param)
|
||||||
<span id="watch-on-youtube">
|
link_yt_embed = IV::HttpServer::Utils.add_params_to_url(link_yt_embed, link_yt_param)
|
||||||
<%-
|
end
|
||||||
link_yt_watch = URI.new(scheme: "https", host: "www.youtube.com", path: "/watch", query: "v=#{video.id}")
|
-%>
|
||||||
link_yt_embed = URI.new(scheme: "https", host: "www.youtube.com", path: "/embed/#{video.id}")
|
<a id="link-yt-watch" rel="noreferrer noopener" data-base-url="<%= link_yt_watch %>" href="<%= link_yt_watch %>"><%= translate(locale, "videoinfo_watch_on_youTube") %></a>
|
||||||
|
<a id="link-yt-embed" rel="noreferrer noopener" data-base-url="<%= link_yt_embed %>" href="<%= link_yt_embed %>"><%= translate(locale, "videoinfo_youTube_embed_link") %></a>
|
||||||
|
</li>
|
||||||
|
|
||||||
if !plid.nil? && !continuation.nil?
|
<li id="watch-on-another-invidious-instance">
|
||||||
link_yt_param = URI::Params{"list" => [plid], "index" => [continuation.to_s]}
|
<%- link_iv_other = IV::Frontend::Misc.redirect_url(env) -%>
|
||||||
link_yt_watch = IV::HttpServer::Utils.add_params_to_url(link_yt_watch, link_yt_param)
|
<a id="link-iv-other" data-base-url="<%= link_iv_other %>" href="<%= link_iv_other %>"><%= translate(locale, "Switch Invidious Instance") %></a>
|
||||||
link_yt_embed = IV::HttpServer::Utils.add_params_to_url(link_yt_embed, link_yt_param)
|
</li>
|
||||||
end
|
|
||||||
-%>
|
|
||||||
<a id="link-yt-watch" rel="noreferrer noopener" data-base-url="<%= link_yt_watch %>" href="<%= link_yt_watch %>"><%= translate(locale, "videoinfo_watch_on_youTube") %></a>
|
|
||||||
(<a id="link-yt-embed" rel="noreferrer noopener" data-base-url="<%= link_yt_embed %>" href="<%= link_yt_embed %>"><%= translate(locale, "videoinfo_youTube_embed_link") %></a>)
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<p id="watch-on-another-invidious-instance">
|
<li id="embed-link">
|
||||||
<%- link_iv_other = IV::Frontend::Misc.redirect_url(env) -%>
|
<%-
|
||||||
<a id="link-iv-other" data-base-url="<%= link_iv_other %>" href="<%= link_iv_other %>"><%= translate(locale, "Switch Invidious Instance") %></a>
|
params_iv_embed = env.params.query.dup
|
||||||
</p>
|
params_iv_embed.delete_all("v")
|
||||||
|
|
||||||
<p id="embed-link">
|
link_iv_embed = URI.new(path: "/embed/#{id}")
|
||||||
<%-
|
link_iv_embed = IV::HttpServer::Utils.add_params_to_url(link_iv_embed, params_iv_embed)
|
||||||
params_iv_embed = env.params.query.dup
|
-%>
|
||||||
params_iv_embed.delete_all("v")
|
<a id="link-iv-embed" data-base-url="<%= link_iv_embed %>" href="<%= link_iv_embed %>"><%= translate(locale, "videoinfo_invidious_embed_link") %></a>
|
||||||
|
</li>
|
||||||
|
|
||||||
link_iv_embed = URI.new(path: "/embed/#{id}")
|
<li id="annotations">
|
||||||
link_iv_embed = IV::HttpServer::Utils.add_params_to_url(link_iv_embed, params_iv_embed)
|
<% if params.annotations %>
|
||||||
-%>
|
<a href="/watch?<%= env.params.query %>&iv_load_policy=3">
|
||||||
<a id="link-iv-embed" data-base-url="<%= link_iv_embed %>" href="<%= link_iv_embed %>"><%= translate(locale, "videoinfo_invidious_embed_link") %></a>
|
<%= translate(locale, "Hide annotations") %>
|
||||||
</p>
|
</a>
|
||||||
|
<% else %>
|
||||||
|
<a href="/watch?<%= env.params.query %>&iv_load_policy=1">
|
||||||
|
<%=translate(locale, "Show annotations")%>
|
||||||
|
</a>
|
||||||
|
<% end %>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
|
||||||
<p id="annotations">
|
<% if user %>
|
||||||
<% if params.annotations %>
|
<% playlists = Invidious::Database::Playlists.select_user_created_playlists(user.email) %>
|
||||||
<a href="/watch?<%= env.params.query %>&iv_load_policy=3">
|
<% if !playlists.empty? %>
|
||||||
<%= translate(locale, "Hide annotations") %>
|
<form data-onsubmit="return_false" action="/playlist_ajax" method="post" target="_blank" class="watch-action-group">
|
||||||
</a>
|
<label for="playlists"><%= translate(locale, "Add to playlist") %></label>
|
||||||
<% else %>
|
<select name="playlist_id" id="playlists">
|
||||||
<a href="/watch?<%= env.params.query %>&iv_load_policy=1">
|
<% playlists.each do |plid, playlist_title| %>
|
||||||
<%=translate(locale, "Show annotations")%>
|
<option data-plid="<%= plid %>" value="<%= plid %>"><%= HTML.escape(playlist_title) %></option>
|
||||||
</a>
|
<% end %>
|
||||||
<% end %>
|
</select>
|
||||||
</p>
|
|
||||||
|
|
||||||
<% if user %>
|
<input type="hidden" name="csrf_token" value="<%= URI.encode_www_form(env.get?("csrf_token").try &.as(String) || "") %>">
|
||||||
<% playlists = Invidious::Database::Playlists.select_user_created_playlists(user.email) %>
|
<input type="hidden" name="action_add_video" value="1">
|
||||||
<% if !playlists.empty? %>
|
<input type="hidden" name="video_id" value="<%= video.id %>">
|
||||||
<form data-onsubmit="return_false" class="pure-form pure-form-stacked" action="/playlist_ajax" method="post" target="_blank">
|
<button data-onclick="add_playlist_video" data-id="<%= video.id %>" type="submit" class="secondary">
|
||||||
<div class="pure-control-group">
|
<%= translate(locale, "Add to playlist") %>
|
||||||
<label for="playlist_id"><%= translate(locale, "Add to playlist: ") %></label>
|
</button>
|
||||||
<select style="width:100%" name="playlist_id" id="playlist_id">
|
</form>
|
||||||
<% playlists.each do |plid, playlist_title| %>
|
<script id="playlist_data" type="application/json">
|
||||||
<option data-plid="<%= plid %>" value="<%= plid %>"><%= HTML.escape(playlist_title) %></option>
|
<%=
|
||||||
<% end %>
|
{
|
||||||
</select>
|
"csrf_token" => URI.encode_www_form(env.get?("csrf_token").try &.as(String) || "")
|
||||||
</div>
|
}.to_pretty_json
|
||||||
|
%>
|
||||||
|
</script>
|
||||||
|
<script src="/js/playlist_widget.js?v=<%= Time.utc.to_unix_ms %>"></script>
|
||||||
|
<% end %>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
<input type="hidden" name="csrf_token" value="<%= URI.encode_www_form(env.get?("csrf_token").try &.as(String) || "") %>">
|
<%= Invidious::Frontend::WatchPage.download_widget(locale, video, video_assets) %>
|
||||||
<input type="hidden" name="action_add_video" value="1">
|
|
||||||
<input type="hidden" name="video_id" value="<%= video.id %>">
|
|
||||||
<button data-onclick="add_playlist_video" data-id="<%= video.id %>" type="submit" class="pure-button pure-button-primary">
|
|
||||||
<b><%= translate(locale, "Add to playlist") %></b>
|
|
||||||
</button>
|
|
||||||
</form>
|
|
||||||
<script id="playlist_data" type="application/json">
|
|
||||||
<%=
|
|
||||||
{
|
|
||||||
"csrf_token" => URI.encode_www_form(env.get?("csrf_token").try &.as(String) || "")
|
|
||||||
}.to_pretty_json
|
|
||||||
%>
|
|
||||||
</script>
|
|
||||||
<script src="/js/playlist_widget.js?v=<%= Time.utc.to_unix_ms %>"></script>
|
|
||||||
<% end %>
|
|
||||||
<% end %>
|
|
||||||
|
|
||||||
<%= Invidious::Frontend::WatchPage.download_widget(locale, video, video_assets) %>
|
<ul class="content-meta">
|
||||||
|
<li id="genre"><%= translate(locale, "Genre: ") %>
|
||||||
|
<% if !video.genre_url %>
|
||||||
|
<%= video.genre %>
|
||||||
|
<% else %>
|
||||||
|
<a href="<%= video.genre_url %>"><%= video.genre %></a>
|
||||||
|
<% end %>
|
||||||
|
</li>
|
||||||
|
<% if video.license %>
|
||||||
|
<li id="license">
|
||||||
|
<% if video.license.empty? %>
|
||||||
|
<%= translate(locale, "License: ") %><%= translate(locale, "Standard YouTube license") %>
|
||||||
|
<% else %>
|
||||||
|
<%= translate(locale, "License: ") %><%= video.license %>
|
||||||
|
<% end %>
|
||||||
|
</li>
|
||||||
|
<% end %>
|
||||||
|
<li id="family_friendly"><%= translate(locale, "Family friendly? ") %><%= translate_bool(locale, video.is_family_friendly) %></li>
|
||||||
|
<% if video.allowed_regions.size != REGIONS.size %>
|
||||||
|
<li id="allowed_regions">
|
||||||
|
<% if video.allowed_regions.size < REGIONS.size // 2 %>
|
||||||
|
<%= translate(locale, "Whitelisted regions: ") %><%= video.allowed_regions.join(", ") %>
|
||||||
|
<% else %>
|
||||||
|
<%= translate(locale, "Blacklisted regions: ") %><%= (REGIONS.to_a - video.allowed_regions).join(", ") %>
|
||||||
|
<% end %>
|
||||||
|
</li>
|
||||||
|
<% end %>
|
||||||
|
</ul>
|
||||||
|
</aside>
|
||||||
|
|
||||||
<p id="views"><i class="icon ion-ios-eye"></i> <%= number_with_separator(video.views) %></p>
|
<div class="video">
|
||||||
<p id="likes"><i class="icon ion-ios-thumbs-up"></i> <%= number_with_separator(video.likes) %></p>
|
<div class="channel">
|
||||||
<p id="dislikes" style="display: none; visibility: hidden;"></p>
|
<div class="title">
|
||||||
<p id="genre"><%= translate(locale, "Genre: ") %>
|
<div class="channel-profile">
|
||||||
<% if !video.genre_url %>
|
<% if !video.author_thumbnail.empty? %>
|
||||||
<%= video.genre %>
|
<a href="/channel/<%= video.ucid %>">
|
||||||
<% else %>
|
<img src="/ggpht<%= URI.parse(video.author_thumbnail).request_target %>" class="profile-pic" alt="<%= author %>" />
|
||||||
<a href="<%= video.genre_url %>"><%= video.genre %></a>
|
</a>
|
||||||
<% end %>
|
<% end %>
|
||||||
</p>
|
<h2 id="channel-name">
|
||||||
<% if video.license %>
|
<a href="/channel/<%= video.ucid %>">
|
||||||
<% if video.license.empty? %>
|
<%= author %><% if !video.author_verified.nil? && video.author_verified %> <i class="icon ion ion-md-checkmark-circle"></i><% end %>
|
||||||
<p id="license"><%= translate(locale, "License: ") %><%= translate(locale, "Standard YouTube license") %></p>
|
</a>
|
||||||
<% else %>
|
</h2>
|
||||||
<p id="license"><%= translate(locale, "License: ") %><%= video.license %></p>
|
</div>
|
||||||
<% end %>
|
|
||||||
<% end %>
|
|
||||||
<p id="family_friendly"><%= translate(locale, "Family friendly? ") %><%= translate_bool(locale, video.is_family_friendly) %></p>
|
|
||||||
<p id="wilson" style="display: none; visibility: hidden;"></p>
|
|
||||||
<p id="rating" style="display: none; visibility: hidden;"></p>
|
|
||||||
<p id="engagement" style="display: none; visibility: hidden;"></p>
|
|
||||||
<% if video.allowed_regions.size != REGIONS.size %>
|
|
||||||
<p id="allowed_regions">
|
|
||||||
<% if video.allowed_regions.size < REGIONS.size // 2 %>
|
|
||||||
<%= translate(locale, "Whitelisted regions: ") %><%= video.allowed_regions.join(", ") %>
|
|
||||||
<% else %>
|
|
||||||
<%= translate(locale, "Blacklisted regions: ") %><%= (REGIONS.to_a - video.allowed_regions).join(", ") %>
|
|
||||||
<% end %>
|
|
||||||
</p>
|
|
||||||
<% end %>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="pure-u-1 <% if params.related_videos || plid %>pure-u-lg-3-5<% else %>pure-u-md-4-5<% end %>">
|
<div class="button-container">
|
||||||
|
<% sub_count_text = video.sub_count_text %>
|
||||||
|
<%= rendered "components/subscribe_widget" %>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="pure-g h-box flexible title">
|
<div class="video-meta">
|
||||||
<div class="pure-u-1-2 flex-left flexible">
|
|
||||||
<a href="/channel/<%= video.ucid %>">
|
|
||||||
<div class="channel-profile">
|
|
||||||
<% if !video.author_thumbnail.empty? %>
|
|
||||||
<img src="/ggpht<%= URI.parse(video.author_thumbnail).request_target %>" alt="" />
|
|
||||||
<% end %>
|
|
||||||
<span id="channel-name"><%= author %><% if !video.author_verified.nil? && video.author_verified %> <i class="icon ion ion-md-checkmark-circle"></i><% end %></span>
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="pure-u-1-2 flex-right flexible button-container">
|
<div>
|
||||||
<div class="pure-u">
|
<% if video.premiere_timestamp.try &.> Time.utc %>
|
||||||
<% sub_count_text = video.sub_count_text %>
|
<time datetime="<%= video.premiere_timestamp.try { |t| t.to_s("%Y-%m-%d") } %>">
|
||||||
<%= rendered "components/subscribe_widget" %>
|
<%= video.premiere_timestamp.try { |t| translate(locale, "Premieres `x`", t.to_s("%B %-d, %R UTC")) } %>
|
||||||
</div>
|
</time>
|
||||||
</div>
|
<% else %>
|
||||||
</div>
|
<time datetime="<%= video.published.to_s("%Y-%m-%d") %>">
|
||||||
|
<%= translate(locale, "Shared `x`", video.published.to_s("%B %-d, %Y")) %>
|
||||||
|
</time>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
|
<div class="video-interaction-meta">
|
||||||
|
<div id="views"><i class="icon ion-ios-eye"></i> <%= number_with_separator(video.views) %></div>
|
||||||
|
<div id="likes"><i class="icon ion-ios-thumbs-up"></i> <%= number_with_separator(video.likes) %></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="h-box">
|
<div id="description-box">
|
||||||
<p id="published-date">
|
<% if video.description.size < 200 || params.extend_desc %>
|
||||||
<% if video.premiere_timestamp.try &.> Time.utc %>
|
<p id="description" class="raw-text"><%= video.description_html %></p>
|
||||||
<b><%= video.premiere_timestamp.try { |t| translate(locale, "Premieres `x`", t.to_s("%B %-d, %R UTC")) } %></b>
|
<% else %>
|
||||||
<% else %>
|
<input id="description-expansion" type="checkbox"/>
|
||||||
<b><%= translate(locale, "Shared `x`", video.published.to_s("%B %-d, %Y")) %></b>
|
<p id="description" class="raw-text"><%= video.description_html %></p>
|
||||||
<% end %>
|
<label for="description-expansion" role="button"></label>
|
||||||
</p>
|
<% end %>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div id="description-box"> <!-- Description -->
|
<% if !video.music.empty? %>
|
||||||
<% if video.description.size < 200 || params.extend_desc %>
|
<input id="music-desc-expansion" type="checkbox"/>
|
||||||
<div id="descriptionWrapper"><%= video.description_html %></div>
|
<label for="music-desc-expansion">
|
||||||
<% else %>
|
<h3 id="music-description-title">
|
||||||
<input id="descexpansionbutton" type="checkbox"/>
|
<%= translate(locale, "Music in this video") %>
|
||||||
<div id="descriptionWrapper"><%= video.description_html %></div>
|
<span class="icon ion-ios-arrow-up"></span>
|
||||||
<label for="descexpansionbutton">
|
<span class="icon ion-ios-arrow-down"></span>
|
||||||
<a></a>
|
</h3>
|
||||||
</label>
|
</label>
|
||||||
<% end %>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<hr>
|
<div id="music-description-box">
|
||||||
|
<% video.music.each do |music| %>
|
||||||
|
<div class="music-item">
|
||||||
|
<div class="music-song"><%= translate(locale, "Song: ") %><%= music.song %></div>
|
||||||
|
<div class="music-artist"><%= translate(locale, "Artist: ") %><%= music.artist %></div>
|
||||||
|
<div class="music-album"><%= translate(locale, "Album: ") %><%= music.album %></div>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
<% if !video.music.empty? %>
|
<aside id="comments">
|
||||||
<input id="music-desc-expansion" type="checkbox"/>
|
<% if nojs %>
|
||||||
<label for="music-desc-expansion">
|
<%= comment_html %>
|
||||||
<h3 id="music-description-title">
|
<% else %>
|
||||||
<%= translate(locale, "Music in this video") %>
|
<noscript>
|
||||||
<span class="icon ion-ios-arrow-up"></span>
|
<a href="/watch?<%= env.params.query %>&nojs=1">
|
||||||
<span class="icon ion-ios-arrow-down"></span>
|
<%= translate(locale, "Hi! Looks like you have JavaScript turned off. Click here to view comments, keep in mind they may take a bit longer to load.") %>
|
||||||
</h3>
|
</a>
|
||||||
</label>
|
</noscript>
|
||||||
|
<% end %>
|
||||||
|
</aside>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div id="music-description-box">
|
<% if params.related_videos || plid %>
|
||||||
<% video.music.each do |music| %>
|
<aside class="more-videos">
|
||||||
<div class="music-item">
|
<% if plid %>
|
||||||
<p class="music-song"><%= translate(locale, "Song: ") %><%= music.song %></p>
|
<div id="playlist"></div>
|
||||||
<p class="music-artist"><%= translate(locale, "Artist: ") %><%= music.artist %></p>
|
<% end %>
|
||||||
<p class="music-album"><%= translate(locale, "Album: ") %><%= music.album %></p>
|
|
||||||
</div>
|
|
||||||
<% end %>
|
|
||||||
</div>
|
|
||||||
<hr>
|
|
||||||
|
|
||||||
<% end %>
|
<% if params.related_videos %>
|
||||||
<div id="comments" class="comments">
|
<div class="related-videos">
|
||||||
<% if nojs %>
|
<% if !video.related_videos.empty? %>
|
||||||
<%= comment_html %>
|
<% if plid %>
|
||||||
<% else %>
|
<div class="control-group">
|
||||||
<noscript>
|
<label for="continue"><%= translate(locale, "preferences_continue_label") %></label>
|
||||||
<a href="/watch?<%= env.params.query %>&nojs=1">
|
<input name="continue" id="continue" type="checkbox" <% if params.continue %>checked<% end %>>
|
||||||
<%= translate(locale, "Hi! Looks like you have JavaScript turned off. Click here to view comments, keep in mind they may take a bit longer to load.") %>
|
</div>
|
||||||
</a>
|
<% end %>
|
||||||
</noscript>
|
<% end %>
|
||||||
<% end %>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<% if params.related_videos || plid %>
|
<% video.related_videos.each do |rv| %>
|
||||||
<div class="pure-u-1 pure-u-lg-1-5">
|
<% if rv["id"]? %>
|
||||||
<% if plid %>
|
<article class="video-card" dir="auto">
|
||||||
<div id="playlist" class="h-box"></div>
|
<div class="thumbnail">
|
||||||
<% end %>
|
<%- if env.get("preferences").as(Preferences).thin_mode -%>
|
||||||
|
<a tabindex="-1" href="/watch?v=<%= rv["id"] %>&listen=<%= params.listen %>" title="<%= HTML.escape(rv["title"]) %>">
|
||||||
|
<div class="thumbnail-placeholder"></div>
|
||||||
|
</a>
|
||||||
|
<%- else -%>
|
||||||
|
<a tabindex="-1" href="/watch?v=<%= rv["id"] %>&listen=<%= params.listen %>" title="<%= HTML.escape(rv["title"]) %>">
|
||||||
|
<img loading="lazy" class="thumbnail" src="/vi/<%= rv["id"] %>/mqdefault.jpg" alt="<%= HTML.escape(rv["title"]) %>" />
|
||||||
|
</a>
|
||||||
|
<%- end -%>
|
||||||
|
|
||||||
<% if params.related_videos %>
|
<div class="bottom-right-overlay">
|
||||||
<div class="h-box">
|
<%- if (length_seconds = rv["length_seconds"]?.try &.to_i?) && length_seconds != 0 -%>
|
||||||
<% if !video.related_videos.empty? %>
|
<div class="length"><%= recode_length_seconds(length_seconds) %></div>
|
||||||
<div <% if plid %>style="display:none"<% end %>>
|
<%- end -%>
|
||||||
<div class="pure-control-group">
|
</div>
|
||||||
<label for="continue"><%= translate(locale, "preferences_continue_label") %></label>
|
</div>
|
||||||
<input name="continue" id="continue" type="checkbox" <% if params.continue %>checked<% end %>>
|
|
||||||
</div>
|
|
||||||
<hr>
|
|
||||||
</div>
|
|
||||||
<% end %>
|
|
||||||
|
|
||||||
<% video.related_videos.each do |rv| %>
|
<h3>
|
||||||
<% if rv["id"]? %>
|
<a href="/watch?v=<%= rv["id"] %>&listen=<%= params.listen %>" title="<%= HTML.escape(rv["title"]) %>">
|
||||||
<div class="pure-u-1">
|
<%= HTML.escape(rv["title"]) %>
|
||||||
|
</a>
|
||||||
|
</h3>
|
||||||
|
|
||||||
<div class="thumbnail">
|
<div class="video-meta-sub">
|
||||||
<%- if !env.get("preferences").as(Preferences).thin_mode -%>
|
<h4>
|
||||||
<a tabindex="-1" href="/watch?v=<%= rv["id"] %>&listen=<%= params.listen %>">
|
<% if !rv["ucid"].empty? %>
|
||||||
<img loading="lazy" class="thumbnail" src="/vi/<%= rv["id"] %>/mqdefault.jpg" alt="" />
|
<a href="/channel/<%= rv["ucid"] %>" title="<%= HTML.escape(rv["author"]) %>">
|
||||||
</a>
|
<%= HTML.escape(rv["author"]) %>
|
||||||
<%- else -%>
|
<% if rv["author_verified"]? == "true" %> <i class="icon ion ion-md-checkmark-circle"></i><% end %>
|
||||||
<div class="thumbnail-placeholder"></div>
|
</a>
|
||||||
<%- end -%>
|
<% else %>
|
||||||
|
<%= rv["author"]? %><% if rv["author_verified"]? == "true" %> <i class="icon ion ion-md-checkmark-circle"></i><% end %>
|
||||||
|
<% end %>
|
||||||
|
</h4>
|
||||||
|
|
||||||
<div class="bottom-right-overlay">
|
<div>
|
||||||
<%- if (length_seconds = rv["length_seconds"]?.try &.to_i?) && length_seconds != 0 -%>
|
<%=
|
||||||
<p class="length"><%= recode_length_seconds(length_seconds) %></p>
|
views = rv["view_count"]?.try &.to_i?
|
||||||
<%- end -%>
|
views ||= rv["view_count_short"]?.try { |x| short_text_to_number(x) }
|
||||||
</div>
|
translate_count(locale, "generic_views_count", views || 0, NumberFormatting::Short)
|
||||||
</div>
|
%>
|
||||||
|
</div>
|
||||||
<div class="video-card-row">
|
</div>
|
||||||
<a href="/watch?v=<%= rv["id"] %>&listen=<%= params.listen %>"><p dir="auto"><%= HTML.escape(rv["title"]) %></p></a>
|
</article>
|
||||||
</div>
|
<% end %> <% # if rv["id"]? %>
|
||||||
|
<% end %> <% # video.related_videos.each do |rv| %>
|
||||||
<h5 class="pure-g">
|
</div> <% # <div class="related-videos"> %>
|
||||||
<div class="pure-u-14-24">
|
<% end %> <% # if params.related_videos %>
|
||||||
<% if !rv["ucid"].empty? %>
|
</aside>
|
||||||
<b style="width:100%"><a href="/channel/<%= rv["ucid"] %>"><%= rv["author"]? %><% if rv["author_verified"]? == "true" %> <i class="icon ion ion-md-checkmark-circle"></i><% end %></a></b>
|
<% end %> <% # if params.related_videos || plid %>
|
||||||
<% else %>
|
</div>
|
||||||
<b style="width:100%"><%= rv["author"]? %><% if rv["author_verified"]? == "true" %> <i class="icon ion ion-md-checkmark-circle"></i><% end %></b>
|
</article>
|
||||||
<% end %>
|
</main>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="pure-u-10-24" style="text-align:right">
|
|
||||||
<b class="width:100%"><%=
|
|
||||||
views = rv["view_count"]?.try &.to_i?
|
|
||||||
views ||= rv["view_count_short"]?.try { |x| short_text_to_number(x) }
|
|
||||||
translate_count(locale, "generic_views_count", views || 0, NumberFormatting::Short)
|
|
||||||
%></b>
|
|
||||||
</div>
|
|
||||||
</h5>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
<% end %>
|
|
||||||
<% end %>
|
|
||||||
</div>
|
|
||||||
<% end %>
|
|
||||||
</div>
|
|
||||||
<% end %>
|
|
||||||
</div>
|
|
||||||
<script src="/js/comments.js?v=<%= ASSET_COMMIT %>"></script>
|
<script src="/js/comments.js?v=<%= ASSET_COMMIT %>"></script>
|
||||||
<script src="/js/watch.js?v=<%= ASSET_COMMIT %>"></script>
|
<script src="/js/watch.js?v=<%= ASSET_COMMIT %>"></script>
|
||||||
|
Loading…
Reference in New Issue
Block a user