aboutsummaryrefslogtreecommitdiffstats
path: root/youtube/templates
diff options
context:
space:
mode:
Diffstat (limited to 'youtube/templates')
-rw-r--r--youtube/templates/base.html258
-rw-r--r--youtube/templates/channel.html207
-rw-r--r--youtube/templates/comments.html77
-rw-r--r--youtube/templates/comments_page.html29
-rw-r--r--youtube/templates/common_elements.html136
-rw-r--r--youtube/templates/delete_comment.html21
-rw-r--r--youtube/templates/embed.html74
-rw-r--r--youtube/templates/error.html37
-rw-r--r--youtube/templates/home.html11
-rw-r--r--youtube/templates/licenses.html64
-rw-r--r--youtube/templates/local_playlist.html63
-rw-r--r--youtube/templates/local_playlists_list.html10
-rw-r--r--youtube/templates/login.html57
-rw-r--r--youtube/templates/playlist.html84
-rw-r--r--youtube/templates/post_comment.html20
-rw-r--r--youtube/templates/search.html62
-rw-r--r--youtube/templates/settings.html86
-rw-r--r--youtube/templates/shared.css5
-rw-r--r--youtube/templates/status.html1
-rw-r--r--youtube/templates/subscription_manager.html99
-rw-r--r--youtube/templates/subscriptions.html82
-rw-r--r--youtube/templates/subscriptions.xml9
-rw-r--r--youtube/templates/unsubscribe_verify.html8
-rw-r--r--youtube/templates/watch.html492
24 files changed, 985 insertions, 1007 deletions
diff --git a/youtube/templates/base.html b/youtube/templates/base.html
index 9127efa..393cc52 100644
--- a/youtube/templates/base.html
+++ b/youtube/templates/base.html
@@ -1,113 +1,179 @@
+{% if settings.app_public %}
+ {% set app_url = settings.app_url|string %}
+{% else %}
+ {% set app_url = settings.app_url|string + ':' + settings.port_number|string %}
+{% endif %}
<!DOCTYPE html>
-<html>
+<html lang="en">
<head>
- <meta charset="utf-8">
+ <meta charset="UTF-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1">
+ <meta http-equiv="Content-Security-Policy" content="default-src 'self' 'unsafe-inline' 'unsafe-eval'; media-src 'self' blob: {{ app_url }}/* data: https://*.googlevideo.com; {{ "img-src 'self' https://*.googleusercontent.com https://*.ggpht.com https://*.ytimg.com;" if not settings.proxy_images else "" }}">
<title>{{ page_title }}</title>
- <meta http-equiv="Content-Security-Policy" content="default-src 'self' 'unsafe-inline'; script-src 'none'; media-src 'self' https://*.googlevideo.com">
- <link href="{{ theme_path }}" type="text/css" rel="stylesheet">
- <link href="/youtube.com/static/shared.css" type="text/css" rel="stylesheet">
- <link href="/youtube.com/static/comments.css" type="text/css" rel="stylesheet">
+ <link title="YT Local" href="/youtube.com/opensearch.xml" rel="search" type="application/opensearchdescription+xml">
<link href="/youtube.com/static/favicon.ico" type="image/x-icon" rel="icon">
- <link title="Youtube local" href="/youtube.com/opensearch.xml" rel="search" type="application/opensearchdescription+xml">
- <style type="text/css">
-{% block style %}
-{{ style }}
-{% endblock %}
- </style>
+ <link href="/youtube.com/static/normalize.css" rel="stylesheet">
+ <link href="{{ theme_path }}" rel="stylesheet">
+ <link href="/youtube.com/shared.css" rel="stylesheet">
+ {% block style %}
+ {{ style }}
+ {% endblock %}
+
+ {% if js_data %}
+ <script>
+ // @license magnet:?xt=urn:btih:0b31508aeb0634b347b8270c7bee4d411b5d4109&dn=agpl-3.0.txt AGPL-v3-or-Later
+ data = {{ js_data|tojson }};
+ // @license-end
+ </script>
+ {% endif %}
</head>
- <body>
- <header>
- <a href="/youtube.com" id="home-link">Home</a>
- <form id="site-search" action="/youtube.com/search">
- <input type="search" name="query" class="search-box" value="{{ search_box_value }}">
- <button type="submit" value="Search" class="search-button">Search</button>
- <div class="dropdown">
- <button class="dropdown-label">Options</button>
- <div class="css-sucks">
- <div class="dropdown-content">
- <h3>Sort by</h3>
- <input type="radio" id="sort_relevance" name="sort" value="0">
- <label for="sort_relevance">Relevance</label>
-
- <input type="radio" id="sort_upload_date" name="sort" value="2">
- <label for="sort_upload_date">Upload date</label>
-
- <input type="radio" id="sort_view_count" name="sort" value="3">
- <label for="sort_view_count">View count</label>
-
- <input type="radio" id="sort_rating" name="sort" value="1">
- <label for="sort_rating">Rating</label>
-
-
- <h3>Upload date</h3>
- <input type="radio" id="time_any" name="time" value="0">
- <label for="time_any">Any</label>
-
- <input type="radio" id="time_last_hour" name="time" value="1">
- <label for="time_last_hour">Last hour</label>
-
- <input type="radio" id="time_today" name="time" value="2">
- <label for="time_today">Today</label>
-
- <input type="radio" id="time_this_week" name="time" value="3">
- <label for="time_this_week">This week</label>
-
- <input type="radio" id="time_this_month" name="time" value="4">
- <label for="time_this_month">This month</label>
-
- <input type="radio" id="time_this_year" name="time" value="5">
- <label for="time_this_year">This year</label>
- <h3>Type</h3>
- <input type="radio" id="type_any" name="type" value="0">
- <label for="type_any">Any</label>
-
- <input type="radio" id="type_video" name="type" value="1">
- <label for="type_video">Video</label>
-
- <input type="radio" id="type_channel" name="type" value="2">
- <label for="type_channel">Channel</label>
-
- <input type="radio" id="type_playlist" name="type" value="3">
- <label for="type_playlist">Playlist</label>
-
- <input type="radio" id="type_movie" name="type" value="4">
- <label for="type_movie">Movie</label>
-
- <input type="radio" id="type_show" name="type" value="5">
- <label for="type_show">Show</label>
-
-
- <h3>Duration</h3>
- <input type="radio" id="duration_any" name="duration" value="0">
- <label for="duration_any">Any</label>
+ <body>
+ <header class="header">
+ <nav class="home">
+ <a href="/youtube.com" id="home-link">YT Local</a>
+ </nav>
+ <form class="form" id="site-search" action="/youtube.com/results">
+ <input type="search" name="search_query" class="search-box" value="{{ search_box_value }}"
+ {{ "autofocus" if (request.path in ("/", "/results") or error_message) else "" }} required placeholder="Type to search...">
+ <button type="submit" value="Search" class="search-button">Search</button>
+ <!-- options -->
+ <div class="dropdown">
+ <!-- hidden box -->
+ <input id="options-toggle-cbox" class="opt-box" type="checkbox">
+ <!-- end hidden box -->
+ <label class="dropdown-label" for="options-toggle-cbox">Options</label>
+ <div class="dropdown-content">
+ <h3>Sort by</h3>
+ <div class="option">
+ <input type="radio" id="sort_relevance" name="sort" value="0">
+ <label for="sort_relevance">Relevance</label>
+ </div>
+ <div class="option">
+ <input type="radio" id="sort_upload_date" name="sort" value="2">
+ <label for="sort_upload_date">Upload date</label>
+ </div>
+ <div class="option">
+ <input type="radio" id="sort_view_count" name="sort" value="3">
+ <label for="sort_view_count">View count</label>
+ </div>
+ <div class="option">
+ <input type="radio" id="sort_rating" name="sort" value="1">
+ <label for="sort_rating">Rating</label>
+ </div>
- <input type="radio" id="duration_short" name="duration" value="1">
- <label for="duration_short">Short (&lt; 4 minutes)</label>
+ <h3>Upload date</h3>
+ <div class="option">
+ <input type="radio" id="time_any" name="time" value="0">
+ <label for="time_any">Any</label>
+ </div>
+ <div class="option">
+ <input type="radio" id="time_last_hour" name="time" value="1">
+ <label for="time_last_hour">Last hour</label>
+ </div>
+ <div class="option">
+ <input type="radio" id="time_today" name="time" value="2">
+ <label for="time_today">Today</label>
+ </div>
+ <div class="option">
+ <input type="radio" id="time_this_week" name="time" value="3">
+ <label for="time_this_week">This week</label>
+ </div>
+ <div class="option">
+ <input type="radio" id="time_this_month" name="time" value="4">
+ <label for="time_this_month">This month</label>
+ </div>
+ <div class="option">
+ <input type="radio" id="time_this_year" name="time" value="5">
+ <label for="time_this_year">This year</label>
+ </div>
- <input type="radio" id="duration_long" name="duration" value="2">
- <label for="duration_long">Long (&gt; 20 minutes)</label>
+ <h3>Type</h3>
+ <div class="option">
+ <input type="radio" id="type_any" name="type" value="0">
+ <label for="type_any">Any</label>
+ </div>
+ <div class="option">
+ <input type="radio" id="type_video" name="type" value="1">
+ <label for="type_video">Video</label>
+ </div>
+ <div class="option">
+ <input type="radio" id="type_channel" name="type" value="2">
+ <label for="type_channel">Channel</label>
+ </div>
+ <div class="option">
+ <input type="radio" id="type_playlist" name="type" value="3">
+ <label for="type_playlist">Playlist</label>
+ </div>
+ <div class="option">
+ <input type="radio" id="type_movie" name="type" value="4">
+ <label for="type_movie">Movie</label>
+ </div>
+ <div class="option">
+ <input type="radio" id="type_show" name="type" value="5">
+ <label for="type_show">Show</label>
+ </div>
- </div>
+ <h3>Duration</h3>
+ <div class="option">
+ <input type="radio" id="duration_any" name="duration" value="0">
+ <label for="duration_any">Any</label>
+ </div>
+ <div class="option">
+ <input type="radio" id="duration_short" name="duration" value="1">
+ <label for="duration_short">Short (&lt; 4 minutes)</label>
+ </div>
+ <div class="option">
+ <input type="radio" id="duration_long" name="duration" value="2">
+ <label for="duration_long">Long (&gt; 20 minutes)</label>
</div>
</div>
+ </div>
+ </form>
+
+ {% if header_playlist_names is defined %}
+ <form class="playlist" id="playlist-edit" action="/youtube.com/edit_playlist" method="post" target="_self">
+ <input class="play-box" name="playlist_name" id="playlist-name-selection" list="playlist-options" type="search" placeholder="Add name of your playlist...">
+ <datalist class="play-hidden" id="playlist-options">
+ {% for playlist_name in header_playlist_names %}
+ <option value="{{ playlist_name }}">{{ playlist_name }}</option>
+ {% endfor %}
+ </datalist>
+ <button class="play-add" type="submit" id="playlist-add-button" name="action" value="add">+List</button>
+ <div class="play-clean">
+ <button type="reset" id="item-selection-reset">Clear</button>
+ </div>
</form>
+ <script src="/youtube.com/static/js/playlistadd.js"></script>
+ {% endif %}
- <form id="playlist-edit" action="/youtube.com/edit_playlist" method="post" target="_self">
- <input name="playlist_name" id="playlist-name-selection" list="playlist-options" type="text">
- <datalist id="playlist-options">
- {% for playlist_name in header_playlist_names %}
- <option value="{{ playlist_name }}">{{ playlist_name }}</option>
- {% endfor %}
- </datalist>
- <button type="submit" id="playlist-add-button" name="action" value="add">Add to playlist</button>
- <button type="reset" id="item-selection-reset">Clear selection</button>
- </form>
</header>
- <main>
-{% block main %}
-{{ main }}
-{% endblock %}
+ <main class="main">
+
+ {% block main %}
+ {{ main }}
+ {% endblock %}
+
</main>
+ <footer class="footer">
+ <div>
+ <a href="https://git.sr.ht/~heckyel/yt-local"
+ rel="noopener noreferrer" target="_blank">
+ Released under the AGPLv3 or later
+ </a>
+ </div>
+ <div>
+ <p>This site is Free/Libre Software</p>
+ {% if current_commit != None %}
+ <p>Current version: {{ current_commit }} @ {{ current_branch }}</p>
+ {% else %}
+ <p>Current version: {{ current_version }}</p>
+ {% endif %}
+ </div>
+ <div>
+ <a href="/youtube.com/licenses" data-jslicense="1" rel="noopener noreferrer" target="_blank">JavaScript licenses</a>
+ </div>
+ </footer>
</body>
+
</html>
diff --git a/youtube/templates/channel.html b/youtube/templates/channel.html
index 48041a0..c43f488 100644
--- a/youtube/templates/channel.html
+++ b/youtube/templates/channel.html
@@ -1,106 +1,39 @@
-{% set page_title = channel_name + ' - Channel' %}
+{% if current_tab == 'search' %}
+ {% set page_title = search_box_value + ' - Page ' + page_number|string %}
+{% else %}
+ {% set page_title = channel_name|string + ' - Channel' %}
+{% endif %}
+
{% extends "base.html" %}
{% import "common_elements.html" as common_elements %}
{% block style %}
- main{
- display:grid;
-{% if current_tab == 'about' %}
- grid-template-rows: 0fr 0fr 1fr;
- grid-template-columns: 0fr 1fr;
-{% else %}
- grid-template-rows: repeat(5, 0fr);
- grid-template-columns: auto 1fr;
-{% endif %}
- }
- main .avatar{
- grid-row:1;
- grid-column:1;
- height:200px;
- width:200px;
- }
- main .summary{
- grid-row:1;
- grid-column:2;
- margin-left: 5px;
- }
- .summary subscribe-unsubscribe, .summary short-description{
- margin-top: 10px;
- }
- main .channel-tabs{
- grid-row:2;
- grid-column: 1 / span 2;
-
- display:grid;
- grid-auto-flow: column;
- justify-content:start;
-
- background-color: var(--interface-color);
- padding: 3px;
- padding-left: 6px;
- }
- #links-metadata{
- display: grid;
- grid-auto-flow: column;
- grid-column-gap: 10px;
- grid-column: 1/span 2;
- justify-content: start;
- padding-top: 8px;
- padding-bottom: 8px;
- padding-left: 6px;
- margin-bottom: 10px;
- }
- #number-of-results{
- font-weight:bold;
- }
- .item-grid{
- padding-left: 20px;
- grid-row:4;
- grid-column: 1 / span 2;
- }
- .item-list{
- width:1000px;
- grid-column: 1 / span 2;
- }
- .page-button-row{
- grid-column: 1 / span 2;
- }
- .tab{
- padding: 5px 75px;
- }
- .channel-info{
- grid-row: 3;
- grid-column: 1 / span 3;
- }
- .channel-info ul{
- padding-left: 40px;
- }
- .channel-info h3{
- margin-left: 40px;
- }
- .channel-info .description{
- white-space: pre-wrap;
- min-width: 0;
- margin-left: 40px;
- }
- .medium-item img{
- max-width: 168px;
- }
+ <link href="/youtube.com/static/message_box.css" rel="stylesheet">
+ <link href="/youtube.com/static/channel.css" rel="stylesheet">
{% endblock style %}
-{% block main %}
- <img class="avatar" src="{{ avatar }}">
- <div class="summary">
- <h2 class="title">{{ channel_name }}</h2>
- <p class="short-description">{{ short_description }}</p>
- <form method="POST" action="/youtube.com/subscriptions" class="subscribe-unsubscribe">
- <input type="submit" value="{{ 'Unsubscribe' if subscribed else 'Subscribe' }}">
- <input type="hidden" name="channel_id" value="{{ channel_id }}">
- <input type="hidden" name="channel_name" value="{{ channel_name }}">
- <input type="hidden" name="action" value="{{ 'unsubscribe' if subscribed else 'subscribe' }}">
- </form>
+{% block main %}
+
+ <div class="author-container">
+ <div class="author">
+ <img alt="{{ channel_name }}" src="{{ avatar }}">
+ <h2>{{ channel_name }}</h2>
+ </div>
+ <div class="summary">
+ <p>{{ short_description }}</p>
+ </div>
+ <div class="subscribe">
+ <form method="POST" action="/youtube.com/subscriptions" class="subscribe-unsubscribe">
+ <input class="btn-subscribe" type="submit" value="{{ 'Unsubscribe' if subscribed else 'Subscribe' }}">
+ <input type="hidden" name="channel_id" value="{{ channel_id }}">
+ <input type="hidden" name="channel_name" value="{{ channel_name }}">
+ <input type="hidden" name="action" value="{{ 'unsubscribe' if subscribed else 'subscribe' }}">
+ </form>
+ </div>
</div>
+ <hr/>
+
<nav class="channel-tabs">
- {% for tab_name in ('Videos', 'Playlists', 'About') %}
+ {% for tab_name in ('Videos', 'Shorts', 'Streams', 'Playlists', 'About') %}
{% if tab_name.lower() == current_tab %}
<a class="tab page-button">{{ tab_name }}</a>
{% else %}
@@ -116,8 +49,17 @@
{% if current_tab == 'about' %}
<div class="channel-info">
<ul>
- {% for stat in stats %}
- <li>{{ stat }}</li>
+ {% for (before_text, stat, after_text) in [
+ ('Joined ', date_joined, ''),
+ ('', approx_view_count, ' views'),
+ ('', approx_subscriber_count, ' subscribers'),
+ ('', approx_video_count, ' videos'),
+ ('Country: ', country, ''),
+ ('Canonical Url: ', canonical_url, ''),
+ ] %}
+ {% if stat %}
+ <li>{{ before_text + stat|string + after_text }}</li>
+ {% endif %}
{% endfor %}
</ul>
<hr>
@@ -126,43 +68,66 @@
<hr>
<ul>
{% for text, url in links %}
- <li><a href="{{ url }}">{{ text }}</a></li>
+ {% if url %}
+ <li><a href="{{ url }}">{{ text }}</a></li>
+ {% else %}
+ <li>{{ text }}</li>
+ {% endif %}
{% endfor %}
</ul>
</div>
{% else %}
- <div id="links-metadata">
- {% if current_tab == 'videos' %}
- {% set sorts = [('1', 'views'), ('2', 'oldest'), ('3', 'newest')] %}
- <div id="number-of-results">{{ number_of_videos }} videos</div>
- {% elif current_tab == 'playlists' %}
- {% set sorts = [('2', 'oldest'), ('3', 'newest'), ('4', 'last video added')] %}
- {% else %}
- {% set sorts = [] %}
- {% endif %}
- {% for sort_number, sort_name in sorts %}
- {% if sort_number == current_sort.__str__() %}
- <a class="sort-button">{{ 'Sorted by ' + sort_name }}</a>
+ <!-- new-->
+ <div id="links-metadata">
+ {% if current_tab in ('videos', 'shorts', 'streams') %}
+ {% set sorts = [('1', 'views'), ('2', 'oldest'), ('3', 'newest'), ('4', 'newest - no shorts'),] %}
+ <div id="number-of-results">{{ number_of_videos }} videos</div>
+ {% elif current_tab == 'playlists' %}
+ {% set sorts = [('2', 'oldest'), ('3', 'newest'), ('4', 'last video added')] %}
+ {% if items %}
+ <h2 class="page-number">Page {{ page_number }}</h2>
+ {% else %}
+ <h2 class="page-number">No items</h2>
+ {% endif %}
+ {% elif current_tab == 'search' %}
+ {% if items %}
+ <h2 class="page-number">Page {{ page_number }}</h2>
+ {% else %}
+ <h2 class="page-number">No results</h2>
+ {% endif %}
{% else %}
- <a class="sort-button" href="{{ channel_url + '/' + current_tab + '?sort=' + sort_number }}">{{ 'Sort by ' + sort_name }}</a>
+ {% set sorts = [] %}
{% endif %}
- {% endfor %}
+
+ {% for sort_number, sort_name in sorts %}
+ {% if sort_number == current_sort.__str__() %}
+ <a class="sort-button">{{ 'Sorted by ' + sort_name }}</a>
+ {% else %}
+ <a class="sort-button" href="{{ channel_url + '/' + current_tab + '?sort=' + sort_number }}">{{ 'Sort by ' + sort_name }}</a>
+ {% endif %}
+ {% endfor %}
</div>
- {% if current_tab != 'about' %}
- <nav class="{{ 'item-list' if current_tab == 'search' else 'item-grid' }}">
+ <div class="video-container {{ current_tab + '-content'}}">
{% for item_info in items %}
{{ common_elements.item(item_info, include_author=false) }}
{% endfor %}
- </nav>
+ </div>
+ <hr/>
- {% if current_tab != 'playlists' %}
- <nav class="page-button-row">
- {{ common_elements.page_buttons(number_of_pages, channel_url + '/' + current_tab, parameters_dictionary) }}
+ <footer class="pagination-container">
+ {% if current_tab in ('videos', 'shorts', 'streams') %}
+ <nav class="pagination-list">
+ {{ common_elements.page_buttons(number_of_pages, channel_url + '/' + current_tab, parameters_dictionary, include_ends=(current_sort.__str__() in '34')) }}
+ </nav>
+ {% elif current_tab == 'playlists' or current_tab == 'search' %}
+ <nav class="next-previous-button-row">
+ {{ common_elements.next_previous_buttons(is_last_page, channel_url + '/' + current_tab, parameters_dictionary) }}
</nav>
{% endif %}
- {% endif %}
-
+ </footer>
+ <!-- /new-->
{% endif %}
+
{% endblock main %}
diff --git a/youtube/templates/comments.html b/youtube/templates/comments.html
index 20cde4e..7bd75e5 100644
--- a/youtube/templates/comments.html
+++ b/youtube/templates/comments.html
@@ -1,30 +1,43 @@
{% import "common_elements.html" as common_elements %}
-{% macro render_comment(comment, include_avatar) %}
+{% macro render_comment(comment, include_avatar, timestamp_links=False) %}
<div class="comment-container">
<div class="comment">
<a class="author-avatar" href="{{ comment['author_url'] }}" title="{{ comment['author'] }}">
{% if include_avatar %}
- <img class="author-avatar-img" src="{{ comment['author_avatar'] }}">
+ <img class="author-avatar-img" alt="{{ comment['author'] }}" src="{{ comment['author_avatar'] }}">
{% endif %}
</a>
- <address>
+ <address class="author-name">
<a class="author" href="{{ comment['author_url'] }}" title="{{ comment['author'] }}">{{ comment['author'] }}</a>
</address>
<a class="permalink" href="{{ comment['permalink'] }}" title="permalink">
- <time datetime="">{{ comment['published'] }}</time>
+ <span>{{ comment['time_published'] }}</span>
</a>
- <span class="text">{{ common_elements.text_runs(comment['text']) }}</span>
- <span class="likes">{{ comment['likes_text'] if comment['likes'] else ''}}</span>
- <div class="bottom-row">
- <a href="{{ comment['replies_url'] }}" class="replies">{{ comment['view_replies_text'] }}</a>
- {% if 'delete_url' is in comment %}
- <a href="{{ comment['delete_url'] }}" target="_blank">Delete</a>
+ {% if timestamp_links %}
+ <span class="comment-text">{{ common_elements.text_runs(comment['text'])|timestamps|safe }}</span>
+ {% else %}
+ <span class="comment-text">{{ common_elements.text_runs(comment['text']) }}</span>
+ {% endif %}
+
+ <span class="comment-likes">{{ comment['likes_text'] if comment['approx_like_count'] else ''}}</span>
+ <div class="button-row">
+ {% if comment['reply_count'] %}
+ {% if settings.use_comments_js and comment['replies_url'] %}
+ <details class="replies" data-src="{{ comment['replies_url'] }}">
+ <summary>{{ comment['view_replies_text'] }}</summary>
+ <a href="{{ comment['replies_url'] }}" class="replies-open-new-tab" target="_blank">Open in new tab</a>
+ <div class="comment_page">loading...</div>
+ </details>
+ {% elif comment['replies_url'] %}
+ <a href="{{ comment['replies_url'] }}" class="replies">{{ comment['view_replies_text'] }}</a>
+ {% else %}
+ <a class="replies">{{ comment['view_replies_text'] }} (error constructing url)</a>
+ {% endif %}
{% endif %}
</div>
</div>
-
</div>
{% endmacro %}
@@ -34,35 +47,19 @@
<a class="sort-button" href="{{ link_url }}">{{ link_text }}</a>
{% endfor %}
</div>
- <div class="comments">
- {% for comment in comments_info['comments'] %}
- {{ render_comment(comment, comments_info['include_avatars']) }}
- {% endfor %}
- </div>
- {% if 'more_comments_url' is in comments_info %}
- <a class="page-button more-comments" href="{{ comments_info['more_comments_url'] }}">More comments</a>
- {% endif %}
-{% endmacro %}
-
-{% macro comment_posting_box(info) %}
- <form action="{{ info['form_action'] }}" method="post" class="comment-form">
- <div id="comment-account-options">
- <label for="account-selection">Account:</label>
- <select id="account-selection" name="channel_id">
- {% for account in info['accounts'] %}
- <option value="{{ account[0] }}">{{ account[1] }}</option>
- {% endfor %}
- </select>
- <a href="/https://youtube.com/login" target="_blank">Add account</a>
+ {% if comments_info['error'] %}
+ <div class="comments">
+ <div class="code-box"><code>{{ comments_info['error'] }}</code></div>
</div>
- <textarea name="comment_text"></textarea>
- {% if info['include_video_id_input'] %}
- <input type="hidden" name="video_id" value="{{ info['video_id'] }}">
+ {% else %}
+ <div class="comments">
+ {% for comment in comments_info['comments'] %}
+ {{ render_comment(comment, comments_info['include_avatars'], True) }}
+ {% endfor %}
+ </div>
+ {% if 'more_comments_url' is in comments_info %}
+ <a class="page-button more-comments" href="{{ comments_info['more_comments_url'] }}">More comments</a>
{% endif %}
- <button type="submit" class="post-comment-button">{{ 'Post reply' if info['replying'] else 'Post comment' }}</button>
- </form>
-{% endmacro %}
-
-
-
+ {% endif %}
+{% endmacro %}
diff --git a/youtube/templates/comments_page.html b/youtube/templates/comments_page.html
index 047404a..3764b10 100644
--- a/youtube/templates/comments_page.html
+++ b/youtube/templates/comments_page.html
@@ -1,14 +1,12 @@
-{% set page_title = ('Replies' if comments_info['is_replies'] else 'Comments page ' + comments_info['page_number']) %}
-{% extends "base.html" %}
-{% import "comments.html" as comments %}
-
-{% block style %}
- .comments-area{
- margin: auto;
- width:640px;
- }
-{% endblock style %}
+{% set page_title = ('Replies' if comments_info['is_replies'] else 'Comments page ' + comments_info['page_number']|string) %}
+{% import "comments.html" as comments with context %}
+{% if not slim %}
+ {% extends "base.html" %}
+ {% block style %}
+ <link href="/youtube.com/static/comments.css" rel="stylesheet">
+ {% endblock style %}
+{% endif %}
{% block main %}
<section class="comments-area">
@@ -24,8 +22,6 @@
</section>
{% endif %}
- {{ comments.comment_posting_box(comment_posting_box_info) }}
-
{% if not comments_info['is_replies'] %}
<div class="comment-links">
{% for link_text, link_url in comments_info['comment_links'] %}
@@ -36,13 +32,16 @@
<div class="comments">
{% for comment in comments_info['comments'] %}
- {{ comments.render_comment(comment, comments_info['include_avatars']) }}
+ {{ comments.render_comment(comment, comments_info['include_avatars'], slim) }}
{% endfor %}
</div>
{% if 'more_comments_url' is in comments_info %}
<a class="page-button more-comments" href="{{ comments_info['more_comments_url'] }}">More comments</a>
{% endif %}
</section>
-{% endblock main %}
-
+ {% if settings.use_comments_js %}
+ <script src="/youtube.com/static/js/common.js"></script>
+ <script src="/youtube.com/static/js/comments.js"></script>
+ {% endif %}
+{% endblock main %}
diff --git a/youtube/templates/common_elements.html b/youtube/templates/common_elements.html
index 67655b3..bacc513 100644
--- a/youtube/templates/common_elements.html
+++ b/youtube/templates/common_elements.html
@@ -9,59 +9,75 @@
{{ text_run["text"] }}
{%- endif -%}
{%- endfor -%}
- {%- else -%}
+ {%- elif runs -%}
{{ runs }}
{%- endif -%}
{% endmacro %}
-{% macro item(info, description=false, horizontal=true, include_author=true) %}
- <div class="item-box {{ info['type'] + '-item-box' }} {{'horizontal-item-box' if horizontal else 'vertical-item-box'}} {{'has-description' if description else 'no-description'}}">
- <div class="item {{ info['type'] + '-item' }}">
- <a class="thumbnail-box" href="{{ info['url'] }}" title="{{ info['title'] }}">
- <img class="thumbnail-img" src="{{ info['thumbnail'] }}">
- {% if info['type'] != 'channel' %}
- <div class="thumbnail-info">
- <span>{{ info['size'] if info['type'] == 'playlist' else info['duration'] }}</span>
- </div>
- {% endif %}
- </a>
+{% macro item(info, description=false, horizontal=true, include_author=true, include_badges=true, lazy_load=false) %}
+ <article class="item-box">
+ {% if info['error'] %}
+ {{ info['error'] }}
+ {% else %}
+ <div class="item-video {{ info['type'] + '-item' }}">
+ <a class="thumbnail-box" href="{{ info['url'] }}" title="{{ info['title'] }}">
+ <div class="thumbnail {% if info['type'] == 'channel' %} channel {% endif %}">
+ {% if lazy_load %}
+ <img class="thumbnail-img lazy" alt="&#x20;" data-src="{{ info['thumbnail'] }}">
+ {% elif info['type'] == 'channel' %}
+ <img class="thumbnail-img channel" alt="&#x20;" src="{{ info['thumbnail'] }}">
+ {% else %}
+ <img class="thumbnail-img" alt="&#x20;" src="{{ info['thumbnail'] }}">
+ {% endif %}
- <div class="title"><a class="title" href="{{ info['url'] }}" title="{{ info['title'] }}">{{ info['title'] }}</a></div>
+ {% if info['type'] != 'channel' %}
+ <p class="length">{{ (info['video_count']|commatize + ' videos') if info['type'] == 'playlist' else info['duration'] }}</p>
+ {% endif %}
+ </div>
+ </a>
+ <h4 class="title"><a href="{{ info['url'] }}" title="{{ info['title'] }}">{{ info['title'] }}</a></h4>
- <ul class="stats {{'vertical-stats' if horizontal and not description and include_author else 'horizontal-stats'}}">
- {% if info['type'] == 'channel' %}
- <li><span>{{ info['subscriber_count'] }} subscribers</span></li>
- <li><span>{{ info['size'] }} videos</span></li>
- {% else %}
- {% if include_author %}
- {% if 'author_url' is in(info) %}
- <li><address title="{{ info['author'] }}">By <a href="{{ info['author_url'] }}">{{ info['author'] }}</a></address></li>
- {% else %}
- <li><address title="{{ info['author'] }}"><b>{{ info['author'] }}</b></address></li>
+ {% if include_author %}
+ {% set author_description = info['author'] %}
+ {% set AUTHOR_DESC_LENGTH = 35 %}
+ {% if author_description != None %}
+ {% if author_description|length >= AUTHOR_DESC_LENGTH %}
+ {% set author_description = author_description[:AUTHOR_DESC_LENGTH].split(' ')[:-1]|join(' ') %}
+ {% if not author_description[-1] in ['.', '?', ':', '!'] %}
+ {% set author_more = author_description + '…' %}
+ {% set author_description = author_more|replace('"','') %}
+ {% endif %}
{% endif %}
{% endif %}
- {% if 'views' is in(info) %}
- <li><span class="views">{{ info['views'] }}</span></li>
- {% endif %}
- {% if 'published' is in(info) %}
- <li><time>{{ info['published'] }}</time></li>
+ {% if info.get('author_url') %}
+ <address title="{{ info['author'] }}"><b><a href="{{ info['author_url'] }}">{{ author_description }}</a></b></address>
+ {% else %}
+ <address title="{{ info['author'] }}"><b>{{ author_description }}</b></address>
{% endif %}
{% endif %}
- </ul>
- {% if description %}
- <span class="description">{{ text_runs(info.get('description', '')) }}</span>
+ <div class="stats {{'horizontal-stats' if horizontal else 'vertical-stats'}}">
+ {% if info['type'] == 'channel' %}
+ <div>{{ info['approx_subscriber_count'] }} subscribers</div>
+ <div>{{ info['video_count']|commatize }} videos</div>
+ {% else %}
+ {% if info.get('time_published') %}
+ <span>{{ info['time_published'] }}</span>
+ {% endif %}
+ {% if info.get('approx_view_count') %}
+ <div class="views">{{ info['approx_view_count'] }} views</div>
+ {% endif %}
+ {% endif %}
+ </div>
+ </div>
+ {% if info['type'] == 'video' %}
+ <input class="item-checkbox" type="checkbox" name="video_info_list" value="{{ info['video_info'] }}" form="playlist-edit">
{% endif %}
- <span class="badges">{{ info['badges']|join(' | ') }}</span>
- </div>
- {% if info['type'] == 'video' %}
- <input class="item-checkbox" type="checkbox" name="video_info_list" value="{{ info['video_info'] }}" form="playlist-edit">
{% endif %}
- </div>
-
+ </article>
{% endmacro %}
-{% macro page_buttons(estimated_pages, url, parameters_dictionary) %}
+{% macro page_buttons(estimated_pages, url, parameters_dictionary, include_ends=false) %}
{% set current_page = parameters_dictionary.get('page', 1)|int %}
{% set parameters_dictionary = parameters_dictionary.to_dict() %}
{% if current_page is le(5) %}
@@ -72,15 +88,53 @@
{% set page_end = [current_page + 4, estimated_pages]|min %}
{% endif %}
+ {% if include_ends and page_start is gt(1) %}
+ {% set _ = parameters_dictionary.__setitem__('page', 1) %}
+ <a class="page-link first-page-button" href="{{ url + '?' + parameters_dictionary|urlencode }}">{{ 1 }}</a>
+ {% endif %}
+
{% for page in range(page_start, page_end+1) %}
{% if page == current_page %}
- <div class="page-button">{{ page }}</div>
+ <a class="page-link is-current">{{ page }}</a>
{% else %}
- {# IMPORTANT: Jinja SUCKS #}
{# https://stackoverflow.com/questions/36886650/how-to-add-a-new-entry-into-a-dictionary-object-while-using-jinja2 #}
{% set _ = parameters_dictionary.__setitem__('page', page) %}
- <a class="page-button" href="{{ url + '?' + parameters_dictionary|urlencode }}">{{ page }}</a>
+ <a class="page-link" href="{{ url + '?' + parameters_dictionary|urlencode }}">{{ page }}</a>
{% endif %}
{% endfor %}
+ {% if include_ends and page_end is lt(estimated_pages) %}
+ {% set _ = parameters_dictionary.__setitem__('page', estimated_pages) %}
+ <a class="page-link last-page-button" href="{{ url + '?' + parameters_dictionary|urlencode }}">{{ estimated_pages }}</a>
+ {% endif %}
+
+{% endmacro %}
+
+{% macro next_previous_buttons(is_last_page, url, parameters_dictionary) %}
+ {% set current_page = parameters_dictionary.get('page', 1)|int %}
+ {% set parameters_dictionary = parameters_dictionary.to_dict() %}
+
+ {% if current_page != 1 %}
+ {% set _ = parameters_dictionary.__setitem__('page', current_page - 1) %}
+ <a class="page-link previous-page" href="{{ url + '?' + parameters_dictionary|urlencode }}">Previous page</a>
+ {% endif %}
+
+ {% if not is_last_page %}
+ {% set _ = parameters_dictionary.__setitem__('page', current_page + 1) %}
+ <a class="page-link next-page" href="{{ url + '?' + parameters_dictionary|urlencode }}">Next page</a>
+ {% endif %}
+{% endmacro %}
+
+{% macro next_previous_ctoken_buttons(prev_ctoken, next_ctoken, url, parameters_dictionary) %}
+ {% set parameters_dictionary = parameters_dictionary.to_dict() %}
+
+ {% if prev_ctoken %}
+ {% set _ = parameters_dictionary.__setitem__('ctoken', prev_ctoken) %}
+ <a class="page-link previous-page" href="{{ url + '?' + parameters_dictionary|urlencode }}">Previous page</a>
+ {% endif %}
+
+ {% if next_ctoken %}
+ {% set _ = parameters_dictionary.__setitem__('ctoken', next_ctoken) %}
+ <a class="page-link next-page" href="{{ url + '?' + parameters_dictionary|urlencode }}">Next page</a>
+ {% endif %}
{% endmacro %}
diff --git a/youtube/templates/delete_comment.html b/youtube/templates/delete_comment.html
deleted file mode 100644
index 28c8f2a..0000000
--- a/youtube/templates/delete_comment.html
+++ /dev/null
@@ -1,21 +0,0 @@
-{% set page_title = 'Delete comment?' %}
-{% extends "base.html" %}
-
-{% block style %}
- main > div, main > form{
- margin: auto;
- margin-top:20px;
- width: 640px;
- }
-{% endblock style %}
-
-{% block main %}
- <div>Are you sure you want to delete this comment?</div>
- <form action="" method="POST">
- {% for parameter_name, parameter_value in parameters %}
- <input type="hidden" name="{{ parameter_name }}" value="{{ parameter_value }}">
- {% endfor %}
- <input type="submit" value="Yes, delete it">
- </form>
-{% endblock %}
-
diff --git a/youtube/templates/embed.html b/youtube/templates/embed.html
new file mode 100644
index 0000000..85d2d78
--- /dev/null
+++ b/youtube/templates/embed.html
@@ -0,0 +1,74 @@
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <meta charset="UTF-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1">
+ <meta http-equiv="Content-Security-Policy" content="default-src 'self' 'unsafe-inline'; media-src 'self' https://*.googlevideo.com; {{ "img-src 'self' https://*.googleusercontent.com https://*.ggpht.com https://*.ytimg.com;" if not settings.proxy_images else "" }}">
+ <title>{{ title }}</title>
+ <link href="/youtube.com/static/favicon.ico" type="image/x-icon" rel="icon">
+ {% if settings.use_video_player == 2 %}
+ <!-- plyr -->
+ <link href="/youtube.com/static/modules/plyr/plyr.css" rel="stylesheet">
+ <!--/ plyr -->
+ {% endif %}
+ <style>
+ body {
+ margin: 0rem;
+ padding: 0rem;
+ }
+ video {
+ width: 100%;
+ height: auto;
+ }
+ /* Prevent this div from blocking right-click menu for video
+ e.g. Firefox playback speed options */
+ .plyr__poster {
+ display: none !important;
+ }
+ </style>
+ {% if js_data %}
+ <script>
+ // @license magnet:?xt=urn:btih:0b31508aeb0634b347b8270c7bee4d411b5d4109&dn=agpl-3.0.txt AGPL-v3-or-Later
+ data = {{ js_data|tojson }};
+ // @license-end
+ </script>
+ {% endif %}
+ </head>
+ <body>
+ <video id="js-video-player" controls autofocus onmouseleave="{{ title }}"
+ oncontextmenu="{{ title }}" onmouseenter="{{ title }}" title="{{ title }}">
+ {% if uni_sources %}
+ <source src="{{ uni_sources[uni_idx]['url'] }}" type="{{ uni_sources[uni_idx]['type'] }}" data-res="{{ uni_sources[uni_idx]['quality'] }}">
+ {% endif %}
+ {% for source in subtitle_sources %}
+ {% if source['on'] %}
+ <track label="{{ source['label'] }}" src="{{ source['url'] }}" kind="subtitles" srclang="{{ source['srclang'] }}" default>
+ {% else %}
+ <track label="{{ source['label'] }}" src="{{ source['url'] }}" kind="subtitles" srclang="{{ source['srclang'] }}">
+ {% endif %}
+ {% endfor %}
+ </video>
+ {% if js_data %}
+ <script>
+ // @license magnet:?xt=urn:btih:0b31508aeb0634b347b8270c7bee4d411b5d4109&dn=agpl-3.0.txt AGPL-v3-or-Later
+ data = {{ js_data|tojson }};
+ // @license-end
+ </script>
+ {% endif %}
+ <script>
+ // @license magnet:?xt=urn:btih:0b31508aeb0634b347b8270c7bee4d411b5d4109&dn=agpl-3.0.txt AGPL-v3-or-Later
+ let storyboard_url = {{ storyboard_url | tojson }};
+ // @license-end
+ </script>
+ {% if settings.use_video_player == 2 %}
+ <!-- plyr -->
+ <script src="/youtube.com/static/modules/plyr/plyr.min.js"
+ integrity="sha512-l6ZzdXpfMHRfifqaR79wbYCEWjLDMI9DnROvb+oLkKq6d7MGroGpMbI7HFpicvmAH/2aQO+vJhewq8rhysrImw=="
+ crossorigin="anonymous"></script>
+ <script src="/youtube.com/static/js/plyr-start.js"></script>
+ <!-- /plyr -->
+ {% elif settings.use_video_player == 1 %}
+ <script src="/youtube.com/static/js/hotkeys.js"></script>
+ {% endif %}
+ </body>
+</html>
diff --git a/youtube/templates/error.html b/youtube/templates/error.html
index e77c92c..97f8ca9 100644
--- a/youtube/templates/error.html
+++ b/youtube/templates/error.html
@@ -1,7 +1,36 @@
-{% set page_title = 'Error' %}
-{% extends "base.html" %}
+{% if error_code %}
+ {% set page_title = 'Error: ' ~ error_code %}
+{% else %}
+ {% set page_title = 'Error' %}
+{% endif %}
+
+{% if not slim %}
+ {% extends "base.html" %}
+{% endif %}
+
+{% if traceback %}
+ {% block style %}
+ <link href="/youtube.com/static/home.css" rel="stylesheet">
+ {% endblock style %}
+{% endif %}
{% block main %}
- {{ error_message }}
+ {% if traceback %}
+ <div class="code-error" id="error-box">
+ <h1>500 Uncaught exception:</h1>
+ <div class="code-box"><code>{{ traceback }}</code></div>
+ <p>Please report this issue at <a href="https://todo.sr.ht/~heckyel/yt-local" target="_blank" rel="noopener noreferrer">https://todo.sr.ht/~heckyel/yt-local</a></p>
+ <p>Remember to include the traceback in your issue and redact any information in it you do not want to share</p>
+ </div>
+ {% else %}
+ <section id="error-message" class="comments-area">
+ <div class="comments">
+ <div class="comment-container">
+ <div class="comment">
+ <span class="comment-text">{{ error_message }}</span>
+ </div>
+ </div>
+ </div>
+ </section>
+ {% endif %}
{% endblock %}
-
diff --git a/youtube/templates/home.html b/youtube/templates/home.html
index 9890f5e..0adac56 100644
--- a/youtube/templates/home.html
+++ b/youtube/templates/home.html
@@ -1,16 +1,7 @@
{% set page_title = title %}
{% extends "base.html" %}
{% block style %}
- ul {
- background-color: var(--interface-color);
- padding: 20px;
- width: 400px;
- margin: auto;
- margin-top: 20px;
- }
- li {
- margin-bottom: 10px;
- }
+ <link href="/youtube.com/static/home.css" rel="stylesheet">
{% endblock style %}
{% block main %}
<ul>
diff --git a/youtube/templates/licenses.html b/youtube/templates/licenses.html
new file mode 100644
index 0000000..dc73bfb
--- /dev/null
+++ b/youtube/templates/licenses.html
@@ -0,0 +1,64 @@
+{% set page_title = title %}
+{% extends "base.html" %}
+{% block style %}
+ <link href="/youtube.com/static/license.css" rel="stylesheet">
+{% endblock style %}
+{% block main %}
+ <table id="jslicense-labels1" class="table">
+ <caption>JavaScript Licensing Table</caption>
+ <thead>
+ <tr>
+ <th>File</th>
+ <th>License</th>
+ <th>Source</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td data-label="File"><a href="/youtube.com/static/js/av-merge.js">av-merge.js</a></td>
+ <td data-label="License"><a href="http://www.gnu.org/licenses/agpl-3.0.html">AGPL-3.0 or later</a></td>
+ <td data-label="Source"><a href="/youtube.com/static/js/av-merge.js">av-merge.js</a></td>
+ </tr>
+ <tr>
+ <td data-label="File"><a href="/youtube.com/static/js/comments.js">comments.js</a></td>
+ <td data-label="License"><a href="http://www.gnu.org/licenses/agpl-3.0.html">AGPL-3.0 or later</a></td>
+ <td data-label="Source"><a href="/youtube.com/static/js/comments.js">comments.js</a></td>
+ </tr>
+ <tr>
+ <td data-label="File"><a href="/youtube.com/static/js/common.js">common.js</a></td>
+ <td data-label="License"><a href="http://www.gnu.org/licenses/agpl-3.0.html">AGPL-3.0 or later</a></td>
+ <td data-label="Source"><a href="/youtube.com/static/js/common.js">common.js</a></td>
+ </tr>
+ <tr>
+ <td data-label="File"><a href="/youtube.com/static/js/hotkeys.js">hotkeys.js</a></td>
+ <td data-label="License"><a href="http://www.gnu.org/licenses/agpl-3.0.html">AGPL-3.0 or later</a></td>
+ <td data-label="Source"><a href="/youtube.com/static/js/hotkeys.js">hotkeys.js</a></td>
+ </tr>
+ <tr>
+ <td data-label="File"><a href="/youtube.com/static/js/playlistadd.js">playlistadd.js</a></td>
+ <td data-label="License"><a href="http://www.gnu.org/licenses/agpl-3.0.html">AGPL-3.0 or later</a></td>
+ <td data-label="Source"><a href="/youtube.com/static/js/playlistadd.js">playlistadd.js</a></td>
+ </tr>
+ <tr>
+ <td data-label="File"><a href="/youtube.com/static/js/plyr-start.js">plyr-start.js</a></td>
+ <td data-label="License"><a href="http://www.gnu.org/licenses/agpl-3.0.html">AGPL-3.0 or later</a></td>
+ <td data-label="Source"><a href="/youtube.com/static/js/plyr-start.js">plyr-start.js</a></td>
+ </tr>
+ <tr>
+ <td data-label="File"><a href="/youtube.com/static/modules/plyr/plyr.min.js">plyr.min.js</a></td>
+ <td data-label="License"><a href="https://spdx.org/licenses/MIT.html">Expat</a></td>
+ <td data-label="Source"><a href="/youtube.com/static/modules/plyr/plyr.js">plyr.js</a></td>
+ </tr>
+ <tr>
+ <td data-label="File"><a href="/youtube.com/static/js/transcript-table.js">transcript-table.js</a></td>
+ <td data-label="License"><a href="http://www.gnu.org/licenses/agpl-3.0.html">AGPL-3.0 or later</a></td>
+ <td data-label="Source"><a href="/youtube.com/static/js/transcript-table.js">transcript-table.js</a></td>
+ </tr>
+ <tr>
+ <td data-label="File"><a href="/youtube.com/static/js/watch.js">watch.js</a></td>
+ <td data-label="License"><a href="http://www.gnu.org/licenses/agpl-3.0.html">AGPL-3.0 or later</a></td>
+ <td data-label="Source"><a href="/youtube.com/static/js/watch.js">watch.js</a></td>
+ </tr>
+ </tbody>
+ </table>
+{% endblock main %}
diff --git a/youtube/templates/local_playlist.html b/youtube/templates/local_playlist.html
index 7ba0642..3286f67 100644
--- a/youtube/templates/local_playlist.html
+++ b/youtube/templates/local_playlist.html
@@ -2,41 +2,48 @@
{% extends "base.html" %}
{% import "common_elements.html" as common_elements %}
{% block style %}
- main > *{
- width: 800px;
- margin: auto;
- }
-
- .playlist-metadata{
- display: flex;
- flex-direction: row;
- justify-content: space-between;
- }
- .playlist-title{
- }
- #playlist-remove-button{
- align-self: center;
- white-space: nowrap;
- }
- #results{
- display: grid;
- grid-auto-rows: 0fr;
- grid-row-gap: 10px;
- }
+ <link href="/youtube.com/static/message_box.css" rel="stylesheet">
+ <link href="/youtube.com/static/local_playlist.css" rel="stylesheet">
{% endblock style %}
-{% block main %}
+{% block main %}
<div class="playlist-metadata">
- <h2 class="playlist-title">{{ playlist_name }}</h2>
+ <h2 class="play-title">{{ playlist_name }}</h2>
+
+ <div id="export-options">
+ <form id="playlist-export" method="post">
+ <select id="export-type" name="export_format">
+ <option value="json">JSON</option>
+ <option value="ids">Video id list (txt)</option>
+ <option value="urls">Video url list (txt)</option>
+ </select>
+ <button type="submit" id="playlist-export-button" name="action" value="export">Export</button>
+ </form>
+ </div>
+ </div>
+
+ <form id="playlist-remove" action="/youtube.com/edit_playlist" method="post" target="_self"></form>
+ <div class="playlist-metadata" id="video-remove-container">
+ <button id="removePlayList" type="submit" name="action" value="remove_playlist" form="playlist-remove" formaction="">Remove playlist</button>
<input type="hidden" name="playlist_page" value="{{ playlist_name }}" form="playlist-edit">
- <button type="submit" id="playlist-remove-button" name="action" value="remove" form="playlist-edit" formaction="">Remove from playlist</button>
+ <button class="play-action" type="submit" id="playlist-remove-button" name="action" value="remove" form="playlist-edit" formaction="">Remove from playlist</button>
</div>
- <div id="results">
+ <div id="results" class="video-container">
{% for video_info in videos %}
{{ common_elements.item(video_info) }}
{% endfor %}
</div>
- <nav class="page-button-row">
- {{ common_elements.page_buttons(num_pages, '/https://www.youtube.com/playlists/' + playlist_name, parameters_dictionary) }}
- </nav>
+ <script>
+ // @license magnet:?xt=urn:btih:1f739d935676111cfff4b4693e3816e664797050&dn=gpl-3.0.txt GPL-v3-or-Later
+ const deletePlayList = document.getElementById('removePlayList');
+ deletePlayList.addEventListener('click', (event) => {
+ return confirm('You are about to permanently delete {{ playlist_name }}\n\nOnce a playlist is permanently deleted, it cannot be recovered.')
+ });
+ // @license-end
+ </script>
+ <footer class="pagination-container">
+ <nav class="pagination-list">
+ {{ common_elements.page_buttons(num_pages, '/https://www.youtube.com/playlists/' + playlist_name, parameters_dictionary) }}
+ </nav>
+ </footer>
{% endblock main %}
diff --git a/youtube/templates/local_playlists_list.html b/youtube/templates/local_playlists_list.html
index 9b5f510..61a6888 100644
--- a/youtube/templates/local_playlists_list.html
+++ b/youtube/templates/local_playlists_list.html
@@ -1,6 +1,10 @@
{% set page_title = 'Local playlists' %}
{% extends "base.html" %}
+{% block style %}
+ <link href="/youtube.com/static/home.css" rel="stylesheet"/>
+{% endblock style %}
+
{% block main %}
<ul>
{% for playlist_name, playlist_url in playlists %}
@@ -8,9 +12,3 @@
{% endfor %}
</ul>
{% endblock main %}
-
-
-
-
-
-
diff --git a/youtube/templates/login.html b/youtube/templates/login.html
deleted file mode 100644
index 384f1ac..0000000
--- a/youtube/templates/login.html
+++ /dev/null
@@ -1,57 +0,0 @@
-{% set page_title = 'Login' %}
-{% extends "base.html" %}
-
-{% block style %}
- main > * {
- width: 640px;
- margin: auto;
- }
- main form{
- background-color: var(--interface-color);
- padding: 10px;
- margin-top:20px;
- display:grid;
- justify-items: start;
- align-content: start;
- grid-row-gap: 10px;
- }
-
- #username, #password{
- grid-column:2;
- width: 250px;
- }
- #add-account-button{
- margin-top:20px;
- }
- #tor-note{
- background-color: var(--interface-color);
- padding: 10px;
- margin-top: 40px;
- }
-{% endblock style %}
-
-{% block main %}
- <form action="" method="POST">
- <div class="form-field">
- <label for="username">Username:</label>
- <input type="text" id="username" name="username">
- </div>
- <div class="form-field">
- <label for="password">Password:</label>
- <input type="password" id="password" name="password">
- </div>
- <div id="save-account-checkbox">
- <input type="checkbox" id="save-account" name="save" checked>
- <label for="save-account">Save account info to disk (password will not be saved, only the login cookie)</label>
- </div>
- <div>
- <input type="checkbox" id="use-tor" name="use_tor">
- <label for="use-tor">Use Tor when logging in (WARNING: This will lock your Google account under normal circumstances, see note below)</label>
- </div>
- <input type="submit" value="Add account" id="add-account-button">
- </form>
- <div id="tor-note"><b>Note on using Tor to log in</b><br>
-Using Tor to log in should only be done if the account was created using a proxy/VPN/Tor to begin with and hasn't been logged in using your IP. Otherwise, it's pointless since Google already knows who the account belongs to. When logging into a google account, it must be logged in using an IP address geographically close to the area where the account was created or where it is logged into regularly. If the account was created using an IP address in America and is logged into from an IP in Russia, Google will block the Russian IP from logging in, assume someone knows your password, lock the account, and make you change your password. If creating an account using Tor, you must remember the IP (or geographic region) it was created in, and only log in using that geographic region for the exit node. This can be accomplished by <a href="https://tor.stackexchange.com/questions/733/can-i-exit-from-a-specific-country-or-node">putting the desired IP in the torrc file</a> to force Tor to use that exit node. Using the login cookie to post comments through Tor is perfectly safe, however.
- </div>
-{% endblock main %}
-
diff --git a/youtube/templates/playlist.html b/youtube/templates/playlist.html
index ab2640f..994523e 100644
--- a/youtube/templates/playlist.html
+++ b/youtube/templates/playlist.html
@@ -1,77 +1,41 @@
-{% set page_title = title + ' - Page ' + parameters_dictionary.get('page', '1') %}
+{% set page_title = title|string + ' - Page ' + parameters_dictionary.get('page', '1') %}
{% extends "base.html" %}
{% import "common_elements.html" as common_elements %}
{% block style %}
- main > * {
- width: 800px;
- margin:auto;
- }
-
- .playlist-metadata{
- display:grid;
- grid-template-columns: 0fr 1fr;
- }
- .playlist-thumbnail{
- grid-row: 1 / span 5;
- grid-column:1;
- justify-self:start;
- width:250px;
- margin-right: 10px;
- }
- .playlist-title{
- grid-row: 1;
- grid-column:2;
- }
- .playlist-author{
- grid-row:2;
- grid-column:2;
- }
- .playlist-stats{
- grid-row:3;
- grid-column:2;
- }
-
- .playlist-description{
- grid-row:4;
- grid-column:2;
- min-width:0px;
- white-space: pre-line;
- }
-
- #results{
- margin-top:10px;
-
- display: grid;
- grid-auto-rows: 0fr;
- grid-row-gap: 10px;
-
- }
+ <link href="/youtube.com/static/message_box.css" rel="stylesheet">
+ <link href="/youtube.com/static/playlist.css" rel="stylesheet">
{% endblock style %}
{% block main %}
+
<div class="playlist-metadata">
- <img class="playlist-thumbnail" src="{{ thumbnail }}">
- <h2 class="playlist-title">{{ title }}</h2>
- <a class="playlist-author" href="{{ author_url }}">{{ author }}</a>
+ <div class="author">
+ <img alt="{{ title }}" src="{{ thumbnail }}">
+ <h2>{{ title }}</h2>
+ </div>
+ <div class="summary">
+ <a class="playlist-author" href="{{ author_url }}">{{ author }}</a>
+ </div>
<div class="playlist-stats">
- <div>{{ views }}</div>
- <div>{{ size }}</div>
+ <div>{{ video_count|commatize }} videos</div>
+ <div>{{ view_count|commatize }} views</div>
+ <div>Last updated {{ time_published }}</div>
</div>
- <div class="playlist-description">{{ common_elements.text_runs(description) }}</div>
</div>
+ <hr/>
+
- <div id="results">
+ <div id="results" class="video-container">
{% for info in video_list %}
{{ common_elements.item(info) }}
{% endfor %}
</div>
- <nav class="page-button-row">
- {{ common_elements.page_buttons(num_pages, '/https://www.youtube.com/playlist', parameters_dictionary) }}
- </nav>
-{% endblock main %}
-
-
-
-
+ <hr/>
+ <footer class="pagination-container">
+ <nav class="pagination-list">
+ {{ common_elements.page_buttons(num_pages, '/https://www.youtube.com/playlist', parameters_dictionary) }}
+ </nav>
+ </footer>
+{% endblock main %}
diff --git a/youtube/templates/post_comment.html b/youtube/templates/post_comment.html
deleted file mode 100644
index ba6a22c..0000000
--- a/youtube/templates/post_comment.html
+++ /dev/null
@@ -1,20 +0,0 @@
-{% set page_title = 'Post reply' if replying else 'Post comment' %}
-{% extends "base.html" %}
-{% import "comments.html" as comments %}
-
-{% block style %}
- .comment-form{
- width: 640px;
- margin: auto;
- justify-content:start;
- }
- textarea{
- width: 460px;
- height: 85px;
- }
-{% endblock style %}
-
-{% block main %}
- {{ comments.comment_posting_box(comment_posting_box_info) }}
-{% endblock %}
-
diff --git a/youtube/templates/search.html b/youtube/templates/search.html
index aef914a..af87c90 100644
--- a/youtube/templates/search.html
+++ b/youtube/templates/search.html
@@ -3,44 +3,32 @@
{% extends "base.html" %}
{% import "common_elements.html" as common_elements %}
{% block style %}
- main > * {
- max-width: 800px;
- margin: auto;
- }
- #result-info{
- margin-top: 10px;
- margin-bottom: 10px;
- padding-left: 10px;
- padding-right: 10px;
- }
- #number-of-results{
- font-weight:bold;
- }
- .item-list{
- padding-left: 10px;
- padding-right: 10px;
- }
- .badge{
- background-color:#cccccc;
- }
+ <link href="/youtube.com/static/message_box.css" rel="stylesheet">
+ <link href="/youtube.com/static/search.css" rel="stylesheet">
{% endblock style %}
{% block main %}
- <div id="result-info">
- <div id="number-of-results">Approximately {{ '{:,}'.format(estimated_results) }} results ({{ '{:,}'.format(estimated_pages) }} pages)</div>
-{% if corrections['type'] == 'showing_results_for' %}
- <div>Showing results for <a>{{ corrections['corrected_query']|safe }}</a></div>
- <div>Search instead for <a href="{{ corrections['original_query_url'] }}">{{ corrections['original_query'] }}</a></div>
-{% elif corrections['type'] == 'did_you_mean' %}
- <div>Did you mean <a href="{{ corrections['corrected_query_url'] }}">{{ corrections['corrected_query']|safe }}</a></div>
-{% endif %}
- </div>
- <div class="item-list">
- {% for info in results %}
- {{ common_elements.item(info, description=true) }}
- {% endfor %}
- </div>
- <nav class="page-button-row">
- {{ common_elements.page_buttons(estimated_pages, '/https://www.youtube.com/search', parameters_dictionary) }}
- </nav>
+ <div class="result-info" id="result-info">
+ <div id="number-of-results">Approximately {{ '{:,}'.format(estimated_results) }} results ({{ '{:,}'.format(estimated_pages) }} pages)</div>
+ {% if corrections['type'] == 'showing_results_for' %}
+ <div>Showing results for <a>{{ common_elements.text_runs(corrections['corrected_query_text']) }}</a></div>
+ <div>Search instead for <a href="{{ corrections['original_query_url'] }}">{{ corrections['original_query_text'] }}</a></div>
+ {% elif corrections['type'] == 'did_you_mean' %}
+ <div>Did you mean <a href="{{ corrections['corrected_query_url'] }}">{{ common_elements.text_runs(corrections['corrected_query_text']) }}</a></div>
+ {% endif %}
+ </div>
+
+ <!-- video item -->
+ <div class="video-container">
+ {% for info in results %}
+ {{ common_elements.item(info, description=true) }}
+ {% endfor %}
+ </div>
+ <hr/>
+ <!-- /video item -->
+ <footer class="pagination-container">
+ <nav class="pagination-list">
+ {{ common_elements.page_buttons(estimated_pages, '/https://www.youtube.com/results', parameters_dictionary) }}
+ </nav>
+ </footer>
{% endblock main %}
diff --git a/youtube/templates/settings.html b/youtube/templates/settings.html
index 19a2461..a4ebabf 100644
--- a/youtube/templates/settings.html
+++ b/youtube/templates/settings.html
@@ -1,65 +1,47 @@
{% set page_title = 'Settings' %}
{% extends "base.html" %}
-{% import "common_elements.html" as common_elements %}
{% block style %}
- .settings-form {
- margin: auto;
- width: 500px;
- margin-top:10px;
- padding: 10px;
- display: block;
- background-color: var(--interface-color);
- }
- .settings-list{
- list-style: none;
- padding: 0px;
- }
- .setting-item{
- margin-bottom: 10px;
- padding: 5px;
- }
- .setting-item label{
- display: inline-block;
- width: 250px;
- }
-
+ <link href="/youtube.com/static/settings.css" rel="stylesheet">
{% endblock style %}
{% block main %}
<form method="POST" class="settings-form">
- <ul class="settings-list">
- {% for setting_name, setting_info, value in settings %}
- {% if not setting_info.get('hidden', false) %}
- <li class="setting-item">
- {% if 'label' is in(setting_info) %}
- <label for="{{ 'setting_' + setting_name }}">{{ setting_info['label'] }}</label>
- {% else %}
- <label for="{{ 'setting_' + setting_name }}">{{ setting_name.replace('_', ' ')|capitalize }}</label>
- {% endif %}
-
- {% if setting_info['type'].__name__ == 'bool' %}
- <input type="checkbox" id="{{ 'setting_' + setting_name }}" name="{{ setting_name }}" {{ 'checked' if value else '' }}>
- {% elif setting_info['type'].__name__ == 'int' %}
- {% if 'options' is in(setting_info) %}
- <select id="{{ 'setting_' + setting_name }}" name="{{ setting_name }}">
- {% for option in setting_info['options'] %}
- <option value="{{ option[0] }}" {{ 'selected' if option[0] == value else '' }}>{{ option[1] }}</option>
- {% endfor %}
- </select>
+ {% for categ in categories %}
+ <h2>{{ categ|capitalize }}</h2>
+ <ul class="settings-list">
+ {% for setting_name, setting_info, value in settings_by_category[categ] %}
+ {% if not setting_info.get('hidden', false) %}
+ <li class="setting-item">
+ {% if 'label' is in(setting_info) %}
+ <label for="{{ 'setting_' + setting_name }}" {% if 'comment' is in(setting_info) %}title="{{ setting_info['comment'] }}" {% endif %}>{{ setting_info['label'] }}</label>
{% else %}
- <input type="number" id="{{ 'setting_' + setting_name }}" name="{{ setting_name }}" value="{{ value }}" step="1">
+ <label for="{{ 'setting_' + setting_name }}" {% if 'comment' is in(setting_info) %}title="{{ setting_info['comment'] }}" {% endif %}>{{ setting_name.replace('_', ' ')|capitalize }}</label>
{% endif %}
- {% elif setting_info['type'].__name__ == 'float' %}
- {% elif setting_info['type'].__name__ == 'str' %}
- <input type="text" id="{{ 'setting_' + setting_name }}" name="{{ setting_name }}" value="{{ value }}">
- {% else %}
- <span>Error: Unknown setting type: setting_info['type'].__name__</span>
- {% endif %}
- </li>
- {% endif %}
- {% endfor %}
- </ul>
+ {% if setting_info['type'].__name__ == 'bool' %}
+ <input type="checkbox" id="{{ 'setting_' + setting_name }}" name="{{ setting_name }}" {{ 'checked' if value else '' }}>
+ {% elif setting_info['type'].__name__ == 'int' %}
+ {% if 'options' is in(setting_info) %}
+ <select id="{{ 'setting_' + setting_name }}" name="{{ setting_name }}">
+ {% for option in setting_info['options'] %}
+ <option value="{{ option[0] }}" {{ 'selected' if option[0] == value else '' }}>{{ option[1] }}</option>
+ {% endfor %}
+ </select>
+ {% else %}
+ <input type="number" id="{{ 'setting_' + setting_name }}" name="{{ setting_name }}" value="{{ value }}" step="1">
+ {% endif %}
+ {% elif setting_info['type'].__name__ == 'float' %}
+
+ {% elif setting_info['type'].__name__ == 'str' %}
+ <input type="text" id="{{ 'setting_' + setting_name }}" name="{{ setting_name }}" value="{{ value }}">
+ {% else %}
+ <span>Error: Unknown setting type: setting_info['type'].__name__</span>
+ {% endif %}
+ </li>
+ {% endif %}
+ {% endfor %}
+ </ul>
+ {% endfor %}
<input type="submit" value="Save settings">
</form>
{% endblock main %}
diff --git a/youtube/templates/shared.css b/youtube/templates/shared.css
new file mode 100644
index 0000000..8f12651
--- /dev/null
+++ b/youtube/templates/shared.css
@@ -0,0 +1,5 @@
+html {
+ font-family: {{ font_family }};
+ background: var(--background);
+ color: var(--text);
+}
diff --git a/youtube/templates/status.html b/youtube/templates/status.html
index 901aa5b..97e2ed4 100644
--- a/youtube/templates/status.html
+++ b/youtube/templates/status.html
@@ -4,4 +4,3 @@
{% block main %}
{{ message }}
{% endblock %}
-
diff --git a/youtube/templates/subscription_manager.html b/youtube/templates/subscription_manager.html
index c9683ce..96082c3 100644
--- a/youtube/templates/subscription_manager.html
+++ b/youtube/templates/subscription_manager.html
@@ -1,79 +1,7 @@
{% set page_title = 'Subscription Manager' %}
{% extends "base.html" %}
{% block style %}
- .import-export{
- display: flex;
- flex-direction: row;
- padding-left: 10px;
- padding-top: 10px;
- }
- .subscriptions-import-form{
- background-color: var(--interface-color);
- display: flex;
- flex-direction: column;
- align-items: flex-start;
- max-width: 300px;
- padding:10px;
- }
- .subscriptions-import-form h2{
- font-size: 20px;
- margin-bottom: 10px;
- }
-
- .import-submit-button{
- margin-top:15px;
- align-self: flex-end;
- }
-
-
- .subscriptions-export-links{
- margin: 0px 0px 0px 20px;
- background-color: var(--interface-color);
- list-style: none;
- max-width: 300px;
- padding:10px;
- }
-
- .sub-list-controls{
- background-color: var(--interface-color);
- padding:10px;
- }
-
-
- .tag-group-list{
- list-style: none;
- margin-left: 10px;
- margin-right: 10px;
- padding: 0px;
- }
- .tag-group{
- border-style: solid;
- margin-bottom: 10px;
- }
-
- .sub-list{
- list-style: none;
- padding:10px;
- column-width: 300px;
- column-gap: 40px;
- }
- .sub-list-item{
- display:flex;
- margin-bottom: 10px;
- break-inside:avoid;
- background-color: var(--interface-color);
- }
- .tag-list{
- margin-left:15px;
- font-weight:bold;
- }
- .sub-list-item-name{
- margin-left:15px;
- }
- .sub-list-checkbox{
- height: 1.5em;
- min-width: 1.5em; // need min-width otherwise browser doesn't respect the width and squishes the checkbox down when there's too many tags
- }
+ <link href="/youtube.com/static/subscription_manager.css" rel="stylesheet">
{% endblock style %}
@@ -87,20 +15,29 @@
{% endfor %}
{% endmacro %}
-
-
{% block main %}
<div class="import-export">
<form class="subscriptions-import-form" enctype="multipart/form-data" action="/youtube.com/import_subscriptions" method="POST">
<h2>Import subscriptions</h2>
- <input type="file" id="subscriptions-import" accept="application/json, application/xml, text/x-opml" name="subscriptions_file">
- <input type="submit" value="Import" class="import-submit-button">
+ <div class="subscriptions-import-options">
+ <input type="file" id="subscriptions-import" accept="application/json, application/xml, text/x-opml, text/csv" name="subscriptions_file" required>
+ <input type="submit" value="Import">
+ </div>
</form>
- <!--<ul class="subscriptions-export-links">
- <li><a href="/youtube.com/subscriptions.opml">Export subscriptions (OPML)</a></li>
- <li><a href="/youtube.com/subscriptions.xml">Export subscriptions (RSS)</a></li>
- </ul>-->
+ <form class="subscriptions-export-form" action="/youtube.com/export_subscriptions" method="POST">
+ <h2>Export subscriptions</h2>
+ <div class="subscriptions-export-options">
+ <select id="export-type" name="export_format" title="Export format">
+ <option value="json_newpipe">JSON (NewPipe)</option>
+ <option value="json_google_takeout">JSON (Old Google Takeout Format)</option>
+ <option value="opml">OPML (RSS, no tags)</option>
+ </select>
+ <label for="include-muted">Include muted</label>
+ <input id="include-muted" type="checkbox" name="include_muted" checked>
+ <input type="submit" value="Export">
+ </div>
+ </form>
</div>
<hr>
diff --git a/youtube/templates/subscriptions.html b/youtube/templates/subscriptions.html
index 12430a5..2823e8d 100644
--- a/youtube/templates/subscriptions.html
+++ b/youtube/templates/subscriptions.html
@@ -1,75 +1,28 @@
-{% set page_title = 'Subscriptions' %}
+{% if current_tag %}
+ {% set page_title = 'Subscriptions - ' + current_tag %}
+{% else %}
+ {% set page_title = 'Subscriptions' %}
+{% endif %}
{% extends "base.html" %}
{% import "common_elements.html" as common_elements %}
{% block style %}
- main{
- display:flex;
- flex-direction: row;
- }
- .video-section{
- flex-grow: 1;
- padding-left: 10px;
- padding-top: 10px;
- }
- .video-section .page-button-row{
- justify-content: center;
- }
- .subscriptions-sidebar{
- flex-basis: 300px;
- background-color: var(--interface-color);
- border-left: 2px;
- }
- .sidebar-links{
- display:flex;
- justify-content: space-between;
- padding-left:10px;
- padding-right: 10px;
- }
-
- .sidebar-list{
- list-style: none;
- padding-left:10px;
- padding-right: 10px;
- }
- .sidebar-list-item{
- display:flex;
- justify-content: space-between;
- margin-bottom: 5px;
- }
- .sub-refresh-list .sidebar-item-name{
- text-overflow: clip;
- white-space: nowrap;
- overflow: hidden;
- max-width: 200px;
- }
+ <link href="/youtube.com/static/message_box.css" rel="stylesheet">
+ <link href="/youtube.com/static/subscription.css" rel="stylesheet">
{% endblock style %}
{% block main %}
- <div class="video-section">
- <nav class="item-grid">
- {% for video_info in videos %}
- {{ common_elements.item(video_info) }}
- {% endfor %}
- </nav>
-
- <nav class="page-button-row">
- {{ common_elements.page_buttons(num_pages, '/youtube.com/subscriptions', parameters_dictionary) }}
- </nav>
- </div>
<div class="subscriptions-sidebar">
<div class="sidebar-links">
- <a href="/youtube.com/subscription_manager" class="sub-manager-link">Subscription Manager</a>
- <form method="POST" class="refresh-all">
+ <a class="sidebar-title" href="/youtube.com/subscription_manager" class="sub-manager-link">Subscription Manager</a>
+ <form class="sidebar-action" method="POST" class="refresh-all">
<input type="submit" value="Check All">
<input type="hidden" name="action" value="refresh">
<input type="hidden" name="type" value="all">
</form>
</div>
- <hr>
-
<ol class="sidebar-list tags">
{% if current_tag %}
<li class="sidebar-list-item">
@@ -95,7 +48,6 @@
</ol>
<hr>
-
<ol class="sidebar-list sub-refresh-list">
{% for subscription in subscription_list %}
<li class="sidebar-list-item {{ 'muted' if subscription['muted'] else '' }}">
@@ -109,7 +61,23 @@
</li>
{% endfor %}
</ol>
+ </div>
+
+ {% if current_tag %}
+ <h2 class="current-tag">{{ current_tag }}</h2>
+ {% endif %}
+ <div class="video-container">
+ {% for video_info in videos %}
+ {{ common_elements.item(video_info) }}
+ {% endfor %}
</div>
+ <hr/>
+
+ <footer class="pagination-container">
+ <nav class="pagination-list">
+ {{ common_elements.page_buttons(num_pages, '/youtube.com/subscriptions', parameters_dictionary) }}
+ </nav>
+ </footer>
{% endblock main %}
diff --git a/youtube/templates/subscriptions.xml b/youtube/templates/subscriptions.xml
new file mode 100644
index 0000000..5365da1
--- /dev/null
+++ b/youtube/templates/subscriptions.xml
@@ -0,0 +1,9 @@
+<opml version="1.1">
+ <body>
+ <outline text="YouTube Subscriptions" title="YouTube Subscriptions">
+ {% for sub in sub_list %}
+ <outline text="{{sub['channel_name']}}" title="{{sub['channel_name']}}" type="rss" xmlUrl="https://www.youtube.com/feeds/videos.xml?channel_id={{sub['channel_id']}}" />
+ {%- endfor %}
+ </outline>
+ </body>
+</opml>
diff --git a/youtube/templates/unsubscribe_verify.html b/youtube/templates/unsubscribe_verify.html
index 98581c0..e899783 100644
--- a/youtube/templates/unsubscribe_verify.html
+++ b/youtube/templates/unsubscribe_verify.html
@@ -1,17 +1,19 @@
{% set page_title = 'Unsubscribe?' %}
{% extends "base.html" %}
+{% block style %}
+ <link href="/youtube.com/static/unsubscribe.css" rel="stylesheet"/>
+{% endblock style %}
{% block main %}
- <span>Are you sure you want to unsubscribe from these channels?</span>
+ <p>Are you sure you want to unsubscribe from these channels?</p>
<form class="subscriptions-import-form" action="/youtube.com/subscription_manager" method="POST">
{% for channel_id, channel_name in unsubscribe_list %}
<input type="hidden" name="channel_ids" value="{{ channel_id }}">
{% endfor %}
-
<input type="hidden" name="action" value="unsubscribe">
<input type="submit" value="Yes, unsubscribe">
</form>
- <ul>
+ <ul class="list-channel">
{% for channel_id, channel_name in unsubscribe_list %}
<li><a href="{{ '/https://www.youtube.com/channel/' + channel_id }}" title="{{ channel_name }}">{{ channel_name }}</a></li>
{% endfor %}
diff --git a/youtube/templates/watch.html b/youtube/templates/watch.html
index 14e953b..0991457 100644
--- a/youtube/templates/watch.html
+++ b/youtube/templates/watch.html
@@ -1,285 +1,263 @@
{% set page_title = title %}
{% extends "base.html" %}
{% import "common_elements.html" as common_elements %}
-{% import "comments.html" as comments %}
+{% import "comments.html" as comments with context %}
{% block style %}
- details > summary{
- background-color: var(--interface-color);
- border-style: outset;
- border-width: 2px;
- font-weight: bold;
- padding-bottom: 2px;
- }
- details > summary:hover{
- text-decoration: underline;
- }
-
- {% if theater_mode %}
- video{
- grid-column: 1 / span 5;
- justify-self: center;
- max-width: 100%;
- width: {{ theater_video_target_width }}px;
- max-height: {{ video_height }}px;
- margin-bottom: 10px;
- background-color: var(--background-color);
- }
- .related-videos-outer{
- grid-row: 2 /span 3;
- width: 400px;
- }
- .video-info{
- width: 640px;
- }
- {% else %}
- video{
- height: 360px;
- width: 640px;
- grid-column: 2;
- }
- .related-videos-outer{
- grid-row: 1 /span 4;
- }
+ <link href="/youtube.com/static/message_box.css" rel="stylesheet">
+ <link href="/youtube.com/static/watch.css" rel="stylesheet">
+ {% if settings.use_video_player == 2 %}
+ <!-- plyr -->
+ <link href="/youtube.com/static/modules/plyr/plyr.css" rel="stylesheet">
+ <link href="/youtube.com/static/modules/plyr/custom_plyr.css" rel="stylesheet">
+ <!--/ plyr -->
{% endif %}
+{% endblock style %}
- main{
- display:grid;
- grid-template-columns: 1fr 640px 40px 400px 1fr;
- grid-template-rows: auto auto auto auto;
- align-content: start;
- }
+{% block main %}
+ {% if playability_error %}
+ <div class="playability-error">
+ <span>{{ 'Error: ' + playability_error }}
+ {% if invidious_reload_button %}
+ <a href="{{ video_url }}&use_invidious=0"><br>
+ Reload without invidious (for usage of new identity button).</a>
+ {% endif %}
+ </span>
+ </div>
+ {% elif (uni_sources.__len__() == 0 or live) and hls_formats.__len__() != 0 %}
+ <div class="live-url-choices">
+ <span>Copy a url into your video player:</span>
+ <ol>
+ {% for fmt in hls_formats %}
+ <li class="url-choice"><div class="url-choice-label">{{ fmt['video_quality'] }}: </div><input class="url-choice-copy" value="{{ fmt['url'] }}" readonly onclick="this.select();"></li>
+ {% endfor %}
+ </ol>
+ </div>
+ {% else %}
+ <figure class="sc-video">
+ <video id="js-video-player" playsinline controls {{ 'autoplay' if settings.autoplay_videos }}>
+ {% if uni_sources %}
+ <source src="{{ uni_sources[uni_idx]['url'] }}" type="{{ uni_sources[uni_idx]['type'] }}" data-res="{{ uni_sources[uni_idx]['quality'] }}">
+ {% endif %}
- .video-info{
- grid-column: 2;
- grid-row: 2;
- display: grid;
- grid-template-rows: 0fr 0fr 0fr 20px 0fr 0fr;
- grid-template-columns: 1fr 1fr;
- align-content: start;
- }
- .video-info > .title{
- grid-column: 1 / span 2;
- min-width: 0;
- }
- .video-info > .is-unlisted{
- background-color: var(--interface-color);
- justify-self:start;
- padding-left:2px;
- padding-right:2px;
- }
- .video-info > address{
- grid-column: 1;
- grid-row: 3;
- justify-self: start;
- }
- .video-info > .views{
- grid-column: 2;
- grid-row: 3;
- justify-self:end;
- }
- .video-info > time{
- grid-column: 1;
- grid-row: 4;
- justify-self:start;
- }
- .video-info > .likes-dislikes{
- grid-column: 2;
- grid-row: 4;
- justify-self:end;
- }
- .video-info > .download-dropdown{
- grid-column:1 / span 2;
- grid-row: 6;
- }
- .video-info > .checkbox{
- justify-self:end;
- align-self: start;
+ {% for source in subtitle_sources %}
+ {% if source['on'] %}
+ <track label="{{ source['label'] }}" src="{{ source['url'] }}" kind="subtitles" srclang="{{ source['srclang'] }}" default>
+ {% else %}
+ <track label="{{ source['label'] }}" src="{{ source['url'] }}" kind="subtitles" srclang="{{ source['srclang'] }}">
+ {% endif %}
+ {% endfor %}
+ </video>
+ </figure>
+ {% endif %}
- grid-row: 5;
- grid-column: 2;
- }
- .video-info > .description{
- background-color:var(--interface-color);
- margin-top:8px;
- white-space: pre-wrap;
- min-width: 0;
- word-wrap: break-word;
- grid-column: 1 / span 2;
- grid-row: 7;
- }
+ <div class="sc-info">
+ <div class="video-info">
+ <h1 class="v-title">{{ title }}</h1>
- .music-list{
- grid-row:8;
- grid-column: 1 / span 2;
- background-color: var(--interface-color);
- }
- .music-list table,th,td{
- border: 1px solid;
- }
- .music-list th,td{
- padding-left:4px;
- padding-right:5px;
- }
- .music-list caption{
- text-align:left;
- font-weight:bold;
- margin-bottom:5px;
- }
- .comments-area-outer{
- grid-column: 2;
- grid-row: 3;
- margin-top:10px;
- }
- .comments-area-inner{
- padding-top: 10px;
- }
- .comment{
- width:640px;
- }
- .related-videos-outer{
- grid-column: 4;
- max-width: 640px;
- }
- .related-videos-inner{
- padding-top: 10px;
- display: grid;
- grid-auto-rows: 94px;
- grid-row-gap: 10px;
- }
+ <ul class="labels">
+ {%- if unlisted -%}
+ <li class="is-unlisted">Unlisted</li>
+ {%- endif -%}
+ {%- if age_restricted -%}
+ <li class="age-restricted">Age-restricted</li>
+ {%- endif -%}
+ {%- if limited_state -%}
+ <li>Limited state</li>
+ {%- endif -%}
+ {%- if live -%}
+ <li>Live</li>
+ {%- endif -%}
+ </ul>
- /* Put related vids below videos when window is too small */
- /* 1100px instead of 1080 because W3C is full of idiots who include scrollbar width */
- @media (max-width:1100px){
- main{
- grid-template-columns: 1fr 640px 40px 1fr;
- }
- .related-videos-outer{
- margin-top: 10px;
- grid-column: 2;
- grid-row: 3;
- width: initial;
- }
- .comments-area-outer{
- grid-row: 4;
- }
- }
+ <address class="v-uploaded">Uploaded by <a href="{{ uploader_channel_url }}">{{ uploader }}</a></address>
+ <span class="v-views">{{ view_count }} views</span>
+ <time class="v-published" datetime="{{ time_published_utc }}">Published on {{ time_published }}</time>
+ <span class="v-likes-dislikes">{{ like_count }} likes</span>
- .download-dropdown-content{
- background-color: var(--interface-color);
- padding: 10px;
- list-style: none;
- margin: 0px;
- }
- li.download-format{
- margin-bottom: 7px;
- }
- .format-attributes{
- list-style: none;
- padding: 0px;
- margin: 0px;
- display: flex;
- flex-direction: row;
- }
- .format-attributes li{
- white-space: nowrap;
- max-height: 1.2em;
- }
- .format-ext{
- width: 60px;
- }
- .format-res{
- width:90px;
- }
-{% endblock style %}
+ <div class="external-player-controls">
+ <input class="speed" id="speed-control" type="text" title="Video speed">
+ {% if settings.use_video_player != 2 %}
+ <select id="quality-select" autocomplete="off">
+ {% for src in uni_sources %}
+ <option value='{"type": "uni", "index": {{ loop.index0 }}}' {{ 'selected' if loop.index0 == uni_idx and not using_pair_sources else '' }} >{{ src['quality_string'] }}</option>
+ {% endfor %}
+ {% for src_pair in pair_sources %}
+ <option value='{"type": "pair", "index": {{ loop.index0}}}' {{ 'selected' if loop.index0 == pair_idx and using_pair_sources else '' }} >{{ src_pair['quality_string'] }}</option>
+ {% endfor %}
+ </select>
+ {% endif %}
+ </div>
+ <input class="v-checkbox" name="video_info_list" value="{{ video_info }}" form="playlist-edit" type="checkbox">
-{% block main %}
- <video controls autofocus>
- {% for video_source in video_sources %}
- <source src="{{ video_source['src'] }}" type="{{ video_source['type'] }}">
- {% endfor %}
+ <span class="v-direct-link"><a href="https://youtu.be/{{ video_id }}" rel="noopener noreferrer" target="_blank">Direct Link</a></span>
- {% for source in subtitle_sources %}
- {% if source['on'] %}
- <track label="{{ source['label'] }}" src="{{ source['url'] }}" kind="subtitles" srclang="{{ source['srclang'] }}" default>
+ {% if settings.use_video_download != 0 %}
+ <details class="v-download">
+ <summary class="download-dropdown-label">Download</summary>
+ <ul class="download-dropdown-content">
+ {% for format in download_formats %}
+ <li class="download-format">
+ <a class="download-link" href="{{ format['url'] }}" download="{{ title }}.{{ format['ext'] }}">
+ {{ format['ext'] }} {{ format['video_quality'] }} {{ format['audio_quality'] }} {{ format['file_size'] }} {{ format['codecs'] }}
+ </a>
+ </li>
+ {% endfor %}
+ {% for download in other_downloads %}
+ <li class="download-format">
+ <a href="{{ download['url'] }}" download>
+ {{ download['ext'] }} {{ download['label'] }}
+ </a>
+ </li>
+ {% endfor %}
+ </ul>
+ </details>
{% else %}
- <track label="{{ source['label'] }}" src="{{ source['url'] }}" kind="subtitles" srclang="{{ source['srclang'] }}">
+ <span class="v-download"></span>
{% endif %}
- {% endfor %}
+ <span class="v-description">{{ common_elements.text_runs(description)|escape|urlize|timestamps|safe }}</span>
- </video>
+ <div class="v-music-list">
+ {% if music_list.__len__() != 0 %}
+ <hr>
+ <table>
+ <caption>Music</caption>
+ <tr>
+ {% for attribute in music_attributes %}
+ <th>{{ attribute }}</th>
+ {% endfor %}
+ </tr>
+ {% for track in music_list %}
+ <tr>
+ {% for attribute in music_attributes %}
+ {% if attribute.lower() == 'title' and track['url'] is not none %}
+ <td><a href="{{ track['url'] }}">{{ track.get(attribute.lower(), '') }}</a></td>
+ {% else %}
+ <td>{{ track.get(attribute.lower(), '') }}</td>
+ {% endif %}
+ {% endfor %}
+ </tr>
+ {% endfor %}
+ </table>
+ {% endif %}
+ </div>
+ <details class="v-more-info">
+ <summary>More info</summary>
+ <div class="more-info-content">
+ <p>Tor exit node: {{ ip_address }}</p>
+ {% if invidious_used %}
+ <p>Used Invidious as fallback.</p>
+ {% endif %}
+ <p class="allowed-countries">Allowed countries: {{ allowed_countries|join(', ') }}</p>
+ {% if settings.use_sponsorblock_js %}
+ <ul class="more-actions">
+ <li><label><input type=checkbox id=skip_sponsors checked>skip sponsors</label> <span id=skip_n></span>
+ </ul>
+ {% endif %}
+ </div>
+ </details>
+ </div>
- <div class="video-info">
- <h2 class="title">{{ title }}</h2>
- {% if unlisted %}
- <span class="is-unlisted">Unlisted</span>
- {% endif %}
- <address>Uploaded by <a href="{{ uploader_channel_url }}">{{ uploader }}</a></address>
- <span class="views">{{ views }} views</span>
+ <div class="side-videos">
+ <!-- playlist -->
+ {% if playlist %}
+ <div class="site-playlist">
+ <div class="playlist-header">
+ <a href="{{ playlist['url'] }}" title="{{ playlist['title'] }}"><h3>{{ playlist['title'] }}</h3></a>
+ <ul class="playlist-metadata">
+ <li><label for="playlist-autoplay-toggle">Autoplay: </label><input id="playlist-autoplay-toggle" type="checkbox" class="autoplay-toggle"></li>
+ {% if playlist['current_index'] is none %}
+ <li>[Error!]/{{ playlist['video_count'] }}</li>
+ {% else %}
+ <li>{{ playlist['current_index']+1 }}/{{ playlist['video_count'] }}</li>
+ {% endif %}
+ <li><a href="{{ playlist['author_url'] }}" title="{{ playlist['author'] }}">{{ playlist['author'] }}</a></li>
+ </ul>
+ </div>
+ <nav class="playlist-videos">
+ {% for info in playlist['items'] %}
+ {# non-lazy load for 5 videos surrounding current video #}
+ {# for non-js browsers or old such that IntersectionObserver doesn't work #}
+ {# -10 is sentinel to not load anything if there's no current_index for some reason #}
+ {% if (playlist.get('current_index', -10) - loop.index0)|abs is lt(5) %}
+ {{ common_elements.item(info, include_badges=false, lazy_load=false) }}
+ {% else %}
+ {{ common_elements.item(info, include_badges=false, lazy_load=true) }}
+ {% endif %}
+ {% endfor %}
+ </nav>
+ </div>
+ {% elif settings.related_videos_mode != 0 %}
+ <div class="related-autoplay"><label for="related-autoplay-toggle">Autoplay: </label><input id="related-autoplay-toggle" type="checkbox" class="autoplay-toggle"></div>
+ {% endif %}
- <time datetime="$upload_date">Published on {{ upload_date }}</time>
- <span class="likes-dislikes">{{ likes }} likes {{ dislikes }} dislikes</span>
- <details class="download-dropdown">
- <summary class="download-dropdown-label">Download</summary>
- <ul class="download-dropdown-content">
- {% for format in download_formats %}
- <li class="download-format">
- <a class="download-link" href="{{ format['url'] }}">
- <ol class="format-attributes">
- <li class="format-ext">{{ format['ext'] }}</li>
- <li class="format-res">{{ format['resolution'] }}</li>
- <li class="format-note">{{ format['note'] }}</li>
- </ol>
- </a>
- </li>
- {% endfor %}
- </ul>
- </details>
- <input class="checkbox" name="video_info_list" value="{{ video_info }}" form="playlist-edit" type="checkbox">
+ {% if subtitle_sources %}
+ <details id="transcript-details">
+ <summary>Transcript</summary>
+ <div id="transcript-div">
+ <select id="select-tt">
+ {% for source in subtitle_sources %}
+ <option>{{ source['label'] }}</option>
+ {% endfor %}
+ </select>
+ <label for="transcript-use-table">Table view</label>
+ <input id="transcript-use-table" type="checkbox">
+ <table id="transcript-table"></table>
+ </div>
+ </details>
+ {% endif %}
- <span class="description">{{ description }}</span>
- <div class="music-list">
- {% if music_list.__len__() != 0 %}
- <hr>
- <table>
- <caption>Music</caption>
- <tr>
- {% for attribute in music_attributes %}
- <th>{{ attribute }}</th>
+ {% if settings.related_videos_mode != 0 %}
+ <details class="related-videos-outer" {{'open' if settings.related_videos_mode == 1 else ''}}>
+ <summary>Related Videos</summary>
+ <nav class="related-videos-inner">
+ {% for info in related %}
+ {{ common_elements.item(info, include_badges=false) }}
{% endfor %}
- </tr>
- {% for track in music_list %}
- <tr>
- {% for attribute in music_attributes %}
- <td>{{ track.get(attribute.lower(), '') }}</td>
- {% endfor %}
- </tr>
- {% endfor %}
- </table>
+ </nav>
+ </details>
{% endif %}
+
</div>
- </div>
- {% if related_videos_mode != 0 %}
- <details class="related-videos-outer" {{'open' if related_videos_mode == 1 else ''}}>
- <summary>Related Videos</summary>
- <nav class="related-videos-inner">
- {% for info in related %}
- {{ common_elements.item(info) }}
- {% endfor %}
- </nav>
- </details>
- {% endif %}
+ <!-- comments -->
+ {% if settings.comments_mode != 0 %}
+ {% if comments_disabled %}
+ <div class="comments-area-outer comments-disabled">Comments disabled</div>
+ {% else %}
+ <details class="comments-area-outer" {{'open' if settings.comments_mode == 1 else ''}}>
+ <summary>{{ comment_count|commatize }} comment{{'s' if comment_count != '1' else ''}}</summary>
+ <div class="comments-area-inner comments-area">
+ {% if comments_info %}
+ {{ comments.video_comments(comments_info) }}
+ {% endif %}
+ </div>
+ </details>
+ {% endif %}
+ {% endif %}
- {% if comments_mode != 0 %}
- <details class="comments-area-outer" {{'open' if comments_mode == 1 else ''}}>
- <summary>Comments</summary>
- <section class="comments-area-inner comments-area">
- {% if comments_info %}
- {{ comments.video_comments(comments_info) }}
- {% endif %}
- </section>
- </details>
+ </div>
+
+ <script src="/youtube.com/static/js/av-merge.js"></script>
+ <script src="/youtube.com/static/js/watch.js"></script>
+ <script>
+ // @license magnet:?xt=urn:btih:0b31508aeb0634b347b8270c7bee4d411b5d4109&dn=agpl-3.0.txt AGPL-v3-or-Later
+ let storyboard_url = {{ storyboard_url | tojson }};
+ // @license-end
+ </script>
+ <script src="/youtube.com/static/js/common.js"></script>
+ <script src="/youtube.com/static/js/transcript-table.js"></script>
+ {% if settings.use_video_player == 2 %}
+ <!-- plyr -->
+ <script src="/youtube.com/static/modules/plyr/plyr.min.js"
+ integrity="sha512-l6ZzdXpfMHRfifqaR79wbYCEWjLDMI9DnROvb+oLkKq6d7MGroGpMbI7HFpicvmAH/2aQO+vJhewq8rhysrImw=="
+ crossorigin="anonymous"></script>
+ <script src="/youtube.com/static/js/plyr-start.js"></script>
+ <!-- /plyr -->
+ {% elif settings.use_video_player == 1 %}
+ <script src="/youtube.com/static/js/hotkeys.js"></script>
{% endif %}
+ {% if settings.use_comments_js %} <script src="/youtube.com/static/js/comments.js"></script> {% endif %}
+ {% if settings.use_sponsorblock_js %} <script src="/youtube.com/static/js/sponsorblock.js"></script> {% endif %}
{% endblock main %}