From 71a30512c9891b7e7ac923b45228a12dc9e76a2e Mon Sep 17 00:00:00 2001 From: rockerBOO Date: Wed, 30 Apr 2025 20:04:21 -0400 Subject: [PATCH] Updated styling, formatting, structure of frontend --- Makefile | 9 +- assets/css/animation.css | 9 + assets/css/default.css | 1657 +++++++++++------ assets/css/embed.css | 20 +- assets/css/empty.css | 36 +- assets/css/player.css | 499 +++-- assets/css/pure-fix.css | 73 + assets/css/search.css | 200 +- assets/css/theme-catppuccin-latte.css | 16 + assets/css/theme-catppuccin-macchiato.css | 16 + assets/css/theme-dracula.css | 16 + assets/css/theme-tokyonight.css | 16 + assets/css/theme.css | 59 + assets/js/comments.js | 68 +- assets/js/handlers.js | 6 +- assets/js/notifications.js | 250 ++- assets/js/playlist_widget.js | 33 +- assets/js/subscribe_widget.js | 40 +- assets/js/theme.js | 44 + assets/js/themes.js | 4 +- assets/js/watch.js | 2 + docker-compose.yml | 65 +- locales/en-US.json | 1 + src/invidious.cr | 3 + src/invidious/config.cr | 3 + src/invidious/frontend/channel_page.cr | 23 +- src/invidious/frontend/comments_youtube.cr | 171 +- src/invidious/frontend/pagination.cr | 44 +- src/invidious/frontend/search_filters.cr | 24 +- src/invidious/frontend/watch_page.cr | 14 +- src/invidious/mixes.cr | 6 +- src/invidious/playlists.cr | 32 +- src/invidious/routes/playlists.cr | 2 + src/invidious/routes/search.cr | 4 + src/invidious/views/add_playlist_items.ecr | 48 +- src/invidious/views/channel.ecr | 5 - src/invidious/views/community.ecr | 14 +- .../views/components/channel_info.ecr | 88 +- src/invidious/views/components/feed_menu.ecr | 10 +- src/invidious/views/components/item.ecr | 205 +- .../views/components/items_paginated.ecr | 2 +- src/invidious/views/components/search_box.ecr | 14 +- .../views/components/subscribe_widget.ecr | 12 +- .../components/video-context-buttons.ecr | 16 +- src/invidious/views/create_playlist.ecr | 55 +- src/invidious/views/edit_playlist.ecr | 80 +- src/invidious/views/feeds/history.ecr | 59 +- src/invidious/views/feeds/playlists.ecr | 41 +- src/invidious/views/feeds/popular.ecr | 4 +- src/invidious/views/feeds/subscriptions.ecr | 96 +- src/invidious/views/feeds/trending.ecr | 42 +- src/invidious/views/playlist.ecr | 188 +- src/invidious/views/search.ecr | 14 +- src/invidious/views/search_homepage.ecr | 16 +- src/invidious/views/template.ecr | 321 ++-- src/invidious/views/user/data_control.ecr | 34 +- src/invidious/views/user/login.ecr | 48 +- src/invidious/views/user/preferences.ecr | 622 ++++--- .../views/user/subscription_manager.ecr | 73 +- src/invidious/views/watch.ecr | 562 +++--- 60 files changed, 3683 insertions(+), 2451 deletions(-) create mode 100644 assets/css/animation.css create mode 100644 assets/css/pure-fix.css create mode 100644 assets/css/theme-catppuccin-latte.css create mode 100644 assets/css/theme-catppuccin-macchiato.css create mode 100644 assets/css/theme-dracula.css create mode 100644 assets/css/theme-tokyonight.css create mode 100644 assets/css/theme.css create mode 100644 assets/js/theme.js diff --git a/Makefile b/Makefile index ec22a0de..70a5de5e 100644 --- a/Makefile +++ b/Makefile @@ -53,7 +53,6 @@ get-libs: invidious: get-libs crystal build src/invidious.cr $(FLAGS) --progress --stats --error-trace - run: invidious ./invidious @@ -73,6 +72,12 @@ verify: crystal build src/invidious.cr -Dskip_videojs_download \ --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 @@ -125,4 +130,4 @@ help: # No targets generates an output named after themselves .PHONY: all get-libs build amd64 run -.PHONY: format test verify clean distclean help +.PHONY: format test verify dev dev-reload clean distclean help diff --git a/assets/css/animation.css b/assets/css/animation.css new file mode 100644 index 00000000..aa5eda62 --- /dev/null +++ b/assets/css/animation.css @@ -0,0 +1,9 @@ +@keyframes spin { + 0% { + transform: rotate(0deg); + } + + 100% { + transform: rotate(360deg); + } +} diff --git a/assets/css/default.css b/assets/css/default.css index 2cedcf0c..03b97d12 100644 --- a/assets/css/default.css +++ b/assets/css/default.css @@ -1,42 +1,386 @@ /* - * Common attributes +f* Common attributes */ +:root { + --sans-serif: ui-rounded, "Hiragino Maru Gothic ProN", "Quicksand", + "Comfortaa", "Manjari", "Arial Rounded MT", "Arial Rounded MT Bold", + "Calibri", source-sans-pro, sans-serif; + --monospace: ui-monospace, "Cascadia Code", "Source Code Pro", "Menlo", + "Consolas", "DejaVu Sans Mono", monospace; + + /** Gap between sections */ + --gap: 2rem; + + /** Gap between parts in a section */ + --secondary-gap: 0.6rem; + + /** Radius for rounded corners */ + --radius: 0.3rem; + + /** Border size */ + --border-width: 0.2rem; +} + +*, +*::before, +*::after { + box-sizing: border-box; +} html, body { - font-family: BlinkMacSystemFont, -apple-system, "Segoe UI", Roboto, Oxygen, - Ubuntu, Cantarell, "Fira Sans", "Droid Sans", "Helvetica Neue", Helvetica, - Arial, sans-serif; + font-family: var(--sans-serif); + background-color: var(--bg-color); + color: var(--fg-color); + overflow-wrap: break-word; + word-wrap: break-word; + max-width: 100%; } -#contents { +body, +input, +textarea { + font-size: 1rem; +} + +p { + line-height: 1.6; + max-width: 50em; +} + +h1, +h2, +h3, +h4, +button, +input, +textarea, +label { + line-height: 1; +} + +h1, +h2, +h3, +h4, +h5, +p { + unicode-bidi: plaintext; + text-align: start; +} + +@media (max-width: 30em) { + h1 { + font-size: 1.4em; + } + + h2 { + font-size: 1.3em; + } + + h2 { + font-size: 1.2em; + } + + .container h1 { + padding: 0 var(--secondary-gap); + } +} + +a { + text-decoration: none; + color: currentcolor; + transition: + opacity 96ms ease-in-out, + color 96ms ease-in-out, + text-decoration-color 96ms ease-in-out, + outline-width 96ms ease-in-out; +} + +p a { + text-decoration-line: underline; + text-decoration-color: var(--secondary-bg-color); + text-decoration-thickness: var(--border-width); +} + +body > footer a { + text-decoration-line: underline; + text-decoration-color: var(--secondary-bg-color); + text-decoration-thickness: var(--border-width); +} + +nav ul li > a { + padding: var(--secondary-gap); + border-radius: var(--radius); +} + +.menu a { + padding: var(--secondary-gap); + width: 100%; + height: 100%; + display: block; +} + +a:hover, +a:focus { + color: var(--accent-color); +} + +a:hover, +a:active { + text-decoration-color: var(--accent-bg-color); +} + +a:focus { + outline: var(--border-width) solid var(--secondary-bg-color); + outline-offset: calc(var(--secondary-gap) / 3); + border-radius: calc(var(--radius) / 2); + text-decoration: none; +} + +a:active { + outline: var(--border-width) solid var(--accent-bg-color); + outline-offset: calc(var(--secondary-gap) / 3); +} + +body > footer a:hover, +body > footer a:focus { + color: var(--accent-bg-color); + text-decoration-color: var(--accent-bg-color); +} + +nav ul li > a:hover, +nav ul li > a:focus { + outline: var(--border-width) solid var(--secondary-bg-color); + outline-offset: calc(var(--secondary-gap) / 3); +} + +nav ul li > a:active { + outline: var(--border-width) solid var(--accent-bg-color); + outline-offset: calc(var(--secondary-gap) / 3); +} + +textarea { + background-color: var(--secondary-bg-color); + border: var(--border-width) solid var(--secondary-bg-color); + border-radius: var(--radius); +} + +/* Make images easier to work with */ +img, +picture { + max-width: 100%; + display: block; +} + +hr { + border: none; + border-top: 1px solid var(--secondary-color); + border-bottom: 1px solid black; +} + +select, +input[type="text"], +input[type="search"], +input[type="password"], +input[type="number"], +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); +} + +/** TODO reduce this selector */ +fieldset div:has(input[type="checkbox"]):not(.control-group), +fieldset div:has(input[type="radio"]):not(.control-group) { + padding: var(--secondary-gap); + border-radius: var(--radius); display: flex; + align-items: center; +} + +fieldset div:has(input[type="checkbox"]:checked + label), +fieldset div:has(input[type="radio"]:checked + label) { + background-color: var(--secondary-bg-color); +} + +input[type="checkbox"], +input[type="radio"] { + min-width: 1em; + min-height: 1em; +} + +fieldset { + border: var(--border-width) solid var(--secondary-bg-color); + border-radius: var(--radius); + margin: var(--gap) 0; + padding: var(--secondary-gap); +} + +textarea:not([cols]) { + min-width: 20em; +} + +textarea:not([rows]) { + min-height: 10em; +} + +label { + cursor: pointer; +} + +legend { + font-size: 1em; + background-color: var(--secondary-bg-color); + border-radius: var(--radius); + padding: var(--secondary-gap) calc(var(--secondary-gap) * 2); + text-transform: uppercase; +} + +/* + * Footer + */ + +body > footer { + padding: calc(var(--gap) * 2) 0; + display: grid; + grid-template-columns: 2fr 1fr 1fr 1fr 1fr; + grid-gap: var(--gap); +} + +@media (max-width: 30em) { + body > footer { + grid-template-columns: 1fr; + padding: var(--secondary-gap); + grid-gap: var(--secondary-gap); + } +} + +nav { + display: flex; + justify-content: space-between; + align-items: center; +} + +nav ul { + list-style: none; + display: flex; + gap: var(--secondary-gap); + padding: 0; + margin: 0; + align-items: flex-start; +} + +nav ul li { + border-radius: var(--radius); + min-width: 1.5em; + min-height: 1.5em; + display: flex; + align-items: center; + justify-content: center; +} + +nav ul li.selected { + background-color: var(--secondary-bg-color); + font-weight: bold; +} + +body > footer nav > ul { flex-direction: column; + align-items: flex-start; + gap: 0; +} + +body > footer nav > ul > li { + padding: var(--secondary-gap) 0; +} + +fieldset > select, +span > select { + color: rgb(49 49 51); +} + +/** +* Main content container +*/ +#contents { min-height: 100vh; - margin: auto; } -.h-box { - padding-left: 1em; - padding-right: 1em; +.container { + margin-right: auto; + margin-left: auto; + max-width: 100%; } -.v-box { - padding-top: 1em; - padding-bottom: 1em; +@media (min-width: 36em) { + .container { + max-width: 31.875em; + padding-right: 0; + padding-left: 0; + } +} + +@media (min-width: 48em) { + .container { + max-width: 43.75em; + } +} + +@media (min-width: 64em) { + .container { + max-width: 60em; + } +} + +@media (min-width: 80em) { + .container { + max-width: 75em; + } +} + +@media (min-width: 96em) { + .container { + max-width: 90em; + } +} + +body > header { + padding: var(--secondary-gap) 0; + opacity: 0.7; + transition: opacity 240ms ease-in-out; +} + +body > header:hover, +body > header:focus-within { + opacity: 1; +} + +time { + font-style: italic; + font-size: 0.95em; + text-wrap: nowrap; + cursor: help; +} + +/** +* Formatting for raw text blocks (comments, description) +*/ +.raw-text { + white-space: pre-wrap; } .deleted { - background-color: rgb(255, 0, 0, 0.5); + background-color: rgb(255 0 0 / 50%); } .underlined { - border-bottom: 1px solid; - margin-bottom: 20px; -} - -.title { - margin: 0.5em 0 1em 0; + border-bottom: 0.125rem solid; + margin-bottom: 1.25rem; } /* A flex container */ @@ -51,6 +395,7 @@ body { flex-flow: row wrap; justify-content: flex-start; } + .flex-right { display: flex; flex: 2 0 auto; @@ -58,84 +403,126 @@ body { justify-content: flex-end; } - /* * Channel page */ -.channel-profile > * { - font-size: 1.17em; - font-weight: bold; - vertical-align: middle; +.title { + display: flex; + justify-content: space-between; + gap: var(--secondary-gap); + margin: var(--secondary-gap) 0; +} + +@media (max-width: 30em) { + .title { + flex-direction: column; + } +} + +.channel-profile { + display: flex; + align-items: center; + gap: var(--secondary-gap); +} + +#channel-name { + margin: 0; +} + +.profile-pic { border-radius: 50%; } +.channel-profile > h3 { + font-size: 1.17em; + font-weight: bold; + vertical-align: middle; + text-wrap: nowrap; +} + +.channel-profile > h4 { + display: flex; + gap: var(--secondary-gap); +} + .channel-profile > img { - width: 48px; + width: 2rem; height: auto; + border-radius: 50%; } body a.channel-owner { - background-color: #008bec; - color: #fff; - border-radius: 9px; - padding: 1px 6px; + background-color: var(--accent-bg-color); + border-radius: var(--radius); + padding: 0 calc(var(--secondary-gap) * 2); +} + +body a.channel-owner:hover { + color: var(--fg-color); } .creator-heart-container { display: inline-block; - padding: 0px 7px 6px 0px; - margin: 0px -7px -4px 0px; + padding: 0 0.4375rem 0.375rem 0; + margin: 0 -0.4375rem -0.25rem 0; } .creator-heart { display: inline-block; position: relative; - width: 16px; - height: 16px; - border: 2px none; + width: 1rem; + height: 1rem; + border: 0.125rem none; } .creator-heart-background-hearted { - width: 16px; - height: 16px; - padding: 0px; + width: 1em; + height: 1em; + padding: 0; position: relative; } .creator-heart-small-hearted { position: absolute; - right: -7px; - bottom: -4px; + right: -0.4375rem; + bottom: -0.25rem; } .creator-heart-small-container { display: block; position: relative; - width: 13px; - height: 13px; - color: rgb(255, 0, 0); + width: 0.8125rem; + height: 0.8125rem; + color: rgb(255 0 0); +} + +.menu { + display: flex; + justify-content: space-between; + margin: var(--gap) 0; +} + +.menu li { + padding: 0; +} + +nav.filters { + margin: var(--gap) 0; } .feed-menu { display: flex; justify-content: center; flex-wrap: wrap; + gap: var(--secondary-gap); + margin: var(--gap) 0; } -.feed-menu-item { +.feed-menu li { text-align: center; -} - -@media screen and (max-width: 640px) { - .feed-menu-item { - flex: 0 0 40%; - } -} - -div { - overflow-wrap: break-word; - word-wrap: break-word; + background-color: var(--secondary-bg-color); + text-transform: uppercase; } .loading { @@ -143,46 +530,302 @@ div { animation: spin 2s linear infinite; } -.playlist-restricted { - height: 20em; - padding-right: 10px; +.subscription-menu { + display: flex; + justify-content: space-between; + align-items: center; + margin: var(--gap) 0; } +.subscription-header { + display: flex; + justify-content: space-between; + align-items: center; +} + +.subscribe { + background-color: var(--accent-bg-color); +} + +.unsubscribe { + background-color: var(--secondary-bg-color); +} + +.history-header { + display: flex; + justify-content: space-between; + align-items: center; +} + +.header-group { + display: flex; + justify-content: space-between; + align-items: center; +} + +.playlist-header { + display: flex; + justify-content: space-between; +} + +.playlist-meta { + display: flex; + justify-content: space-between; + align-items: center; + margin: var(--gap) 0; +} + +.playlist-meta ul { + list-style: none; + display: flex; + gap: var(--secondary-gap); + margin: 0; + padding: 0; +} + +.playlist-restricted { + height: 20em; + padding-right: 0.625rem; +} + +/* +* Watch +*/ +.watch-meta { + opacity: 0.6; + transition: opacity 96ms ease-in-out; +} + +.watch-meta:hover, +.watch-meta:focus-within { + opacity: 1; +} + +.watch-meta nav ul { + flex-direction: column; + align-items: flex-start; +} + +.watch-meta nav li { + padding: 0; +} + +.watch { + display: grid; + grid-template-columns: 1fr 3fr 1fr; + grid-gap: var(--gap); + max-width: 100%; +} + +@media (max-width: 30em) { + .watch { + grid-template-columns: 1fr; + padding: 0 var(--secondary-gap); + } +} + +.watch:has(details[open]) { + grid-template-columns: 3fr 1fr; +} + +/* Initially hide the content of the details section */ +details[open] ~ .watch-meta { + display: block; +} + +/* Hide the grid item when the details box is closed */ +details:not([open]) ~ .watch-meta { + display: none; +} + +.watch-context { + display: flex; + justify-content: space-between; + align-items: flex-start; +} + +.watch-context nav ul { + flex-direction: column; +} + +.video-header { + display: flex; + justify-content: space-between; +} + +.listen { + display: flex; + align-items: center; + font-size: 1.25em; +} + +.video-meta { + display: flex; + gap: var(--secondary-gap); + justify-content: space-between; +} + +@media (max-width: 30em) { + .video-meta { + flex-direction: column; + } +} + +.video-interaction-meta { + display: flex; + gap: var(--secondary-gap); + padding-inline: var(--secondary-gap); +} + +@media (max-width: 30em) { + .video-interaction-meta { + padding-inline: 0; + } +} + +.content-meta { + list-style: none; + margin: 0; + padding: 0; +} /* * Buttons */ -body a.pure-button { - color: rgba(0,0,0,.8); +button { + background-color: transparent; + border: none; +} + +button, +*[role="button"], +*[type="submit"], +summary { + line-height: 1; + font-size: inherit; + color: currentcolor; + border: unset; + border-radius: var(--radius); + padding: var(--secondary-gap) calc(var(--secondary-gap) * 2); + transition: outline-width 96ms ease-in-out; + outline-width: 0; + outline-style: solid; + outline-color: var(--secondary-bg-color); + outline-offset: calc(var(--border-width) / 1.5); + white-space: nowrap; +} + +/** prevent inner elements from being click targets */ +button > *, +*[role="button"] > *, +*[type="submit"] > * { + pointer-events: none; +} + +button:focus, +*[role="button"]:focus, +*[type="submit"]:focus, +*[type="number"]:focus, +summary:focus, +select:focus { + outline-width: var(--border-width); +} + +button:active, +*[role="button"]:active, +*[type="submit"]:active, +*[type="number"]:active, +summary:active, +select:active { + transform: translate(0.125rem 0.125rem); + outline-color: var(--accent-bg-color); +} + +button:hover, +*[role="button"]:hover, +*[type="submit"]:hover, +*[type="number"]:hover, +summary:hover, +select:hover { + outline-width: var(--border-width); +} + +a[role="button"] { + text-decoration: none; + color: currentcolor; } button.pure-button-primary, -body a.pure-button-primary, +a.pure-button-primary, .channel-owner:hover, .channel-owner:focus { - background-color: #a0a0a0; - color: rgba(35, 35, 35, 1); + background-color: var(--accent-bg-color); } -.pure-button-primary, +.primary, +.pure-button-primary { + font-weight: bold; + background-color: var(--accent-bg-color); +} + +.secondary, .pure-button-secondary { - border: 1px solid #a0a0a0; - border-radius: 3px; - margin: 0 .4em; + color: var(--secondary-color); + background-color: var(--secondary-bg-color); } -.pure-button-secondary.low-profile { - padding: 5px 10px; - margin: 0; +.pure-button-primary:hover, +.pure-button-primary:focus { + border-color: var(--accent-color); + background-color: var(--accent-bg-color); +} + +.secondary:hover, +.secondary:focus, +.pure-button-secondary:hover, +.pure-button-secondary:focus { + border-color: var(--secondary-color); + background-color: var(--secondary-bg-color); } -/* Has to be combined with flex-left/right */ .button-container { - flex-flow: wrap; - gap: 0.5em 0.75em; + display: flex; + gap: var(--secondary-gap); + align-items: center; } +.controls { + display: grid; + gap: var(--secondary-gap); +} + +.control-group { + display: grid; + grid-template-columns: 1fr 1fr; + gap: var(--secondary-gap); + place-items: center flex-start; +} + +.control-message { + display: flex; + align-items: center; + gap: var(--secondary-gap); +} + +.action-controls { + display: grid; + padding: var(--secondary-gap); + padding-left: calc(50% + (var(--secondary-gap) / 2)); + gap: var(--gap); + place-items: center flex-start; + background-color: var(--secondary-bg-color); + border-radius: var(--radius); +} + +.control-group label { + justify-self: flex-end; +} /* * Video thumbnails @@ -191,229 +834,436 @@ body a.pure-button-primary, div.thumbnail { position: relative; width: 100%; - box-sizing: border-box; + background-color: var(--secondary-bg-color); } img.thumbnail { - display: block; /* See: https://stackoverflow.com/a/11635197 */ width: 100%; + max-width: 100%; + max-height: 100%; object-fit: cover; aspect-ratio: 16 / 9; } .thumbnail-placeholder { - min-height: 50px; - border: 2px dotted; + min-height: calc(var(--gap) * 2); + background-color: var(--secondary-bg-color); } div.watched-overlay { z-index: 50; position: absolute; - top: 0; - left: 0; - right: 0; - bottom: 0; - background-color: rgba(255,255,255,.4); + inset: 0; + background-color: var(--watched-overlay-color); } div.watched-indicator { position: absolute; left: 0; bottom: 0; - height: 4px; + height: 0.25rem; width: 100%; - background-color: red; + background-color: var(--accent-bg-color); } -div.thumbnail > .top-left-overlay, -div.thumbnail > .bottom-right-overlay { - z-index: 100; - position: absolute; - padding: 0; - margin: 0; - font-size: 16px; +.top-left-overlay { + top: var(--secondary-gap); + left: var(--secondary-gap); } -.top-left-overlay { top: 0.6em; left: 0.6em; } -.bottom-right-overlay { bottom: 0.6em; right: 0.6em; } - .length { - padding: 1px; - margin: -2px 0; - color: #fff; - border-radius: 3px; + bottom: var(--secondary-gap); + right: var(--secondary-gap); + z-index: 100; + position: absolute; + padding: 0 var(--secondary-gap); + border-radius: var(--radius); + color: white; + background-color: hsl(175deg 0 0% / 90%); + line-height: 1; } -.length, .top-left-overlay button { - color: #eee; - background-color: rgba(35, 35, 35, 0.85) !important; +div.thumbnail > .top-left-overlay { + z-index: 100; + position: absolute; } +.length, +.top-left-overlay button { + opacity: 0.9; +} + +/* +* Related videos +*/ + +.related-videos { + display: grid; + grid-gap: var(--gap); + grid-template-columns: 18rem; +} + +/* +* Videos +*/ + +.videos { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(20em, 1fr)); + grid-gap: var(--gap); +} + +.notifications { + margin-bottom: var(--gap); +} + +/* +* Video card +*/ + +.video-card { + display: flex; + gap: var(--secondary-gap); + flex-direction: column; +} + +.video-card h3, +.video-card h4 { + margin: 0; + font-weight: normal; + line-height: 1.1; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.video-card p { + font-size: 0.9em; +} + +.video-meta-sub { + display: grid; + grid-template-columns: auto auto; + justify-content: space-between; + align-items: center; + opacity: 0.7; + transition: opacity 96ms ease-in-out; +} + +.video-meta-sub h3 { + margin: 0; +} + +.video-meta-sub:hover, +.video-meta-sub:focus-within { + opacity: 1; +} + +.video-card.hide { + opacity: 0.3; + pointer-events: none; +} /* * Navbar */ .navbar { - margin: 1em 0; - display: flex; /* this is also defined in framework, but in case of future changes */ + display: grid; + grid-template-columns: 1fr 2fr 1fr; + grid-template-areas: "index-link site-search user-nav"; align-items: center; + grid-gap: var(--secondary-gap); justify-content: space-between; } -.navbar > div { - flex: 1; +@media (max-width: 30em) { + .navbar { + grid-template-columns: 1fr 1fr; + grid-template-areas: + "index-link user-nav" + "site-search site-search"; + } } -.searchbar { - flex-grow: 2; /* take double the space of the other items */ +.navbar ul:nth-child(3) { + justify-self: flex-end; } -.navbar a { - padding: 0; /* this way it will stay aligned with content under */ -} - -.navbar .index-link { - font-weight: bold; - display: inline; -} - -.searchbar .pure-form { - display: flex; -} - -.searchbar .pure-form fieldset { +.navbar li { padding: 0; - flex: 1; + text-wrap: nowrap; } -.searchbar input[type="search"] { - width: 100%; - margin: 1px; - - border: 1px solid; - border-color: rgba(0,0,0,0); - border-bottom-color: #CCC; - border-radius: 0; - - box-shadow: none; - appearance: none; - -webkit-appearance: none; +#site-search { + display: flex; + justify-content: center; + grid-area: site-search; } -.searchbar input[type="search"]:focus { - margin: 0; - border: 2px solid; - border-color: rgba(0,0,0,0); - border-bottom-color: #FED; +#index-link { + grid-area: index-link; +} + +#user-nav { + grid-area: user-nav; + display: flex; + align-items: center; +} + +@media (max-width: 30em) { + #index-link { + margin-left: var(--secondary-gap); + } + + #user-nav { + margin-right: var(--secondary-gap); + } +} + +.login, +.logout { + all: unset; +} + +.login:hover, +.logout:hover, +.login:focus, +.logout:focus { + color: var(--accent-color); +} + +.login:hover, +.login:active, +.logout:hover, +.logout:active { + text-decoration-color: var(--accent-bg-color); +} + +.search-box { + display: flex; + align-items: center; + position: relative; +} + +.search-box:focus-within button { + transform: translateX(3em); +} + +.search-box button { + position: absolute; + left: 19em; + transition: transform 96ms ease-in-out; +} + +@media (max-width: 30em) { + .search-box button { + display: none; + } +} + +.search { + width: 20em; +} + +input[type="search"]:hover, +input[type="text"]:hover, +input[type="password"]:hover, +textarea:hover { + outline: var(--border-width) solid var(--secondary-bg-color); + outline-offset: var(--border-width); +} + +input[type="search"]:focus, +input[type="text"]:focus, +input[type="password"]:focus, +textarea:focus { + outline: var(--border-width) solid var(--accent-color); + outline-offset: var(--border-width); } /* https://stackoverflow.com/a/55170420 */ input[type="search"]::-webkit-search-cancel-button { - -webkit-appearance: none; - height: 14px; - width: 14px; - background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB4AAAAeCAYAAAA7MK6iAAAAn0lEQVR42u3UMQrDMBBEUZ9WfQqDmm22EaTyjRMHAlM5K+Y7lb0wnUZPIKHlnutOa+25Z4D++MRBX98MD1V/trSppLKHqj9TTBWKcoUqffbUcbBBEhTjBOV4ja4l4OIAZThEOV6jHO8ARXD+gPPvKMABinGOrnu6gTNUawrcQKNCAQ7QeTxORzle3+sDfjJpPCqhJh7GixZq4rHcc9l5A9qZ+WeBhgEuAAAAAElFTkSuQmCC); - background-size: 14px; + height: 1em; + width: 1em; + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB4AAAAeCAYAAAA7MK6iAAAAn0lEQVR42u3UMQrDMBBEUZ9WfQqDmm22EaTyjRMHAlM5K+Y7lb0wnUZPIKHlnutOa+25Z4D++MRBX98MD1V/trSppLKHqj9TTBWKcoUqffbUcbBBEhTjBOV4ja4l4OIAZThEOV6jHO8ARXD+gPPvKMABinGOrnu6gTNUawrcQKNCAQ7QeTxORzle3+sDfjJpPCqhJh7GixZq4rHcc9l5A9qZ+WeBhgEuAAAAAElFTkSuQmCC"); + background-size: 1em; } -.searchbar #searchbutton { - border: none; - background: none; - margin-top: 0; +/* #searchbutton { */ +/* border: none; */ +/* background: none; */ +/* margin-top: 0; */ +/* } */ +/**/ +/* #searchbutton:hover { */ +/* color: var(--accent-color); */ +/* } */ +.search-action { + background-color: var(--secondary-bg-color); + padding: var(--secondary-gap); } -.searchbar #searchbutton:hover { - color: rgb(0, 182, 240); -} - -.user-field { - display: flex; - flex-direction: row; - justify-content: flex-end; - align-items: center; -} - -.user-field div { - width: auto; -} - -.user-field div:not(:last-child) { - margin-right: 1em; -} - - /* * Responsive rules */ -@media only screen and (max-aspect-ratio: 16/9) { - .player-dimensions.vjs-fluid { - padding-top: 46.86% !important; - } - - #player-container { - padding-bottom: 46.86% !important; - } -} - -@media screen and (max-width: 767px) { - .navbar { - flex-direction: column; - } - - .navbar > div { - display: flex; - justify-content: center; - margin-bottom: 25px; - } - - .navbar > .searchbar > form { - width: 75%; - } - - h1 { - font-size: 1.25em; - margin: 0.42em 0; - } - - /* Space out the subscribe & RSS buttons and align them to the left */ - .title.flexible { display: block; } - .title.flexible > .flex-right { margin: 0.75em 0; justify-content: flex-start; } - - /* Space out buttons to make them easier to tap */ - .user-field { font-size: 125%; } - .user-field > :not(:last-child) { margin-right: 1.75em; } - - .icon-buttons { font-size: 125%; } - .icon-buttons > :not(:last-child) { margin-right: 0.75em; } -} - -@media screen and (max-width: 320px) { - .navbar > .searchbar > form { - width: 100%; - margin: 0 1em; - } -} - - +/* @media only screen and (max-aspect-ratio: 16/9) { */ +/* .player-dimensions.vjs-fluid { */ +/* padding-top: 46.86%; */ +/* } */ +/**/ +/* #player-container { */ +/* padding-bottom: 46.86%; */ +/* } */ +/* } */ +/**/ /* * Video "cards" (results/playlist/channel videos) */ -.video-card-row { margin: 15px 0; } +.video-card-row { + margin: 0.9375rem 0; +} -p.channel-name { margin: 0; } -p.video-data { margin: 0; font-weight: bold; font-size: 80%; } +.item-block h3 { + font-weight: normal; +} +.icon-buttons ul { + gap: 0; +} + +.icon-buttons li { + padding: 0; +} + +.icon-buttons li a { + padding: var(--secondary-gap); +} /* * Comments & community posts */ +.community { + margin: var(--gap) auto; +} + +.comments-header { + font-size: 0.9em; + margin: var(--gap) 0; +} + +.comments-header h3 { + font-weight: normal; +} + +.comments-header li { + display: flex; + padding: 0; + align-items: center; + gap: var(--secondary-gap); + text-wrap: nowrap; +} + +@media (max-width: 30em) { + .comments-header ul { + flex-direction: column; + } +} + .comments { - max-width: 800px; - margin: auto; + max-width: 50rem; + display: grid; + grid-gap: var(--gap); + justify-content: stretch; +} + +.replies { + padding-left: var(--secondary-gap); + border-left: 0.2em solid var(--secondary-bg-color); +} + +@media (max-width: 30em) { + .replies { + padding-left: var(--secondary-gap); + } +} + +.comment { + display: flex; + flex-direction: column; +} + +.comment h4 { + margin: 0; + font-size: 1em; + opacity: 0.6; + transition: opacity 96ms ease-in-out; +} + +.comment h4:hover { + opacity: 1; +} + +.comment-header { + display: grid; + grid-auto-flow: column; + align-items: center; + gap: var(--gap); + justify-content: space-between; + font-size: 0.9em; +} + +@media (max-width: 30em) { + .comment-header { + gap: var(--secondary-gap); + justify-content: stretch; + } + + .comment-header .comment-meta-sub { + justify-self: flex-end; + } +} + +ul.comment-meta-sub { + display: flex; + list-style: none; + gap: var(--secondary-gap); + padding: 0; + margin: 0; + opacity: 0.7; + transition: opacity 240ms ease-in-out; +} + +ul.comment-meta-sub:hover, +ul.comment-meta-sub:focus { + opacity: 1; +} + +.comment-meta-sub li { + text-wrap: nowrap; +} + +.replies button { + font-size: 0.95em; +} + +.comment-temporal { + display: flex; + gap: var(--secondary-gap); + align-items: center; + align-content: center; +} + +.comment-temporal time { + opacity: 0.7; + transition: opacity 96ms ease-in-out; +} + +.comment-temporal time:hover { + opacity: 1; } /* @@ -439,344 +1289,61 @@ p.video-data { margin: 0; font-weight: bold; font-size: 80%; } border: none; } - /* * Page navigation */ -.page-nav-container { margin: 15px 0 30px 0; } - -.page-prev-container { text-align: start; } -.page-next-container { text-align: end; } - -.page-prev-container, -.page-next-container { - display: inline-block; -} - - -/* - * Footer - */ - -footer { - margin-top: auto; - padding: 1.5em 0; - text-align: center; - max-height: 30vh; -} - -.light-theme footer { - color: #7c7c7c; -} - -.dark-theme footer { - color: #adadad; -} - -.light-theme footer a { - color: #7c7c7c !important; -} - -.dark-theme footer a { - color: #adadad !important; -} - -footer span { - margin: 4px 0; - display: block; -} - -/* keyframes */ - -@keyframes spin { - 0% { - transform: rotate(0deg); - } - 100% { - transform: rotate(360deg); - } -} - -fieldset > select, -span > select { - color: rgba(49, 49, 51, 1); +.pagination { + margin: calc(var(--gap) / 2) 0; } .pure-control-group label { word-wrap: normal; } - -/* - * Light theme - */ - -.light-theme a:hover, -.light-theme a:active, -.light-theme summary:hover, -.light-theme a:focus, -.light-theme summary:focus { - color: #075A9E !important; -} - -.light-theme .pure-button-primary:hover, -.light-theme .pure-button-primary:focus, -.light-theme .pure-button-secondary:hover, -.light-theme .pure-button-secondary:focus { - color: #fff !important; - border-color: rgba(0, 182, 240, 0.75) !important; - background-color: rgba(0, 182, 240, 0.75) !important; -} - -.light-theme .pure-button-secondary:not(.low-profile) { - color: #335d7a; - background-color: #fff2; -} - -.light-theme a { - color: #335d7a; - text-decoration: none; -} - -/* All links that do not fit with the default color goes here */ -.light-theme a:not([data-id]) > .icon, -.light-theme .pure-u-lg-1-5 > .h-box > a[href^="/watch?"], -.light-theme .playlist-restricted > ol > li > a { - color: #303030; -} - -.light-theme .pure-menu-heading { - color: #565d64; -} - -@media (prefers-color-scheme: light) { - .no-theme a:hover, - .no-theme a:active, - .no-theme summary:hover, - .no-theme a:focus, - .no-theme summary:focus { - color: #075A9E !important; - } - - .no-theme .pure-button-primary:hover, - .no-theme .pure-button-primary:focus, - .no-theme .pure-button-secondary:hover, - .no-theme .pure-button-secondary:focus { - color: #fff !important; - border-color: rgba(0, 182, 240, 0.75) !important; - background-color: rgba(0, 182, 240, 0.75) !important; - } - - .no-theme .pure-button-secondary:not(.low-profile) { - color: #335d7a; - background-color: #fff2; - } - - .no-theme a { - color: #335d7a; - text-decoration: none; - } - - /* All links that do not fit with the default color goes here */ - .no-theme a:not([data-id]) > .icon, - .no-theme .pure-u-lg-1-5 > .h-box > a[href^="/watch?"], - .no-theme .playlist-restricted > ol > li > a { - color: #303030; - } - - .no-theme footer { - color: #7c7c7c; - } - - .no-theme footer a { - color: #7c7c7c !important; - } - - .light-theme .pure-menu-heading { - color: #565d64; - } -} - - -/* - * Dark theme - */ - -.dark-theme a:hover, -.dark-theme a:active, -.dark-theme summary:hover, -.dark-theme a:focus, -.dark-theme summary:focus { - color: rgb(0, 182, 240); -} - -.dark-theme .pure-button-primary:hover, -.dark-theme .pure-button-primary:focus, -.dark-theme .pure-button-secondary:hover, -.dark-theme .pure-button-secondary:focus { - color: #fff !important; - border-color: rgb(0, 182, 240) !important; - background-color: rgba(0, 182, 240, 1) !important; -} - -.dark-theme .pure-button-secondary { - background-color: #0002; - color: #ddd; -} - -.dark-theme a { - color: #adadad; - text-decoration: none; -} - -body.dark-theme { - background-color: rgba(35, 35, 35, 1); - color: #f0f0f0; -} - -.dark-theme .pure-form legend { - color: #f0f0f0; -} - -.dark-theme .pure-menu-heading { - color: #f0f0f0; -} - -.dark-theme input, -.dark-theme select, -.dark-theme textarea { - color: rgba(35, 35, 35, 1); -} - -.dark-theme .pure-form input[type="file"] { - color: #f0f0f0; -} - -.dark-theme .searchbar input { - background-color: inherit; - color: inherit; -} - -@media (prefers-color-scheme: dark) { - .no-theme a:hover, - .no-theme a:active, - .no-theme a:focus { - color: rgb(0, 182, 240); - } - - .no-theme .pure-button-primary:hover, - .no-theme .pure-button-primary:focus, - .no-theme .pure-button-secondary:hover, - .no-theme .pure-button-secondary:focus { - color: #fff !important; - border-color: rgb(0, 182, 240) !important; - background-color: rgba(0, 182, 240, 1) !important; - } - - .no-theme .pure-button-secondary { - background-color: #0002; - color: #ddd; - } - - .no-theme a { - color: #adadad; - text-decoration: none; - } - - body.no-theme { - background-color: rgba(35, 35, 35, 1); - color: #f0f0f0; - } - - .no-theme .pure-form legend { - color: #f0f0f0; - } - - .no-theme .pure-menu-heading { - color: #f0f0f0; - } - - .no-theme input, - .no-theme select, - .no-theme textarea { - color: rgba(35, 35, 35, 1); - } - - .no-theme .pure-form input[type="file"] { - color: #f0f0f0; - } - - .no-theme .searchbar input { - background-color: inherit; - color: inherit; - } - - .no-theme footer { - color: #adadad; - } - - .no-theme footer a { - color: #adadad !important; - } -} - - /* * Miscellanous */ - -/*With commit d9528f5 all contents of the page is now within a flexbox. However, -the hr element is rendered improperly within one. -See https://stackoverflow.com/a/34372979 for more info */ -hr { - margin: 10px 0 10px 0; -} - -/* Description Expansion Styling*/ -#descexpansionbutton, +/* Description Expansion Styling */ +#description-expansion, #music-desc-expansion { display: none; } -#descexpansionbutton ~ div { +#description-expansion ~ p, +#music-desc-expansion ~ p { overflow: hidden; } -#descexpansionbutton:not(:checked) ~ div { +#description-expansion:not(:checked) ~ p { max-height: 8.3em; } -#descexpansionbutton:checked ~ div { +#description-expansion:checked ~ p { overflow: unset; height: 100%; } -#descexpansionbutton ~ label { +#description-expansion ~ label { order: 1; - margin-top: 20px; + margin-top: 1.25rem; } -label[for="descexpansionbutton"]:hover, +label[for="description-expansion"]:hover, label[for="music-desc-expansion"]:hover { cursor: pointer; } /* Bidi (bidirectional text) support */ -h1, h2, h3, h4, h5, p, -#descriptionWrapper, +.raw-text, +#description, #description-box, #music-description-box { unicode-bidi: plaintext; text-align: start; } -#descriptionWrapper { - max-width: 600px; - white-space: pre-wrap; -} - #music-description-box { display: none; } @@ -797,22 +1364,56 @@ h1, h2, h3, h4, h5, p, /* Select all the music items except the first one */ .music-item + .music-item { - border-top: 1px solid #ffffff; + border-top: 1px solid white; } /* Center the "invidious" logo on the search page */ -#logo > h1 { text-align: center; } - -/* IE11 fixes */ -:-ms-input-placeholder { color: #888; } - -/* Wider settings name to less word wrap */ -.pure-form-aligned .pure-control-group label { width: 19em; } +#logo > h1 { + text-align: center; +} .channel-emoji { - margin: 0 2px; + margin: 0 0.125rem; } #download_widget { - width: 100%; + width: 100%; +} + +.watch-action-group { + margin: var(--gap) 0; + display: grid; + grid-gap: var(--secondary-gap); +} + +.subscriptions { + display: grid; + grid-template-columns: 1fr 1fr 1fr; + grid-gap: var(--gap); +} + +.subscriptions > div { + border: 0.15em solid var(--secondary-bg-color); + border-radius: var(--radius); + padding: 1em; +} + +.secondary-button { + border: none; + padding: 0.6em 1em; + background-color: var(--secondary-bg-color); + color: var(--secondary-color); +} + +fieldset.preferences, +fieldset.form { + display: grid; + grid-gap: var(--secondary-gap); + padding-block: var(--gap); + margin: var(--gap) 0; +} + +#views, +#likes { + text-wrap: nowrap; } diff --git a/assets/css/embed.css b/assets/css/embed.css index cbafcfea..74188b69 100644 --- a/assets/css/embed.css +++ b/assets/css/embed.css @@ -10,18 +10,18 @@ } .watch-on-invidious { - font-size: 1.3em !important; + font-size: 1.3em; font-weight: bold; white-space: nowrap; - margin: 0 1em 0 1em !important; + margin: 0 1em; order: 3; } +/**/ +/* .watch-on-invidious > a { */ +/* color: white; */ +/* } */ -.watch-on-invidious > a { - color: white; -} - -.watch-on-invidious > a:hover, -.watch-on-invidious > a:focus { - color: rgba(0, 182, 240, 1);; -} +/* .watch-on-invidious > a:hover, */ +/* .watch-on-invidious > a:focus { */ +/* color: ; */ +/* } */ diff --git a/assets/css/empty.css b/assets/css/empty.css index 6ad1515d..94ff4aab 100644 --- a/assets/css/empty.css +++ b/assets/css/empty.css @@ -1,16 +1,32 @@ + +/* Widget covers the whole page */ #search-widget { - text-align: center; - margin: 20vh 0 50px 0; + position: absolute; + height: 100%; + width: 100%; + display: grid; + justify-content: center; + grid-gap: var(--gap); + top: 0; + left: 0; + pointer-events: none; } -#logo > h1 { - font-size: 3.5em; - margin: 0; - padding: 0; +.search-homepage { + display: grid; + grid-template-rows: 1fr 1fr; } -@media screen and (max-width: 1500px) and (max-height: 1000px) { - #logo > h1 { - font-size: 10vmin; - } +#search-widget > h1 { + align-self: flex-end; + font-size: 3em; + text-transform: uppercase; + margin: 0; + padding: 0; + text-align: center; +} + +.searchbar { + /* reset pointer events for interactive components */ + pointer-events: initial; } diff --git a/assets/css/player.css b/assets/css/player.css index 9cb400ad..ff730a3d 100644 --- a/assets/css/player.css +++ b/assets/css/player.css @@ -1,92 +1,106 @@ +.video-js { + font-family: inherit; + font-size: inherit; +} + /* Youtube player style */ -.video-js.player-style-youtube .vjs-progress-control { - height: 0; -} +/* .video-js.player-style-youtube .vjs-progress-control { */ +/* 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 { - position: absolute; - right: 0; - left: 0; - width: 100%; - margin: 0; -} +/* .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: 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 { - background: linear-gradient(rgba(0,0,0,0.1), rgba(0, 0, 0,0.5)); -} - -.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; -} +/* .video-js .vjs-text-track-display > div > div > div { */ +/* background-color: rgb(0 0 0 / 75%); */ +/* border-radius: 0.5em; */ +/* padding: 0.3em; */ +/* } */ .vjs-play-control, .vjs-volume-panel, @@ -123,143 +137,252 @@ ul.vjs-menu-content::-webkit-scrollbar { order: 7; } -.vjs-playback-rate > .vjs-menu { - width: 50px; +/* .vjs-playback-rate > .vjs-menu { */ +/* 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; flex-direction: row; 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 { display: none; } -.video-js .vjs-icon-cog { - font-size: 18px; +.vjs-playback-rate .vjs-playback-rate-value { + font-size: 1em; + line-height: 3rem; } -.video-js .vjs-control-bar, -.vjs-menu-button-popup .vjs-menu .vjs-menu-content { - background-color: rgba(35, 35, 35, 0.75); +.vjs-button > .vjs-icon-placeholder::before { + font-size: 1.25em; + line-height: 3rem; } -.vjs-menu li.vjs-menu-item:focus, -.vjs-menu li.vjs-menu-item:hover { - background-color: rgba(255, 255, 255, 0.75); - color: rgba(49, 49, 51, 0.75); +.vjs-menu li { + font-size: 1em; } -.vjs-menu li.vjs-selected, -.vjs-menu li.vjs-selected:focus, -.vjs-menu li.vjs-selected:hover { - background-color: rgba(0, 182, 240, 0.75); +.video-js .vjs-control { + width: 3em; } -/* Progress Bar */ -.video-js .vjs-slider { - background-color: rgba(15, 15, 15, 0.5); +.video-js .vjs-time-control { + width: auto; } -.video-js .vjs-load-progress, -.video-js .vjs-load-progress div { - background: rgba(87, 87, 88, 1); +.vjs-poster { + background-size: cover; } +.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 button:hover { - color: rgba(0, 182, 240, 1); + background-color: var(--accent-bg-color); } .video-js.player-style-invidious .vjs-play-progress { - background-color: rgba(0, 182, 240, 1); + background-color: var(--accent-bg-color); } -/* Overlay */ -.video-js .vjs-overlay { - background-color: rgba(35, 35, 35, 0.75) !important; -} -.video-js .vjs-overlay * { - color: rgba(255, 255, 255, 1) !important; - text-align: center; +.vjs-modal-dialog .vjs-modal-dialog-content { + padding: var(--gap); } -/* ProgressBar marker */ -.vjs-marker { - background-color: rgba(255, 255, 255, 1); - z-index: 0; -} - -/* 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 { - background-color: rgba(35, 35, 35, 0.75); +.video-js .vjs-big-play-button:hover { + opacity: 0.9; } -.video-js .vjs-current-time, -.video-js .vjs-time-divider, -.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; - } +.vjs-control-bar { + background-color: var(--secondary-bg-color-dark); } diff --git a/assets/css/pure-fix.css b/assets/css/pure-fix.css new file mode 100644 index 00000000..4aceaa96 --- /dev/null +++ b/assets/css/pure-fix.css @@ -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; +} diff --git a/assets/css/search.css b/assets/css/search.css index 7036fd28..5a6e7fb9 100644 --- a/assets/css/search.css +++ b/assets/css/search.css @@ -1,121 +1,111 @@ summary { - /* This should hide the marker */ - display: block; - - font-size: 1.17em; - font-weight: bold; - margin: 0 auto 10px auto; - cursor: pointer; + display: block; + font-size: 1.17em; + margin: 0 auto 0.625em; + cursor: pointer; } summary::-webkit-details-marker, -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; +summary::marker { + display: none; } -details[open] > summary:before { content: "[ − ]"; } - - -#filters-box { - padding: 10px 20px 20px 10px; - margin: 10px 15px; +summary::before { + content: "+ "; + text-align: center; } -#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; - flex-wrap: wrap; - flex-direction: row; - align-items: flex-start; - align-content: flex-start; - justify-content: flex-start; + gap: var(--secondary-gap); } - -fieldset, legend { - display: contents !important; - border: none !important; - margin: 0 !important; - padding: 0 !important; -} - - -.filter-column { - display: inline-block; - display: inline-flex; - width: max-content; - min-width: max-content; - max-width: 16em; - margin: 15px; - flex-grow: 2; - flex-basis: auto; - flex-direction: column; -} -.filter-name, .filter-options { - display: block; - padding: 5px 10px; - margin: 0; - text-align: start; -} - -.filter-options div { margin: 6px 0; } -.filter-options div * { vertical-align: middle; } -.filter-options label { margin: 0 10px; } - - -#filters-apply { - text-align: right; /* IE11 only */ - text-align: end; /* Override for compatible browsers */ -} +/* #filters-box { */ +/* background-color: var(--secondary-bg-color); */ +/* } */ +/**/ +/* #filters-flex { */ +/* display: flex; */ +/* flex-flow: row wrap; */ +/* align-items: flex-start; */ +/* place-content: flex-start flex-start; */ +/* } */ +/**/ +/* .filter-column { */ +/* display: inline-block; */ +/* display: inline-flex; */ +/* width: max-content; */ +/* min-width: max-content; */ +/* max-width: 16em; */ +/* margin: 0.9375rem; */ +/* flex-grow: 2; */ +/* flex-basis: auto; */ +/* flex-direction: column; */ +/* } */ +/**/ +/* .filter-name, */ +/* .filter-options { */ +/* display: block; */ +/* padding: 0.3125rem 0.625rem; */ +/* margin: 0; */ +/* text-align: start; */ +/* } */ +/**/ +/* .filter-options div { */ +/* margin: 0.375rem 0; */ +/* } */ +/**/ +/* .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 */ .no-results-error { - text-align: center; - line-height: 180%; - font-size: 110%; - padding: 15px 15px 125px 15px; + text-align: center; + font-size: 1.1em; + padding: 1em 1em 8em; } -/* Responsive rules */ - -@media only screen and (max-width: 800px) { - summary { font-size: 1.30em; } - #filters-box { - margin: 10px 0 0 0; - padding: 0; - } - #filters-apply { - text-align: center; - padding: 15px; - } -} - -/* 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; - } -} +/* @media only screen and (max-width: 50px) { */ +/* summary { */ +/* font-size: 1.3em; */ +/* } */ +/**/ +/* #filters-box { */ +/* margin: 0.6em 0 0; */ +/* padding: 0; */ +/* } */ +/**/ +/* #filters-apply { */ +/* text-align: center; */ +/* padding: 1em; */ +/* } */ +/* } */ +/**/ diff --git a/assets/css/theme-catppuccin-latte.css b/assets/css/theme-catppuccin-latte.css new file mode 100644 index 00000000..e541fca5 --- /dev/null +++ b/assets/css/theme-catppuccin-latte.css @@ -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; +} diff --git a/assets/css/theme-catppuccin-macchiato.css b/assets/css/theme-catppuccin-macchiato.css new file mode 100644 index 00000000..45d3e632 --- /dev/null +++ b/assets/css/theme-catppuccin-macchiato.css @@ -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; +} diff --git a/assets/css/theme-dracula.css b/assets/css/theme-dracula.css new file mode 100644 index 00000000..69331e02 --- /dev/null +++ b/assets/css/theme-dracula.css @@ -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; +} diff --git a/assets/css/theme-tokyonight.css b/assets/css/theme-tokyonight.css new file mode 100644 index 00000000..654d24ce --- /dev/null +++ b/assets/css/theme-tokyonight.css @@ -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; +} diff --git a/assets/css/theme.css b/assets/css/theme.css new file mode 100644 index 00000000..57ab937b --- /dev/null +++ b/assets/css/theme.css @@ -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); +} diff --git a/assets/js/comments.js b/assets/js/comments.js index 35ffa96e..57c99f52 100644 --- a/assets/js/comments.js +++ b/assets/js/comments.js @@ -1,6 +1,6 @@ var video_data = JSON.parse(document.getElementById('video_data').textContent); -var spinnerHTML = '

'; +var spinnerHTML = '
'; var spinnerHTMLwithHR = spinnerHTML + '
'; String.prototype.supplant = function (o) { @@ -11,14 +11,14 @@ String.prototype.supplant = function (o) { }; function toggle_comments(event) { - var target = event.target; - var body = target.parentNode.parentNode.parentNode.children[1]; - if (body.style.display === 'none') { - target.textContent = '[ − ]'; - body.style.display = ''; + const target = event.target; + const comments = document.querySelector(".comments"); + if (comments.style.display === 'none') { + target.textContent = '−'; + comments.style.display = ''; } else { - target.textContent = '[ + ]'; - body.style.display = 'none'; + target.textContent = '+'; + comments.style.display = 'none'; } } @@ -39,6 +39,7 @@ function hide_youtube_replies(event) { function show_youtube_replies(event) { var target = event.target; + console.log(target); var sub_text = target.getAttribute('data-inner-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'}, { on200: function (response) { var commentInnerHtml = ' \ -
\ -

\ - [ − ] \ +

\ - \ - ' + \ + \ +
  • ' if (video_data.support_reddit) { - commentInnerHtml += ' \ + commentInnerHtml += ' \ ' } - commentInnerHtml += ' \ -
  • \ -
    {contentHtml}
    \ -
    ' + commentInnerHtml += ' \ + \ + \ +
    {contentHtml}
    ' commentInnerHtml = commentInnerHtml.supplant({ contentHtml: response.contentHtml, redditComments: video_data.reddit_comments_text, @@ -104,9 +106,9 @@ function get_youtube_comments() { }) }); 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) { - comments.children[0].children[1].children[0].onclick = swap_comments; + comments.children[1].children[1].onclick = swap_comments; } }, onNon200: onNon200, // declared above @@ -122,7 +124,7 @@ function get_youtube_comments() { function get_youtube_replies(target, load_more, load_replies) { var continuation = target.getAttribute('data-continuation'); - var body = target.parentNode.parentNode; + var body = target.parentNode; var fallback = body.innerHTML; body.innerHTML = spinnerHTML; 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, {}, { on200: function (response) { if (load_more) { - body = body.parentNode.parentNode; + body = body.parentNode; body.removeChild(body.lastElementChild); body.insertAdjacentHTML('beforeend', response.contentHtml); } else { body.removeChild(body.lastElementChild); - var p = document.createElement('p'); - var a = document.createElement('a'); - p.appendChild(a); + var div = document.createElement('div'); + var button = document.createElement('button'); + div.appendChild(button); - a.href = 'javascript:void(0)'; - a.onclick = hide_youtube_replies; - a.setAttribute('data-sub-text', video_data.hide_replies_text); - a.setAttribute('data-inner-text', video_data.show_replies_text); - a.textContent = video_data.hide_replies_text; + button.onclick = hide_youtube_replies; + button.setAttribute('data-sub-text', video_data.hide_replies_text); + button.setAttribute('data-inner-text', video_data.show_replies_text); + button.textContent = video_data.hide_replies_text; var div = document.createElement('div'); div.innerHTML = response.contentHtml; - body.appendChild(p); body.appendChild(div); } }, @@ -171,4 +171,4 @@ function get_youtube_replies(target, load_more, load_replies) { body.innerHTML = fallback; } }); -} \ No newline at end of file +} diff --git a/assets/js/handlers.js b/assets/js/handlers.js index 539974fb..da58d7fd 100644 --- a/assets/js/handlers.js +++ b/assets/js/handlers.js @@ -58,13 +58,13 @@ el.onclick = function () { mark_unwatched(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) { - 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) { - el.onclick = function () { remove_playlist_item(el); }; + el.onclick = function (e) { remove_playlist_item(e); }; }); document.querySelectorAll('[data-onclick="revoke_token"]').forEach(function (el) { el.onclick = function () { revoke_token(el); }; diff --git a/assets/js/notifications.js b/assets/js/notifications.js index 55b7a15c..2c8552c8 100644 --- a/assets/js/notifications.js +++ b/assets/js/notifications.js @@ -1,131 +1,197 @@ -'use strict'; -var notification_data = JSON.parse(document.getElementById('notification_data').textContent); +"use strict"; +var notification_data = JSON.parse( + document.getElementById("notification_data").textContent, +); /** Boolean meaning 'some tab have stream' */ -const STORAGE_KEY_STREAM = 'stream'; +const STORAGE_KEY_STREAM = "stream"; /** 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_mock = { close: function () { } }; +var notifications_mock = { close: function () {} }; -function get_subscriptions() { - helpers.xhr('GET', '/api/v1/auth/subscriptions', { +async function get_subscriptions_call() { + return new Promise((resolve) => { + helpers.xhr( + "GET", + "/api/v1/auth/subscriptions", + { retries: 5, - entity_name: 'subscriptions' - }, { - on200: create_notification_stream - }); + entity_name: "subscriptions", + }, + { + 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) { - // 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 - notifications = new SSE( - '/api/v1/auth/notifications', { - withCredentials: true, - payload: 'topics=' + subscriptions.map(function (subscription) { return subscription.authorId; }).join(','), - headers: { 'Content-Type': 'application/x-www-form-urlencoded' } - }); - delivered = []; + // 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 + notifications = new SSE("/api/v1/auth/notifications", { + withCredentials: true, + payload: + "topics=" + + subscriptions + .map(function (subscription) { + 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) { - if (!event.id) return; + notifications.onmessage = function (event) { + if (!event.id) return; - var notification = JSON.parse(event.data); - console.info('Got notification:', notification); + var notification = JSON.parse(event.data); + console.info("Got notification:", notification); - // Ignore not actual and delivered notifications - if (start_time > notification.published || delivered.includes(notification.videoId)) return; + // Ignore not actual and delivered notifications + 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; - notification_count++; - helpers.storage.set(STORAGE_KEY_NOTIF_COUNT, notification_count); + let notification_count = helpers.storage.get(STORAGE_KEY_NOTIF_COUNT) || 0; + 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 - if (window.Notification && Notification.permission === 'granted') { - var notification_text = notification.liveNow ? notification_data.live_now_text : notification_data.upload_text; - notification_text = notification_text.replace('`x`', notification.author); + // permission for notifications handled on settings page. JS handler is in handlers.js + if (window.Notification && Notification.permission === "granted") { + var notification_text = notification.liveNow + ? notification_data.live_now_text + : notification_data.upload_text; + notification_text = notification_text.replace("`x`", notification.author); - var system_notification = new Notification(notification_text, { - body: notification.title, - icon: '/ggpht' + new URL(notification.authorThumbnails[2].url).pathname, - img: '/ggpht' + new URL(notification.authorThumbnails[4].url).pathname - }); + var system_notification = new Notification(notification_text, { + body: notification.title, + icon: "/ggpht" + new URL(notification.authorThumbnails[2].url).pathname, + img: "/ggpht" + new URL(notification.authorThumbnails[4].url).pathname, + }); - system_notification.onclick = function (e) { - open('/watch?v=' + notification.videoId, '_blank'); - }; - } - }; + system_notification.onclick = function (e) { + open("/watch?v=" + notification.videoId, "_blank"); + }; + } + }; - notifications.addEventListener('error', function (e) { - console.warn('Something went wrong with notifications, trying to reconnect...'); - notifications = notifications_mock; - setTimeout(get_subscriptions, 1000); - }); + notifications.addEventListener("error", function (e) { + console.warn( + "Something went wrong with notifications, trying to reconnect...", + ); + notifications = notifications_mock; - notifications.stream(); + }); + + notifications.stream(); } 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); - if (notification_count > 0) { - notification_ticker.innerHTML = - '' + notification_count + ' '; - } else { - notification_ticker.innerHTML = - ''; - } + const notification_count = helpers.storage.get(STORAGE_KEY_STREAM); + if (notification_count > 0) { + notification_ticker.innerHTML = + '' + + notification_count + + ' '; + } else { + notification_ticker.innerHTML = + ''; + } } function start_stream_if_needed() { - // random wait for other tabs set 'stream' flag - setTimeout(function () { - if (!helpers.storage.get(STORAGE_KEY_STREAM)) { - // if no one set 'stream', set it by yourself and start stream - helpers.storage.set(STORAGE_KEY_STREAM, true); - notifications = notifications_mock; - get_subscriptions(); - } - }, Math.random() * 1000 + 50); // [0.050 .. 1.050) second + // random wait for other tabs set 'stream' flag + setTimeout( + function () { + if (!helpers.storage.get(STORAGE_KEY_STREAM)) { + // if no one set 'stream', set it by yourself and start stream + helpers.storage.set(STORAGE_KEY_STREAM, true); + notifications = notifications_mock; + get_subscriptions(); + } + }, + 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 (e.key === STORAGE_KEY_NOTIF_COUNT) - update_ticker_count(); - - // if 'stream' key was removed - if (e.key === STORAGE_KEY_STREAM && !helpers.storage.get(STORAGE_KEY_STREAM)) { - if (notifications) { - // restore it if we have active stream - helpers.storage.set(STORAGE_KEY_STREAM, true); - } else { - start_stream_if_needed(); - } + // if 'stream' key was removed + if ( + e.key === STORAGE_KEY_STREAM && + !helpers.storage.get(STORAGE_KEY_STREAM) + ) { + if (notifications) { + // restore it if we have active stream + helpers.storage.set(STORAGE_KEY_STREAM, true); + } else { + start_stream_if_needed(); } + } }); -addEventListener('load', function () { - var notification_count_el = document.getElementById('notification_count'); - var notification_count = notification_count_el ? parseInt(notification_count_el.textContent) : 0; - helpers.storage.set(STORAGE_KEY_NOTIF_COUNT, notification_count); +addEventListener("load", function () { + var notification_count_el = document.getElementById("notification_count"); + var notification_count = notification_count_el + ? parseInt(notification_count_el.textContent) + : 0; + helpers.storage.set(STORAGE_KEY_NOTIF_COUNT, notification_count); - if (helpers.storage.get(STORAGE_KEY_STREAM)) - helpers.storage.remove(STORAGE_KEY_STREAM); - start_stream_if_needed(); + if (helpers.storage.get(STORAGE_KEY_STREAM)) + helpers.storage.remove(STORAGE_KEY_STREAM); + start_stream_if_needed(); }); -addEventListener('unload', function () { - // let chance to other tabs to be a streamer via firing 'storage' event - if (notifications) helpers.storage.remove(STORAGE_KEY_STREAM); +addEventListener("unload", function () { + // let chance to other tabs to be a streamer via firing 'storage' event + 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); + } + }); + } +} diff --git a/assets/js/playlist_widget.js b/assets/js/playlist_widget.js index c92592ac..3f73622d 100644 --- a/assets/js/playlist_widget.js +++ b/assets/js/playlist_widget.js @@ -2,8 +2,9 @@ var playlist_data = JSON.parse(document.getElementById('playlist_data').textContent); var payload = 'csrf_token=' + playlist_data.csrf_token; -function add_playlist_video(target) { - var select = target.parentNode.children[0].children[1]; +function add_playlist_video(event) { + const target = event.target; + var select = document.querySelector("#playlists"); var option = select.children[select.selectedIndex]; 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}, { on200: function (response) { - option.textContent = '✓' + option.textContent; + option.textContent = '✓ ' + option.textContent; } }); } -function add_playlist_item(target) { - var tile = target.parentNode.parentNode.parentNode.parentNode.parentNode; - tile.style.display = 'none'; +function add_playlist_item(event) { + event.preventDefault(); + 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' + - '&video_id=' + target.getAttribute('data-id') + + '&video_id=' + video_id + '&playlist_id=' + target.getAttribute('data-plid'); helpers.xhr('POST', url, {payload: payload}, { onNon200: function (xhr) { - tile.style.display = ''; + card.classList.remove("hide"); } }); } -function remove_playlist_item(target) { - var tile = target.parentNode.parentNode.parentNode.parentNode.parentNode; - tile.style.display = 'none'; +function remove_playlist_item(event) { + event.preventDefault(); + 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' + - '&set_video_id=' + target.getAttribute('data-index') + + '&set_video_id=' + video_index + '&playlist_id=' + target.getAttribute('data-plid'); helpers.xhr('POST', url, {payload: payload}, { onNon200: function (xhr) { - tile.style.display = ''; + card.classList.remove("hide"); } }); } diff --git a/assets/js/subscribe_widget.js b/assets/js/subscribe_widget.js index 7665a00b..51ade385 100644 --- a/assets/js/subscribe_widget.js +++ b/assets/js/subscribe_widget.js @@ -3,7 +3,6 @@ var subscribe_data = JSON.parse(document.getElementById('subscribe_data').textCo var payload = 'csrf_token=' + subscribe_data.csrf_token; var subscribe_button = document.getElementById('subscribe'); -subscribe_button.parentNode.action = 'javascript:void(0)'; if (subscribe_button.getAttribute('data-type') === 'subscribe') { subscribe_button.onclick = subscribe; @@ -11,10 +10,29 @@ if (subscribe_button.getAttribute('data-type') === 'subscribe') { subscribe_button.onclick = unsubscribe; } -function subscribe() { - var fallback = subscribe_button.innerHTML; - subscribe_button.onclick = unsubscribe; - subscribe_button.innerHTML = '' + subscribe_data.unsubscribe_text + ' | ' + subscribe_data.sub_count_text + ''; +function toggleSubscribeButton() { + subscribe_button.classList.remove("primary"); + subscribe_button.classList.remove("secondary"); + 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' + '&c=' + subscribe_data.ucid; @@ -22,15 +40,15 @@ function subscribe() { helpers.xhr('POST', url, {payload: payload, retries: 5, entity_name: 'subscribe request'}, { onNon200: function (xhr) { subscribe_button.onclick = subscribe; - subscribe_button.innerHTML = fallback; + subscribe_button.textContent = fallback; } }); } -function unsubscribe() { - var fallback = subscribe_button.innerHTML; - subscribe_button.onclick = subscribe; - subscribe_button.innerHTML = '' + subscribe_data.subscribe_text + ' | ' + subscribe_data.sub_count_text + ''; +function unsubscribe(e) { + e.preventDefault(); + var fallback = subscribe_button.textContent; + toggleSubscribeButton(); var url = '/subscription_ajax?action_remove_subscriptions=1&redirect=false' + '&c=' + subscribe_data.ucid; @@ -38,7 +56,7 @@ function unsubscribe() { helpers.xhr('POST', url, {payload: payload, retries: 5, entity_name: 'unsubscribe request'}, { onNon200: function (xhr) { subscribe_button.onclick = unsubscribe; - subscribe_button.innerHTML = fallback; + subscribe_button.textContent = fallback; } }); } diff --git a/assets/js/theme.js b/assets/js/theme.js new file mode 100644 index 00000000..1c615d27 --- /dev/null +++ b/assets/js/theme.js @@ -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"); + +// +// +// diff --git a/assets/js/themes.js b/assets/js/themes.js index 84a9f6d9..545d2c18 100644 --- a/assets/js/themes.js +++ b/assets/js/themes.js @@ -1,13 +1,13 @@ 'use strict'; var toggle_theme = document.getElementById('toggle_theme'); -toggle_theme.href = 'javascript:void(0)'; const STORAGE_KEY_THEME = 'dark_mode'; const THEME_DARK = 'dark'; const THEME_LIGHT = 'light'; // 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 newTheme = isDarkTheme ? THEME_LIGHT : THEME_DARK; setTheme(newTheme); diff --git a/assets/js/watch.js b/assets/js/watch.js index 26ad138f..bdfc63cf 100644 --- a/assets/js/watch.js +++ b/assets/js/watch.js @@ -69,6 +69,8 @@ function get_playlist(plid) { helpers.xhr('GET', plid_url, {retries: 5, entity_name: 'playlist'}, { on200: function (response) { + if (response === null) return; + playlist.innerHTML = response.playlistHtml; if (!response.nextVideo) return; diff --git a/docker-compose.yml b/docker-compose.yml index 905dff50..404b9a87 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -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" services: invidious: - build: - context: . - dockerfile: docker/Dockerfile - image: rockerboo/invidious + image: quay.io/invidious/invidious:master + # image: quay.io/invidious/invidious:master-arm64 # ARM64/AArch64 devices restart: unless-stopped + # Remove "127.0.0.1:" if used from an external IP ports: - - "127.0.0.1:9999:3000" + - "127.0.0.1:3000:3000" environment: # Please read the following file for a comprehensive list of all available # configuration options and their associated syntax: @@ -29,22 +20,63 @@ services: host: invidious-db port: 5432 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: # domain: # https_only: false # statistics_enabled: false - hmac_key: "CHANGE_ME!!" + # Use the key generated in the 2nd step + hmac_key: "foo0te5naijooTho" healthcheck: test: wget -nv --tries=1 --spider http://127.0.0.1:3000/api/v1/trending || exit 1 interval: 30s timeout: 5s 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: image: docker.io/library/postgres:14 restart: unless-stopped volumes: - - postgresdata:/var/lib/postgresql/data + - postgresdata-companion:/var/lib/postgresql/data - ./config/sql:/config/sql - ./docker/init-invidious-db.sh:/docker-entrypoint-initdb.d/init-invidious-db.sh environment: @@ -55,4 +87,5 @@ services: test: ["CMD-SHELL", "pg_isready -U $$POSTGRES_USER -d $$POSTGRES_DB"] volumes: - postgresdata: + postgresdata-companion: + companioncache: diff --git a/locales/en-US.json b/locales/en-US.json index c23f6bc3..d9dedd32 100644 --- a/locales/en-US.json +++ b/locales/en-US.json @@ -463,6 +463,7 @@ "next_steps_error_message": "After which you should try to: ", "next_steps_error_message_refresh": "Refresh", "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_documentation": "Documentation", "footer_source_code": "Source code", diff --git a/src/invidious.cr b/src/invidious.cr index b422dcbb..bbda5715 100644 --- a/src/invidious.cr +++ b/src/invidious.cr @@ -133,6 +133,9 @@ Kemal.config.extra_options do |parser| Invidious::Database::Migrator.new(PG_DB).migrate exit end + parser.on("--disable-static-cache", "Disable static file handler cache") do + CONFIG.disable_static_cache = true + end end Kemal::CLI.new ARGV diff --git a/src/invidious/config.cr b/src/invidious/config.cr index c4ca622f..573cc2a7 100644 --- a/src/invidious/config.cr +++ b/src/invidious/config.cr @@ -158,6 +158,9 @@ class Config # Playlist length limit 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) case disabled = CONFIG.disable_proxy when Bool diff --git a/src/invidious/frontend/channel_page.cr b/src/invidious/frontend/channel_page.cr index fe7d6d6e..3452211b 100644 --- a/src/invidious/frontend/channel_page.cr +++ b/src/invidious/frontend/channel_page.cr @@ -23,22 +23,15 @@ module Invidious::Frontend::ChannelPage tab_name = tab.to_s.downcase if channel.tabs.includes? tab_name - str << %(
    \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 << "\t" - str << translate(locale, "channel_tab_#{tab_name}_label") - str << "\n" - else - # Video tab doesn't have the last path component - url = tab.videos? ? base_url : "#{base_url}/#{tab_name}" - - str << %(\t) - str << translate(locale, "channel_tab_#{tab_name}_label") - str << "\n" - end - - str << "
    " + str << %(
  • \n) + str << %(\t) + str << translate(locale, "channel_tab_#{tab_name}_label") + str << "\n" + str << "
  • " end end end diff --git a/src/invidious/frontend/comments_youtube.cr b/src/invidious/frontend/comments_youtube.cr index a0e1d783..29686b1c 100644 --- a/src/invidious/frontend/comments_youtube.cr +++ b/src/invidious/frontend/comments_youtube.cr @@ -13,14 +13,9 @@ module Invidious::Frontend::Comments ) replies_html = <<-END_HTML -
    -
    - +
    +
    END_HTML elsif comments["authorId"]? && !comments["singlePost"]? @@ -32,21 +27,20 @@ module Invidious::Frontend::Comments ) replies_html = <<-END_HTML -
    -
    - + END_HTML end - if !thin_mode - author_thumbnail = "/ggpht#{URI.parse(child["authorThumbnails"][-1]["url"].as_s).request_target}" - else + if thin_mode author_thumbnail = "" + else + author_thumbnail_url = "/ggpht#{URI.parse(child["authorThumbnails"][-1]["url"].as_s).request_target}" + + author_thumbnail = <<-THUMBNAIL + + THUMBNAIL end author_name = HTML.escape(child["author"].as_s) @@ -65,18 +59,76 @@ module Invidious::Frontend::Comments str << %(width="16" height="16" />) end end + html << <<-END_HTML -
    -
    - +
    +
    +
    +
    + #{author_thumbnail} +

    + #{author_name} + #{sponsor_icon} +

    + +
    +
    -
    -

    - - #{author_name} - - #{sponsor_icon} -

    #{child["contentHtml"]}

    + END_HTML + + html << <<-END_HTML +
      + END_HTML + + + if child["likeCount"]? + html << <<-END_HTML +
    • + #{number_with_separator(child["likeCount"])} +
    • + 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 +
    • + + + + + + + + +
    • + END_HTML + end + + if comments["videoId"]? + html << <<-END_HTML +
    • + YT +
    • + END_HTML + elsif comments["authorId"]? + html << <<-END_HTML +
    • + YT +
    • + END_HTML + end + + html << <<-END_HTML +
    +
    +
    +

    #{child["contentHtml"]}

    END_HTML if child["attachment"]? @@ -87,10 +139,8 @@ module Invidious::Frontend::Comments attachment = attachment["imageThumbnails"][1] html << <<-END_HTML -
    -
    - -
    +
    +
    END_HTML when "video" @@ -141,50 +191,8 @@ module Invidious::Frontend::Comments end end - html << <<-END_HTML -

    - #{translate(locale, "`x` ago", recode_date(Time.unix(child["published"].as_i64), locale))} #{child["isEdited"] == true ? translate(locale, "(edited)") : ""} - | - END_HTML - - if comments["videoId"]? - html << <<-END_HTML - [YT] - | - END_HTML - elsif comments["authorId"]? - html << <<-END_HTML - [YT] - | - END_HTML - end html << <<-END_HTML - #{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 -   - - - - - - - - - END_HTML - end - - html << <<-END_HTML -

    #{replies_html}
    @@ -193,16 +201,17 @@ module Invidious::Frontend::Comments if comments["continuation"]? html << <<-END_HTML -
    - +
    +
    END_HTML end + + # Closes comment box + html << <<-END_HTML +
    + END_HTML end end end diff --git a/src/invidious/frontend/pagination.cr b/src/invidious/frontend/pagination.cr index 3f931f4e..2fb369a0 100644 --- a/src/invidious/frontend/pagination.cr +++ b/src/invidious/frontend/pagination.cr @@ -5,7 +5,7 @@ module Invidious::Frontend::Pagination private def previous_page(str : String::Builder, locale : String?, url : String) # Link - str << %() + str << %() if locale_is_rtl?(locale) # 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) # Link - str << %() + str << %() if locale_is_rtl?(locale) # Inverted arrow ("next" points to the left) str << %() - str << "  " + str << " " str << translate(locale, "Next page") else # 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) return String.build do |str| - str << %(
    \n) - str << %(\n) - str << %(
    \n\n) + str << %(\n\n) end end def nav_ctoken(locale : String?, *, base_url : String | URI, ctoken : String?) + puts ctoken return String.build do |str| - str << %(
    \n) - str << %(\n) - str << %(
    \n\n) end end end diff --git a/src/invidious/frontend/search_filters.cr b/src/invidious/frontend/search_filters.cr index 8ac0af2e..c2111b7b 100644 --- a/src/invidious/frontend/search_filters.cr +++ b/src/invidious/frontend/search_filters.cr @@ -5,15 +5,15 @@ module Invidious::Frontend::SearchFilters def generate(filters : Search::Filters, query : String, page : Int, locale : String) : String return String.build(8000) do |str| str << "
    \n" - str << "\t
    " + str << "\t
    " str << "\t\t" << translate(locale, "search_filters_title") << "\n" - str << "\t\t
    \n" + str << "\t\t\n" str << "\t\t\t\n" str << "\t\t\t\n" - str << "\t\t\t
    " + str << "\t\t\t
    " filter_wrapper(date) filter_wrapper(type) @@ -23,32 +23,32 @@ module Invidious::Frontend::SearchFilters str << "\t\t\t
    \n" - str << "\t\t\t
    " - str << "
    \n" str << "\t\t
    \n" str << "\t
    \n" - str << "
    \n" + # str << "
    \n" end end # Generate wrapper HTML (`
    `, filter name, etc...) around the # `` elements of a search filter macro filter_wrapper(name) - str << "\t\t\t\t
    \n" + str << "\t\t\t\t
    \n" - str << "\t\t\t\t\t
    " + str << "\t\t\t\t\t" str << translate(locale, "search_filters_{{name}}_label") - str << "
    \n" + str << "\n" - str << "\t\t\t\t\t
    \n" + str << "\t\t\t\t\t\n" make_{{name}}_filter_options(str, filters.{{name}}, locale) - str << "\t\t\t\t\t
    " + str << "\t\t\t\t\t" - str << "\t\t\t\t
    \n" + str << "\t\t\t\t\n" end # Generates the HTML for the list of radio buttons of the "date" search filter diff --git a/src/invidious/frontend/watch_page.cr b/src/invidious/frontend/watch_page.cr index c8cb7110..cb9ca76a 100644 --- a/src/invidious/frontend/watch_page.cr +++ b/src/invidious/frontend/watch_page.cr @@ -25,7 +25,7 @@ module Invidious::Frontend::WatchPage return String.build(4000) do |str| str << "\n" str << "\n" - str << "\t
    \n" + # str << "\t
    \n" str << "\t\t
    \n" + # str << "\t
    \n" - str << "\t\n" + # str << "\t
    " str << "\n" end diff --git a/src/invidious/mixes.cr b/src/invidious/mixes.cr index 823ca85b..915ab206 100644 --- a/src/invidious/mixes.cr +++ b/src/invidious/mixes.cr @@ -98,11 +98,11 @@ def template_mix(mix)
    -

    #{recode_length_seconds(video["lengthSeconds"].as_i)}

    +
    #{recode_length_seconds(video["lengthSeconds"].as_i)}
    -

    #{video["title"]}

    +

    #{video["title"]}

    - #{video["author"]} + #{video["author"]}

    diff --git a/src/invidious/playlists.cr b/src/invidious/playlists.cr index a51e88b4..558e9053 100644 --- a/src/invidious/playlists.cr +++ b/src/invidious/playlists.cr @@ -450,6 +450,8 @@ end def extract_playlist_videos(initial_data : Hash(String, JSON::Any)) videos = [] of PlaylistVideo + puts initial_data.inspect + if initial_data["contents"]? tabs = initial_data["contents"]["twoColumnBrowseResultsRenderer"]["tabs"] 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 || "" author = i["shortBylineText"]?.try &.["runs"][0]["text"].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 live = false @@ -512,23 +517,29 @@ def template_playlist(playlist) #{playlist["title"]} -
    -
      +
      +
        END_HTML playlist["videos"].as_a.each do |video| html += <<-END_HTML -
      1. - +
      2. -

        #{video["title"]}

        -

        - #{video["author"]} -

        - +

        + + #{video["title"]} + +

        +

        + + #{video["author"]} + +

      3. END_HTML end @@ -536,7 +547,6 @@ def template_playlist(playlist) html += <<-END_HTML
      -
      END_HTML html diff --git a/src/invidious/routes/playlists.cr b/src/invidious/routes/playlists.cr index 9c6843e9..a109be42 100644 --- a/src/invidious/routes/playlists.cr +++ b/src/invidious/routes/playlists.cr @@ -267,6 +267,8 @@ module Invidious::Routes::Playlists show_next: (items.size >= 20) ) + puts items + env.set "add_playlist_items", plid templated "add_playlist_items" end diff --git a/src/invidious/routes/search.cr b/src/invidious/routes/search.cr index 44970922..5cd73dd7 100644 --- a/src/invidious/routes/search.cr +++ b/src/invidious/routes/search.cr @@ -67,6 +67,10 @@ module Invidious::Routes::Search redirect_url = Invidious::Frontend::Misc.redirect_url(env) + puts items.size + puts (items.size >= 20) + puts query.page + # Pagination page_nav_html = Frontend::Pagination.nav_numeric(locale, base_url: "/search?#{query.to_http_params}", diff --git a/src/invidious/views/add_playlist_items.ecr b/src/invidious/views/add_playlist_items.ecr index 6aea82ae..842a6c2e 100644 --- a/src/invidious/views/add_playlist_items.ecr +++ b/src/invidious/views/add_playlist_items.ecr @@ -3,25 +3,38 @@ <% end %> -
      -
      -
      -
      -
      - <%= translate(locale, "Editing playlist `x`", %|"#{HTML.escape(playlist.title)}"|) %> - -
      - value="<%= HTML.escape(query.text) %>"<% end %> - placeholder="<%= translate(locale, "Search for videos") %>"> - -
      -
      -
      -
      -
      +
      +

      <%= translate(locale, "Editing playlist") %>

      +
      +

      <%= HTML.escape(playlist.title) %>

      + +
      +
      + <%= translate(locale, "Search for videos") %> + + + +
      +
      + - <%= rendered "components/items_paginated" %> diff --git a/src/invidious/views/channel.ecr b/src/invidious/views/channel.ecr index a84e44bc..a1e40ab7 100644 --- a/src/invidious/views/channel.ecr +++ b/src/invidious/views/channel.ecr @@ -46,9 +46,4 @@ <%= rendered "components/channel_info" %> -
      -
      -
      - - <%= rendered "components/items_paginated" %> diff --git a/src/invidious/views/community.ecr b/src/invidious/views/community.ecr index d2a305d3..bb8dfb5e 100644 --- a/src/invidious/views/community.ecr +++ b/src/invidious/views/community.ecr @@ -17,18 +17,12 @@ <%= rendered "components/channel_info" %> -
      -
      -
      - <% if error_message %> -
      -

      <%= error_message %>

      -
      +

      <%= error_message %>

      <% else %> -
      - <%= IV::Frontend::Comments.template_youtube(items.not_nil!, locale, thin_mode) %> -
      +
      + <%= IV::Frontend::Comments.template_youtube(items.not_nil!, locale, thin_mode) %> +
      <% end %> <% else %> - "> - <%= translate(locale, "Subscribe") %> | <%= sub_count_text %> + <%= translate(locale, "Subscribe") %> | <%= sub_count_text %> <% end %> diff --git a/src/invidious/views/components/video-context-buttons.ecr b/src/invidious/views/components/video-context-buttons.ecr index 22458a03..7599d955 100644 --- a/src/invidious/views/components/video-context-buttons.ecr +++ b/src/invidious/views/components/video-context-buttons.ecr @@ -1,21 +1,29 @@ - \ No newline at end of file diff --git a/src/invidious/views/create_playlist.ecr b/src/invidious/views/create_playlist.ecr index 807244e6..2e29fd5a 100644 --- a/src/invidious/views/create_playlist.ecr +++ b/src/invidious/views/create_playlist.ecr @@ -2,38 +2,31 @@ <%= translate(locale, "Create playlist") %> - Invidious <% end %> -
      -
      -
      -
      -
      -
      - <%= translate(locale, "Create playlist") %> + +
      + <%= translate(locale, "Create playlist") %> -
      - - "> -
      +
      + + "> +
      -
      - - -
      +
      + + +
      -
      - -
      - -
      - -
      -
      -
      -
      + + + +
      + +
      + diff --git a/src/invidious/views/edit_playlist.ecr b/src/invidious/views/edit_playlist.ecr index 34157c67..c2e381b4 100644 --- a/src/invidious/views/edit_playlist.ecr +++ b/src/invidious/views/edit_playlist.ecr @@ -5,56 +5,62 @@ <% end %> -
      - + -
      -
      -

      -
      -
      - -
      -
      - - <%= HTML.escape(playlist.author) %> | - <%= translate_count(locale, "generic_videos_count", playlist.video_count) %> | - +
        +
      • + <%= HTML.escape(playlist.author) %> +
      • +
      • + <%= translate_count(locale, "generic_videos_count", playlist.video_count) %> +
      • +
      • -
      -
      + + -
      - -
      +
      + Edit playlist +
      + + +
      + +
      + + +
      +
      + +
      +
      -
      -
      -
      - <%= rendered "components/items_paginated" %> diff --git a/src/invidious/views/feeds/history.ecr b/src/invidious/views/feeds/history.ecr index bda4e1f3..87e7bf7c 100644 --- a/src/invidious/views/feeds/history.ecr +++ b/src/invidious/views/feeds/history.ecr @@ -2,20 +2,18 @@ <%= translate(locale, "History") %> - Invidious <% end %> -
      -
      -

      <%= translate_count(locale, "generic_videos_count", user.watched.size, NumberFormatting::HtmlSpan) %>

      -
      - - +
      +

      <%= translate_count(locale, "generic_videos_count", user.watched.size, NumberFormatting::HtmlSpan) %>

      +
      -
      +
      <% watched.each do |item| %> -
      -
      -
      - - - +
      +
      + + + -
      -
      " method="post"> - "> - -
      -
      -
      -

      -
      -
      +
      +
      " method="post"> + "> + +
      +
      +
      +
      <% end %>
      diff --git a/src/invidious/views/feeds/playlists.ecr b/src/invidious/views/feeds/playlists.ecr index 2a4b6edd..125ac3dd 100644 --- a/src/invidious/views/feeds/playlists.ecr +++ b/src/invidious/views/feeds/playlists.ecr @@ -4,38 +4,33 @@ <%= rendered "components/feed_menu" %> -
      -
      -

      <%= translate(locale, "user_created_playlists", %(#{items_created.size})) %>

      -
      - - +
      +

      <%= translate(locale, "user_created_playlists", %(#{items_created.size})) %>

      +
      -
      +
      <% items_created.each do |item| %> <%= rendered "components/item" %> <% end %>
      -
      -
      -

      <%= translate(locale, "user_saved_playlists", %(#{items_saved.size})) %>

      -
      -
      +

      <%= translate(locale, "user_saved_playlists", %(#{items_saved.size})) %>

      -
      +
      <% items_saved.each do |item| %> + <% puts item %> <%= rendered "components/item" %> <% end %>
      diff --git a/src/invidious/views/feeds/popular.ecr b/src/invidious/views/feeds/popular.ecr index 5fbe539c..0bb7439c 100644 --- a/src/invidious/views/feeds/popular.ecr +++ b/src/invidious/views/feeds/popular.ecr @@ -11,10 +11,10 @@ <%= rendered "components/feed_menu" %> -
      +
      <% popular_videos.each do |item| %> <%= rendered "components/item" %> <% end %> -
      + diff --git a/src/invidious/views/feeds/subscriptions.ecr b/src/invidious/views/feeds/subscriptions.ecr index c36bd00f..b7593f11 100644 --- a/src/invidious/views/feeds/subscriptions.ecr +++ b/src/invidious/views/feeds/subscriptions.ecr @@ -5,63 +5,59 @@ <%= rendered "components/feed_menu" %> - +
      +
      + <% if CONFIG.enable_user_notifications %> -<% if CONFIG.enable_user_notifications %> +
      + <%= translate_count(locale, "subscriptions_unseen_notifs_count", notifications.size) %> +
      + <% else %> +
      + <% end %> -
      - <%= translate_count(locale, "subscriptions_unseen_notifs_count", notifications.size) %> -
      + +
      -<% if !notifications.empty? %> -
      -
      -
      -<% end %> + <% if CONFIG.enable_user_notifications %> + <% if !notifications.empty? %> +
      + <% notifications.each do |item| %> + <%= rendered "components/item" %> + <% end %> +
      + <% end %> -
      -<% notifications.each do |item| %> - <%= rendered "components/item" %> -<% end %> -
      + <% end %> -<% end %> - -
      -
      -
      - - - + + -
      -<% videos.each do |item| %> - <%= rendered "components/item" %> -<% end %> -
      +
      + <% videos.each do |item| %> + <%= rendered "components/item" %> + <% end %> +
      +
      diff --git a/src/invidious/views/feeds/trending.ecr b/src/invidious/views/feeds/trending.ecr index 7dc416c6..02652731 100644 --- a/src/invidious/views/feeds/trending.ecr +++ b/src/invidious/views/feeds/trending.ecr @@ -11,39 +11,31 @@ <%= rendered "components/feed_menu" %> -
      -
      +
      -
      -
      - <% {"Default", "Music", "Gaming", "Movies"}.each do |option| %> -
      - <% if trending_type == option %> - <%= translate(locale, option) %> - <% else %> - - <%= translate(locale, option) %> - - <% end %> -
      - <% end %> -
      -
      -
      + + + -
      -
      -
      - -
      +
      <% trending.each do |item| %> <%= rendered "components/item" %> <% end %> -
      + diff --git a/src/invidious/views/playlist.ecr b/src/invidious/views/playlist.ecr index c27ddba6..7c3e254f 100644 --- a/src/invidious/views/playlist.ecr +++ b/src/invidious/views/playlist.ecr @@ -6,109 +6,141 @@ <% end %> -
      -

      <%= title %>

      +
      +

      <%= title %>

      - + + +
      -
      -
      - <% if playlist.is_a? InvidiousPlaylist %> - - <% if playlist.author == user.try &.email %> - <%= author %> | - <% else %> - <%= author %> | - <% end %> - <%= translate_count(locale, "generic_videos_count", playlist.video_count) %> | - <%= translate(locale, "Updated `x` ago", recode_date(playlist.updated, locale)) %> | - <% case playlist.as(InvidiousPlaylist).privacy when %> - <% when PlaylistPrivacy::Public %> - <%= translate(locale, "Public") %> - <% when PlaylistPrivacy::Unlisted %> - <%= translate(locale, "Unlisted") %> - <% when PlaylistPrivacy::Private %> - <%= translate(locale, "Private") %> - <% end %> - - <% else %> - - <% if !author.empty? %> - <%= author %> | - <% elsif !playlist.subtitle.nil? %> - <% subtitle = playlist.subtitle || "" %> - <%= HTML.escape(subtitle[0..subtitle.rindex(" • ") || subtitle.size]) %> | - <% end %> - <%= translate_count(locale, "generic_videos_count", playlist.video_count) %> | - <%= translate(locale, "Updated `x` ago", recode_date(playlist.updated, locale)) %> - - <% end %> + - <% if !playlist.is_a? InvidiousPlaylist %> -
      - - <%= translate(locale, "View playlist on YouTube") %> - - | +
      +
        + <% if playlist.is_a? InvidiousPlaylist %> +
      • + <%= translate_count(locale, "generic_videos_count", playlist.video_count) %>
      • + +
      • + +
      • +
      • + <% case playlist.as(InvidiousPlaylist).privacy when %> + <% when PlaylistPrivacy::Public %> + <%= translate(locale, "Public") %> + <% when PlaylistPrivacy::Unlisted %> + <%= translate(locale, "Unlisted") %> + <% when PlaylistPrivacy::Private %> + <%= translate(locale, "Private") %> + <% end %> +
      • + <% else %> +
      • + <%= translate_count(locale, "generic_videos_count", playlist.video_count) %> +
      • +
      • + +
      • + <% end %> - <% if env.get("preferences").as(Preferences).automatic_instance_redirect%> - "> - <%= translate(locale, "Switch Invidious Instance") %> - - <% else %> - - <%= translate(locale, "Switch Invidious Instance") %> - - <% end %> -
      - <% end %> -
      + + +
      -
      -
      <%= playlist.description_html %>
      -
      - -
      -
      -
      +
      <%= playlist.description_html %>
      <% if playlist.is_a?(InvidiousPlaylist) && playlist.author == user.try &.email %> <% end %> - + <%= rendered "components/items_paginated" %> diff --git a/src/invidious/views/search.ecr b/src/invidious/views/search.ecr index b1300214..7acee69f 100644 --- a/src/invidious/views/search.ecr +++ b/src/invidious/views/search.ecr @@ -5,15 +5,17 @@ <%= Invidious::Frontend::SearchFilters.generate(query.filters, query.text, query.page, locale) %> -
      - <%- if items.empty? -%> -
      +
      - <%= translate(locale, "search_message_no_results") %>

      - <%= translate(locale, "search_message_change_filters_or_query") %>

      - <%= translate(locale, "search_message_use_another_instance", redirect_url) %> + <%= translate(locale, "search_message_no_results") %> +
      +
      + <%= translate(locale, "search_message_change_filters_or_query") %> +
      +
      + <%= translate(locale, "search_message_use_another_instance", redirect_url) %>
      <%- else -%> diff --git a/src/invidious/views/search_homepage.ecr b/src/invidious/views/search_homepage.ecr index 2424a1cf..d269667a 100644 --- a/src/invidious/views/search_homepage.ecr +++ b/src/invidious/views/search_homepage.ecr @@ -6,15 +6,11 @@ <% end %> -<%= rendered "components/feed_menu" %> +
      + <%= rendered "components/feed_menu" %> -
      - -
      - -
      +
      +

      Invidious

      + <% autofocus = true %><%= rendered "components/search_box" %> +
      diff --git a/src/invidious/views/template.ecr b/src/invidious/views/template.ecr index 9904b4fc..1b73cf9b 100644 --- a/src/invidious/views/template.ecr +++ b/src/invidious/views/template.ecr @@ -18,8 +18,11 @@ + + + @@ -27,135 +30,209 @@ -theme"> -
      -
      - +
      + +
      - +
      + + + + +
      + +
      +

      <%= translate(locale, "Legal")%>

      + +
      +
      +
      + + <% if env.get? "user" %> diff --git a/src/invidious/views/user/data_control.ecr b/src/invidious/views/user/data_control.ecr index 9ce42c99..bd66276d 100644 --- a/src/invidious/views/user/data_control.ecr +++ b/src/invidious/views/user/data_control.ecr @@ -2,17 +2,18 @@ <%= translate(locale, "Import and Export Data") %> - Invidious <% end %> -
      -
      -
      +

      Import/export

      + + +
      <%= translate(locale, "Import") %> -
      +
      + <%= translate(locale, "Export") %> -
      diff --git a/src/invidious/views/user/login.ecr b/src/invidious/views/user/login.ecr index 2b03d280..3baa87ed 100644 --- a/src/invidious/views/user/login.ecr +++ b/src/invidious/views/user/login.ecr @@ -2,76 +2,84 @@ <%= translate(locale, "Log in") %> - Invidious <% end %> -
      -
      -
      -
      <% case account_type when %> <% else # "invidious" %> - -
      + +
      + Log in <% if email %> +
      +
      <% else %> - - "> +
      + + "> +
      <% end %> <% if password %> - +
      + +
      <% else %> - - "> +
      + + "> +
      <% end %> +
      + <% if captcha %> <% case captcha_type when %> <% when "image" %> <% captcha = captcha.not_nil! %> - + <% captcha[:tokens].each_with_index do |token, i| %> <% end %> +
      +
      <% else # "text" %> <% captcha = captcha.not_nil! %> <% captcha[:tokens].each_with_index do |token, i| %> <% end %> +
      "> +
      <% end %> - <% case captcha_type when %> <% when "image" %> <% else # "text" %> <% end %> <% else %> +
      +
      <% end %> -
      <% end %> -
      -
      -
      -
      diff --git a/src/invidious/views/user/preferences.ecr b/src/invidious/views/user/preferences.ecr index cf8b5593..ce11113f 100644 --- a/src/invidious/views/user/preferences.ecr +++ b/src/invidious/views/user/preferences.ecr @@ -2,362 +2,382 @@ <%= translate(locale, "Preferences") %> - Invidious <% end %> -
      -
      -
      - <%= translate(locale, "preferences_category_player") %> +

      Preferences

      -
      - - checked<% end %>> -
      + +
      + <%= translate(locale, "preferences_category_player") %> -
      - - checked<% end %>> -
      +
      + + checked<% end %>> +
      -
      - - checked<% end %>> -
      +
      + + checked<% end %>> +
      -
      - - checked<% end %>> -
      +
      + + checked<% end %>> +
      -
      - - checked<% end %>> -
      +
      + + checked<% end %>> +
      -
      - - checked<% end %> <% if CONFIG.disabled?("local") %>disabled<% end %>> -
      +
      + + checked<% end %>> +
      -
      - - checked<% end %>> -
      +
      + + checked<% end %> <% if CONFIG.disabled?("local") %>disabled<% end %>> +
      -
      - - -
      +
      + + checked<% end %>> +
      -
      - - -
      +
      + + +
      - <% if !CONFIG.disabled?("dash") %> -
      - - -
      - <% end %> +
      + + +
      -
      - - - <%= preferences.volume %> -
      + <% if !CONFIG.disabled?("dash") %> +
      + + +
      + <% end %> -
      - - <% preferences.comments.each_with_index do |comments, index| %> - - <% end %> -
      +
      + +
      + + <%= preferences.volume %> +
      +
      -
      - - <% preferences.captions.each_with_index do |caption, index| %> - - <% end %> -
      +
      + +
      + <% preferences.comments.each_with_index do |comments, index| %> + + <% end %> +
      +
      -
      - - checked<% end %>> -
      +
      + +
      + <% preferences.captions.each_with_index do |caption, index| %> + + <% end %> +
      +
      -
      - - checked<% end %>> -
      +
      + + checked<% end %>> +
      -
      - - checked<% end %>> -
      +
      + + checked<% end %>> +
      -
      - - checked<% end %>> -
      +
      + + checked<% end %>> +
      -
      - - checked<% end %>> -
      +
      + + checked<% end %>> +
      - <%= translate(locale, "preferences_category_visual") %> +
      + + checked<% end %>> +
      +
      +
      + <%= translate(locale, "preferences_category_visual") %> -
      - - -
      +
      + + +
      -
      - - -
      +
      + + +
      -
      - - -
      +
      + + +
      -
      - - -
      +
      + + +
      -
      - - checked<% end %>> -
      +
      + + checked<% end %>> +
      - <% if env.get?("user") %> - <% feed_options = {"", "Popular", "Trending", "Subscriptions", "Playlists"} %> - <% else %> - <% feed_options = {"", "Popular", "Trending"} %> - <% end %> + <% if env.get?("user") %> + <% feed_options = {"", "Popular", "Trending", "Subscriptions", "Playlists"} %> + <% else %> + <% feed_options = {"", "Popular", "Trending"} %> + <% end %> -
      - - -
      +
      + + +
      -
      - - <% (feed_options.size - 1).times do |index| %> - - <% end %> -
      - <% if env.get? "user" %> -
      - - checked<% end %>> -
      - <% end %> +
      + +
      + <% (feed_options.size - 1).times do |index| %> + + <% end %> +
      +
      + <% if env.get? "user" %> +
      + + checked<% end %>> +
      + <% end %> - <%= translate(locale, "preferences_category_misc") %> +
      +
      -
      - - checked<% end %>> -
      + <%= translate(locale, "preferences_category_misc") %> - <% if env.get? "user" %> - <%= translate(locale, "preferences_category_subscription") %> +
      + + checked<% end %>> +
      -
      - - checked<% end %>> -
      +
      -
      - - checked<% end %>> -
      + <% if env.get? "user" %> +
      + <%= translate(locale, "preferences_category_subscription") %> -
      - - -
      +
      + + checked<% end %>> +
      -
      - - -
      +
      + + checked<% end %>> +
      -
      - <% if preferences.unseen_only %> - - <% else %> - - <% end %> - checked<% end %>> -
      +
      + + +
      -
      - - checked<% end %>> -
      +
      + + +
      - <% if CONFIG.enable_user_notifications %> -
      - - checked<% end %>> -
      +
      + <% if preferences.unseen_only %> + + <% else %> + + <% end %> + checked<% end %>> +
      - <% # Web notifications are only supported over HTTPS %> - <% if Kemal.config.ssl || CONFIG.https_only %> - - <% end %> - <% end %> - <% end %> +
      + + checked<% end %>> +
      - <% if env.get?("user") && CONFIG.admins.includes? env.get?("user").as(Invidious::User).email %> - <%= translate(locale, "preferences_category_admin") %> + <% if CONFIG.enable_user_notifications %> +
      + + checked<% end %>> +
      -
      - - -
      + <% # Web notifications are only supported over HTTPS %> + <% if Kemal.config.ssl || CONFIG.https_only %> + + <% end %> + <% end %> +
      + <% end %> -
      - - <% (feed_options.size - 1).times do |index| %> - - <% end %> -
      + <% if env.get?("user") && CONFIG.admins.includes? env.get?("user").as(Invidious::User).email %> +
      + <%= translate(locale, "preferences_category_admin") %> -
      - - checked<% end %>> -
      +
      + + +
      + +
      + + <% (feed_options.size - 1).times do |index| %> + + <% end %> +
      + +
      + + checked<% end %>> +
      -
      - - checked<% end %>> -
      +
      + + checked<% end %>> +
      -
      - - checked<% end %>> -
      +
      + + checked<% end %>> +
      -
      - - checked<% end %>> -
      +
      + + checked<% end %>> +
      -
      - - checked<% end %>> -
      +
      + + checked<% end %>> +
      -
      - - -
      - <% end %> +
      + + +
      +
      + <% end %> - <% if env.get? "user" %> - <%= translate(locale, "preferences_category_data") %> + <% if env.get? "user" %> +
      + <%= translate(locale, "preferences_category_data") %> - + - + - + - + - + - + - + - - <% end %> + +
      + <% end %> -
      - -
      -
      -
      -
      + +
      + +
      + diff --git a/src/invidious/views/user/subscription_manager.ecr b/src/invidious/views/user/subscription_manager.ecr index c9801f09..90af60b6 100644 --- a/src/invidious/views/user/subscription_manager.ecr +++ b/src/invidious/views/user/subscription_manager.ecr @@ -2,51 +2,40 @@ <%= translate(locale, "Subscription manager") %> - Invidious <% end %> -
      - - - + +
      <% subscriptions.each do |channel| %> -
      -
      -
      -

      + -
      -
      -

      -
      " method="post"> - "> - "> -
      -

      -
      -

      - - <% if subscriptions[-1].author != channel.author %> -
      - <% end %> -
      +
      " method="post"> + "> + "> +
      +
      <% end %> +
      diff --git a/src/invidious/views/watch.ecr b/src/invidious/views/watch.ecr index 45c58a16..05ed223b 100644 --- a/src/invidious/views/watch.ecr +++ b/src/invidious/views/watch.ecr @@ -34,11 +34,11 @@ we're going to need to do it here in order to allow for translations. --> @@ -70,305 +70,317 @@ we're going to need to do it here in order to allow for translations. %> -
      - <%= rendered "components/player" %> -
      +
      +
      +
      + <%= rendered "components/player" %> +
      -
      -

      - <%= title %> - <% if params.listen %> - " href="/watch?<%= env.params.query %>&listen=0"> - - - <% else %> - " href="/watch?<%= env.params.query %>&listen=1"> - - - <% end %> -

      +
      +

      + <%= title %> +

      + +
      - <% if !video.is_listed %> -

      - <%= translate(locale, "Unlisted") %> -

      - <% end %> +
      +
      + <% if video.reason %> +

      + <%= video.reason %> +

      + <% elsif video.premiere_timestamp.try &.> Time.utc %> +

      + +

      + <% elsif video.live_now %> +

      + +

      + <% end %> + -

      - <% if params.annotations %> - - <%= translate(locale, "Hide annotations") %> - - <% else %> - - <%=translate(locale, "Show annotations")%> - - <% end %> -

      + <% if user %> + <% playlists = Invidious::Database::Playlists.select_user_created_playlists(user.email) %> + <% if !playlists.empty? %> +
      + + - <% if user %> - <% playlists = Invidious::Database::Playlists.select_user_created_playlists(user.email) %> - <% if !playlists.empty? %> - -
      - - -
      + "> + + + +
      + + + <% end %> + <% end %> - "> - - - - - - - <% end %> - <% end %> + <%= Invidious::Frontend::WatchPage.download_widget(locale, video, video_assets) %> - <%= Invidious::Frontend::WatchPage.download_widget(locale, video, video_assets) %> + + -

      <%= number_with_separator(video.views) %>

      -

      <%= number_with_separator(video.likes) %>

      - -

      <%= translate(locale, "Genre: ") %> - <% if !video.genre_url %> - <%= video.genre %> - <% else %> - <%= video.genre %> - <% end %> -

      - <% if video.license %> - <% if video.license.empty? %> -

      <%= translate(locale, "License: ") %><%= translate(locale, "Standard YouTube license") %>

      - <% else %> -

      <%= translate(locale, "License: ") %><%= video.license %>

      - <% end %> - <% end %> -

      <%= translate(locale, "Family friendly? ") %><%= translate_bool(locale, video.is_family_friendly) %>

      - - - - <% if video.allowed_regions.size != REGIONS.size %> -

      - <% 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 %> -

      - <% end %> -
      -
      +
      +
      +
      + -
      +
      + <% sub_count_text = video.sub_count_text %> + <%= rendered "components/subscribe_widget" %> +
      +
      -
      - +
      -
      -
      - <% sub_count_text = video.sub_count_text %> - <%= rendered "components/subscribe_widget" %> -
      -
      -
      +
      + <% if video.premiere_timestamp.try &.> Time.utc %> + + <% else %> + + <% end %> +
      +
      +
      <%= number_with_separator(video.views) %>
      +
      <%= number_with_separator(video.likes) %>
      +
      +
      -
      -

      - <% if video.premiere_timestamp.try &.> Time.utc %> - <%= video.premiere_timestamp.try { |t| translate(locale, "Premieres `x`", t.to_s("%B %-d, %R UTC")) } %> - <% else %> - <%= translate(locale, "Shared `x`", video.published.to_s("%B %-d, %Y")) %> - <% end %> -

      +
      + <% if video.description.size < 200 || params.extend_desc %> +

      <%= video.description_html %>

      + <% else %> + +

      <%= video.description_html %>

      + + <% end %> +
      +
      -
      - <% if video.description.size < 200 || params.extend_desc %> -
      <%= video.description_html %>
      - <% else %> - -
      <%= video.description_html %>
      - - <% end %> -
      + <% if !video.music.empty? %> + + -
      +
      + <% video.music.each do |music| %> +
      +
      <%= translate(locale, "Song: ") %><%= music.song %>
      +
      <%= translate(locale, "Artist: ") %><%= music.artist %>
      +
      <%= translate(locale, "Album: ") %><%= music.album %>
      +
      + <% end %> +
      + <% end %> - <% if !video.music.empty? %> - - + +
      -
      - <% video.music.each do |music| %> -
      -

      <%= translate(locale, "Song: ") %><%= music.song %>

      -

      <%= translate(locale, "Artist: ") %><%= music.artist %>

      -

      <%= translate(locale, "Album: ") %><%= music.album %>

      -
      - <% end %> -
      -
      + <% if params.related_videos || plid %> +
      -
      + <% if params.related_videos %> + + + <% end %> <% # if rv["id"]? %> + <% end %> <% # video.related_videos.each do |rv| %> +
      <% # + +