aboutsummaryrefslogtreecommitdiff
path: root/static/index.js
diff options
context:
space:
mode:
Diffstat (limited to 'static/index.js')
-rw-r--r--static/index.js175
1 files changed, 175 insertions, 0 deletions
diff --git a/static/index.js b/static/index.js
new file mode 100644
index 0000000..e1c1f41
--- /dev/null
+++ b/static/index.js
@@ -0,0 +1,175 @@
+/* setup player */
+let player
+const titleBar = document.getElementById("title")
+
+function onPlayerReady(event) {
+ document.title = titleBar.textContent = player.videoTitle
+}
+
+function onYouTubeIframeAPIReady() {
+ player = new YT.Player("player", {
+ host: "https://www.youtube-nocookie.com",
+ events: {
+ "onReady": onPlayerReady,
+ },
+ })
+}
+
+/* helpers */
+function getApiReq(path) {
+ return fetch("/api" + path)
+ .then(async res => {
+ if (!res.ok) {
+ const err = await res.text();
+ throw new Error(`Server error: ${err}`);
+ }
+ return res.json()
+ })
+ .then(data => {
+ return data
+ })
+ .catch(error => {
+ console.error("Fetch error:", error)
+ })
+}
+
+function toggleVis(e) {
+ e.style.display = e.style.display ? null : "none"
+ //window.location.hash = e.id && !e.style.display ? e.id : ""
+}
+
+function createAnchors(text) {
+ const urlRegex = /(https?:\/\/[a-zA-Z0-9#$%&-./=?@_~]+[a-zA-Z0-9/]|@[a-zA-Z0-9.-_]+|[0-9]+:[0-9]+)/gi;
+ return text.replace(urlRegex, url => {
+ let href = url
+ if (url[0] == "@") {
+ href = `https://youtube.com/${url}`
+ } else if (parseInt(url[0]) !== NaN) {
+ const ts = url.split(":").reverse()
+ let t = 0
+ for (let i = 0; i < ts.length; i++)
+ t += ts[i] * Math.pow(60, i)
+ href = `https://youtube.com/watch?v=${player.playerInfo.videoData.video_id}&t=${t}`
+ }
+ return `<a href="${href}" target="_blank" rel="noopener noreferrer">${url}</a>`
+ })
+}
+
+/* setup video details buttons */
+const buttonDetails = document.getElementById("toggle-details")
+const divDetails = document.getElementById("details")
+let detailsLoaded = false
+
+async function toggleDetails() {
+ if (!player)
+ return
+ if (detailsLoaded) {
+ toggleVis(divDetails)
+ return
+ }
+ // elements
+ const channel = document.getElementById("details-channel")
+ const likes = document.getElementById("details-likes")
+ const views = document.getElementById("details-views")
+ const date = document.getElementById("details-date")
+ const desc = document.getElementById("details-desc")
+ const tags = document.getElementById("details-tags")
+
+ // req
+ const d = (await getApiReq(`/details?id=${player.playerInfo.videoData.video_id}`)).items[0]
+
+ // populate
+ channel.textContent = d.snippet.channelTitle
+ channel.href = `https://www.youtube.com/channel/${d.snippet.channelId}`
+ views.textContent = new Intl.NumberFormat().format(d.statistics.viewCount)
+ likes.textContent = new Intl.NumberFormat().format(d.statistics.likeCount)
+ date.textContent = d.snippet.publishedAt
+ if (d.snippet.description)
+ desc.innerHTML = createAnchors(d.snippet.description.replaceAll("\n", "<br>"))
+ tags.textContent = d.snippet.tags.join(", ")
+
+ // done
+ detailsLoaded = true
+ toggleVis(divDetails)
+}
+
+/* setup video comments buttons */
+const buttonComments = document.getElementById("toggle-comments")
+const divComments = document.getElementById("comments")
+const commentTemplate = document.getElementById("template-comment")
+let commentsLoaded = false
+
+function genComment(cd, parent, replies) {
+ const c = commentTemplate.content.cloneNode(true)
+
+ const author = c.querySelector(".author")
+ const date = c.querySelector(".date")
+ const dateM = c.querySelector(".date.modified")
+ const body = c.querySelector(".body")
+ const likes = c.querySelector(".likes")
+
+ author.textContent = cd.authorDisplayName
+ author.href = cd.authorChannelUrl
+ date.textContent = cd.publishedAt
+ if (cd.publishedAt === cd.updatedAt)
+ dateM.remove()
+ else
+ dateM.textContent = `(Modified ${cd.updatedAt})`
+ body.innerHTML = cd.textDisplay
+ likes.textContent = new Intl.NumberFormat().format(cd.likeCount)
+
+ if (replies) {
+ const divReplies = c.querySelector(".replies")
+ for (const reply of replies)
+ genComment(reply.snippet, divReplies)
+ }
+ parent.append(c)
+}
+
+async function toggleComments() {
+ if (!player)
+ return
+ if (commentsLoaded) {
+ toggleVis(divComments)
+ return
+ }
+
+ // req
+ const d = (await getApiReq(`/comments?id=${player.playerInfo.videoData.video_id }`))
+
+ // populate
+ for (const cdd of d.items) {
+ const cd = cdd.snippet.topLevelComment.snippet
+ const replies = cdd.replies ? cdd.replies.comments : null
+ genComment(cd, divComments, replies)
+ }
+
+ // done
+ commentsLoaded = true
+ toggleVis(divComments)
+}
+
+buttonDetails.addEventListener("click", () => {
+ toggleDetails()
+ toggleComments()
+ buttonDetails.remove()
+})
+
+/* timestamps in comments */
+function timestampLinkClick(e) {
+ if (e.target.tagName !== "A")
+ return
+
+ const url = new URL(e.target.href)
+ if (url.pathname !== "/watch" || url.searchParams.get("v") !== player.playerInfo.videoData.video_id)
+ return
+
+ e.preventDefault()
+
+ const t = url.searchParams.get("t")
+ if (!t)
+ return
+ player.seekTo(t)
+}
+
+document.addEventListener("click", timestampLinkClick)