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}}