From 1ac6ce7dcec8d9ee1b80b808fdf97568c4bc13a9 Mon Sep 17 00:00:00 2001 From: Preston Date: Thu, 15 Mar 2018 10:59:50 -0400 Subject: [PATCH] Features: Video Duration, Double click to full screen and more. Loading Subscriptions is MUCH faster. --- package.json | 1 - src/{ => icons}/icon.icns | 0 src/{ => icons}/icon.ico | Bin src/{ => icons}/icon.png | Bin src/{ => icons}/icon.svg | 0 src/js/channels.js | 8 +- src/js/events.js | 6 + src/js/history.js | 8 +- src/js/player.js | 29 ++- src/js/savedVideos.js | 7 +- src/js/subscriptions.js | 272 +++++++++++++++++++---------- src/js/videos.js | 147 ++++++++++++++-- src/style/player.css | 117 ++++++++----- src/templates/recommendations.html | 5 +- src/templates/videoList.html | 5 +- 15 files changed, 425 insertions(+), 180 deletions(-) rename src/{ => icons}/icon.icns (100%) rename src/{ => icons}/icon.ico (100%) rename src/{ => icons}/icon.png (100%) rename src/{ => icons}/icon.svg (100%) diff --git a/package.json b/package.json index 0740915a1..aca807d80 100644 --- a/package.json +++ b/package.json @@ -65,7 +65,6 @@ "jquery": "^3.3.1", "mustache": "^2.3.0", "nedb": "^1.8.0", - "node-async-loop": "^1.2.2", "opml-to-json": "0.0.3", "youtube-dl": "^1.12.2" } diff --git a/src/icon.icns b/src/icons/icon.icns similarity index 100% rename from src/icon.icns rename to src/icons/icon.icns diff --git a/src/icon.ico b/src/icons/icon.ico similarity index 100% rename from src/icon.ico rename to src/icons/icon.ico diff --git a/src/icon.png b/src/icons/icon.png similarity index 100% rename from src/icon.png rename to src/icons/icon.png diff --git a/src/icon.svg b/src/icons/icon.svg similarity index 100% rename from src/icon.svg rename to src/icons/icon.svg diff --git a/src/js/channels.js b/src/js/channels.js index ac7131139..4f37714fc 100644 --- a/src/js/channels.js +++ b/src/js/channels.js @@ -80,8 +80,12 @@ function goToChannel(channelId) { order: 'date', }, function (data) { // Display recent uploads to #main - data['items'].forEach((video) => { - displayVideo(video); + let grabDuration = getDuration(data.items); + + grabDuration.then((videoList) => { + videoList.items.forEach((video) => { + displayVideo(video, 'history'); + }); }); }); }); diff --git a/src/js/events.js b/src/js/events.js index 123bacae9..63a856404 100644 --- a/src/js/events.js +++ b/src/js/events.js @@ -199,6 +199,10 @@ let videoShortcutHandler = function(event) { } }; +let fullscreenVideo = function(event){ + $('.videoPlayer').get(0).webkitRequestFullscreen() +} + /** * --------------------------- * Bind click events @@ -208,6 +212,8 @@ $(document).on('click', '#showComments', showComments); $(document).on('click', '.videoPlayer', playPauseVideo); +$(document).on('dblclick', '.videoPlayer', fullscreenVideo); + $(document).on('keydown', videoShortcutHandler); $(document).on('click', '#confirmNo', hideConfirmFunction); diff --git a/src/js/history.js b/src/js/history.js index 7c17870c6..0b8636afd 100644 --- a/src/js/history.js +++ b/src/js/history.js @@ -81,8 +81,12 @@ function showHistory(){ maxResults: 50, }, function (data) { createVideoListContainer('Watch History:'); - data['items'].forEach((video) => { - displayVideo(video, 'history'); + let grabDuration = getDuration(data.items); + + grabDuration.then((videoList) => { + videoList.items.forEach((video) => { + displayVideo(video, 'history'); + }); }); stopLoadingAnimation() }); diff --git a/src/js/player.js b/src/js/player.js index 85c102157..438e1ab15 100644 --- a/src/js/player.js +++ b/src/js/player.js @@ -69,17 +69,6 @@ function playVideo(videoId) { return; } - const checkSubscription = isSubscribed(channelId); - - // Change the subscribe button text depending on if the user has subscribed to the channel or not. - checkSubscription.then((results) => { - if (results === false) { - subscribeText = 'SUBSCRIBE'; - } else { - subscribeText = 'UNSUBSCRIBE'; - } - }); - const checkSavedVideo = videoIsSaved(videoId); // Change the save button icon and text depending on if the user has saved the video or not. @@ -94,7 +83,7 @@ function playVideo(videoId) { }); /* - * FreeTube calls youtube-dl server to grab the direct video URL. + * FreeTube calls youtube-dl to grab the direct video URL. */ youtubedlGetInfo(videoId, (info) => { console.log(info); @@ -109,7 +98,6 @@ function playVideo(videoId) { let dateString = info['upload_date']; dateString = [dateString.slice(0, 4), '-', dateString.slice(4)].join(''); dateString = [dateString.slice(0, 7), '-', dateString.slice(7)].join(''); - console.log(dateString); const publishedDate = dateFormat(dateString, "mmm dS, yyyy"); // Figure out the width for the like/dislike bar. @@ -127,7 +115,6 @@ function playVideo(videoId) { // Grab all subtitles Object.keys(videoSubtitles).forEach((subtitle) => { - console.log(subtitle); subtitleLabel = subtitle.toUpperCase(); subtitleUrl = videoSubtitles[subtitle]['url']; @@ -142,7 +129,6 @@ function playVideo(videoId) { // Search through the returned object to get the 480p and 720p video URLs (If available) Object.keys(videoUrls).forEach((key) => { - console.log(key); switch (videoUrls[key]['format_id']) { case '18': video480p = videoUrls[key]['url']; @@ -173,11 +159,18 @@ function playVideo(videoId) { if (!useEmbedPlayer) { videoHtml = ''; - - console.log(videoHtml); } - console.log(defaultUrl); + const checkSubscription = isSubscribed(channelId); + + // Change the subscribe button text depending on if the user has subscribed to the channel or not. + checkSubscription.then((results) => { + if (results === false) { + subscribeText = 'SUBSCRIBE'; + } else { + subscribeText = 'UNSUBSCRIBE'; + } + }); // API Request youtubeAPI('channels', { diff --git a/src/js/savedVideos.js b/src/js/savedVideos.js index 9e2cde613..70b6bb4ae 100644 --- a/src/js/savedVideos.js +++ b/src/js/savedVideos.js @@ -143,8 +143,11 @@ function showSavedVideos(){ }, (data) => { // Render the videos to the screen createVideoListContainer('Saved Videos:'); - data.items.forEach((video) => { - displayVideo(video, 'saved'); + let grabDuration = getDuration(data.items); + grabDuration.then((videoList) => { + videoList.items.forEach((video) => { + displayVideo(video, 'saved'); + }); }); stopLoadingAnimation(); }); diff --git a/src/js/subscriptions.js b/src/js/subscriptions.js index 12293e716..4ce2a204b 100644 --- a/src/js/subscriptions.js +++ b/src/js/subscriptions.js @@ -18,23 +18,23 @@ along with FreeTube. If not, see . /* -* File for all functions related to subscriptions. -*/ + * File for all functions related to subscriptions. + */ /** -* Add a channel to the user's subscription database. -* -* @param {string} channelId - The channel ID to add to the subscriptions database. -* -* @return {Void} -*/ + * Add a channel to the user's subscription database. + * + * @param {string} channelId - The channel ID to add to the subscriptions database. + * + * @return {Void} + */ function addSubscription(channelId, useToast = true) { console.log(channelId); // Request YouTube API youtubeAPI('channels', { part: 'snippet', id: channelId, - }, function (data){ + }, function(data) { const channelInfo = data['items'][0]['snippet']; const channelName = channelInfo['title']; const thumbnail = channelInfo['thumbnails']['high']['url']; @@ -47,7 +47,7 @@ function addSubscription(channelId, useToast = true) { // Refresh the list of subscriptions on the side navigation bar. subDb.insert(channel, (err, newDoc) => { - if (useToast){ + if (useToast) { showToast('Added ' + channelName + ' to subscriptions.'); displaySubs(); } @@ -56,12 +56,12 @@ function addSubscription(channelId, useToast = true) { } /** -* Remove a channel from the subscriptions database. -* -* @param {string} channelId - The channel ID to be removed. -* -* @return {Void} -*/ + * Remove a channel from the subscriptions database. + * + * @param {string} channelId - The channel ID to be removed. + * + * @return {Void} + */ function removeSubscription(channelId) { subDb.remove({ channelId: channelId @@ -73,10 +73,10 @@ function removeSubscription(channelId) { } /** -* Load the recent uploads of the user's subscriptions. -* -* @return {Void} -*/ + * Load the recent uploads of the user's subscriptions. + * + * @return {Void} + */ function loadSubscriptions() { clearMainContainer(); const loading = document.getElementById('loading'); @@ -89,8 +89,82 @@ function loadSubscriptions() { // Welcome to callback hell, we hope you enjoy your stay. subscriptions.then((results) => { + let channelId = ''; + let videoList = []; + + if (results.length > 0) { + let counter = 0; + + for (let i = 0; i < results.length; i++) { + channelId = results[i]['channelId']; + + youtubeAPI('search', { + part: 'snippet', + channelId: channelId, + type: 'video', + maxResults: 15, + order: 'date', + }, (data) => { + console.log(data); + videoList = videoList.concat(data.items); + counter++; + if (counter === results.length) { + videoList.sort((a, b) => { + const date1 = Date.parse(a.snippet.publishedAt); + const date2 = Date.parse(b.snippet.publishedAt); + + return date2.valueOf() - date1.valueOf(); + }); + + // Render the videos to the application. + createVideoListContainer('Latest Subscriptions:'); + + // The YouTube website limits the subscriptions to 100 before grabbing more so we only show 100 + // to keep the app running at a good speed. + if (videoList.length < 50) { + let grabDuration = getDuration(videoList.slice(0, 49)); + + grabDuration.then((list) => { + list.items.forEach((video) => { + displayVideo(video); + }); + }); + } else { + console.log(videoList); + let finishedList = []; + let firstBatchDuration = getDuration(videoList.slice(0, 49)); + + firstBatchDuration.then((list1) => { + finishedList = finishedList.concat(list1.items); + let secondBatchDuration = getDuration(videoList.slice(50, 99)); + + secondBatchDuration.then((list2) => { + finishedList = finishedList.concat(list2.items); + finishedList.forEach((video) => { + displayVideo(video); + }); + stopLoadingAnimation(); + }); + }); + } + } + } + ); + } + + + } else { + // User has no subscriptions. Display message. + const container = document.getElementById('main'); + stopLoadingAnimation(); + + container.innerHTML = `

Your Subscription list is currently empty. Start adding subscriptions + to see them here.

`; + } + // Yes, This function is the thing that needs to most improvment + /* if (results.length > 0) { showToast('Getting Subscriptions. This may take a while...'); @@ -101,68 +175,84 @@ function loadSubscriptions() { * while then sorting them afterwards, this was my best solution at the time. I'm sure someone more * experienced in Node can help out with this. */ - asyncLoop(results, (sub, next) => { - const channelId = sub['channelId']; +/* + asyncLoop(results, (sub, next) => { + const channelId = sub['channelId']; - /* - * Grab the channels 15 most recent uploads. Typically this should be enough. - * This number can be changed if we feel necessary. - */ - youtubeAPI('search', { - part: 'snippet', // Try getting content details for video duration in the near future. - channelId: channelId, - type: 'video', - maxResults: 15, - order: 'date', - }, function (data){ - videoList = videoList.concat(data['items']); - // Iterate through the next object in the loop. - next(); - }); - }, (err) => { - // Sort the videos by date - videoList.sort((a, b) => { - const date1 = Date.parse(a.snippet.publishedAt); - const date2 = Date.parse(b.snippet.publishedAt); + /* + * Grab the channels 15 most recent uploads. Typically this should be enough. + * This number can be changed if we feel necessary. + */ +/* + youtubeAPI('search', { + part: 'snippet', + channelId: channelId, + type: 'video', + maxResults: 15, + order: 'date', + }, (data) => { + videoList = videoList.concat(data.items); - return date2.valueOf() - date1.valueOf(); - }); + next(); + }); + }, (err) => { + // Sort the videos by date + videoList.sort((a, b) => { + const date1 = Date.parse(a.snippet.publishedAt); + const date2 = Date.parse(b.snippet.publishedAt); - // Render the videos to the application. - createVideoListContainer('Latest Subscriptions:'); + return date2.valueOf() - date1.valueOf(); + }); - // The YouTube website limits the subscriptions to 100 before grabbing more so we only show 100 - // to keep the app running at a good speed. - if(videoList.length < 100){ - videoList.forEach((video) => { - console.log('Getting all videos'); - displayVideo(video); + // Render the videos to the application. + createVideoListContainer('Latest Subscriptions:'); + + // The YouTube website limits the subscriptions to 100 before grabbing more so we only show 100 + // to keep the app running at a good speed. + if(videoList.length < 50){ + let grabDuration = getDuration(videoList.slice(0,49)); + + grabDuration.then((list) => { + list.items.forEach((video) => { + displayVideo(video); + }); + }); + } + else{ + let finishedList = [] + let firstBatchDuration = getDuration(videoList.slice(0, 49)); + + firstBatchDuration.then((list1) => { + finishedList = finishedList.concat(list1.items); + let secondBatchDuration = getDuration(videoList.slice(50, 99)); + + secondBatchDuration.then((list2) => { + finishedList = finishedList.concat(list2.items); + finishedList.forEach((video) => { + displayVideo(video); + }); + }); + }); + } + stopLoadingAnimation() }); - } - else{ - console.log('Getting top 100 videos'); - for(let i = 0; i < 100; i++){ - displayVideo(videoList[i]); - } - } - stopLoadingAnimation() - }); - } else { - // User has no subscriptions. Display message. - const container = document.getElementById('main'); - stopLoadingAnimation() + } else { + // User has no subscriptions. Display message. + const container = document.getElementById('main'); + stopLoadingAnimation() - container.innerHTML = `

Your Subscription list is currently empty. Start adding subscriptions - to see them here.

`; - } + container.innerHTML = `

Your Subscription list is currently empty. Start adding subscriptions + to see them here.

`; + } + });*/ }); } /** -* Get the list of subscriptions from the user's subscription database. -* -* @return {promise} The list of subscriptions. -*/ + * Get the list of subscriptions from the user's subscription database. + * + * @return {promise} The list of subscriptions. + */ function returnSubscriptions() { return new Promise((resolve, reject) => { subDb.find({}, (err, subs) => { @@ -172,10 +262,10 @@ function returnSubscriptions() { } /** -* Display the list of subscriptions on the side navigation bar. -* -* @return {Void} -*/ + * Display the list of subscriptions on the side navigation bar. + * + * @return {Void} + */ function displaySubs() { const subList = document.getElementById('subscriptions'); @@ -205,11 +295,11 @@ function displaySubs() { } /** -* Adds / Removes a subscription based on if the channel is in the database or not. -* @param {string} channelId - The channel ID to check -* -* @return {Void} -*/ + * Adds / Removes a subscription based on if the channel is in the database or not. + * @param {string} channelId - The channel ID to check + * + * @return {Void} + */ function toggleSubscription(channelId) { event.stopPropagation(); @@ -219,12 +309,12 @@ function toggleSubscription(channelId) { checkIfSubscribed.then((results) => { if (results === false) { - if(subscribeButton != null){ + if (subscribeButton != null) { subscribeButton.innerHTML = 'UNSUBSCRIBE'; } addSubscription(channelId); } else { - if(subscribeButton != null){ + if (subscribeButton != null) { subscribeButton.innerHTML = 'SUBSCRIBE'; } removeSubscription(channelId); @@ -233,15 +323,17 @@ function toggleSubscription(channelId) { } /** -* Check if the user is subscribed to a channel or not. -* -* @param {string} channelId - The channel ID to check -* -* @return {promise} - A boolean value if the channel is currently subscribed or not. -*/ + * Check if the user is subscribed to a channel or not. + * + * @param {string} channelId - The channel ID to check + * + * @return {promise} - A boolean value if the channel is currently subscribed or not. + */ function isSubscribed(channelId) { return new Promise((resolve, reject) => { - subDb.find({channelId: channelId}, (err, docs) => { + subDb.find({ + channelId: channelId + }, (err, docs) => { if (jQuery.isEmptyObject(docs)) { resolve(false); } else { diff --git a/src/js/videos.js b/src/js/videos.js index 102dae29a..5fece66f1 100644 --- a/src/js/videos.js +++ b/src/js/videos.js @@ -41,20 +41,64 @@ function search(nextPageToken = '') { youtubeAPI('search', { q: query, - part: 'id, snippet', + part: 'id', type: 'video', pageToken: nextPageToken, maxResults: 25, }, function (data){ + let grabDuration = getDuration(data.items); + + grabDuration.then((videoList) => { + videoList.items.forEach(displayVideo); + }); + if (nextPageToken === '') { createVideoListContainer('Search results:'); stopLoadingAnimation(); } - data.items.forEach(displayVideo); addNextPage(data.nextPageToken); }) } +/** +* Grab the duration of the videos +* +* @param {array} data - An array of videos to get the duration from +* +* @return {promise} - The list of videos with the duration included. +*/ +function getDuration(data){ + return new Promise((resolve, reject) => { + let videoIdList = ''; + + for(let i = 0; i < data.length; i++){ + if (videoIdList === ''){ + if(typeof(data[i]['id']) === 'string'){ + videoIdList = data[i]['id']; + } + else{ + videoIdList = data[i]['id']['videoId']; + } + } + else{ + if(typeof(data[i]['id']) === 'string'){ + videoIdList = videoIdList + ', ' + data[i]['id']; + } + else{ + videoIdList = videoIdList + ', ' + data[i]['id']['videoId']; + } + } + } + + youtubeAPI('videos', { + part: 'snippet, contentDetails', + id: videoIdList + }, (data) => { + resolve(data); + }); + }); +} + /** * Display a video on the page. Function is typically contained in a loop. * @@ -66,9 +110,14 @@ function search(nextPageToken = '') { */ function displayVideo(video, listType = '') { const videoSnippet = video.snippet; + + const videoDuration = parseVideoDuration(video.contentDetails.duration); + //const videoDuration = '00:00'; + // Grab the published date for the video and convert to a user readable state. const dateString = new Date(videoSnippet.publishedAt); const publishedDate = dateFormat(dateString, "mmm dS, yyyy"); + const searchMenu = $('#videoListContainer').html(); const videoId = video.id; @@ -94,6 +143,7 @@ function displayVideo(video, listType = '') { channelName: videoSnippet.channelTitle, videoDescription: videoSnippet.description, channelId: videoSnippet.channelId, + videoDuration: videoDuration, publishedDate: publishedDate, liveText: liveText, deleteHtml: deleteHtml, @@ -143,26 +193,30 @@ function addNextPage(nextPageToken) { */ function showVideoRecommendations(videoId) { youtubeAPI('search', { - part: 'snippet', + part: 'id', type: 'video', relatedToVideoId: videoId, maxResults: 15, }, function (data){ - const recommendations = data.items; - recommendations.forEach((data) => { - const snippet = data.snippet; + let grabDuration = getDuration(data.items); + grabDuration.then((videoList) => { + videoList.items.forEach((video) => { + const snippet = video.snippet; + const videoDuration = parseVideoDuration(video.contentDetails.duration); - const recommTemplate = require('./templates/recommendations.html') - mustache.parse(recommTemplate); - const rendered = mustache.render(recommTemplate, { - videoId: data.id.videoId, - videoTitle: snippet.title, - channelName: snippet.channelTitle, - videoThumbnail: snippet.thumbnails.medium.url, - publishedDate: dateFormat(snippet.publishedAt, "mmm dS, yyyy") + const recommTemplate = require('./templates/recommendations.html') + mustache.parse(recommTemplate); + const rendered = mustache.render(recommTemplate, { + videoId: video.id, + videoTitle: snippet.title, + channelName: snippet.channelTitle, + videoThumbnail: snippet.thumbnails.medium.url, + videoDuration: videoDuration, + publishedDate: dateFormat(snippet.publishedAt, "mmm dS, yyyy") + }); + const recommendationHtml = $('#recommendations').html(); + $('#recommendations').html(recommendationHtml + rendered); }); - const recommendationHtml = $('#recommendations').html(); - $('#recommendations').html(recommendationHtml + rendered); }); }); } @@ -195,6 +249,57 @@ function parseVideoLink() { } } +/** +* Convert duration into a more readable format +* +* @param {string} durationString - The string containing the video duration. Formated as 'PT12H34M56S' +* +* @return {string} - The formated string. Ex: 12:34:56 +*/ +function parseVideoDuration(durationString){ + let match = durationString.match(/PT(\d+H)?(\d+M)?(\d+S)?/); + let duration = ''; + + match = match.slice(1).map(function(x) { + if (x != null) { + return x.replace(/\D/, ''); + } + }); + + let hours = (parseInt(match[0]) || 0); + let minutes = (parseInt(match[1]) || 0); + let seconds = (parseInt(match[2]) || 0); + + if (hours != 0){ + duration = hours + ':'; + } + else{ + duration = minutes + ':'; + } + + if (hours != 0 && minutes < 10){ + duration = duration + '0' + minutes + ':'; + } + else if (hours != 0 && minutes > 10){ + duration = duration + minutes + ':'; + } + else if (hours != 0 && minutes == 0){ + duration = duration + '00:'; + } + + if (seconds == 0){ + duration = duration + '00'; + } + else if (seconds < 10){ + duration = duration + '0' + seconds; + } + else{ + duration = duration + seconds; + } + + return duration; +} + /** * Grab the most popular videos over the last couple of days and display them. * @@ -213,15 +318,21 @@ function showMostPopular() { // Applications grab these. Videos in the 'Trending' tab on YouTube will be different. // And there is no way to grab those videos. youtubeAPI('search', { - part: 'snippet', + part: 'id', order: 'viewCount', type: 'video', publishedAfter: d.toISOString(), maxResults: 50, }, function (data){ createVideoListContainer('Most Popular:'); + console.log(data); + let grabDuration = getDuration(data.items); + + grabDuration.then((videoList) => { + console.log(videoList); + videoList.items.forEach(displayVideo); + }); stopLoadingAnimation(); - data.items.forEach(displayVideo); }); } diff --git a/src/style/player.css b/src/style/player.css index 219860a69..f877c88ac 100644 --- a/src/style/player.css +++ b/src/style/player.css @@ -1,14 +1,14 @@ -iframe{ +iframe { width: 100%; height: 41.25vw; } -#main{ - margin-top: 80px; - margin-left: 250px; +#main { + margin-top: 80px; + margin-left: 250px; } -.video{ +.video { width: 95%; max-width: 1000px; height: 145px; @@ -16,68 +16,90 @@ iframe{ overflow: hidden; } -.videoThumbnail{ +.videoThumbnail { width: 275px; + height: 160px; float: left; cursor: pointer; } -.videoTitle{ +.videoThumbnail img{ + width: 100%; +} + +.videoTitle { font-weight: bold; margin-left: 285px; margin-top: 5px; cursor: pointer; } -.channelName{ +.channelName { margin-left: 285px; font-size: 14px; cursor: pointer; } -.videoDescription{ +.videoDescription { margin-left: 285px; font-size: 13px; cursor: pointer; + height: 45px; + overflow: hidden; } -.videoPlayer{width: 100%;} - -.statistics{ - padding: 20px; - padding-bottom: 45px; +.videoDuration { + display: inline-block; + float: right; + position: relative; + bottom: 36px; + color: white; + background-color: black; + opacity: 0.7; + padding: 2px; + font-size: 13px; + text-align: right; } -.title{ +.videoPlayer { + width: 100%; +} + +.statistics { + padding: 20px; + padding-bottom: 45px; +} + +.title { font-weight: bold; font-size: 25px; } -.views{ +.views { margin-top: -10px; float: left; } -.details{ +.details { padding: 15px; } -.likeContainer{ +.likeContainer { width: 300px; float: right; } -.likes{ +.likes { float: left; font-size: 12px; } -.dislikes{ +.dislikes { float: right; font-size: 12px; } -.dislikeBar{ +.dislikeBar { background-color: #9E9E9E; width: 300px; height: 5px; @@ -86,14 +108,14 @@ iframe{ -webkit-border-radius: 200px 200px 200px 200px; } -.likeBar{ +.likeBar { height: 5px; background-color: #2196F3; border-radius: 200px 200px 200px 200px; -webkit-border-radius: 200px 200px 200px 200px; } -#channelIcon{ +#channelIcon { float: left; width: 80px; border-radius: 200px 200px 200px 200px; @@ -115,26 +137,26 @@ iframe{ cursor: pointer; } -#channelName{ +#channelName { font-weight: bold; margin-left: 95px; font-size: 16px; cursor: pointer; } -#publishDate{ +#publishDate { margin-left: 95px; font-size: 13px; margin-top: -10px; } -#description{ +#description { white-space: pre-line; max-height: 200px; overflow-y: auto; } -.playerSubButton{ +.playerSubButton { float: right; width: 125px; height: 50px; @@ -144,7 +166,7 @@ iframe{ cursor: pointer; } -.smallButton{ +.smallButton { float: right; height: 30px; font-size: 10px; @@ -156,15 +178,15 @@ iframe{ cursor: pointer; } -.videoQuality{ +.videoQuality { width: 42px; } -.videoQuality:hover .qualityTypes{ +.videoQuality:hover .qualityTypes { visibility: visible; } -.qualityTypes{ +.qualityTypes { visibility: hidden; width: 72px; position: relative; @@ -172,27 +194,27 @@ iframe{ right: 15px; } -.qualityTypes ul{ +.qualityTypes ul { list-style-type: none; position: relative; right: 24px; } -.qualityTypes ul li{ +.qualityTypes ul li { width: 72px; position: relative; right: 15px; } -.videoSpeed{ +.videoSpeed { width: 42px; } -.videoSpeed:hover .speedTypes{ +.videoSpeed:hover .speedTypes { visibility: visible; } -.speedTypes{ +.speedTypes { visibility: hidden; width: 72px; position: relative; @@ -200,19 +222,19 @@ iframe{ right: 15px; } -.speedTypes ul{ +.speedTypes ul { list-style-type: none; position: relative; right: 24px; } -.speedTypes ul li{ +.speedTypes ul li { width: 72px; position: relative; right: 15px; } -#showComments{ +#showComments { text-align: center; height: 40px; line-height: 40px; @@ -220,33 +242,38 @@ iframe{ margin-bottom: 15px; } -#recommendations{ +#recommendations { width: 100%; } -.recommendVideo{ +.recommendVideo { cursor: pointer; height: 150px; } -.recommendThumbnail{ +.recommendThumbnail { width: 250px; + height: 145px; float: left; } -.recommendTitle{ +.recommendThumbnail img{ + width: 100%; +} + +.recommendTitle { font-size: 16px; font-weight: bold; margin-left: 260px; } -.recommendChannel{ +.recommendChannel { margin-left: 260px; font-size: 14px; margin-top: -10px; } -.recommendDate{ +.recommendDate { margin-left: 260px; font-size: 11px; } diff --git a/src/templates/recommendations.html b/src/templates/recommendations.html index bebf2d464..a253a25d0 100644 --- a/src/templates/recommendations.html +++ b/src/templates/recommendations.html @@ -1,5 +1,8 @@
- +
+ +

{{videoDuration}}

+

{{videoTitle}}

{{channelName}}

{{publishedDate}}

diff --git a/src/templates/videoList.html b/src/templates/videoList.html index d2531eb4d..8a4fffeb4 100644 --- a/src/templates/videoList.html +++ b/src/templates/videoList.html @@ -1,6 +1,9 @@
{{{deleteHtml}}} - +
+ +

{{videoDuration}}

+

{{videoTitle}}

{{channelName}} - {{publishedDate}}

{{videoDescription}}