diff --git a/api/subscriptions.js b/api/subscriptions.js index d199728..a7b4294 100644 --- a/api/subscriptions.js +++ b/api/subscriptions.js @@ -22,6 +22,8 @@ module.exports = [ channels = db.prepare(`SELECT * FROM Channels WHERE ucid IN (${template}) ORDER BY name`).all(subscriptions) // get refreshed status refreshed = db.prepare(`SELECT min(refreshed) as min, max(refreshed) as max, count(refreshed) as count FROM Channels WHERE ucid IN (${template})`).get(subscriptions) + // get watched videos + const watchedVideos = user.getWatchedVideos() // get videos if (subscriptions.length) { hasSubscriptions = true @@ -29,12 +31,15 @@ module.exports = [ videos = db.prepare(`SELECT * FROM Videos WHERE authorId IN (${template}) ORDER BY published DESC LIMIT 60`).all(subscriptions) .map(video => { video.publishedText = timeToPastText(video.published * 1000) + console.log(watchedVideos, video.videoId) + video.watched = watchedVideos.includes(video.videoId) return video }) } } - const instanceOrigin = user.getSettingsOrDefaults().instance - return render(200, "pug/subscriptions.pug", {hasSubscriptions, videos, channels, refreshed, timeToPastText, instanceOrigin}) + const settings = user.getSettingsOrDefaults() + const instanceOrigin = settings.instance + return render(200, "pug/subscriptions.pug", {settings, hasSubscriptions, videos, channels, refreshed, timeToPastText, instanceOrigin}) } } ] diff --git a/api/video.js b/api/video.js index 4748fc1..3bb004d 100644 --- a/api/video.js +++ b/api/video.js @@ -37,22 +37,35 @@ function formatOrder(format) { async function renderVideo(videoPromise, {user, id, instanceOrigin}) { try { + // resolve video const video = await videoPromise if (!video) throw new Error("The instance returned null.") if (video.error) throw new InstanceError(video.error, video.identifier) - // video data additional processing + // process stream list ordering for (const format of video.formatStreams.concat(video.adaptiveFormats)) { if (!format.second__height && format.resolution) format.second__height = +format.resolution.slice(0, -1) if (!format.second__order) format.second__order = formatOrder(format) } + // process length text for (const rec of video.recommendedVideos) { if (!rec.second__lengthText && rec.lengthSeconds > 0) { rec.second__lengthText = converters.lengthSecondsToLengthText(rec.lengthSeconds) } } + // get subscription data const subscribed = user.isSubscribed(video.authorId) + // process watched videos + user.addWatchedVideoMaybe(video.videoId) + const watchedVideos = user.getWatchedVideos() + if (watchedVideos.length) { + for (const rec of video.recommendedVideos) { + rec.watched = watchedVideos.includes(rec.videoId) + } + } return render(200, "pug/video.pug", {video, subscribed, instanceOrigin}) } catch (e) { + // show an appropriate error message + // these should probably be split out to their own files let message = pug.render("pre= error", {error: e.stack || e.toString()}) if (e instanceof fetch.FetchError) { const template = ` diff --git a/pug/channel.pug b/pug/channel.pug index a82f297..0ab4c34 100644 --- a/pug/channel.pug +++ b/pug/channel.pug @@ -28,5 +28,4 @@ block content .videos each video in data.latestVideos - .channel-video - +video_list_item(video, instanceOrigin) + +video_list_item("channel-video", video, instanceOrigin) diff --git a/pug/includes/video-list-item.pug b/pug/includes/video-list-item.pug index 58e5f31..2d1343a 100644 --- a/pug/includes/video-list-item.pug +++ b/pug/includes/video-list-item.pug @@ -1,19 +1,20 @@ -mixin video_list_item(video, instanceOrigin) - - let link = `/watch?v=${video.videoId}` - a(href=link tabindex="-1").thumbnail - img(src=`/vi/${video.videoId}/mqdefault.jpg` width=320 height=180 alt="").image - if video.second__lengthText != undefined - span.duration= video.second__lengthText - .info - div.title: a(href=link).title-link= video.title - div.author-line - a(href=`/channel/${video.authorId}`).author= video.author - - const views = video.viewCountText || video.second__viewCountText - if views - = ` • ` - span.views= views - if video.publishedText - = ` • ` - span.published= video.publishedText - if video.descriptionHtml - div.description!= video.descriptionHtml +mixin video_list_item(className, video, instanceOrigin) + div(class={[className]: true, "video-list-item--watched": video.watched}) + - let link = `/watch?v=${video.videoId}` + a(href=link tabindex="-1").thumbnail + img(src=`/vi/${video.videoId}/mqdefault.jpg` width=320 height=180 alt="").image + if video.second__lengthText != undefined + span.duration= video.second__lengthText + .info + div.title: a(href=link).title-link= video.title + div.author-line + a(href=`/channel/${video.authorId}`).author= video.author + - const views = video.viewCountText || video.second__viewCountText + if views + = ` • ` + span.views= views + if video.publishedText + = ` • ` + span.published= video.publishedText + if video.descriptionHtml + div.description!= video.descriptionHtml diff --git a/pug/search.pug b/pug/search.pug index 159e91f..cbfa60a 100644 --- a/pug/search.pug +++ b/pug/search.pug @@ -8,5 +8,4 @@ block head block content main.search-page each result in results - .search-result - +video_list_item(result, instanceOrigin) + +video_list_item("search-result", result, instanceOrigin) diff --git a/pug/settings.pug b/pug/settings.pug index 1f09ca4..adec6c8 100644 --- a/pug/settings.pug +++ b/pug/settings.pug @@ -37,9 +37,9 @@ block content "https://invidious.fdn.fr" ]) - +select("save_history", "Watch history", false, [ - {value: "0", text: "Don't save"}, - {value: "1", text: "Save"} + +select("save_history", "Watched videos history", false, [ + {value: "0", text: "Don't store"}, + {value: "1", text: "Store on server"} ]) +select("local", "Fetch videos", false, [ diff --git a/pug/subscriptions.pug b/pug/subscriptions.pug index 5b65e7b..a7a5fdd 100644 --- a/pug/subscriptions.pug +++ b/pug/subscriptions.pug @@ -27,9 +27,13 @@ block content if notLoaded div #{notLoaded} subscriptions have not been refreshed at all + if settings.save_history + input(type="checkbox" id="watched-videos-display") + .watched-videos-display-container + label(for="watched-videos-display").watched-videos-display-label Hide watched videos + each video in videos - .subscriptions-video - +video_list_item(video, instanceOrigin) + +video_list_item("subscriptions-video", video, instanceOrigin) else .no-subscriptions h2 You have no subscriptions. diff --git a/pug/video.pug b/pug/video.pug index f6e47fb..74a9049 100644 --- a/pug/video.pug +++ b/pug/video.pug @@ -66,8 +66,7 @@ block content aside.related-videos h2.related-header Related videos each r in video.recommendedVideos - .related-video - +video_list_item(r, instanceOrigin) + +video_list_item("related-video", r, instanceOrigin) else //- error diff --git a/sass/includes/base.sass b/sass/includes/base.sass index 81febd1..9845ecc 100644 --- a/sass/includes/base.sass +++ b/sass/includes/base.sass @@ -54,7 +54,6 @@ details border-radius: 8px summary - text-decoration: underline cursor: pointer line-height: 1 margin-bottom: 0 diff --git a/sass/includes/subscriptions-page.sass b/sass/includes/subscriptions-page.sass index 40d10a9..2947aab 100644 --- a/sass/includes/subscriptions-page.sass +++ b/sass/includes/subscriptions-page.sass @@ -33,3 +33,35 @@ .name font-size: 22px color: c.$fg-main + + +#watched-videos-display + position: relative + left: 10px + display: block + z-index: 1 + height: 42px + margin: 0 + +.watched-videos-display-container + position: relative + display: grid // why does the default not work??? + top: -42px + background: c.$bg-accent-x + line-height: 1 + border-radius: 8px + margin-bottom: -18px + + .watched-videos-display-label + padding: 12px 0px 12px 32px + cursor: pointer + +#watched-videos-display:checked ~ .video-list-item--watched + display: none + +.video-list-item--watched + background: c.$bg-darker + padding: 8px 8px 0px + + .thumbnail .image + opacity: 0.4 diff --git a/utils/getuser.js b/utils/getuser.js index 33778b1..f358d7d 100644 --- a/utils/getuser.js +++ b/utils/getuser.js @@ -57,6 +57,22 @@ class User { return false } } + + getWatchedVideos() { + const settings = this.getSettingsOrDefaults() + if (this.token && settings.save_history) { + return db.prepare("SELECT videoID FROM WatchedVideos WHERE token = ?").pluck().all(this.token) + } else { + return [] + } + } + + addWatchedVideoMaybe(videoID) { + const settings = this.getSettingsOrDefaults() + if (videoID && this.token && settings.save_history) { + db.prepare("INSERT OR IGNORE INTO WatchedVideos (token, videoID) VALUES (?, ?)").run([this.token, videoID]) + } + } } /** diff --git a/utils/upgradedb.js b/utils/upgradedb.js index 6438831..72c256f 100644 --- a/utils/upgradedb.js +++ b/utils/upgradedb.js @@ -28,6 +28,11 @@ const deltas = [ function() { db.prepare("ALTER TABLE Settings ADD COLUMN local INTEGER DEFAULT 0") .run() + }, + // 3: +WatchedVideos + function() { + db.prepare("CREATE TABLE WatchedVideos (token TEXT NOT NULL, videoID TEXT NOT NULL, PRIMARY KEY (token, videoID))") + .run() } ]