mirror of
https://gitea.invidious.io/iv-org/invidious-copy-2022-08-14.git
synced 2024-08-15 00:53:20 +00:00
Add community page
This commit is contained in:
parent
2cc25b1e6e
commit
bcd239ac2b
22 changed files with 422 additions and 31 deletions
101
assets/js/community.js
Normal file
101
assets/js/community.js
Normal file
|
@ -0,0 +1,101 @@
|
|||
String.prototype.supplant = function (o) {
|
||||
return this.replace(/{([^{}]*)}/g, function (a, b) {
|
||||
var r = o[b];
|
||||
return typeof r === 'string' || typeof r === 'number' ? r : a;
|
||||
});
|
||||
}
|
||||
|
||||
function hide_youtube_replies(event) {
|
||||
var target = event.target;
|
||||
|
||||
sub_text = target.getAttribute('data-inner-text');
|
||||
inner_text = target.getAttribute('data-sub-text');
|
||||
|
||||
body = target.parentNode.parentNode.children[1];
|
||||
body.style.display = 'none';
|
||||
|
||||
target.innerHTML = sub_text;
|
||||
target.onclick = show_youtube_replies;
|
||||
target.setAttribute('data-inner-text', inner_text);
|
||||
target.setAttribute('data-sub-text', sub_text);
|
||||
}
|
||||
|
||||
function show_youtube_replies(event) {
|
||||
var target = event.target;
|
||||
|
||||
sub_text = target.getAttribute('data-inner-text');
|
||||
inner_text = target.getAttribute('data-sub-text');
|
||||
|
||||
body = target.parentNode.parentNode.children[1];
|
||||
body.style.display = '';
|
||||
|
||||
target.innerHTML = sub_text;
|
||||
target.onclick = hide_youtube_replies;
|
||||
target.setAttribute('data-inner-text', inner_text);
|
||||
target.setAttribute('data-sub-text', sub_text);
|
||||
}
|
||||
|
||||
function number_with_separator(val) {
|
||||
while (/(\d+)(\d{3})/.test(val.toString())) {
|
||||
val = val.toString().replace(/(\d+)(\d{3})/, '$1' + ',' + '$2');
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
function get_youtube_replies(target, load_more) {
|
||||
var continuation = target.getAttribute('data-continuation');
|
||||
|
||||
var body = target.parentNode.parentNode;
|
||||
var fallback = body.innerHTML;
|
||||
body.innerHTML =
|
||||
'<h3 style="text-align:center"><div class="loading"><i class="icon ion-ios-refresh"></i></div></h3>';
|
||||
|
||||
var url = '/api/v1/channels/comments/' + community_data.ucid +
|
||||
'?format=html' +
|
||||
'&hl=' + community_data.preferences.locale +
|
||||
'&thin_mode=' + community_data.preferences.thin_mode +
|
||||
'&continuation=' + continuation;
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.responseType = 'json';
|
||||
xhr.timeout = 10000;
|
||||
xhr.open('GET', url, true);
|
||||
|
||||
xhr.onreadystatechange = function () {
|
||||
if (xhr.readyState == 4) {
|
||||
if (xhr.status == 200) {
|
||||
if (load_more) {
|
||||
body = body.parentNode.parentNode;
|
||||
body.removeChild(body.lastElementChild);
|
||||
body.innerHTML += xhr.response.contentHtml;
|
||||
} else {
|
||||
body.removeChild(body.lastElementChild);
|
||||
|
||||
var p = document.createElement('p');
|
||||
var a = document.createElement('a');
|
||||
p.appendChild(a);
|
||||
|
||||
a.href = 'javascript:void(0)';
|
||||
a.onclick = hide_youtube_replies;
|
||||
a.setAttribute('data-sub-text', community_data.hide_replies_text);
|
||||
a.setAttribute('data-inner-text', community_data.show_replies_text);
|
||||
a.innerText = community_data.hide_replies_text;
|
||||
|
||||
var div = document.createElement('div');
|
||||
div.innerHTML = xhr.response.contentHtml;
|
||||
|
||||
body.appendChild(p);
|
||||
body.appendChild(div);
|
||||
}
|
||||
} else {
|
||||
body.innerHTML = fallback;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
xhr.ontimeout = function () {
|
||||
console.log('Pulling comments failed.');
|
||||
body.innerHTML = fallback;
|
||||
}
|
||||
|
||||
xhr.send();
|
||||
}
|
|
@ -316,5 +316,6 @@
|
|||
"Video mode": "وضع الفيديو",
|
||||
"Videos": "الفيديوهات",
|
||||
"Playlists": "قوائم التشغيل",
|
||||
"Community": "",
|
||||
"Current version: ": "الإصدار الحالى"
|
||||
}
|
|
@ -316,5 +316,6 @@
|
|||
"Video mode": "Videomodus",
|
||||
"Videos": "Videos",
|
||||
"Playlists": "Wiedergabelisten",
|
||||
"Community": "",
|
||||
"Current version: ": "Aktuelle Version: "
|
||||
}
|
|
@ -361,5 +361,6 @@
|
|||
"Video mode": "Λειτουργία βίντεο",
|
||||
"Videos": "Βίντεο",
|
||||
"Playlists": "Λίστες Αναπαραγωγής",
|
||||
"Community": "",
|
||||
"Current version: ": "Τρέχουσα έκδοση: "
|
||||
}
|
|
@ -361,5 +361,6 @@
|
|||
"Video mode": "Video mode",
|
||||
"Videos": "Videos",
|
||||
"Playlists": "Playlists",
|
||||
"Community": "Community",
|
||||
"Current version: ": "Current version: "
|
||||
}
|
|
@ -316,5 +316,6 @@
|
|||
"Video mode": "Videa reĝimo",
|
||||
"Videos": "Videoj",
|
||||
"Playlists": "Ludlistoj",
|
||||
"Community": "",
|
||||
"Current version: ": "Nuna versio: "
|
||||
}
|
|
@ -316,5 +316,6 @@
|
|||
"Video mode": "Modo de vídeo",
|
||||
"Videos": "Vídeos",
|
||||
"Playlists": "Listas de reproducción",
|
||||
"Community": "",
|
||||
"Current version: ": "Versión actual: "
|
||||
}
|
|
@ -316,5 +316,6 @@
|
|||
"Video mode": "Mode Vidéo",
|
||||
"Videos": "Vidéos",
|
||||
"Playlists": "Liste de lecture",
|
||||
"Community": "",
|
||||
"Current version: ": "Version actuelle : "
|
||||
}
|
|
@ -315,5 +315,6 @@
|
|||
"Video mode": "Modalità video",
|
||||
"Videos": "",
|
||||
"Playlists": "",
|
||||
"Community": "",
|
||||
"Current version: ": ""
|
||||
}
|
|
@ -316,5 +316,6 @@
|
|||
"Video mode": "Video-modus",
|
||||
"Videos": "Videoer",
|
||||
"Playlists": "Spillelister",
|
||||
"Community": "",
|
||||
"Current version: ": "Nåværende versjon: "
|
||||
}
|
|
@ -316,5 +316,6 @@
|
|||
"Video mode": "Videomodus",
|
||||
"Videos": "Video's",
|
||||
"Playlists": "Afspeellijsten",
|
||||
"Community": "",
|
||||
"Current version: ": "Huidige versie: "
|
||||
}
|
|
@ -316,5 +316,6 @@
|
|||
"Video mode": "Tryb wideo",
|
||||
"Videos": "Filmy",
|
||||
"Playlists": "Playlisty",
|
||||
"Community": "",
|
||||
"Current version: ": "Aktualna wersja: "
|
||||
}
|
|
@ -316,5 +316,6 @@
|
|||
"Video mode": "Видео режим",
|
||||
"Videos": "Видео",
|
||||
"Playlists": "Плейлисты",
|
||||
"Community": "",
|
||||
"Current version: ": "Текущая версия: "
|
||||
}
|
|
@ -316,5 +316,6 @@
|
|||
"Video mode": "Відеорежим",
|
||||
"Videos": "Відео",
|
||||
"Playlists": "Плейлисти",
|
||||
"Community": "",
|
||||
"Current version: ": "Поточна версія: "
|
||||
}
|
|
@ -316,5 +316,6 @@
|
|||
"Video mode": "视频模式",
|
||||
"Videos": "视频",
|
||||
"Playlists": "播放列表",
|
||||
"Community": "",
|
||||
"Current version: ": "当前版本:"
|
||||
}
|
|
@ -2861,6 +2861,16 @@ get "/user/:user/videos" do |env|
|
|||
env.redirect "/channel/#{user}/videos"
|
||||
end
|
||||
|
||||
get "/user/:user/about" do |env|
|
||||
user = env.params.url["user"]
|
||||
env.redirect "/channel/#{user}"
|
||||
end
|
||||
|
||||
get "/channel:ucid/about" do |env|
|
||||
ucid = env.params.url["ucid"]
|
||||
env.redirect "/channel/#{ucid}"
|
||||
end
|
||||
|
||||
get "/channel/:ucid" do |env|
|
||||
locale = LOCALES[env.get("preferences").as(Preferences).locale]?
|
||||
|
||||
|
@ -2968,6 +2978,46 @@ get "/channel/:ucid/playlists" do |env|
|
|||
templated "playlists"
|
||||
end
|
||||
|
||||
get "/channel/:ucid/community" do |env|
|
||||
locale = LOCALES[env.get("preferences").as(Preferences).locale]?
|
||||
|
||||
user = env.get? "user"
|
||||
if user
|
||||
user = user.as(User)
|
||||
subscriptions = user.subscriptions
|
||||
end
|
||||
subscriptions ||= [] of String
|
||||
|
||||
ucid = env.params.url["ucid"]
|
||||
|
||||
thin_mode = env.params.query["thin_mode"]? || env.get("preferences").as(Preferences).thin_mode
|
||||
thin_mode = thin_mode == "true"
|
||||
|
||||
continuation = env.params.query["continuation"]?
|
||||
# sort_by = env.params.query["sort_by"]?.try &.downcase
|
||||
|
||||
begin
|
||||
channel = get_about_info(ucid, locale)
|
||||
rescue ex
|
||||
error_message = ex.message
|
||||
env.response.status_code = 500
|
||||
next templated "error"
|
||||
end
|
||||
|
||||
if !channel.tabs.includes? "community"
|
||||
next env.redirect "/channel/#{channel.ucid}"
|
||||
end
|
||||
|
||||
begin
|
||||
items = JSON.parse(fetch_channel_community(ucid, continuation, locale, config, Kemal.config, "json", thin_mode))
|
||||
rescue ex
|
||||
env.response.status_code = 500
|
||||
error_message = ex.message
|
||||
end
|
||||
|
||||
templated "community"
|
||||
end
|
||||
|
||||
# API Endpoints
|
||||
|
||||
get "/api/v1/stats" do |env|
|
||||
|
@ -3757,12 +3807,17 @@ end
|
|||
|
||||
ucid = env.params.url["ucid"]
|
||||
|
||||
continuation = env.params.query["continuation"]?
|
||||
thin_mode = env.params.query["thin_mode"]?
|
||||
thin_mode = thin_mode == "true"
|
||||
|
||||
format = env.params.query["format"]?
|
||||
format ||= "json"
|
||||
|
||||
continuation = env.params.query["continuation"]?
|
||||
# sort_by = env.params.query["sort_by"]?.try &.downcase
|
||||
|
||||
begin
|
||||
fetch_channel_community(ucid, continuation, locale, config, Kemal.config)
|
||||
fetch_channel_community(ucid, continuation, locale, config, Kemal.config, format, thin_mode)
|
||||
rescue ex
|
||||
env.response.status_code = 400
|
||||
error_message = {"error" => ex.message}.to_json
|
||||
|
|
|
@ -123,6 +123,7 @@ struct AboutChannel
|
|||
is_family_friendly: Bool,
|
||||
allowed_regions: Array(String),
|
||||
related_channels: Array(AboutRelatedChannel),
|
||||
tabs: Array(String),
|
||||
})
|
||||
end
|
||||
|
||||
|
@ -617,7 +618,7 @@ def extract_channel_playlists_cursor(url, auto_generated)
|
|||
end
|
||||
|
||||
# TODO: Add "sort_by"
|
||||
def fetch_channel_community(ucid, continuation, locale, config, kemal_config)
|
||||
def fetch_channel_community(ucid, continuation, locale, config, kemal_config, format, thin_mode)
|
||||
client = make_client(YT_URL)
|
||||
headers = HTTP::Headers.new
|
||||
headers["User-Agent"] = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36"
|
||||
|
@ -632,11 +633,10 @@ def fetch_channel_community(ucid, continuation, locale, config, kemal_config)
|
|||
raise error_message
|
||||
end
|
||||
|
||||
ucid = response.body.match(/https:\/\/www.youtube.com\/channel\/(?<ucid>UC[a-zA-Z0-9_-]{22})/).not_nil!["ucid"]
|
||||
|
||||
if !continuation || continuation.empty?
|
||||
response = JSON.parse(response.body.match(/window\["ytInitialData"\] = (?<info>.*?);\n/).try &.["info"] || "{}")
|
||||
ucid = response["responseContext"]["serviceTrackingParams"]
|
||||
.as_a.select { |service| service["service"] == "GFEEDBACK" }[0]?.try &.["params"]
|
||||
.as_a.select { |param| param["key"] == "browse_id" }[0]?.try &.["value"].as_s
|
||||
body = response["contents"]?.try &.["twoColumnBrowseResultsRenderer"]["tabs"].as_a.select { |tab| tab["tabRenderer"]?.try &.["selected"].as_bool.== true }[0]?
|
||||
|
||||
if !body
|
||||
|
@ -645,6 +645,8 @@ def fetch_channel_community(ucid, continuation, locale, config, kemal_config)
|
|||
|
||||
body = body["tabRenderer"]["content"]["sectionListRenderer"]["contents"][0]["itemSectionRenderer"]
|
||||
else
|
||||
continuation = produce_channel_community_continuation(ucid, continuation)
|
||||
|
||||
headers["cookie"] = response.cookies.add_request_headers(headers)["cookie"]
|
||||
headers["content-type"] = "application/x-www-form-urlencoded"
|
||||
|
||||
|
@ -663,10 +665,6 @@ def fetch_channel_community(ucid, continuation, locale, config, kemal_config)
|
|||
response = client.post("/comment_service_ajax?action_get_comments=1&ctoken=#{continuation}&continuation=#{continuation}&hl=en&gl=US", headers, form: post_req)
|
||||
body = JSON.parse(response.body)
|
||||
|
||||
ucid = body["response"]["responseContext"]["serviceTrackingParams"]
|
||||
.as_a.select { |service| service["service"] == "GFEEDBACK" }[0]?.try &.["params"]
|
||||
.as_a.select { |param| param["key"] == "browse_id" }[0]?.try &.["value"].as_s
|
||||
|
||||
body = body["response"]["continuationContents"]["itemSectionContinuation"]? ||
|
||||
body["response"]["continuationContents"]["backstageCommentsContinuation"]?
|
||||
|
||||
|
@ -685,7 +683,7 @@ def fetch_channel_community(ucid, continuation, locale, config, kemal_config)
|
|||
raise error_message
|
||||
end
|
||||
|
||||
JSON.build do |json|
|
||||
response = JSON.build do |json|
|
||||
json.object do
|
||||
json.field "authorId", ucid
|
||||
json.field "comments" do
|
||||
|
@ -755,6 +753,7 @@ def fetch_channel_community(ucid, continuation, locale, config, kemal_config)
|
|||
|
||||
json.field "likeCount", like_count
|
||||
json.field "commentId", post["postId"]? || post["commentId"]? || ""
|
||||
json.field "authorIsChannelOwner", post["authorEndpoint"]["browseEndpoint"]["browseId"] == ucid
|
||||
|
||||
if attachment = post["backstageAttachment"]?
|
||||
json.field "attachment" do
|
||||
|
@ -837,7 +836,7 @@ def fetch_channel_community(ucid, continuation, locale, config, kemal_config)
|
|||
json.field "replies" do
|
||||
json.object do
|
||||
json.field "replyCount", reply_count
|
||||
json.field "continuation", continuation
|
||||
json.field "continuation", extract_channel_community_cursor(continuation)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -847,11 +846,71 @@ def fetch_channel_community(ucid, continuation, locale, config, kemal_config)
|
|||
end
|
||||
|
||||
if body["continuations"]?
|
||||
continuation = body["continuations"][0]["nextContinuationData"]["continuation"]
|
||||
json.field "continuation", continuation
|
||||
continuation = body["continuations"][0]["nextContinuationData"]["continuation"].as_s
|
||||
json.field "continuation", extract_channel_community_cursor(continuation)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if format == "html"
|
||||
response = JSON.parse(response)
|
||||
content_html = template_youtube_comments(response, locale, thin_mode)
|
||||
|
||||
response = JSON.build do |json|
|
||||
json.object do
|
||||
json.field "contentHtml", content_html
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return response
|
||||
end
|
||||
|
||||
def produce_channel_community_continuation(ucid, cursor)
|
||||
cursor = URI.escape(cursor)
|
||||
continuation = IO::Memory.new
|
||||
|
||||
continuation.write(Bytes[0xe2, 0xa9, 0x85, 0xb2, 0x02])
|
||||
continuation.write(write_var_int(3 + ucid.size + write_var_int(cursor.size).size + cursor.size))
|
||||
|
||||
continuation.write(Bytes[0x12, ucid.size])
|
||||
continuation.print(ucid)
|
||||
|
||||
continuation.write(Bytes[0x1a])
|
||||
continuation.write(write_var_int(cursor.size))
|
||||
continuation.print(cursor)
|
||||
continuation.rewind
|
||||
|
||||
continuation = Base64.urlsafe_encode(continuation.to_slice)
|
||||
continuation = URI.escape(continuation)
|
||||
|
||||
return continuation
|
||||
end
|
||||
|
||||
def extract_channel_community_cursor(continuation)
|
||||
continuation = URI.unescape(continuation)
|
||||
continuation = Base64.decode(continuation)
|
||||
|
||||
# 0xe2 0xa9 0x85 0xb2 0x02
|
||||
continuation += 5
|
||||
|
||||
total_size = read_var_int(continuation[0, 4])
|
||||
continuation += write_var_int(total_size).size
|
||||
|
||||
# 0x12
|
||||
continuation += 1
|
||||
ucid_size = continuation[0]
|
||||
continuation += 1
|
||||
ucid = continuation[0, ucid_size]
|
||||
continuation += ucid_size
|
||||
|
||||
# 0x1a
|
||||
continuation += 1
|
||||
until continuation[0] == 'E'.ord
|
||||
continuation += 1
|
||||
end
|
||||
|
||||
return String.new(continuation)
|
||||
end
|
||||
|
||||
def get_about_info(ucid, locale)
|
||||
|
@ -947,6 +1006,8 @@ def get_about_info(ucid, locale)
|
|||
auto_generated = true
|
||||
end
|
||||
|
||||
tabs = about.xpath_nodes(%q(//ul[@id="channel-navigation-menu"]/li/a/span)).map { |node| node.content.downcase }
|
||||
|
||||
return AboutChannel.new(
|
||||
ucid: ucid,
|
||||
author: author,
|
||||
|
@ -961,7 +1022,8 @@ def get_about_info(ucid, locale)
|
|||
joined: joined,
|
||||
is_family_friendly: is_family_friendly,
|
||||
allowed_regions: allowed_regions,
|
||||
related_channels: related_channels
|
||||
related_channels: related_channels,
|
||||
tabs: tabs
|
||||
)
|
||||
end
|
||||
|
||||
|
|
|
@ -112,7 +112,7 @@ def fetch_youtube_comments(id, db, continuation, format, locale, thin_mode, regi
|
|||
end
|
||||
end
|
||||
|
||||
comments = JSON.build do |json|
|
||||
response = JSON.build do |json|
|
||||
json.object do
|
||||
if body["header"]?
|
||||
count_text = body["header"]["commentsHeaderRenderer"]["countText"]
|
||||
|
@ -223,15 +223,15 @@ def fetch_youtube_comments(id, db, continuation, format, locale, thin_mode, regi
|
|||
end
|
||||
|
||||
if format == "html"
|
||||
comments = JSON.parse(comments)
|
||||
content_html = template_youtube_comments(comments, locale, thin_mode)
|
||||
response = JSON.parse(response)
|
||||
content_html = template_youtube_comments(response, locale, thin_mode)
|
||||
|
||||
comments = JSON.build do |json|
|
||||
response = JSON.build do |json|
|
||||
json.object do
|
||||
json.field "contentHtml", content_html
|
||||
|
||||
if comments["commentCount"]?
|
||||
json.field "commentCount", comments["commentCount"]
|
||||
if response["commentCount"]?
|
||||
json.field "commentCount", response["commentCount"]
|
||||
else
|
||||
json.field "commentCount", 0
|
||||
end
|
||||
|
@ -239,7 +239,7 @@ def fetch_youtube_comments(id, db, continuation, format, locale, thin_mode, regi
|
|||
end
|
||||
end
|
||||
|
||||
return comments
|
||||
return response
|
||||
end
|
||||
|
||||
def fetch_reddit_comments(id, sort_by = "confidence")
|
||||
|
@ -286,7 +286,7 @@ def template_youtube_comments(comments, locale, thin_mode)
|
|||
<div class="pure-u-23-24">
|
||||
<p>
|
||||
<a href="javascript:void(0)" data-continuation="#{child["replies"]["continuation"]}"
|
||||
onclick="get_youtube_replies(this)">#{translate(locale, "View `x` replies", child["replies"]["replyCount"].to_s)}</a>
|
||||
onclick="get_youtube_replies(this)">#{translate(locale, "View `x` replies", number_with_separator(child["replies"]["replyCount"]))}</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -300,9 +300,9 @@ def template_youtube_comments(comments, locale, thin_mode)
|
|||
end
|
||||
|
||||
html << <<-END_HTML
|
||||
<div class="pure-g">
|
||||
<div class="pure-g" style="width:100%">
|
||||
<div class="channel-profile pure-u-4-24 pure-u-md-2-24">
|
||||
<img style="padding-right:1em;padding-top:1em" src="#{author_thumbnail}">
|
||||
<img style="padding-right:1em;padding-top:1em;width:90%" src="#{author_thumbnail}">
|
||||
</div>
|
||||
<div class="pure-u-20-24 pure-u-md-22-24">
|
||||
<p>
|
||||
|
@ -310,11 +310,66 @@ def template_youtube_comments(comments, locale, thin_mode)
|
|||
<a class="#{child["authorIsChannelOwner"] == true ? "channel-owner" : ""}" href="#{child["authorUrl"]}">#{child["author"]}</a>
|
||||
</b>
|
||||
<p style="white-space:pre-wrap">#{child["contentHtml"]}</p>
|
||||
<span title="#{Time.unix(child["published"].as_i64).to_s(translate(locale, "%A %B %-d, %Y"))}">#{translate(locale, "`x` ago", recode_date(Time.unix(child["published"].as_i64), locale))} #{child["isEdited"] == true ? translate(locale, "(edited)") : ""}</span>
|
||||
|
|
||||
<a href="https://www.youtube.com/watch?v=#{comments["videoId"]}&lc=#{child["commentId"]}" title="#{translate(locale, "YouTube comment permalink")}">[YT]</a>
|
||||
|
|
||||
<i class="icon ion-ios-thumbs-up"></i> #{number_with_separator(child["likeCount"])}
|
||||
END_HTML
|
||||
|
||||
if child["attachment"]?
|
||||
attachment = child["attachment"]
|
||||
|
||||
case attachment["type"]
|
||||
when "image"
|
||||
attachment = attachment["imageThumbnails"][1]
|
||||
|
||||
html << <<-END_HTML
|
||||
<div class="pure-g">
|
||||
<div class="pure-u-1 pure-u-md-1-2">
|
||||
<img style="width:100%" src="/ggpht#{URI.parse(attachment["url"].as_s).full_path}">
|
||||
</div>
|
||||
</div>
|
||||
END_HTML
|
||||
when "video"
|
||||
html << <<-END_HTML
|
||||
<div class="pure-g">
|
||||
<div class="pure-u-1 pure-u-md-1-2">
|
||||
<div style="position:relative;width:100%;height:0;padding-bottom:56.25%;margin-bottom:5px">
|
||||
END_HTML
|
||||
|
||||
if attachment["error"]?
|
||||
html << <<-END_HTML
|
||||
<p>#{attachment["error"]}</p>
|
||||
END_HTML
|
||||
else
|
||||
html << <<-END_HTML
|
||||
<iframe id='ivplayer' type='text/html' style='position:absolute;width:100%;height:100%;left:0;top:0' src='/embed/#{attachment["videoId"]?}' frameborder='0'></iframe>
|
||||
END_HTML
|
||||
end
|
||||
|
||||
html << <<-END_HTML
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
END_HTML
|
||||
end
|
||||
end
|
||||
|
||||
html << <<-END_HTML
|
||||
<span title="#{Time.unix(child["published"].as_i64).to_s(translate(locale, "%A %B %-d, %Y"))}">#{translate(locale, "`x` ago", recode_date(Time.unix(child["published"].as_i64), locale))} #{child["isEdited"] == true ? translate(locale, "(edited)") : ""}</span>
|
||||
|
|
||||
END_HTML
|
||||
|
||||
if comments["videoId"]?
|
||||
html << <<-END_HTML
|
||||
<a href="https://www.youtube.com/watch?v=#{comments["videoId"]}&lc=#{child["commentId"]}" title="#{translate(locale, "YouTube comment permalink")}">[YT]</a>
|
||||
|
|
||||
END_HTML
|
||||
elsif comments["authorId"]?
|
||||
html << <<-END_HTML
|
||||
<a href="https://www.youtube.com/channel/#{comments["authorId"]}/community?lb=#{child["commentId"]}" title="#{translate(locale, "YouTube comment permalink")}">[YT]</a>
|
||||
|
|
||||
END_HTML
|
||||
end
|
||||
|
||||
html << <<-END_HTML
|
||||
<i class="icon ion-ios-thumbs-up"></i> #{number_with_separator(child["likeCount"])}
|
||||
END_HTML
|
||||
|
||||
if child["creatorHeart"]?
|
||||
|
|
|
@ -49,6 +49,11 @@
|
|||
<a href="/channel/<%= channel.ucid %>/playlists"><%= translate(locale, "Playlists") %></a>
|
||||
<% end %>
|
||||
</div>
|
||||
<div class="pure-u-1 pure-md-1-3">
|
||||
<% if channel.tabs.includes? "community" %>
|
||||
<a href="/channel/<%= channel.ucid %>/community"><%= translate(locale, "Community") %></a>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pure-u-1-3"></div>
|
||||
<div class="pure-u-1-3">
|
||||
|
|
80
src/invidious/views/community.ecr
Normal file
80
src/invidious/views/community.ecr
Normal file
|
@ -0,0 +1,80 @@
|
|||
<% content_for "header" do %>
|
||||
<title><%= channel.author %> - Invidious</title>
|
||||
<% end %>
|
||||
|
||||
<% if channel.banner %>
|
||||
<div class="h-box">
|
||||
<img style="width:100%" src="/ggpht<%= URI.parse(channel.banner.not_nil!.gsub("=w1060-", "=w1280-")).full_path %>">
|
||||
</div>
|
||||
|
||||
<div class="h-box">
|
||||
<hr>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<div class="pure-g h-box">
|
||||
<div class="pure-u-2-3">
|
||||
<div class="channel-profile">
|
||||
<img src="/ggpht<%= URI.parse(channel.author_thumbnail).full_path %>">
|
||||
<span><%= channel.author %></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pure-u-1-3" style="text-align:right">
|
||||
<h3>
|
||||
<a href="/feed/channel/<%= channel.ucid %>"><i class="icon ion-logo-rss"></i></a>
|
||||
</h3>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="h-box">
|
||||
<% ucid = channel.ucid %>
|
||||
<% author = channel.author %>
|
||||
<% sub_count_text = number_to_short_text(channel.sub_count) %>
|
||||
<%= rendered "components/subscribe_widget" %>
|
||||
</div>
|
||||
|
||||
<div class="pure-g h-box">
|
||||
<div class="pure-u-1-3">
|
||||
<a href="https://www.youtube.com/channel/<%= channel.ucid %>/community"><%= translate(locale, "View channel on YouTube") %></a>
|
||||
<% if !channel.auto_generated %>
|
||||
<div class="pure-u-1 pure-md-1-3">
|
||||
<a href="/channel/<%= channel.ucid %>"><%= translate(locale, "Videos") %></a>
|
||||
</div>
|
||||
<% end %>
|
||||
<div class="pure-u-1 pure-md-1-3">
|
||||
<a href="/channel/<%= channel.ucid %>/playlists"><%= translate(locale, "Playlists") %></a>
|
||||
</div>
|
||||
<div class="pure-u-1 pure-md-1-3">
|
||||
<% if channel.tabs.includes? "community" %>
|
||||
<b><%= translate(locale, "Community") %></b>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pure-u-2-3"></div>
|
||||
</div>
|
||||
|
||||
<div class="h-box">
|
||||
<hr>
|
||||
</div>
|
||||
|
||||
<% if error_message %>
|
||||
<div class="h-box">
|
||||
<p><%= error_message %></p>
|
||||
</div>
|
||||
<% else %>
|
||||
<div class="h-box pure-g" id="comments">
|
||||
<%= template_youtube_comments(items.not_nil!, locale, thin_mode) %>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<script>
|
||||
var community_data = {
|
||||
ucid: '<%= channel.ucid %>',
|
||||
youtube_comments_text: '<%= HTML.escape(translate(locale, "View YouTube comments")) %>',
|
||||
comments_text: '<%= HTML.escape(translate(locale, "View `x` comments", "{commentCount}")) %>',
|
||||
hide_replies_text: '<%= HTML.escape(translate(locale, "Hide replies")) %>',
|
||||
show_replies_text: '<%= HTML.escape(translate(locale, "Show replies")) %>',
|
||||
preferences: <%= env.get("preferences").as(Preferences).to_json %>,
|
||||
}
|
||||
</script>
|
||||
<script src="/js/community.js?v=<%= ASSET_COMMIT %>"></script>
|
|
@ -9,6 +9,20 @@
|
|||
<body>
|
||||
<h1><%= translate(locale, "JavaScript license information") %></h1>
|
||||
<table id="jslicense-labels1">
|
||||
<tr>
|
||||
<td>
|
||||
<a href="/js/community.js?v=<%= ASSET_COMMIT %>">community.js</a>
|
||||
</td>
|
||||
|
||||
<td>
|
||||
<a href="https://www.gnu.org/licenses/agpl-3.0.html">AGPL-3.0</a>
|
||||
</td>
|
||||
|
||||
<td>
|
||||
<a href="/js/community.js?v=<%= ASSET_COMMIT %>"><%= translate(locale, "source") %></a>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
<a href="/js/embed.js?v=<%= ASSET_COMMIT %>">embed.js</a>
|
||||
|
|
|
@ -36,7 +36,7 @@
|
|||
<div class="pure-g h-box">
|
||||
<div class="pure-g pure-u-1-3">
|
||||
<div class="pure-u-1 pure-md-1-3">
|
||||
<a href="https://www.youtube.com/channel/<%= channel.ucid %>"><%= translate(locale, "View channel on YouTube") %></a>
|
||||
<a href="https://www.youtube.com/channel/<%= channel.ucid %>/playlists"><%= translate(locale, "View channel on YouTube") %></a>
|
||||
</div>
|
||||
<div class="pure-u-1 pure-md-1-3">
|
||||
<a href="/channel/<%= channel.ucid %>"><%= translate(locale, "Videos") %></a>
|
||||
|
@ -46,6 +46,11 @@
|
|||
<b><%= translate(locale, "Playlists") %></b>
|
||||
<% end %>
|
||||
</div>
|
||||
<div class="pure-u-1 pure-md-1-3">
|
||||
<% if channel.tabs.includes? "community" %>
|
||||
<a href="/channel/<%= channel.ucid %>/community"><%= translate(locale, "Community") %></a>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pure-u-1-3"></div>
|
||||
<div class="pure-u-1-3">
|
||||
|
|
Loading…
Reference in a new issue