diff --git a/.gitignore b/.gitignore index 6dae747..946f815 100644 --- a/.gitignore +++ b/.gitignore @@ -34,3 +34,5 @@ Icon # Files that might appear on external disk .Spotlight-V100 .Trashes +node_modules +coverage diff --git a/README.md b/README.md index 664168b..26d52c3 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,14 @@ Youtube Lite allows you to lazyload youtube videos into your NodeBB Forum with n ##Changes in V3 + 0.6.0 + - Works with NodeBB 1.0 + - Moved most of the work to the server, to include API calls (keeps API key + from being exposed to users). + - LRU cache to reduce quota usage. + - Supports `t=`, '`start=`', `end=` query string parameters + - With valid API key displays title, channel and duration information + - Support `#` in query strings 0.5.0 - Plugin now supports [Youtube API v3](https://developers.google.com/youtube/v3/?hl=en) diff --git a/library.js b/library.js index 33f8014..57687f2 100644 --- a/library.js +++ b/library.js @@ -1,44 +1,379 @@ "use strict"; +// https://developers.google.com/youtube/v3/docs/videos +var LRU = require('lru-cache'), + cache = LRU( 500 ); +var https = require('https'); var controllers = require('./lib/controllers'); -var YoutubeLite = {}, - embed = '
'; +var winston = require('winston'); - var regularUrl = //g; +var YoutubeLite = {}; +YoutubeLite.apiKey = null; +YoutubeLite.cache = cache; -YoutubeLite.init = function(params, callback) { - var router = params.router, - hostMiddleware = params.middleware, - hostControllers = params.controllers; - - // We create two routes for every view. One API call, and the actual route itself. - // Just add the buildHeader middleware to your route and NodeBB will take care of everything for you. +YoutubeLite.youtubeUrl = /(

|^)((]*?>)(\4\7)<\/a>)(|<\/p>)/m; - router.get('/admin/plugins/youtube-lite', hostMiddleware.admin.buildHeader, controllers.renderAdminPage); - router.get('/api/admin/plugins/youtube-lite', controllers.renderAdminPage); +YoutubeLite.init = function(params, callback) { + var router = params.router, + hostMiddleware = params.middleware, + hostControllers = params.controllers, + db = module.parent.require('./database'); + // We create two routes for every view. One API call, and the actual route itself. + // Just add the buildHeader middleware to your route and NodeBB will take care of everything for you. - callback(); + router.get('/admin/plugins/youtube-lite', hostMiddleware.admin.buildHeader, controllers.renderAdminPage); + router.get('/api/admin/plugins/youtube-lite', controllers.renderAdminPage); + + db.getObjectField('settings:youtube-lite', 'id', function(err, result){ + YoutubeLite.apiKey = result; + callback(); + }); }; YoutubeLite.addAdminNavigation = function(header, callback) { - header.plugins.push({ - route: '/plugins/youtube-lite', - icon: 'fa-youtube', - name: 'Youtube Lite' - }); + header.plugins.push({ + route: '/plugins/youtube-lite', + icon: 'fa-youtube', + name: 'Youtube Lite' + }); - callback(null, header); + callback(null, header); }; -YoutubeLite.parse = function(data, callback) { - if (!data || !data.postData || !data.postData.content) { - return callback(null, data); +YoutubeLite.apiRequest = function( videoId, callback ){ + var req = https.request({ + host: 'www.googleapis.com', + path: '/youtube/v3/videos?id=' + videoId + '&key=' + YoutubeLite.apiKey + '&part=snippet,contentDetails&fields=items(snippet(title,channelTitle,thumbnails),contentDetails(duration))', + port: 443, + agent: false, + json: true, + method: 'GET' + },(res) => { + res.setEncoding('utf8'); + var videos = ''; + res.on('data', (data) => { + videos += data; + }); + res.on('end', function(){ + callback(null, videos); + }); + }); + req.end(); + + req.on('error', (err) => { + winston.error('[youtube-lite] error looking up video id: [' + videoId + ']' ); + winston.error( err ); + callback( err ); + }); +} + +YoutubeLite.fetchSnippet = function( videoId, callback ){ + var cachedSnippet = cache.get(videoId); + if( cachedSnippet ){ + return callback(null, cachedSnippet); + } + else{ + if( YoutubeLite.apiKey ){ + return YoutubeLite.apiRequest( videoId, function(err, videos){ + if( err ){ + return callback(err); + } + videos = JSON.parse(videos); + if( !videos.items || videos.items.length == 0 ){ + cache.set( videoId, null ); + return callback(null, null); + } + var snippet = videos.items[0].snippet; + snippet.title = replaceAll( snippet.title, '<', '<'); + snippet.channelTitle = replaceAll( snippet.channelTitle, '<', '<'); + snippet.duration = timeToString( parseDuration( videos.items[0].contentDetails.duration ) ); + cache.set( videoId, snippet ); + callback( null, snippet ); + }); + } + else{ + var snippet = { + title: 'Youtube Video', + thumbnails: { + medium: {url: 'https://i.ytimg.com/vi/' + videoId + '/mqdefault.jpg'}, + default: {url: 'https://i.ytimg.com/vi/' + videoId + '/default.jpg'}, + high: {url: 'https://i.ytimg.com/vi/' + videoId + '/hqdefault.jpg'}, + standard: {url: 'https://i.ytimg.com/vi/' + videoId + '/sddefault.jpg'} + } + }; + callback(null, snippet); + } + } +} + +function replaceAll(text, search, replace) { + if (replace === undefined) { + return text.toString(); + } + return text.split(search).join(replace); +} + +function spliceSlice(str, index, count, add) { +return str.slice(0, index) + (add || "") + str.slice(index + count); +} + +/* +[ +0 '

https://youtu.be/ggCuyOeDl5M?t=30s&end=50s

', +1 '

', +2 'https://youtu.be/ggCuyOeDl5M?t=30s&end=50s', +3 '', +4 'https://youtu.be/', +5 undefined, +6 'https://', +7 'ggCuyOeDl5M?t=30s&end=50s', +8 'ggCuyOeDl5M', +9 't=30s&end=50s', +10 'https://youtu.be/ggCuyOeDl5M?t=30s&end=50s', + index: 11, + input: '

foo

\n

https://youtu.be/ggCuyOeDl5M?t=30s&end=50s

\n' ] + +0: whole match +1: opening paragraph tag (optional) +2: entire ... tag +3: tag +4: https//url inside tag +5: http://url inside tag +6: https +7: query string +8: video id +9: rest of query string (minus starting character) +10: text between ... +11: ending tag +*/ + +function getParams( params ){ + var result = {}; + var queryString = []; + for( var i = 0; i < params.length; ++i ){ + var param = params[i]; + if( param.indexOf('t=') == 0 ){ + var val = parseTime( param.substring( param.indexOf('=')+1) ); + queryString.push( 'start=' + val ); + result.start = timeToString( parseInt( val, 10 ) ); + } + else if( + param.indexOf('start=') == 0 || + param.indexOf('end=') == 0 + ){ + var ex = param.indexOf('='); + var val = parseTime( param.substring( ex+1 ) ); + queryString.push( param.substring(0, ex+1) + val ); + result[param.substring(0, ex)] = timeToString( parseInt( val, 10 ) ); + } + else if( + param.indexOf('iv_load_policy=') == 0 + ){ + queryString.push( param ); + } + } + result.queryString = queryString.join('&'); + return result; +} + + +function filter(data, match, preview, callback){ + if(match){ + var videoId = match[8]; + YoutubeLite.fetchSnippet(videoId, + function(err, snippet){ + if( err ){ + return callback(err); + } + if( !snippet ){ + // not a valid video, skip it + return callback(null, data); + } + + var params = getParams( (match[9] || '').split('&') ); + var queryString = params.queryString; + var content = match[1] + match[3] + ' ' + snippet.title; + + if( snippet.duration ){ + content += ' – '; + + if( params.start ){ + if( params.end ){ + content += '[' + params.start + '..' + params.end + ']'; + } + else{ + content += '[' + params.start + '..' + snippet.duration + ']'; + } + } + else if( params.end ){ + content += '[00:00..' + params.end + ']'; + } + + content += ' ' + snippet.duration + '
— ' + snippet.channelTitle +'
'; + } + else if( params.start || params.end ){ + // No API key, but we still have a start / end we can display + content += ' – '; + + if( params.start ){ + if( params.end ){ + content += '[' + params.start + '..' + params.end + ']'; + } + else{ + content += '[' + params.start + '..]'; + } + } + else if( params.end ){ + content += '[00:00..' + params.end + ']'; + } + content += ''; + } + content += ''; + var thumbnails = snippet.thumbnails; + if( preview ){ + var img = thumbnails.medium || thumbnails.default || thumbnails.high || thumbnails.standard; + content += '
'; + } + else{ + + var img = thumbnails.high || thumbnails.standard || thumbnails.default || thumbnails.medium; + content += '
' + + '\n
' + + '\n ' + + '\n
' + + '\n
'; + } + + content += match[11]; + data = data.substring(0, match.index) + content + data.substring( match.index + match[0].length ) ; + + // Check for more... + filter(data, data.match(YoutubeLite.youtubeUrl), preview, callback); + }); + } + else{ + callback(null, data); + } +} +function parseTime(PT) { + var output = []; + var seconds = 0; + var matches = PT.match(/(?:(\d*)H)?(?:(\d*)M)?(?:(\d*)S)?(\d*)?/i); + var parts = [ + { // hours + pos: 1, + multiplier: 3600 + }, + { // minutes + pos: 2, + multiplier: 60 + }, + { // seconds + pos: 3, + multiplier: 1 + }, + { // seconds (raw) + pos: 4, + multiplier: 1 + } + ]; + + for (var i = 0; i < parts.length; i++) { + if (typeof matches[parts[i].pos] != 'undefined') { + seconds += parseInt(matches[parts[i].pos]) * parts[i].multiplier; } - if (data.postData.content.match(regularUrl)) { - data.postData.content = data.postData.content.replace(regularUrl, embed); + } + + return seconds; +}; + +YoutubeLite.parseRaw = function(data, callback){ + if (!data ) { + return callback(null, data); + } + filter(data, data.match(YoutubeLite.youtubeUrl), true, callback); +}; + +YoutubeLite.parsePost = function(data, callback) { + if (!data || !data.postData || !data.postData.content) { + return callback(null, data); + } + var content = data.postData.content; + filter(content, content.match(YoutubeLite.youtubeUrl), false, function(err, content){ + if(err){ + winston.error('[youtube-lite] error parsing pid ' + data.postData.pid ); + return callback(null, data); } + data.postData.content = content; callback(null, data); + }); +}; + +function parseDuration(PT, settings) { + var durationInSec = 0; + var matches = PT.match(/P(?:(\d*)Y)?(?:(\d*)M)?(?:(\d*)W)?(?:(\d*)D)?T(?:(\d*)H)?(?:(\d*)M)?(?:(\d*)S)?/i); + var parts = [ + { // years + pos: 1, + multiplier: 86400 * 365 + }, + { // months + pos: 2, + multiplier: 86400 * 30 + }, + { // weeks + pos: 3, + multiplier: 604800 + }, + { // days + pos: 4, + multiplier: 86400 + }, + { // hours + pos: 5, + multiplier: 3600 + }, + { // minutes + pos: 6, + multiplier: 60 + }, + { // seconds + pos: 7, + multiplier: 1 + } + ]; + + for (var i = 0; i < parts.length; i++) { + if (typeof matches[parts[i].pos] != 'undefined') { + durationInSec += parseInt(matches[parts[i].pos]) * parts[i].multiplier; + } + } + return durationInSec; + +}; - }; +function timeToString(seconds){ + var output = []; + // Hours extraction + if (seconds > 3599) { + output.push(parseInt(seconds / 3600)); + seconds %= 3600; + } + // Minutes extraction with leading zero + output.push(('0' + parseInt(seconds / 60)).slice(-2)); + // Seconds extraction with leading zero + output.push(('0' + seconds % 60).slice(-2)); + + return output.join(':'); +} module.exports = YoutubeLite; diff --git a/package.json b/package.json index 2e2e027..76b46db 100644 --- a/package.json +++ b/package.json @@ -2,17 +2,27 @@ "name": "nodebb-plugin-youtube-lite", "version": "0.5.0", "nbbpm": { - "compatibility": "^0.6.0 || ^0.7.0 || ^0.8.0 || ^0.9.0" + "compatibility": "^1.0.3 " }, "description": "NodeBB Youtube Lite Plugin", "main": "library.js", "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" + "test": "./node_modules/.bin/istanbul cover ./node_modules/.bin/_mocha -- ./tests -t 100000" }, "repository": { "type": "git", "url": "git+https://github.com/a5mith/nodebb-plugin-youtube-lite.git" }, + "dependencies":{ + "lru-cache": "^4.0.1" + }, + "devDependencies": { + "chai": "^3.5.0", + "istanbul": "^0.4.2", + "mocha": "^2.4.5", + "winston": "^2.2.0", + "sinon": "^1.17" + }, "keywords": [ "nodebb", "plugin", diff --git a/plugin.json b/plugin.json index 94ecbdf..33ef55d 100644 --- a/plugin.json +++ b/plugin.json @@ -5,14 +5,14 @@ "url": "https://github.com/a5mith/nodebb-plugin-youtube-lite", "library": "./library.js", "hooks": [ - { "hook": "filter:parse.post", "method": "parse" }, - { "hook": "filter:admin.header.build", "method": "addAdminNavigation" }, - { "hook": "static:app.load", "method": "init" } + { "hook": "filter:parse.raw", "method": "parseRaw", "priority": 10 }, + { "hook": "filter:parse.post", "method": "parsePost", "priority": 10 }, + { "hook": "filter:admin.header.build", "method": "addAdminNavigation" }, + { "hook": "static:app.load", "method": "init" } ], "templates": "templates", "scripts": [ - "static/lib/lazyYT.js", - "static/lib/main.js" + "static/lib/lazyYT.js" ], "staticDirs": { "static": "./static" diff --git a/static/lib/lazyYT.js b/static/lib/lazyYT.js index f9b6cce..173f8f0 100644 --- a/static/lib/lazyYT.js +++ b/static/lib/lazyYT.js @@ -1,220 +1,13 @@ ;(function ($) { 'use strict'; - - function setUp($el, settings) { - var width = $el.data('width'), - height = $el.data('height'), - title = $el.attr('title') || $el.data('title'), - display_title = $el.data('display-title'), - ratio = ($el.data('ratio')) ? $el.data('ratio') : settings.default_ratio, - display_duration = $el.data('display-duration'), + $.fn.lazyYT = function (e) { + + var $el = $(e.parentElement.parentElement), id = $el.data('youtube-id'), - padding_bottom, - innerHtml = [], - $thumb, - thumb_img, - loading_text = $el.text() ? $el.text() : settings.loading_text, youtube_parameters = $el.data('parameters') || ''; - - ratio = ratio.split(":"); - - youtube_parameters += '&' + settings.youtube_parameters; - - if (typeof display_title != "boolean") { - display_title = settings.display_title; - } - - if (typeof display_duration != "boolean") { - display_duration = settings.display_duration; - } - - // width and height might override default_ratio value - if (typeof width === 'number' && typeof height === 'number') { - $el.width(width); - padding_bottom = height + 'px'; - } else if (typeof width === 'number') { - $el.width(width); - padding_bottom = (width * ratio[1] / ratio[0]) + 'px'; - } else { - width = $el.width(); - - // no width means that container is fluid and will be the size of its parent - if (width == 0) { - width = $el.parent().width(); - } - - padding_bottom = (ratio[1] / ratio[0] * 100) + '%'; - } - - // - // This HTML will be placed inside 'lazyYT' container - - innerHtml.push('
'); - - // Play button from YouTube (exactly as it is in YouTube) - innerHtml.push(''); // end of .ytp-large-play-button - - // video time from YouTube (exactly as it is in YouTube) - if (display_duration) { - innerHtml.push(''); - } - innerHtml.push('
'); // end of .ytp-thumbnail - - // Video title (info bar) - if (display_title) { - innerHtml.push('
'); - innerHtml.push('
'); - innerHtml.push('
'); - innerHtml.push(''); // /.ytp-title-text - innerHtml.push('
'); // /.ytp-title - innerHtml.push('
'); // /.ytp-chrome-top - } - - $el.css({ - 'padding-bottom': padding_bottom - }) - .html(innerHtml.join('')); - - if (width > 320) { - thumb_img = 'hqdefault.jpg'; - } else if (width > 120) { - thumb_img = 'mqdefault.jpg'; - } else if (width == 0) { // sometimes it fails on fluid layout - thumb_img = 'hqdefault.jpg'; - } else { - thumb_img = 'default.jpg'; - } - - $thumb = $el.find('.ytp-thumbnail').css({ - 'background-image': ['url(http://img.youtube.com/vi/', id, '/', thumb_img, ')'].join('') - }) - .addClass('lazyYT-image-loaded') - .on('click', function (e) { - e.preventDefault(); - if (!$el.hasClass('lazyYT-video-loaded') && $thumb.hasClass('lazyYT-image-loaded')) { - $el.html('') - .addClass(settings.video_loaded_class); - - // execute callback - if (typeof settings.callback == 'function') { // make sure the callback is a function - settings.callback.call($el); // brings the scope to the callback - } - } - }); - - if ((!title && display_title) || display_duration) { - var youtube_data_url = ['https://www.googleapis.com/youtube/v3/videos?id=', id, '&key=', settings.yt_api_key, '&part=snippet']; - if (display_duration) youtube_data_url.push(',contentDetails'); // this extra info now costs some quota points, so we retrieve it only when necessary. More on quota: https://developers.google.com/youtube/v3/getting-started#quota - - $.getJSON(youtube_data_url.join(''), function (data) { - var item = data.items[0]; - // console.log(item.snippet.title); - - $el.find('#lazyYT-title-' + id).text(item.snippet.title); - - if (display_duration) { - $el.find('.video-time') - .text(parseDuration(item.contentDetails.duration, settings)) - .show(); - } - - }); - } - - }; - - function parseDuration(PT, settings) { - var output = []; - var durationInSec = 0; - var matches = PT.match(/P(?:(\d*)Y)?(?:(\d*)M)?(?:(\d*)W)?(?:(\d*)D)?T(?:(\d*)H)?(?:(\d*)M)?(?:(\d*)S)?/i); - var parts = [ - { // years - pos: 1, - multiplier: 86400 * 365 - }, - { // months - pos: 2, - multiplier: 86400 * 30 - }, - { // weeks - pos: 3, - multiplier: 604800 - }, - { // days - pos: 4, - multiplier: 86400 - }, - { // hours - pos: 5, - multiplier: 3600 - }, - { // minutes - pos: 6, - multiplier: 60 - }, - { // seconds - pos: 7, - multiplier: 1 - } - ]; - - for (var i = 0; i < parts.length; i++) { - if (typeof matches[parts[i].pos] != 'undefined') { - durationInSec += parseInt(matches[parts[i].pos]) * parts[i].multiplier; - } - } - - // Hours extraction - if (durationInSec > 3599) { - output.push(parseInt(durationInSec / 3600)); - durationInSec %= 3600; - } - // Minutes extraction with leading zero - output.push(('0' + parseInt(durationInSec / 60)).slice(-2)); - // Seconds extraction with leading zero - output.push(('0' + durationInSec % 60).slice(-2)); - - return output.join(':'); - }; - - $.fn.lazyYT = function (yt_api_key, newSettings) { - var defaultSettings = { - yt_api_key: yt_api_key, - - youtube_parameters: 'rel=0', - loading_text: 'Loading...', - display_title: true, - default_ratio: '16:9', - display_duration: false, - callback: null, - - // Advanced settings - video_loaded_class: 'lazyYT-video-loaded', - container_class: 'lazyYT-container' - }; - var settings = $.extend(defaultSettings, newSettings); - - return this.each(function () { - var $el = $(this).addClass(settings.container_class); - setUp($el, settings); - }); - }; - + $el.html( + '') + .addClass('lazyYT-video-loaded'); + }; }(jQuery)); + diff --git a/static/lib/main.js b/static/lib/main.js deleted file mode 100644 index f9a97bf..0000000 --- a/static/lib/main.js +++ /dev/null @@ -1,10 +0,0 @@ -"use strict"; - - $(window).on('action:widgets.loaded', function() { - $('.js-lazyYT').lazyYT(); - }); - - - $(window).on('action:posts.loaded', function(){ - $('.js-lazyYT').delay(500).lazyYT(); - }); diff --git a/static/style.less b/static/style.less index 1b04d59..fc34cf0 100644 --- a/static/style.less +++ b/static/style.less @@ -17,6 +17,7 @@ iframe.lazytube { padding: 0 0 56.25% 0; overflow: hidden; background-color: @body-bg; + margin-bottom: 10px; } .lazyYT-container iframe { diff --git a/tests/googleapi.js b/tests/googleapi.js new file mode 100644 index 0000000..fb4c178 --- /dev/null +++ b/tests/googleapi.js @@ -0,0 +1,64 @@ +'use strict'; +var winston = require('winston'); +var sinon = require('sinon'); +var expect = require("chai").expect; +var youtubeLite = require("../library"); + +process.on('uncaughtException', function (err) { + winston.error('Encountered error while running test suite: ' + err.message); +}); + + +describe('Youtube API call',function(){ + var sandbox = null; + beforeEach(function(){ + sandbox = sinon.sandbox.create(); + youtubeLite.apiKey = 'fakekey'; + }); + afterEach(function(){ + sandbox.restore(); + }); + + it("doesn't crash on a bad video id",function(){ + sandbox.stub( youtubeLite, 'apiRequest').yields( null, "{}" ); + youtubeLite.fetchSnippet( "badVideo", function( err, snippet ){ + expect( snippet ).to.be.null; + }); + }); + + it("returns {title, channelTitle, thumbnails, duration} on a good video id",function(){ + sandbox.stub( youtubeLite, 'apiRequest').yields( null, JSON.stringify( + { + items: [ + { + snippet: { + title: 'Video title!', + channelTitle: 'Channel Title!', + thumbnails:{ + default:{ url:'https://i.ytimg.com/vi/goodvideo/default.jpg'}, + high:{ url:'https://i.ytimg.com/vi/goodvideo/hqdefault.jpg'}, + medium:{ url:'https://i.ytimg.com/vi/goodvideo/mqdefault.jpg'}, + standard:{ url:'https://i.ytimg.com/vi/goodvideo/sddefault.jpg'}, + } + }, + contentDetails: {duration: 'PT3H2M31S'} + } + ] + }) ); + youtubeLite.fetchSnippet( "goodvideo", function( err, snippet ){ + expect( snippet ).to.deep.equal( + { + title: 'Video title!', + channelTitle: 'Channel Title!', + duration: '3:02:31', + thumbnails:{ + default:{ url:'https://i.ytimg.com/vi/goodvideo/default.jpg'}, + high:{ url:'https://i.ytimg.com/vi/goodvideo/hqdefault.jpg'}, + medium:{ url:'https://i.ytimg.com/vi/goodvideo/mqdefault.jpg'}, + standard:{ url:'https://i.ytimg.com/vi/goodvideo/sddefault.jpg'}, + } + } ); + + }); + }); +}); diff --git a/tests/regex.js b/tests/regex.js new file mode 100644 index 0000000..0be8689 --- /dev/null +++ b/tests/regex.js @@ -0,0 +1,175 @@ +'use strict'; +var winston = require('winston'); + +process.on('uncaughtException', function (err) { + winston.error('Encountered error while running test suite: ' + err.message); +}); + +function replaceAll(text, searchReplace) { + if (searchReplace === undefined) { + return text.toString(); + } + if( !Array.isArray(searchReplace) ){ + searchReplace = [searchReplace]; + } + for( var i = 0; i < searchReplace.length; ++i ){ + var sr = searchReplace[i]; + text = text.split( sr.search ).join( sr.replace ); + } + return text; +} + +var expect = require("chai").expect; + +var youtubeLite = require("../library"); +var rawTitlePrefix = ' Youtube Video'; +var rawTimes = ' – {time}'; +var rawTitleSuffix = '
' +var rawSimpleTemplate = rawTitlePrefix + rawTitleSuffix; +var rawTimesTemplate = rawTitlePrefix + rawTimes + rawTitleSuffix; +var postQueryStringPrefixTemplate = ' Youtube Video – {time}'; +var postSimplePrefixTemplate = ' Youtube Video'; +var postSuffix = +'
' + +'\n
' + +'\n ' + +'\n
' + +'\n
'; +var postSimpleTemplate = postSimplePrefixTemplate + postSuffix; +var postQueryStringTemplate = postQueryStringPrefixTemplate + postSuffix; + +var videoIdSearchReplace = {search:'{videoId}', replace:'fXhUgV9qzI0'}; +var blankQueryStringSearchReplace = {search:'{queryString}', replace:''}; +var blankDataParameterstringSearchReplace = {search:'{dataParameters}', replace:''}; + +var posts = [ + { + description: 'Simple post with only the youtube link', + content: '

https://youtu.be/fXhUgV9qzI0

', + expected:{ + raw: '

' + replaceAll( rawSimpleTemplate, [videoIdSearchReplace, blankQueryStringSearchReplace]) + '

', + post: '

' + replaceAll( postSimpleTemplate, [videoIdSearchReplace, blankQueryStringSearchReplace, blankDataParameterstringSearchReplace]) + '

' + } + }, + { + description: 'Markdown link with a youtube URL should be left alone', + content: '

linked

', + expected: { + raw: '

linked

', + post: '

linked

' + } + }, + { + description: 'Video URLs on consecutive lines', + videoId: 'fXhUgV9qzI0', + content: '

https://youtu.be/fXhUgV9qzI0
\nhttps://youtu.be/fXhUgV9qzI0

', + expected: { + raw: '

' + replaceAll( rawSimpleTemplate, [videoIdSearchReplace, blankQueryStringSearchReplace]) + '
\n' + + replaceAll( rawSimpleTemplate, [videoIdSearchReplace, blankQueryStringSearchReplace]) + '

', + post: '

' + replaceAll( postSimpleTemplate, [videoIdSearchReplace, blankQueryStringSearchReplace, blankDataParameterstringSearchReplace]) + '
\n' + + replaceAll( postSimpleTemplate, [videoIdSearchReplace, blankQueryStringSearchReplace, blankDataParameterstringSearchReplace]) + '

' + } + }, + { + description: 'Link with text in front should be left alone', + content: '

Look at thishttps://youtu.be/fXhUgV9qzI0

', + expected: { + raw: '

Look at thishttps://youtu.be/fXhUgV9qzI0

', + post: '

Look at thishttps://youtu.be/fXhUgV9qzI0

' + } + }, + { + description: 'Link with text behind should be left alone', + content: '

https://youtu.be/fXhUgV9qzI0 uh huh

', + expected: { + raw: '

https://youtu.be/fXhUgV9qzI0 uh huh

', + post: '

https://youtu.be/fXhUgV9qzI0 uh huh

' + } + }, + { + description: 'Link with text in front and behind should be left alone', + content: '

Look: https://youtu.be/fXhUgV9qzI0 uh huh

', + expected: { + raw: '

Look: https://youtu.be/fXhUgV9qzI0 uh huh

', + post: '

Look: https://youtu.be/fXhUgV9qzI0 uh huh

' + } + }, + { + description: 'Simple post with only the youtu.be link with a start time', + videoId: 'fXhUgV9qzI0', + content: '

https://youtu.be/fXhUgV9qzI0?t=55s

', + expected: { + raw: '

' + replaceAll( rawTimesTemplate, [videoIdSearchReplace,{search:'{time}',replace:'[00:55..]'}, {search:'{queryString}', replace:'?t=55s'}]) + '

', + post: '

' + replaceAll( postQueryStringTemplate, [ + videoIdSearchReplace, + {search:'{queryString}', replace:'t=55s'}, + {search:'{time}',replace:'[00:55..]'}, + {search:'{dataParameters}', replace:'start=55'} + ]) + '

' + } + }, + { + description: 'Simple post with only the youtube.com link with a start time', + videoId: 'fXhUgV9qzI0', + content: '

https://youtube.com/watch?v=fXhUgV9qzI0&t=55s

', + expected: { + raw: '

' + replaceAll( rawTimesTemplate, [ + {search:'youtu.be/',replace:'youtube.com/watch?v='}, + videoIdSearchReplace, + {search:'{time}',replace:'[00:55..]'}, + {search:'{queryString}', replace:'&t=55s'}]) + '

', + post: '

' + replaceAll( postQueryStringTemplate, [ + videoIdSearchReplace, + {search:'youtu.be/',replace:'youtube.com/watch?v='}, + {search:'?{queryString}',replace:'&{queryString}'}, + {search:'{queryString}', replace:'t=55s'}, + {search:'{time}',replace:'[00:55..]'}, + {search:'{dataParameters}', replace:'start=55'} + ]) + '

' + } + } +]; + + +describe( 'Parsing a ', function(){ + var i; + for( i = 0; i < posts.length; ++i ){ + var post = posts[i]; + describe( post.description + ' in preview', function(){ + + var data = new Object(); + var expectedValue = post.expected.raw; + data = post.content; + it('converts the post correctly', function(){ + youtubeLite.parseRaw( data, function( callback, theData ){ + expect( theData ).to.equal( expectedValue ); + }); + + }); + }); + describe( post.description + ' in the final post', function(){ + + var data = new Object(); + var expectedValue = post.expected.post; + data.postData = new Object(); + data.postData.content = post.content; + it('converts the post correctly', function(){ + youtubeLite.parsePost( data, function( callback, theData ){ + expect( theData.postData.content ).to.equal( expectedValue ); + }); + + }); + }); + } +}); + +