mirror of
https://git.sr.ht/~cadence/cloudtube
synced 2024-11-22 15:47:30 +00:00
Add subscriptions page
This commit is contained in:
parent
f24e1bb39c
commit
59a7489545
30
api/subscriptions.js
Normal file
30
api/subscriptions.js
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
const {render} = require("pinski/plugins")
|
||||||
|
const db = require("./utils/db")
|
||||||
|
const {fetchChannelLatest} = require("./utils/youtube")
|
||||||
|
const {getUser} = require("./utils/getuser")
|
||||||
|
|
||||||
|
module.exports = [
|
||||||
|
{
|
||||||
|
route: `/subscriptions`, methods: ["GET"], code: async ({req}) => {
|
||||||
|
const user = getUser(req)
|
||||||
|
let hasSubscriptions = false
|
||||||
|
let videos = []
|
||||||
|
let channels = []
|
||||||
|
if (user.token) {
|
||||||
|
const subscriptions = user.getSubscriptions()
|
||||||
|
const channelPrepared = db.prepare("SELECT * FROM Channels WHERE ucid = ?")
|
||||||
|
channels = subscriptions.map(id => channelPrepared.get(id)).sort((a, b) => {
|
||||||
|
if (a.name < b.name) return -1
|
||||||
|
else if (b.name > a.name) return 1
|
||||||
|
else return 0
|
||||||
|
})
|
||||||
|
if (subscriptions.length) {
|
||||||
|
hasSubscriptions = true
|
||||||
|
const all = await Promise.all(subscriptions.map(id => fetchChannelLatest(id)))
|
||||||
|
videos = all.flat(1).sort((a, b) => b.published - a.published).slice(0, 60)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return render(200, "pug/subscriptions.pug", {hasSubscriptions, videos, channels})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
@ -1,6 +1,5 @@
|
|||||||
const crypto = require("crypto")
|
const crypto = require("crypto")
|
||||||
const {parse: parseCookie} = require("cookie")
|
const {parse: parseCookie} = require("cookie")
|
||||||
|
|
||||||
const constants = require("./constants")
|
const constants = require("./constants")
|
||||||
const db = require("./db")
|
const db = require("./db")
|
||||||
|
|
||||||
@ -26,7 +25,7 @@ class User {
|
|||||||
|
|
||||||
getSubscriptions() {
|
getSubscriptions() {
|
||||||
if (this.token) {
|
if (this.token) {
|
||||||
return db.prepare("SELECT ucid FROM Subscriptions WHERE token = ?").pluck().all(ucid)
|
return db.prepare("SELECT ucid FROM Subscriptions WHERE token = ?").pluck().all(this.token)
|
||||||
} else {
|
} else {
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
|
@ -12,4 +12,14 @@ async function fetchChannel(ucid) {
|
|||||||
return channel
|
return channel
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function fetchChannelLatest(ucid) {
|
||||||
|
return fetch(`http://localhost:3000/api/v1/channels/${ucid}/latest`).then(res => res.json()).then(root => {
|
||||||
|
root.forEach(video => {
|
||||||
|
video.descriptionHtml = video.descriptionHtml.replace(/<a /g, '<a tabindex="-1" ')
|
||||||
|
})
|
||||||
|
return root
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
module.exports.fetchChannel = fetchChannel
|
module.exports.fetchChannel = fetchChannel
|
||||||
|
module.exports.fetchChannelLatest = fetchChannelLatest
|
||||||
|
76
html/static/images/subscriptions.svg
Normal file
76
html/static/images/subscriptions.svg
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||||
|
xmlns:cc="http://creativecommons.org/ns#"
|
||||||
|
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
width="30"
|
||||||
|
height="24.999874"
|
||||||
|
viewBox="0 0 7.9374998 6.6145502"
|
||||||
|
version="1.1"
|
||||||
|
id="svg27"
|
||||||
|
inkscape:version="0.92.4 (5da689c313, 2019-01-14)"
|
||||||
|
sodipodi:docname="subscriptions.svg">
|
||||||
|
<defs
|
||||||
|
id="defs21" />
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="base"
|
||||||
|
pagecolor="#36393f"
|
||||||
|
bordercolor="#666666"
|
||||||
|
borderopacity="1.0"
|
||||||
|
inkscape:pageopacity="0"
|
||||||
|
inkscape:pageshadow="2"
|
||||||
|
inkscape:zoom="22.627417"
|
||||||
|
inkscape:cx="17.632604"
|
||||||
|
inkscape:cy="10.153317"
|
||||||
|
inkscape:document-units="px"
|
||||||
|
inkscape:current-layer="layer1"
|
||||||
|
showgrid="true"
|
||||||
|
units="px"
|
||||||
|
inkscape:window-width="1440"
|
||||||
|
inkscape:window-height="877"
|
||||||
|
inkscape:window-x="0"
|
||||||
|
inkscape:window-y="0"
|
||||||
|
inkscape:window-maximized="1"
|
||||||
|
inkscape:snap-bbox="true"
|
||||||
|
inkscape:snap-bbox-edge-midpoints="false"
|
||||||
|
inkscape:snap-text-baseline="false"
|
||||||
|
inkscape:snap-center="false"
|
||||||
|
inkscape:snap-global="false"
|
||||||
|
inkscape:pagecheckerboard="false">
|
||||||
|
<inkscape:grid
|
||||||
|
type="xygrid"
|
||||||
|
id="grid828"
|
||||||
|
originx="32.014582"
|
||||||
|
originy="-164.3063" />
|
||||||
|
</sodipodi:namedview>
|
||||||
|
<metadata
|
||||||
|
id="metadata24">
|
||||||
|
<rdf:RDF>
|
||||||
|
<cc:Work
|
||||||
|
rdf:about="">
|
||||||
|
<dc:format>image/svg+xml</dc:format>
|
||||||
|
<dc:type
|
||||||
|
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||||
|
<dc:title />
|
||||||
|
</cc:Work>
|
||||||
|
</rdf:RDF>
|
||||||
|
</metadata>
|
||||||
|
<g
|
||||||
|
inkscape:label="Layer 1"
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer1"
|
||||||
|
transform="translate(32.014583,-126.07915)">
|
||||||
|
<path
|
||||||
|
id="rect15"
|
||||||
|
style="opacity:1;fill:#c4c4c4;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.05833316;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:fill markers stroke"
|
||||||
|
d="m -29.641547,126.07915 h 3.191429 c 0.288608,0 0.520954,0.23235 0.520954,0.52096 H -30.1625 c 0,-0.28861 0.232345,-0.52096 0.520953,-0.52096 z m -0.90981,0.79375 h 5.011047 c 0.37089,0 0.669473,0.29859 0.669476,0.66948 l 10e-7,0.12427 -6.35,-0.0163 v -0.10799 c 0,-0.37089 0.298586,-0.66948 0.669476,-0.66948 z m -0.404893,1.0583 c -0.586322,0 -1.058333,0.33169 -1.058333,0.91801 v 2.86801 c 0,0.58632 0.472011,0.97648 1.058333,0.97648 h 5.820834 c 0.586321,0 1.058333,-0.39016 1.058333,-0.97648 v -2.86801 c 0,-0.58632 -0.472012,-0.91801 -1.058333,-0.91801 z m 4.241825,2.16468 c 0.167331,0.0992 0.189069,0.27627 0.01203,0.3666 l -2.09776,0.9851 c -0.131847,0.0763 -0.304206,-0.002 -0.302769,-0.19054 v -1.88797 c 0,-0.20313 0.17976,-0.33275 0.340294,-0.24486 z"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
sodipodi:nodetypes="ssccscssccsccsssssssssccccccc" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 3.1 KiB |
@ -10,6 +10,8 @@ html
|
|||||||
body.show-focus
|
body.show-focus
|
||||||
nav.main-nav
|
nav.main-nav
|
||||||
a(href="/").link.home CloudTube
|
a(href="/").link.home CloudTube
|
||||||
|
a(href="/subscriptions" title="Subscriptions").link.subscriptions-link
|
||||||
|
img(src=getStaticURL("html", "/static/images/subscriptions.svg") width=30 height=25 alt="Subscriptions.").subscriptions-icon
|
||||||
form(method="get" action="/search").search-form
|
form(method="get" action="/search").search-form
|
||||||
input(type="text" placeholder="Search" name="q" autocomplete="off" value=query).search
|
input(type="text" placeholder="Search" name="q" autocomplete="off" value=query).search
|
||||||
|
|
||||||
|
@ -1,14 +1,16 @@
|
|||||||
mixin video_list_item(video)
|
mixin video_list_item(video)
|
||||||
- let link = `/watch?v=${video.videoId}`
|
- let link = `/watch?v=${video.videoId}`
|
||||||
a(href=link).thumbnail
|
a(href=link tabindex="-1").thumbnail
|
||||||
img(src=`https://i.ytimg.com/vi/${video.videoId}/mqdefault.jpg` width=320 height=180 alt="").image
|
img(src=`https://i.ytimg.com/vi/${video.videoId}/mqdefault.jpg` width=320 height=180 alt="").image
|
||||||
span.duration= video.second__lengthText
|
span.duration= video.second__lengthText
|
||||||
.info
|
.info
|
||||||
div.title: a(href=link).title-link= video.title
|
div.title: a(href=link).title-link= video.title
|
||||||
div.author-line
|
div.author-line
|
||||||
a(href=`/channel/${video.authorId}`).author= video.author
|
a(href=`/channel/${video.authorId}`).author= video.author
|
||||||
= ` • `
|
- const views = video.viewCountText || video.second__viewCountText
|
||||||
span.views= video.viewCountText || video.second__viewCountText
|
if views
|
||||||
|
= ` • `
|
||||||
|
span.views= views
|
||||||
if video.publishedText
|
if video.publishedText
|
||||||
= ` • `
|
= ` • `
|
||||||
span.published= video.publishedText
|
span.published= video.publishedText
|
||||||
|
12
pug/subscriptions.pug
Normal file
12
pug/subscriptions.pug
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
extends includes/layout.pug
|
||||||
|
|
||||||
|
include includes/video-list-item.pug
|
||||||
|
|
||||||
|
block head
|
||||||
|
title Subscriptions - CloudTube
|
||||||
|
|
||||||
|
block content
|
||||||
|
main.subscriptions-page
|
||||||
|
each video in videos
|
||||||
|
.subscriptions-video
|
||||||
|
+video_list_item(video)
|
9
sass/includes/subscriptions-page.sass
Normal file
9
sass/includes/subscriptions-page.sass
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
@use "video-list-item.sass" as *
|
||||||
|
|
||||||
|
.subscriptions-page
|
||||||
|
padding: 40px 20px 20px
|
||||||
|
max-width: 900px
|
||||||
|
margin: 0 auto
|
||||||
|
|
||||||
|
.subscriptions-video
|
||||||
|
@include subscriptions-video
|
@ -59,6 +59,7 @@
|
|||||||
grid-template-columns: 240px 1fr
|
grid-template-columns: 240px 1fr
|
||||||
margin-bottom: 20px
|
margin-bottom: 20px
|
||||||
overflow: hidden
|
overflow: hidden
|
||||||
|
max-height: 150px
|
||||||
|
|
||||||
.image
|
.image
|
||||||
width: 240px
|
width: 240px
|
||||||
@ -94,3 +95,6 @@
|
|||||||
|
|
||||||
@mixin channel-video
|
@mixin channel-video
|
||||||
@include large-item
|
@include large-item
|
||||||
|
|
||||||
|
@mixin subscriptions-video
|
||||||
|
@include large-item
|
||||||
|
@ -5,10 +5,11 @@
|
|||||||
@use "includes/search-page.sass"
|
@use "includes/search-page.sass"
|
||||||
@use "includes/home-page.sass"
|
@use "includes/home-page.sass"
|
||||||
@use "includes/channel-page.sass"
|
@use "includes/channel-page.sass"
|
||||||
|
@use "includes/subscriptions-page.sass"
|
||||||
|
|
||||||
@font-face
|
@font-face
|
||||||
font-family: "Bariol"
|
font-family: "Bariol"
|
||||||
src: url(/static/fonts/bariol.woff)
|
src: url(/static/fonts/bariol.woff?statichash=1)
|
||||||
|
|
||||||
@mixin button-base
|
@mixin button-base
|
||||||
appearance: none
|
appearance: none
|
||||||
@ -74,6 +75,8 @@
|
|||||||
text-decoration: none
|
text-decoration: none
|
||||||
margin: 1px 8px 1px 0px
|
margin: 1px 8px 1px 0px
|
||||||
font-size: 20px
|
font-size: 20px
|
||||||
|
display: flex
|
||||||
|
align-items: center
|
||||||
|
|
||||||
&.home
|
&.home
|
||||||
font-weight: bold
|
font-weight: bold
|
||||||
@ -98,3 +101,7 @@
|
|||||||
&:hover, &:focus
|
&:hover, &:focus
|
||||||
border: 1px solid c.$edge-grey
|
border: 1px solid c.$edge-grey
|
||||||
margin: 0px
|
margin: 0px
|
||||||
|
|
||||||
|
.subscriptions-link:hover, .subscriptions-link:focus
|
||||||
|
.subscriptions-icon
|
||||||
|
filter: brightness(2)
|
||||||
|
@ -20,6 +20,8 @@ const {setInstance} = require("pinski/plugins")
|
|||||||
|
|
||||||
server.addStaticHashTableDir("html/static/js")
|
server.addStaticHashTableDir("html/static/js")
|
||||||
server.addStaticHashTableDir("html/static/js/elemjs")
|
server.addStaticHashTableDir("html/static/js/elemjs")
|
||||||
|
server.addStaticHashTableDir("html/static/images")
|
||||||
|
server.addStaticHashTableDir("html/static/fonts")
|
||||||
|
|
||||||
server.addAPIDir("api")
|
server.addAPIDir("api")
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user