diff options
Diffstat (limited to 'static')
| -rw-r--r-- | static/index.js | 191 |
1 files changed, 98 insertions, 93 deletions
diff --git a/static/index.js b/static/index.js index c577050..c753e3f 100644 --- a/static/index.js +++ b/static/index.js @@ -1,7 +1,18 @@ -/* setup player */ -let player +/* static elements */ const titleBar = document.getElementById("title") +const buttonDetails = document.getElementById("toggle-details") +const divDetails = document.getElementById("details") +const buttonComments = document.getElementById("toggle-comments") +const divComments = document.getElementById("comments") +const commentTemplate = document.getElementById("template-comment") + +/* state */ +let player +let detailsLoaded = false +let commentsLoaded = false + +/* setup player */ function onPlayerReady(_e) { document.title = titleBar.textContent = player.videoTitle } @@ -15,28 +26,25 @@ function onYouTubeIframeAPIReady() { }) } + /* helpers */ async function getApiReq(path) { return fetch("/api" + path) .then(async res => { - if (!res.ok) { - const err = await res.text(); - throw new Error(`Server error: ${err}`); - } + if (!res.ok) + throw new Error("Server error:", await res.text()) return res.json() }) - .then(data => { - return data - }) - .catch(error => { - console.error("Fetch error:", error) - }) + .catch(e => console.error(e)) } function toggleVis(el) { - el.style.display = el.style.display ? null : "none" + const v = !!el.style.display + el.style.display = v ? null : "none" + return v } +// TODO this func could be moved to server function createAnchors(text) { function _a(href, innerText) { return `<a href="${href}" rel="noopener noreferrer">${innerText ?? href}</a>` @@ -47,9 +55,10 @@ function createAnchors(text) { const timestampRegex = /[0-9]+:[0-9]+/g return text + .replaceAll("\n", "<br>") .replace(urlRegex, url => { - const ytRegex = /^(https?:\/\/)?(www\.)?(youtu(be\.com|\.be|be-nocookie\.com))\//gi - const href = url.replace(ytRegex, `${window.location.origin}/`) + const ytWatchRegex = /^(https?:\/\/)?(www\.)?(youtu(be\.com|\.be|be-nocookie\.com))\/watch/gi + const href = url.replace(ytWatchRegex, `${window.location.origin}/watch`) return _a(href, url) }) .replace(channelRegex, channel => { @@ -61,113 +70,108 @@ function createAnchors(text) { }) } -/* setup video details buttons */ -const buttonDetails = document.getElementById("toggle-details") -const divDetails = document.getElementById("details") -let detailsLoaded = false +function fillFields(data, fieldContainer) { + // see html if this doesn't make sense + const fieldElements = fieldContainer.querySelectorAll("[data-f]") + const fieldElementsMap = {} + for (const fe of fieldElements) { + const f = fe.dataset.f + fieldElementsMap[f] = fe + if (fe.dataset.skip !== undefined) + continue + + const v = data[f] + switch (fe.tagName) { + case "DIV": + fe.innerHTML = fe.dataset.anchor !== undefined ? createAnchors(v) : v + break + case "SPAN": // only used for stats + fe.textContent = new Intl.NumberFormat().format(v) + break + case "TIME": + fe.textContent = fe.datetime = v + break + case "A": // only used for channel links + fe.href = `https://www.youtube.com/${v}` + default: + fe.textContent = v + } + } + return fieldElementsMap +} + +/* setup video details */ 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") - const comments = document.getElementById("details-comments") - - // 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) - comments.textContent = new Intl.NumberFormat().format(d.statistics.commentCount) - date.textContent = d.snippet.publishedAt - if (d.snippet.description) - desc.innerHTML = createAnchors(d.snippet.description.replaceAll("\n", "<br>")) - tags.textContent = d.snippet.tags.join(", ") + return false + if (detailsLoaded) + return toggleVis(divDetails) + + + // data + const data = await getApiReq(`/details?id=${player.playerInfo.videoData.video_id}`) + + // pop + const fieldElements = fillFields(data, divDetails) + fieldElements.author.href = `https://www.youtube.com/channel/${data.channel}` + fieldElements.tags.textContent = data.tags.join(", ") // done detailsLoaded = true - toggleVis(divDetails) + return 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 = `(Edited ${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) -} +/* setup video comments */ async function toggleComments() { if (!player) - return - if (commentsLoaded) { - toggleVis(divComments) - return + return false + if (commentsLoaded) + return toggleVis(divComments) + + // data + const data = await getApiReq(`/comments?id=${player.playerInfo.videoData.video_id }`) + + // pop + function _genComment(commentData, parent) { + const c = commentTemplate.content.cloneNode(true) + + const fieldElements = fillFields(commentData, c) + if (commentData.published === commentData.updated) + fieldElements.updated.parentElement.remove() + + if (commentData.replies) { + const divReplies = c.querySelector(".replies") + for (const rc of commentData.replies) + _genComment(rc, divReplies) + } + parent.append(c) } - // 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) - } + for (const commentData of data) + _genComment(commentData, divComments) // done commentsLoaded = true - toggleVis(divComments) + return toggleVis(divComments) } + +/* details button */ buttonDetails.addEventListener("click", () => { toggleDetails() toggleComments() buttonDetails.remove() }) + /* timestamps in comments */ function timestampLinkClick(e) { if (e.target.tagName !== "A") return + // FIXME this needs to check origin as well const url = new URL(e.target.href) if (url.pathname !== "/watch" || url.searchParams.get("v") !== player.playerInfo.videoData.video_id) return @@ -180,6 +184,7 @@ function timestampLinkClick(e) { player.seekTo(t) // change url to match window in case clicking on youtube.com link + // FIXME get ridded of replace youtube links w/ host links url.protocol = window.location.protocol url.hostname = window.location.hostname url.port = window.location.port |
