diff --git a/.gitignore b/.gitignore index 17603a82..ba932f8b 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,10 @@ release src/js/background/key/*.js !src/js/background/key/*.example.js +# Injected CSS isn't compiled from LESS, but all other CSS is compiled with LESS. Commit the LESS file rather than the CSS +src/css/*.css +!src/css/*Inject.css + # Node node_modules diff --git a/Gruntfile.js b/Gruntfile.js index 7e59d35b..c5b4c382 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -1,445 +1,289 @@ /*jslint node: true*/ -// Provides methods which may be executed from the command prompt by being in this files cwd. -// Type grunt to run the default method, or "grunt paramater" to run a specific method. -// // Options: -// * grunt deploy: Pass a version to creat dist .zip. Otherwise, test production without updating manifest version or creating a .zip/linting, just walk through normal steps. -// -// See here for more information: http://gruntjs.com/sample-gruntfile +// * grunt: Lint JavaScript, LESS, and _locales +// * grunt build: Build a test release +// * grunt build --newVersion="vx.xxx": Build a release 'use strict'; -module.exports = function (grunt) { - grunt.initConfig({ - // Read project settings from package.json in order to be able to reference the properties with grunt. - pkg: grunt.file.readJSON('package.json'), - // Compress image sizes and move to dist folder - imagemin: { - dynamic: { - files: [{ - expand: true, - cwd: 'dist/img', - src: ['**/*.{png,jpg,gif}'], - dest: 'dist/img/' - }] - } - }, - // Improve code quality by applying a code-quality check with jshint - jshint: { - // Files to analyze: - files: ['Gruntfile.js', 'src/js/**/*.js', 'test/js/**/*.js'], - - options: { - // Override JSHint defaults for the extension - globals: { - jQuery: true, - console: true - }, - - // Don't validate third-party libraries - ignores: ['src/js/thirdParty/**/*.js'] - } - }, - less: { - files: { - expand: true, - ieCompat: false, - cwd: 'src/less/', - src: 'foreground.less', - dest: 'dist/css', - ext: '.css' - } - }, - recess: { - dist: { - src: 'src/less/foreground.less', - options: { - // TODO: Remove these hopefully - noUniversalSelectors: false, - strictPropertyOrder: false - } - } - }, - requirejs: { - production: { - options: { - appDir: 'src', - dir: 'dist/', - // Inlines the text for any text! dependencies, to avoid the separate - // async XMLHttpRequest calls to load those dependencies. - inlineText: true, - stubModules: ['text'], - useStrict: true, - mainConfigFile: 'src/js/common/requireConfig.js', - // List the modules that will be optimized. All their immediate and deep - // dependencies will be included in the module's file when the build is done - modules: [{ - name: 'background/main', - include: ['background/plugins'] - }, { - name: 'background/application', - exclude: ['background/main'] - }, { - name: 'foreground/main', - include: ['foreground/plugins'] - }, { - name: 'foreground/application', - exclude: ['foreground/main'] - }], - // Skip optimizins because there's no load benefit for an extension and it makes error debugging hard. - optimize: 'none', - optimizeCss: 'none', - preserveLicenseComments: false, - // Don't leave a copy of the file if it has been concatenated into a larger one. - removeCombined: true, - fileExclusionRegExp: /^\.|vsdoc.js$|.css$/ - } - - } - } - }); - - grunt.loadNpmTasks('grunt-contrib-clean'); - grunt.loadNpmTasks('grunt-contrib-concat'); - grunt.loadNpmTasks('grunt-contrib-copy'); - grunt.loadNpmTasks('grunt-contrib-compress'); - grunt.loadNpmTasks('grunt-contrib-imagemin'); - grunt.loadNpmTasks('grunt-contrib-jshint'); - grunt.loadNpmTasks('grunt-contrib-less'); - grunt.loadNpmTasks('grunt-contrib-requirejs'); - grunt.loadNpmTasks('grunt-text-replace'); - grunt.loadNpmTasks('grunt-recess'); - - var _ = require('lodash'); - - // Generate a versioned zip file after transforming relevant files to production-ready versions. - grunt.registerTask('deploy', 'Transform and copy extension to /dist folder and generate a dist-ready .zip file. If no version passed, just test', function (version) { - var isDebugDeploy = version === undefined; - grunt.config.set('isDebugDeploy', isDebugDeploy); - - // Update version number in manifest.json: - if (isDebugDeploy) { - grunt.log.write('NOTICE: version is undefined, running as debug deploy and not production. To run as production, pass version. e.g.: deploy:0.98'); - grunt.option('version', 'Debug'); - } else { - grunt.option('version', version); - } - - // Lint JS - grunt.task.run('jshint'); - // Lint LESS/CSS - grunt.task.run('recess'); - // Make sure _locales aren't out of date. - grunt.task.run('diffLocales'); - - // It's necessary to run requireJS first because it will overwrite manifest-transform. - grunt.task.run('requirejs'); - - if (!isDebugDeploy) { - // Leave the debug key in for testing, but it has to be removed for deployment to the web store - grunt.task.run('remove-key-from-manifest'); - } - - grunt.task.run('manifest-transform', 'disable-localDebug', 'concat-injected-javascript', 'copy-injected-css', 'less', 'update-css-references', 'imagemin', 'update-require-config-paths', 'cleanup-dist-folder'); - - // Spit out a zip and update manifest file version if not a test. - if (isDebugDeploy) { - grunt.task.run('prep-chrome-distribution'); - grunt.task.run('prep-opera-distribution'); - } - else { - // Update the version of Streamus since we're actually deploying it and not just testing Grunt. - grunt.task.run('update-dist-manifest-version'); - grunt.task.run('prep-chrome-distribution'); - grunt.task.run('prep-opera-distribution'); - grunt.task.run('update-src-manifest-version'); - } - }); - - // Update the manifest file's version number -- new version is being distributed and it is good to keep files all in sync. - grunt.registerTask('update-dist-manifest-version', 'updates the manifest version to the to-be latest distributed version', function () { - grunt.config.set('replace', { - updateManifestVersion: { - src: ['dist/manifest.json'], - overwrite: true, - replacements: [{ - from: /"version": "\d{0,3}.\d{0,3}"/, - to: '"version": "' + grunt.option('version') + '"' - }] - } - }); - - grunt.task.run('replace'); - }); - - // Update the manifest file's version number -- new version is being distributed and it is good to keep files all in sync. - grunt.registerTask('update-src-manifest-version', 'updates the manifest version to the to-be latest distributed version', function () { - grunt.config.set('replace', { - updateManifestVersion: { - src: ['src/manifest.json'], - overwrite: true, - replacements: [{ - from: /"version": "\d{0,3}.\d{0,3}"/, - to: '"version": "' + grunt.option('version') + '"' - }] - } - }); - - grunt.task.run('replace'); - }); - - grunt.registerTask('concat-injected-javascript', 'injected javascript files don\'t use requireJS so they have to be manually concatted', function () { - grunt.config.set('concat', { - inject: { - files: { - 'dist/js/inject/beatportInject.js': ['src/js/thirdParty/jquery.js', 'src/js/inject/beatportInject.js'], - 'dist/js/inject/streamusInject.js': ['src/js/thirdParty/jquery.js', 'src/js/thirdParty/lodash.js', 'src/js/inject/streamusInject.js'], - 'dist/js/inject/streamusShareInject.js': ['src/js/thirdParty/jquery.js', 'src/js/thirdParty/lodash.js', 'src/js/inject/streamusShareInject.js'], - 'dist/js/inject/youTubeInject.js': ['src/js/thirdParty/jquery.js', 'src/js/thirdParty/lodash.js', 'src/js/inject/youTubeInject.js'], - 'dist/js/inject/youTubeIFrameInject.js': ['src/js/thirdParty/jquery.js', 'src/js/thirdParty/lodash.js', 'src/js/inject/youTubeIFrameInject.js'] - } - } - }); - - grunt.task.run('concat'); - }); - - grunt.registerTask('copy-injected-css', 'copies injected css from src to dist folder', function () { - grunt.config.set('copy', { - inject: { - files: { - 'dist/css/beatportInject.css': ['src/css/beatportInject.css'] - } - } - }); - - grunt.task.run('copy'); - }); - - grunt.registerTask('cleanup-dist-folder', 'removes the template folder since it was inlined into javascript and deletes build.txt', function () { - // Can't delete a full directory -- clean it up. - grunt.config.set('clean', ['dist/template', 'dist/less', 'dist/js/background/key']); - grunt.task.run('clean'); - grunt.file.delete('dist/template'); - grunt.file.delete('dist/less'); - grunt.file.delete('dist/js/thirdParty/mocha.js'); - grunt.file.delete('dist/js/thirdParty/chai.js'); - grunt.file.delete('dist/js/thirdParty/sinon.js'); - grunt.file.delete('dist/js/test'); - grunt.file.delete('dist/test.html'); - - grunt.file.delete('dist/build.txt'); - }); - - grunt.registerTask('update-require-config-paths', 'changes the paths for require config so they work for deployment', function () { - grunt.config.set('replace', { - removeDebuggingKeys: { - src: ['dist/js/**/main.js'], - overwrite: true, - replacements: [{ - // Change all main files paths to requireConfig for to be accurate for deployment. - from: '../common/requireConfig', - to: 'common/requireConfig' - }] - } - }); - - grunt.task.run('replace'); - }); - - grunt.registerTask('update-css-references', 'replace less reference in foreground with css', function() { - grunt.config.set('replace', { - replaceLessReferences: { - src: ['dist/foreground.html'], - overwrite: true, - replacements: [{ - from: 'less/', - to: 'css/' - }, { - from: '.less', - to: '.css' - }, { - from: 'stylesheet/less', - to: 'stylesheet' - }] - } - }); - - grunt.task.run('replace'); - }); - - grunt.registerTask('remove-key-from-manifest', 'removes the key from manifest, separate because needed for testing deployment', function() { - grunt.config.set('replace', { - removeDebuggingKeys: { - src: ['dist/manifest.json'], - overwrite: true, - replacements: [{ - // Remove manifest key -- can't upload to Chrome Web Store if this entry exists in manifest.json, but helps with debugging. - from: /"key".*/, - to: '' - }] - } - }); - - grunt.task.run('replace'); - }); - - // Remove debugging information from the manifest file - grunt.registerTask('manifest-transform', 'removes debugging info from the manifest.json', function () { - var isDebugDeploy = grunt.config.get('isDebugDeploy'); - var replacements = []; - - // Don't remove this when testing debug deploy because server will throw CORS error - if (!isDebugDeploy) { - replacements.push({ - // Remove permissions that're only needed for debugging. - from: /".*localhost:.*,/g, - to: '' - }); - } - - grunt.config.set('replace', { - removeDebuggingKeys: { - src: ['dist/manifest.json'], - overwrite: true, - replacements: replacements.concat([{ - // Transform inject javascript to reference uglified/concat versions for deployment. - from: '"js": ["js/thirdParty/lodash.js", "js/thirdParty/jquery.js", "js/inject/youTubeIFrameInject.js"]', - to: '"js": ["js/inject/youTubeIFrameInject.js"]' - }, { - from: '"js": ["js/thirdParty/lodash.js", "js/thirdParty/jquery.js", "js/inject/youTubeInject.js"]', - to: '"js": ["js/inject/youTubeInject.js"]' - }, { - from: '"js": ["js/thirdParty/lodash.js", "js/thirdParty/jquery.js", "js/inject/streamusShareInject.js"]', - to: '"js": ["js/inject/streamusShareInject.js"]' - }, { - from: '"js": ["js/thirdParty/lodash.js", "js/thirdParty/jquery.js", "js/inject/streamusInject.js"]', - to: '"js": ["js/inject/streamusInject.js"]' - }, { - from: '"js": ["js/thirdParty/jquery.js", "js/inject/beatportInject.js"]', - to: '"js": ["js/inject/beatportInject.js"]' - }]) - } - }); - - grunt.task.run('replace'); - }); - - // Remove debugging information from the JavaScript - grunt.registerTask('disable-localDebug', 'ensure debugging flag is turned off', function () { - grunt.config.set('replace', { - transformSettings: { - src: ['dist/js/background/background.js'], - overwrite: true, - replacements: [{ - // Find the line that looks like: "localDebug: true" and set it to false. Local debugging is for development only. - from: 'localDebug: true', - to: 'localDebug: false' - }] - } - }); - - grunt.task.run('replace'); - }); - - // Zip up the distribution folder and give it a build name. The folder can then be uploaded to the Chrome Web Store. - grunt.registerTask('prep-chrome-distribution', 'compress the files which are ready to be uploaded to the Chrome Web Store into a .zip', function () { - // There's no need to cleanup any old version because this will overwrite if it exists. - grunt.config.set('compress', { - dist: { - options: { - archive: 'release/Streamus v' + grunt.option('version') + '/chrome/' + 'Streamus v' + grunt.option('version') + '.zip' - }, - files: [{ - src: ['**'], - dest: '', - cwd: 'dist/', - expand: true - }] - } - }); - - grunt.task.run('compress'); - }); - - grunt.registerTask('prep-opera-distribution', '', function() { - // Copy the distribution folder into opera directory. - var operaDirectory = 'release/Streamus v' + grunt.option('version') + '/opera'; - - grunt.config.set('copy', { - files: { - cwd: 'dist/', - src: '**/*', - dest: operaDirectory, - expand: true - } - }); - - grunt.task.run('copy'); - - // Remove background and notifications from manifest as they aren't available in opera yet. - grunt.config.set('replace', { - removeDebuggingKeys: { - src: [operaDirectory + '/manifest.json'], - overwrite: true, - replacements: [{ - from: '"background",', - to: '' - }] - } - }); - - grunt.task.run('replace'); - - var operaLocalesDirectory = operaDirectory + '/_locales/'; - - // Delete all non-english translations for Opera because they have stricter translation policies I don't care about complying with. - grunt.config.set('clean', { - nonEnglishLocales: [operaLocalesDirectory + '*', '!' + operaLocalesDirectory + 'en'] - }); - grunt.task.run('clean'); - - // There's no need to cleanup any old version because this will overwrite if it exists. - grunt.config.set('compress', { - dist: { - options: { - archive: 'release/Streamus v' + grunt.option('version') + '/opera/' + 'Streamus v' + grunt.option('version') + '.zip' - }, - files: [{ - src: ['**'], - dest: '', - cwd: operaDirectory, - expand: true - }] - } - }); - - grunt.task.run('compress'); - }); - - grunt.registerTask('diffLocales', 'ensure that all of the message.json files located under _locales are in-sync with the English version', function () { - var englishJson = grunt.file.readJSON('src/_locales/en/messages.json'); - var englishKeys = _.keys(englishJson); - - grunt.file.recurse('src/_locales/', function (abspath, rootdir, subdir) { - var json = grunt.file.readJSON(abspath); - var keys = _.keys(json); - - var missingEnglishKeys = _.difference(englishKeys, keys); - var extraNonEnglishKeys = _.difference(keys, englishKeys); - - if (missingEnglishKeys.length > 0) { - grunt.log.error('The translation for ' + subdir + ' is missing keys: \n- ' + missingEnglishKeys.join('\n- ')); - } - - if (extraNonEnglishKeys.length > 0) { - grunt.log.error('The translation for ' + subdir + ' has extra keys: \n- ' + extraNonEnglishKeys.join('\n- ')); - } - }); - }); - - grunt.registerTask('test', 'Ran by TravisCI', function () { - grunt.task.run('diffLocales'); - grunt.task.run('jshint'); - grunt.task.run('recess'); - }); +var _ = require('lodash'); + +module.exports = function(grunt) { + + require('load-grunt-tasks')(grunt); + + // Setup environment variables before initializing config so that initConfig can use the variables. + var versionParameter = grunt.option('newVersion'); + // Strip out v because need to pass a string to grunt or else trailing zeros get dropped (i.e. can't provide --newVersion=0.170, interpreted as 0.17) + var version = _.isUndefined(versionParameter) ? 'Debug' : versionParameter.replace('v', ''); + var isDebug = !versionParameter; + var baseReleaseDirectory = 'release/Streamus v' + version; + var chromeReleaseDirectory = baseReleaseDirectory + '/chrome/'; + var operaReleaseDirectory = baseReleaseDirectory + '/opera/'; + + grunt.initConfig({ + // Read project settings from package.json in order to be able to reference the properties with grunt. + pkg: grunt.file.readJSON('package.json'), + meta: { + // Set this value dynamically to inform other packages where to write information. + releaseDirectory: '' + }, + // Compress image sizes and move to dist folder + imagemin: { + files: { + expand: true, + cwd: 'dist/img', + src: ['**/*.{png,jpg,gif}'], + dest: 'dist/img/' + } + }, + // Improve code quality by applying a code-quality check with jshint + jshint: { + // A full list of options and their defaults here: https://github.com/jshint/jshint/blob/master/examples/.jshintrc + options: { + camelcase: true, + immed: true, + latedef: true, + newcap: true, + nonew: true, + quotmark: 'single', + jquery: true, + maxparams: 5, + // TODO: Reduce these values. + maxdepth: 4, + maxstatements: 50, + maxcomplexity: 13, + maxlen: 90001, + // Don't validate third-party libraries + ignores: ['src/js/thirdParty/**/*.js'] + }, + + files: ['Gruntfile.js', 'src/js/**/*.js'], + }, + // Compile LESS to CSS + less: { + options: { + ieCompat: false, + compress: true, + strictImports: true, + strictMath: true, + strictUnits: true + }, + + files: { + expand: true, + cwd: 'src/less/', + src: 'foreground.less', + dest: 'src/css/', + ext: '.css' + } + }, + // Ensure LESS code-quality by comparing it against Twitter's ruleset. + // Using a slightly modified version which has support for modern browser properties + recess: { + foreground: { + src: 'src/less/foreground.less', + options: { + noUniversalSelectors: false, + strictPropertyOrder: false + } + } + }, + requirejs: { + production: { + // All r.js options can be found here: https://github.com/jrburke/r.js/blob/master/build/example.build.js + options: { + appDir: 'src', + mainConfigFile: 'src/js/common/requireConfig.js', + dir: 'dist/', + // Skip optimizing because there's no load benefit for an extension and it makes error debugging hard. + optimize: 'none', + optimizeCss: 'none', + // Inlines the text for any text! dependencies, to avoid the separate + // async XMLHttpRequest calls to load those dependencies. + inlineText: true, + useStrict: true, + stubModules: ['text'], + findNestedDependencies: true, + // Don't leave a copy of the file if it has been concatenated into a larger one. + removeCombined: true, + // List the modules that will be optimized. All their immediate and deep + // dependencies will be included in the module's file when the build is done + modules: [{ + name: 'background/main', + insertRequire: ['background/main'] + }, { + name: 'foreground/main', + insertRequire: ['foreground/main'] + }], + fileExclusionRegExp: /^\.|vsdoc.js$|\.example$|test|test.html|less$/ + } + } + }, + replace: { + // Update the version in manifest.json and package.json with the provided version + updateVersion: { + src: ['src/manifest.json', 'package.json'], + overwrite: true, + replacements: [{ + from: /"version": "\d{0,3}.\d{0,3}"/, + to: '"version": "' + version + '"' + }] + }, + // Not all Chrome permissions supported on other browsers (Opera). Remove them from manifest.json when building a release. + invalidPermissions: { + src: ['<%= meta.releaseDirectory %>/manifest.json'], + overwrite: true, + replacements: [{ + from: '"background",', + to: '' + }, { + from: '"identity.email",', + to: '' + }] + }, + // Ensure that the localDebug flag is not set to true when building a release. + localDebug: { + src: ['dist/js/background/background.js'], + overwrite: true, + replacements: [{ + // Find the line that looks like: "localDebug: true" and set it to false. Local debugging is for development only. + from: 'localDebug: true', + to: 'localDebug: false' + }] + }, + // Replace debugging and non-concatenated file references in manifest.json + transformManifest: { + src: ['dist/manifest.json'], + overwrite: true, + replacements: [{ + // Remove manifest key because it can't be uploaded to the web store, but it's helpful to have in debugging to keep the extension ID stable. + from: /"key".*/, + to: function(match) { + // Don't remove key when testing because server will throw CORS errors. + return isDebug ? match : ''; + } + },{ + // Transform inject javascript to reference uglified/concat versions for production. + from: '"js": ["js/thirdParty/lodash.js", "js/thirdParty/jquery.js", "js/inject/youTubeIFrameInject.js"]', + to: '"js": ["js/inject/youTubeIFrameInject.js"]' + }, { + from: '"js": ["js/thirdParty/lodash.js", "js/thirdParty/jquery.js", "js/inject/youTubeInject.js"]', + to: '"js": ["js/inject/youTubeInject.js"]' + }, { + from: '"js": ["js/thirdParty/jquery.js", "js/inject/beatportInject.js"]', + to: '"js": ["js/inject/beatportInject.js"]' + }] + } + }, + compress: { + // Zip up browser-specific folders which are ready for release. The .zip file is then uploaded to the appropriate store + release: { + options: { + archive: '<%= meta.releaseDirectory %>Streamus v' + version + '.zip' + }, + files: [{ + expand: true, + cwd: '<%= meta.releaseDirectory %>', + src: ['**'] + }] + } + }, + clean: { + // Remove all non-English translations from the _locales folder (in Opera) because translation requirements are stricter than what I'm willing to fulfill. + locales: { + files: [{ + expand: true, + cwd: '<%= meta.releaseDirectory %>/_locales/', + src: ['*', '!/en'] + }] + }, + // Cleanup the dist folder of files which don't need to be pushed to production. + dist: { + files: [{ + expand: true, + cwd: 'dist/', + src: ['template', 'build.txt'] + }] + } + }, + copy: { + release: { + expand: true, + cwd: 'dist/', + src: '**/*', + dest: '<%= meta.releaseDirectory %>' + } + }, + concat: { + // Injected JavaScript does not use RequireJS so they need to be concatenated and moved to dist with a separate task + injectedJs: { + files: { + 'dist/js/inject/beatportInject.js': ['src/js/thirdParty/jquery.js', 'src/js/inject/beatportInject.js'], + 'dist/js/inject/youTubeInject.js': ['src/js/thirdParty/jquery.js', 'src/js/thirdParty/lodash.js', 'src/js/inject/youTubeInject.js'], + 'dist/js/inject/youTubeIFrameInject.js': ['src/js/thirdParty/jquery.js', 'src/js/thirdParty/lodash.js', 'src/js/inject/youTubeIFrameInject.js'] + } + } + }, + watch: { + less: { + options: { + nospawn: true + }, + files: ['src/less/*'], + tasks: ['less'] + } + } + }); + + grunt.registerTask('build', 'Build release and place .zip files in /release directory.', function() { + // Ensure tests pass before performing any sort of bundling. + grunt.task.run('test'); + + if (!isDebug) { + grunt.task.run('replace:updateVersion'); + } + + grunt.task.run('requirejs', 'replace:transformManifest', 'replace:localDebug', 'concat:injectedJs', 'less', 'imagemin', 'clean:dist'); + + // Build chrome release + grunt.task.run('compressRelease:' + chromeReleaseDirectory); + // Build opera release + grunt.task.run('compressRelease:' + operaReleaseDirectory + ':sanitize=true'); + }); + + grunt.registerTask('diffLocales', 'ensure that all of the message.json files located under _locales are in-sync with the English version', function() { + var englishJson = grunt.file.readJSON('src/_locales/en/messages.json'); + var englishKeys = _.keys(englishJson); + + grunt.file.recurse('src/_locales/', function(abspath, rootdir, subdir) { + var json = grunt.file.readJSON(abspath); + var keys = _.keys(json); + + var missingEnglishKeys = _.difference(englishKeys, keys); + var extraNonEnglishKeys = _.difference(keys, englishKeys); + + if (missingEnglishKeys.length > 0) { + grunt.log.error('The translation for ' + subdir + ' is missing keys: \n- ' + missingEnglishKeys.join('\n- ')); + } + + if (extraNonEnglishKeys.length > 0) { + grunt.log.error('The translation for ' + subdir + ' has extra keys: \n- ' + extraNonEnglishKeys.join('\n- ')); + } + }); + }); + + grunt.registerTask('default', 'An alias task for running tests.', ['test']); + + grunt.registerTask('test', 'Run tests and code-quality analysis', ['diffLocales', 'jshint', 'recess']); + + grunt.registerTask('compressRelease', 'A synchronous wrapper around compress:release', function(releaseDirectory, sanitize) { + grunt.config.set('meta.releaseDirectory', releaseDirectory); + grunt.task.run('copy:release'); + + if (sanitize) { + grunt.task.run('replace:invalidPermissions', 'clean:locales'); + } + + grunt.task.run('compress:release'); + }); }; \ No newline at end of file diff --git a/Streamus Chrome Extension.csproj b/Streamus Chrome Extension.csproj index 15b32be7..db9a1b6d 100644 --- a/Streamus Chrome Extension.csproj +++ b/Streamus Chrome Extension.csproj @@ -42,7 +42,8 @@ - + + @@ -71,7 +72,18 @@ + + + + + + + + + + + @@ -118,7 +130,6 @@ - @@ -202,14 +213,12 @@ - - @@ -263,11 +272,7 @@ - - - - @@ -288,8 +293,6 @@ - - @@ -318,20 +321,17 @@ - + + Designer + - - - - - @@ -340,6 +340,7 @@ + @@ -445,6 +446,8 @@ + + diff --git a/package.json b/package.json index 9d762a14..9519c6d9 100644 --- a/package.json +++ b/package.json @@ -1,10 +1,10 @@ { "name": "Streamus", - "version": "0.0.98", + "version": "0.0.170", "private": true, - "description": "A Description for Streamus Here", + "description": "Streamus - The most popular Chrome extension YouTube music player", "devDependencies": { - "grunt": "~0.4.2", + "grunt": "^0.4.5", "grunt-contrib-clean": "^0.6.0", "grunt-contrib-compress": "^0.13.0", "grunt-contrib-concat": "^0.5.1", @@ -17,9 +17,11 @@ "grunt-contrib-nodeunit": "^0.4.1", "grunt-contrib-requirejs": "~0.4.1", "grunt-contrib-uglify": "^0.8.0", + "grunt-contrib-watch": "^0.6.1", "grunt-recess": "https://github.com/MeoMix/grunt-recess/tarball/master", "grunt-text-replace": "^0.4.0", "grunt-usemin": "^3.0.0", + "load-grunt-tasks": "^3.1.0", "lodash": "^3.3.0" }, "main": "Gruntfile.js", diff --git a/src/_locales/ko/messages.json b/src/_locales/ko/messages.json new file mode 100644 index 00000000..5ff21ec9 --- /dev/null +++ b/src/_locales/ko/messages.json @@ -0,0 +1,528 @@ +{ + "extensionName": { + "message": "Streamus(스트리머스)" + }, + "extensionDescription": { + "message": "간단하고 빠르게 음악을 들으세요." + }, + "toggleRadioCommandDescription": { + "message": "라디오 끄기/켜기" + }, + "toggleShuffleCommandDescription": { + "message": "셔플 끄기/켜기" + }, + "toggleRepeatCommandDescription": { + "message": "반복 끄기/모든 노래/현재 노래" + }, + "increaseVolumeCommandDescription": { + "message": "소리 늘리기" + }, + "decreaseVolumeCommandDescription": { + "message": "소리 줄이기" + }, + "showActiveSongCommandDescription": { + "message": "자세히" + }, + "nextSongCommandDescription": { + "message": "다음곡" + }, + "previousSongCommandDescription": { + "message": "이전곡" + }, + "toggleSongCommandDescription": { + "message": "재생/정지" + }, + "deleteSongCommandDescription": { + "message": "노래 삭제" + }, + "copySongURLCommandDescription": { + "message": "링크 복사" + }, + "copySongTitleURLCommandDescription": { + "message": "노래 제목과 링크 복사" + }, + "save": { + "message": "저장" + }, + "copyUrl": { + "message": "주소 복사" + }, + "copyTitleAndUrl": { + "message": "제목과 주소 복사" + }, + "delete": { + "message": "삭제" + }, + "clearStream": { + "message": "스트림 지우기" + }, + "saveStream": { + "message": "스트림 저장" + }, + "addToStreamus": { + "message": "Streamus에 등록" + }, + "youTubePlayerErrorSongNotFound": { + "message": "노래가 지워졌거나 비공개 상태입니다." + }, + "youTubePlayerErrorNoPlayEmbedded": { + "message": "노래가 재생될 수 없습니다." + }, + "errorEncountered": { + "message": "에러가 발생했습니다." + }, + "saving": { + "message": "저장중" + }, + "installed": { + "message": "설치됨" + }, + "repeatOff": { + "message": "반복 끄기" + }, + "repeatSong": { + "message": "노래 반복" + }, + "repeatAll": { + "message": "전체 반복" + }, + "radioOn": { + "message": "라디오 켜기" + }, + "radioOff": { + "message": "라디오 끄기" + }, + "shufflingOn": { + "message": "셔플 켜기" + }, + "shufflingOff": { + "message": "셔플 끄기" + }, + "elapsedTime": { + "message": "재생된 시간" + }, + "remainingTime": { + "message": "남은 시간" + }, + "totalTime": { + "message": "전체 시간" + }, + "highest": { + "message": "높음" + }, + "auto": { + "message": "자동" + }, + "lowest": { + "message": "낮음" + }, + "reload": { + "message": "리로드" + }, + "playAll": { + "message": "모두 재생" + }, + "addAll": { + "message": "모두 등록" + }, + "addSelected": { + "message": "선택한 노래 등록" + }, + "playSelected": { + "message": "선택한 노래 재생" + }, + "saveSelected": { + "message": "선택한 노래 저장" + }, + "search": { + "message": "검색" + }, + "searching": { + "message": "검색중" + }, + "noResultsFound": { + "message": "결과 없음" + }, + "clearStream": { + "message": "스트림 제거" + }, + "createPlaylist": { + "message": "재생목록 생성" + }, + "ok": { + "message": "확인" + }, + "playlist": { + "message": "재생목록" + }, + "stream": { + "message": "스트림" + }, + "settings": { + "message": "설정" + }, + "edit": { + "message": "수정" + }, + "cantDeleteLastPlaylist": { + "message": "재생목록 삭제 불가" + }, + "clearStreamQuestion": { + "message": "스트림을 제거하시겠습니까?" + }, + "play": { + "message": "재생" + }, + "add": { + "message": "등록" + }, + "title": { + "message": "제목" + }, + "url": { + "message": "주소" + }, + "playlists": { + "message": "재생목록" + }, + "remind": { + "message": "알림" + }, + "reminders": { + "message": "알리미" + }, + "remindClearStream": { + "message": "스트림 제거 알림" + }, + "remindDeletePlaylist": { + "message": "재생목록 제거 알림" + }, + "minute": { + "message": "분" + }, + "minutes": { + "message": "분" + }, + "hours": { + "message": "시간" + }, + "days": { + "message": "일" + }, + "errorLoadingTitle": { + "message": "제목 불러오기 실패" + }, + "keyboardCommandFailure": { + "message": "키보드 커맨드 실패" + }, + "streamEmpty": { + "message": "스트림 비어있음" + }, + "playlistEmpty": { + "message": "빈 재생목록" + }, + "create": { + "message": "생성" + }, + "update": { + "message": "업데이트" + }, + "wouldYouLikeTo": { + "message": "하시겠습니까" + }, + "general": { + "message": "일반" + }, + "startTyping": { + "message": "입력하세요" + }, + "resultsWillAppearAsYouSearch": { + "message": "검색을 시작하시면 결과가 나옵니다." + }, + "trySearchingForSomethingElse": { + "message": "다른 것도 찾아보세요." + }, + "watchOnYouTube": { + "message": "유투브에서 보기" + }, + "notSignedIn": { + "message": "로그인 안됨" + }, + "signingIn": { + "message": "로그인 중" + }, + "signIn": { + "message": "로그인" + }, + "signInFailed": { + "message": "로그인 실패" + }, + "pleaseWait": { + "message": "기다려주세요" + }, + "openToSearch": { + "message": "검색 열기" + }, + "anUpdateIsAvailable": { + "message": "업데이트가 가능합니다" + }, + "pressEnterToPlay": { + "message": "엔터를 눌러 재생" + }, + "volume": { + "message": "소리" + }, + "songAdded": { + "message": "노래 추가됨" + }, + "addSong": { + "message": "노래 추가" + }, + "song": { + "message": "노래" + }, + "songs": { + "message": "노래" + }, + "cantSkipToNextSong": { + "message": "다음 노래 재생 불가" + }, + "cantGoBackToPreviousSong": { + "message": "이전 노래 재생 불가" + }, + "cantToggleSong": { + "message": "노래 토글 불가" + }, + "searchForSongs": { + "message": "노래 찾기" + }, + "songQuality": { + "message": "노래 품질" + }, + "whyNotAddASongFromAPlaylistOr": { + "message": "재생목록에서 노래를 추가하거나", + "description": "The full sentence here is 'Why not add a song from a playlist or search for songs?' but I need to wrap part of the sentence in HTML" + }, + "searchAndPlay": { + "message": "검색과 재생" + }, + "searchAndAdd": { + "message": "검색과 추가" + }, + "linkAccountsMessage": { + "message": "구글 계정에 스트리머스가 연동되지 않았습니다. 구글 계정을 연동시키면 다른 컴퓨터에서도 재생목록을 이용할 수 있습니다." + }, + "link": { + "message": "계정 연동", + "description": "This is 'Link' as in 'Link my account'" + }, + "googleSignInMessage": { + "message": "크롬에 로그인 되지 않았습니다. 이 컴퓨터에서만 재생목록을 사용할 수 있습니다." + }, + "remindGoogleSignIn": { + "message": "구글 로그인 알림" + }, + "keyboardShortcuts": { + "message": "키보드 단축키" + }, + "export": { + "message": "추출" + }, + "openInTab": { + "message": "탭에서 열기" + }, + "browserSettings": { + "message": "브라우저 설정" + }, + "contextMenus": { + "message": "메뉴" + }, + "websiteEnhancements": { + "message": "웹사이트 향상" + }, + "textSelection": { + "message": "텍스트 선택" + }, + "youTubeLinks": { + "message": "유투브 링크" + }, + "youTubePages": { + "message": "유투브 페이지" + }, + "youTube": { + "message": "유투브" + }, + "beatport": { + "message": "비트포트" + }, + "fileType": { + "message": "파일 타입" + }, + "csv":{ + "message": "CSV" + }, + "json": { + "message": "JSON" + }, + "failedToFindSong": { + "message": "노래를 찾지 못했습니다" + }, + "failedToFindSongs": { + "message": "노래를 찾지 못했습니다" + }, + "loadingYouTube": { + "message": "유투브 로딩중" + }, + "loadingYouTubeFailed": { + "message": "유투브 로딩 실패" + }, + "loadAttempt": { + "message": "$currentLoadAttempt$ / $maxLoadAttempts$ 시도중", + "placeholders": { + "currentLoadAttempt": { + "content": "$1" + }, + "maxLoadAttempts": { + "content": "$2" + } + } + }, + "songAlreadyInCollection": { + "message": "노래가 이미 $collectionName$ 에 있습니다", + "placeholders": { + "collectionName": { + "content": "$1" + } + } + }, + "allSongsAlreadyInCollection": { + "message": "모든 노래가 이미 $collectionName$ 에 있습니다", + "placeholders": { + "collectionName": { + "content": "$1" + } + } + }, + "songsAlreadyInCollection": { + "message": "$duplicateSongCount$ / $draggedSongCount$ 노래가 이미 $collectionName$ 에 있습니다", + "placeholders": { + "duplicateSongCount": { + "content": "$1" + }, + "draggedSongCount": { + "content": "$2" + }, + "collectionName": { + "content": "$3" + } + } + }, + "urlCopied": { + "message": "주소 복사됨" + }, + "copyFailed": { + "message": "복사 실패" + }, + "playlistExported": { + "message": "재생 목록 추출 성공." + }, + "playing": { + "message": "재생중" + }, + "buffering": { + "message": "버퍼링" + }, + "paused": { + "message": "멈춤" + }, + "cancel": { + "message": "취소" + }, + "errorLoadingUrl": { + "message": "주소 로딩 실패" + }, + "playlistLoaded": { + "message": "재생목록 준비됨" + }, + "playlistCreated": { + "message": "재생목록 생성됨" + }, + "extensionConflict": { + "message": "다른 확장프로그램이나 어떤 이유로 인해 유투브가 Flash를 사용합니다.$lineBreak$ 그 확장프로그램을 삭제하고 문제를 해결한 후, 스트리머스를 재시작해야 합니다.", + "placeholders": { + "lineBreak": { + "content": "$1" + } + } + }, + "searchResults": { + "message": "검색 결과" + }, + "collectionCantBeDeleted": { + "message": "$collectionName$ 삭제 불가능", + "placeholders": { + "collectionName": { + "content": "$1" + } + } + }, + "collectionSelected": { + "message": "$count$ $collectionName$ 선택됨", + "description": "This is either 0 songs selected, 1 song selected, or 2 songs selected", + "placeholders": { + "count": { + "content": "$1" + }, + "collectionName": { + "content": "$2" + } + } + }, + "saveAll": { + "message": "모두 저장" + }, + "saveActiveSongCommandDescription": { + "message": "노래 저장" + }, + "songSavedToPlaylist": { + "message": "$songTitle$ 가 $playlistTitle$ 에 저장됨", + "placeholders": { + "songTitle": { + "content": "$1" + }, + "playlistTitle": { + "content": "$2" + } + } + }, + "desktopNotifications": { + "message": "데스크탑 알림" + }, + "showNotifications": { + "message": "알림 보기" + }, + "notificationDuration": { + "message": "알림 지속시간" + }, + "oneSecond": { + "message": "1초" + }, + "twoSeconds": { + "message": "2초" + }, + "threeSeconds": { + "message": "3초" + }, + "fourSeconds": { + "message": "4초" + }, + "fiveSeconds": { + "message": "5초" + }, + "tenSeconds": { + "message": "10초" + }, + "playlistUrl": { + "message": "재생목록 주소" + } +} \ No newline at end of file diff --git a/src/_locales/ro/messages.json b/src/_locales/ro/messages.json new file mode 100644 index 00000000..c2759e1f --- /dev/null +++ b/src/_locales/ro/messages.json @@ -0,0 +1,528 @@ +{ + "extensionName": { + "message": "Streamus" + }, + "extensionDescription": { + "message": "Simplu, muzica in stream." + }, + "toggleRadioCommandDescription": { + "message": "Radio pornit/oprit" + }, + "toggleShuffleCommandDescription": { + "message": "Aleator pornit/oprit" + }, + "toggleRepeatCommandDescription": { + "message": "Nu repeta/toate cantecele/cantecul curent" + }, + "increaseVolumeCommandDescription": { + "message": "Creste volumul" + }, + "decreaseVolumeCommandDescription": { + "message": "Scade volumul" + }, + "showActiveSongCommandDescription": { + "message": "Arata detalii cantec" + }, + "nextSongCommandDescription": { + "message": "Urmatorul cantec" + }, + "previousSongCommandDescription": { + "message": "Cantec anterior" + }, + "toggleSongCommandDescription": { + "message": "Porneste/Opreste cantec" + }, + "deleteSongCommandDescription": { + "message": "Sterge cantec" + }, + "copySongURLCommandDescription": { + "message": "Copiaza URLul cantecului" + }, + "copySongTitleURLCommandDescription": { + "message": "Copiaza titlul cantecului si URLul" + }, + "save": { + "message": "Salveaza" + }, + "copyUrl": { + "message": "Copiaza URL" + }, + "copyTitleAndUrl": { + "message": "Copiaza titlu si URL" + }, + "delete": { + "message": "Sterge" + }, + "clearStream": { + "message": "Goleste stream" + }, + "saveStream": { + "message": "Salveaza stream" + }, + "addToStreamus": { + "message": "Adauga la Streamus" + }, + "youTubePlayerErrorSongNotFound": { + "message": "Cantecul a fost marcat privat sau eliminat." + }, + "youTubePlayerErrorNoPlayEmbedded": { + "message": "Nu pot reda cantecul." + }, + "errorEncountered": { + "message": "A aparut o eroare" + }, + "saving": { + "message": "Salveaza" + }, + "installed": { + "message": "Instalat" + }, + "repeatOff": { + "message": "Nu repeta" + }, + "repeatSong": { + "message": "Repeta cantec" + }, + "repeatAll": { + "message": "Repeta toate" + }, + "radioOn": { + "message": "Radio pornit" + }, + "radioOff": { + "message": "Radio oprit" + }, + "shufflingOn": { + "message": "Redare aleatoare pornita" + }, + "shufflingOff": { + "message": "Redare aleatoare oprita" + }, + "elapsedTime": { + "message": "Timp trecut" + }, + "remainingTime": { + "message": "Timp ramas" + }, + "totalTime": { + "message": "Timp total" + }, + "highest": { + "message": "Cel mai mare" + }, + "auto": { + "message": "Auto" + }, + "lowest": { + "message": "Cel mai mic" + }, + "reload": { + "message": "Reincarca" + }, + "playAll": { + "message": "Porneste toate" + }, + "addAll": { + "message": "Adauga toate" + }, + "addSelected": { + "message": "Adauga cele selectate" + }, + "playSelected": { + "message": "Porneste cele selectate" + }, + "saveSelected": { + "message": "Salveaza cele selectate" + }, + "search": { + "message": "Cauta" + }, + "searching": { + "message": "Caut" + }, + "noResultsFound": { + "message": "Nu am gasit nici un rezultat" + }, + "clearStream": { + "message": "Goleste stream" + }, + "createPlaylist": { + "message": "Creaza lista" + }, + "ok": { + "message": "Ok" + }, + "playlist": { + "message": "Lista" + }, + "stream": { + "message": "Stream" + }, + "settings": { + "message": "Setari" + }, + "edit": { + "message": "Editeaza" + }, + "cantDeleteLastPlaylist": { + "message": "Nu pot sterge ultima lista" + }, + "clearStreamQuestion": { + "message": "Golesc stream?" + }, + "play": { + "message": "Porneste" + }, + "add": { + "message": "Adauga" + }, + "title": { + "message": "Titlu" + }, + "url": { + "message": "URL" + }, + "playlists": { + "message": "Liste" + }, + "remind": { + "message": "Aminteste" + }, + "reminders": { + "message": "Memento" + }, + "remindClearStream": { + "message": "Aminteste goleste stream" + }, + "remindDeletePlaylist": { + "message": "Aminteste sterge lista" + }, + "minute": { + "message": "Minut" + }, + "minutes": { + "message": "Minute" + }, + "hours": { + "message": "Ore" + }, + "days": { + "message": "Zile" + }, + "errorLoadingTitle": { + "message": "Nu am putut incarca titlul" + }, + "keyboardCommandFailure": { + "message": "Comanda de la tastatura nu a functionat" + }, + "streamEmpty": { + "message": "Stream gol" + }, + "playlistEmpty": { + "message": "Lista goala" + }, + "create": { + "message": "Creaza" + }, + "update": { + "message": "Actualizezi" + }, + "wouldYouLikeTo": { + "message": "Ai dori sa" + }, + "general": { + "message": "General" + }, + "startTyping": { + "message": "Incepe sa scrii" + }, + "resultsWillAppearAsYouSearch": { + "message": "Rezultatele vor aparea in timp ce cauti." + }, + "trySearchingForSomethingElse": { + "message": "Incearca o alta cautare." + }, + "watchOnYouTube": { + "message": "Vezi pe YouTube" + }, + "notSignedIn": { + "message": "Nu esti autentificat" + }, + "signingIn": { + "message": "In curs de autentificare" + }, + "signIn": { + "message": "Autentifica" + }, + "signInFailed": { + "message": "Autentificare esuata" + }, + "pleaseWait": { + "message": "Asteapta" + }, + "openToSearch": { + "message": "Deschide cautarea" + }, + "anUpdateIsAvailable": { + "message": "Actualizare disponibila" + }, + "pressEnterToPlay": { + "message": "Enter pentru redare" + }, + "volume": { + "message": "Volum" + }, + "songAdded": { + "message": "Cantec adaugat" + }, + "addSong": { + "message": "Adauga cantec" + }, + "song": { + "message": "Cantec" + }, + "songs": { + "message": "Cantece" + }, + "cantSkipToNextSong": { + "message": "Nu pot sari la cantecul urmator" + }, + "cantGoBackToPreviousSong": { + "message": "Nu pot merge la cantecul anterior" + }, + "cantToggleSong": { + "message": "Nu pot schimba cantecul" + }, + "searchForSongs": { + "message": "Cauta cantece" + }, + "songQuality": { + "message": "Calitatea cantecului" + }, + "whyNotAddASongFromAPlaylistOr": { + "message": "De ce sa nu adaugi un cantec dintr-o lista sau sa cauti unul anume?", + "description": "The full sentence here is 'Why not add a song from a playlist or search for songs?' but I need to wrap part of the sentence in HTML" + }, + "searchAndPlay": { + "message": "Cauta si porneste" + }, + "searchAndAdd": { + "message": "Cauta si adauga" + }, + "linkAccountsMessage": { + "message": "Streamus nu este conectat cu contul Chrome. Daca cele doua conturi vor fi conectate, listele vor aparea si pe alte computere." + }, + "link": { + "message": "Conecteaza", + "description": "This is 'Link' as in 'Link my account'" + }, + "googleSignInMessage": { + "message": "Nu esti autentificat in Chrome. Listele sunt disponibile numai pe acest computer" + }, + "remindGoogleSignIn": { + "message": "Reaminteste autentificare Google" + }, + "keyboardShortcuts": { + "message": "Scurtaturi tastatura" + }, + "export": { + "message": "Export" + }, + "openInTab": { + "message": "Deschide in tab nou" + }, + "browserSettings": { + "message": "Setari browser" + }, + "contextMenus": { + "message": "Meniuri context" + }, + "websiteEnhancements": { + "message": "Imbunatatiri Website" + }, + "textSelection": { + "message": "Textul selectat" + }, + "youTubeLinks": { + "message": "Linkuri YouTube" + }, + "youTubePages": { + "message": "Pagini YouTube" + }, + "youTube": { + "message": "YouTube" + }, + "beatport": { + "message": "Beatport" + }, + "fileType": { + "message": "Tip fisier" + }, + "csv":{ + "message": "CSV" + }, + "json": { + "message": "JSON" + }, + "failedToFindSong": { + "message": "Nu sa gasit cantecul" + }, + "failedToFindSongs": { + "message": "Nu s-a gasit nici un cantec" + }, + "loadingYouTube": { + "message": "Incarca YouTube" + }, + "loadingYouTubeFailed": { + "message": "Nu s-a reusit incarcarea YouTube" + }, + "loadAttempt": { + "message": "$currentLoadAttempt$ incercari din $maxLoadAttempts$", + "placeholders": { + "currentLoadAttempt": { + "content": "$1" + }, + "maxLoadAttempts": { + "content": "$2" + } + } + }, + "songAlreadyInCollection": { + "message": "Cantec deja in $collectionName$", + "placeholders": { + "collectionName": { + "content": "$1" + } + } + }, + "allSongsAlreadyInCollection": { + "message": "Toate cantecele sunt deja in $collectionName$", + "placeholders": { + "collectionName": { + "content": "$1" + } + } + }, + "songsAlreadyInCollection": { + "message": "$duplicateSongCount$ cantece din $draggedSongCount$ sunt deja in lista $collectionName$", + "placeholders": { + "duplicateSongCount": { + "content": "$1" + }, + "draggedSongCount": { + "content": "$2" + }, + "collectionName": { + "content": "$3" + } + } + }, + "urlCopied": { + "message": "URL copiat" + }, + "copyFailed": { + "message": "Copiere esuata" + }, + "playlistExported": { + "message": "Lista exportata cu succes." + }, + "playing": { + "message": "Redare" + }, + "buffering": { + "message": "Pre-incarcare" + }, + "paused": { + "message": "Pauza" + }, + "cancel": { + "message": "Renunta" + }, + "errorLoadingUrl": { + "message": "Eroare la incarcarea URL" + }, + "playlistLoaded": { + "message": "Lista incarcata" + }, + "playlistCreated": { + "message": "Lista creata" + }, + "extensionConflict": { + "message": "O problema sau o alta extensie a fortat Youtube sa foloseasca Flash.$lineBreak$ Streamus nu va functiona corect in aceste conditii. Rezolvati problema sau opriti extensia, apoi re-porniti Streamus", + "placeholders": { + "lineBreak": { + "content": "$1" + } + } + }, + "searchResults": { + "message": "Rezultatele cautarii" + }, + "collectionCantBeDeleted": { + "message": "Nu se poate sterge $collectionName$", + "placeholders": { + "collectionName": { + "content": "$1" + } + } + }, + "collectionSelected": { + "message": "$count$ $collectionName$ alese", + "description": "This is either 0 songs selected, 1 song selected, or 2 songs selected", + "placeholders": { + "count": { + "content": "$1" + }, + "collectionName": { + "content": "$2" + } + } + }, + "saveAll": { + "message": "Salveaza tot" + }, + "saveActiveSongCommandDescription": { + "message": "Salveaza cantec" + }, + "songSavedToPlaylist": { + "message": "$songTitle$ salvat in lista $playlistTitle$", + "placeholders": { + "songTitle": { + "content": "$1" + }, + "playlistTitle": { + "content": "$2" + } + } + }, + "desktopNotifications": { + "message": "Notificari Desktop" + }, + "showNotifications": { + "message": "Arata notificari" + }, + "notificationDuration": { + "message": "Durata notificarii" + }, + "oneSecond": { + "message": "O secunda" + }, + "twoSeconds": { + "message": "Doua secunde" + }, + "threeSeconds": { + "message": "Trei secunde" + }, + "fourSeconds": { + "message": "Patru secunde" + }, + "fiveSeconds": { + "message": "Cingi secunde" + }, + "tenSeconds": { + "message": "Zece secunde" + }, + "playlistUrl": { + "message": "Lista URL" + } +} \ No newline at end of file diff --git a/src/css/mocha.css b/src/css/test/mocha.css similarity index 100% rename from src/css/mocha.css rename to src/css/test/mocha.css diff --git a/src/foreground.html b/src/foreground.html index b277b580..7f61b996 100644 --- a/src/foreground.html +++ b/src/foreground.html @@ -6,7 +6,7 @@ Streamus - + diff --git a/src/js/background/application.js b/src/js/background/application.js index 10a97faf..86cfb12a 100644 --- a/src/js/background/application.js +++ b/src/js/background/application.js @@ -1,9 +1,9 @@ -define(function (require) { +define(function(require) { 'use strict'; var BackgroundArea = require('background/model/backgroundArea'); var BackgroundAreaView = require('background/view/backgroundAreaView'); - + var Application = Marionette.Application.extend({ // Set this flag to true to enable localhost server & debugging flags. localDebug: false, @@ -11,7 +11,7 @@ serverUrl: '', // A unique identifier for this Streamus instance. Useful for telling logs apart without a signed in user. instanceId: '', - + regions: { backgroundAreaRegion: '#backgroundAreaRegion' }, @@ -34,18 +34,18 @@ this._setInstanceId(); this.on('start', this._onStart); }, - - _onStart: function () { + + _onStart: function() { this._showBackground(); }, - _setServerUrl: function () { + _setServerUrl: function() { this.serverUrl = this.localDebug ? 'http://localhost:39853/' : 'https://aws-server.streamus.com/Streamus/'; }, - _setInstanceId: function () { + _setInstanceId: function() { var instanceId = window.localStorage.getItem('instanceId'); - + if (instanceId === null) { instanceId = 'instance_' + _.now(); window.localStorage.setItem('instanceId', instanceId); @@ -54,7 +54,7 @@ this.instanceId = instanceId; }, - _showBackground: function () { + _showBackground: function() { this.backgroundAreaRegion.show(new BackgroundAreaView({ model: new BackgroundArea() })); diff --git a/src/js/background/collection/clientErrors.js b/src/js/background/collection/clientErrors.js index beb576ba..d96474c5 100644 --- a/src/js/background/collection/clientErrors.js +++ b/src/js/background/collection/clientErrors.js @@ -1,4 +1,4 @@ -define(function (require) { +define(function(require) { 'use strict'; var ClientError = require('background/model/clientError'); @@ -9,8 +9,8 @@ return Streamus.serverUrl + 'ClientError/'; }, // Don't allow duplicate ClientErrors by stack + lineNumber. - add: function (addedClientError) { - var isDuplicate = this.any(function (clientError) { + add: function(addedClientError) { + var isDuplicate = this.any(function(clientError) { var lineNumberDuplicate = clientError.get('lineNumber') === addedClientError.get('lineNumber'); var stackDuplicate = clientError.get('stack') === addedClientError.get('stack'); diff --git a/src/js/background/collection/playlistItems.js b/src/js/background/collection/playlistItems.js index 29fd4728..92374019 100644 --- a/src/js/background/collection/playlistItems.js +++ b/src/js/background/collection/playlistItems.js @@ -1,4 +1,4 @@ -define(function (require) { +define(function(require) { 'use strict'; var CollectionMultiSelect = require('background/mixin/collectionMultiSelect'); @@ -12,26 +12,25 @@ playlistId: -1, userFriendlyName: chrome.i18n.getMessage('playlist'), mixins: [CollectionMultiSelect, CollectionSequence, CollectionUniqueSong], - - url: function () { + + url: function() { return Streamus.serverUrl + 'PlaylistItem/'; }, - - initialize: function (models, options) { + + initialize: function(models, options) { if (!_.isUndefined(options)) { this.playlistId = options.playlistId; } }, - // TODO: Rename to saveSongs - addSongs: function (songs, options) { + addSongs: function(songs, options) { options = _.isUndefined(options) ? {} : options; songs = songs instanceof Backbone.Collection ? songs.models : _.isArray(songs) ? songs : [songs]; var itemsToCreate = []; var index = _.isUndefined(options.index) ? this.length : options.index; - _.each(songs, function (song) { + _.each(songs, function(song) { var playlistItem = new PlaylistItem({ playlistId: this.playlistId, song: song, @@ -43,14 +42,14 @@ this.add(playlistItem, { at: index }); - + // If the item was added successfully to the collection (not duplicate) then allow for it to be created. if (!_.isUndefined(playlistItem.collection)) { itemsToCreate.push(playlistItem); index++; } }, this); - + if (itemsToCreate.length === 1) { // Default to Backbone if only creating 1 item. itemsToCreate[0].save({}, { @@ -62,7 +61,7 @@ } }, - getDisplayInfo: function () { + getDisplayInfo: function() { var totalItemsDuration = this._getTotalDuration(); var prettyTimeWithWords = Utility.prettyPrintTimeWithWords(totalItemsDuration); @@ -72,28 +71,28 @@ var displayInfo = songs.length + ' ' + songString + ', ' + prettyTimeWithWords; return displayInfo; }, - - _getTotalDuration: function () { + + _getTotalDuration: function() { var songDurations = _.invoke(this.pluck('song'), 'get', 'duration'); - var totalDuration = _.reduce(songDurations, function (memo, songDuration) { + var totalDuration = _.reduce(songDurations, function(memo, songDuration) { return memo + songDuration; }, 0); return totalDuration; }, - + _bulkCreate: function(itemsToCreate, options) { $.ajax({ url: Streamus.serverUrl + 'PlaylistItem/CreateMultiple', type: 'POST', contentType: 'application/json; charset=utf-8', data: JSON.stringify(itemsToCreate), - success: function (createdItems) { + success: function(createdItems) { // For each of the createdItems, remap properties back to the old items. - _.each(createdItems, function (createdItem) { + _.each(createdItems, function(createdItem) { // Remap items based on their client id. - var matchingNewItem = this.find(function (newItem) { + var matchingNewItem = this.find(function(newItem) { return newItem.cid === createdItem.cid; }); @@ -103,7 +102,7 @@ // Call set to move attributes from parsedCreatedItem to matchingItemToCreate. matchingNewItem.set(parsedNewItem); }, this); - + if (options && options.success) { options.success(); } @@ -111,9 +110,9 @@ error: options ? options.error : null }); }, - - _getBySongId: function (songId) { - return this.find(function (playlistItem) { + + _getBySongId: function(songId) { + return this.find(function(playlistItem) { return playlistItem.get('song').get('id') === songId; }); } diff --git a/src/js/background/collection/playlists.js b/src/js/background/collection/playlists.js index 9270bee7..837cfe5a 100644 --- a/src/js/background/collection/playlists.js +++ b/src/js/background/collection/playlists.js @@ -1,4 +1,4 @@ -define(function (require) { +define(function(require) { 'use strict'; var SyncActionType = require('background/enum/syncActionType'); @@ -10,38 +10,37 @@ var Playlists = Backbone.Collection.extend({ model: Playlist, userId: null, - + mixins: [CollectionSequence], - + url: function() { return Streamus.serverUrl + 'Playlist/'; }, - - initialize: function (models, options) { + + initialize: function(models, options) { this.userId = options.userId; - + chrome.runtime.onMessage.addListener(this._onChromeRuntimeMessage.bind(this)); this.on('add', this._onAdd); this.on('remove', this._onRemove); this.on('change:active', this._onChangeActive); this.on('reset', this._onReset); }, - - getActivePlaylist: function () { + + getActivePlaylist: function() { return this.findWhere({ active: true }); }, - // Expects options: { shortId, urlFriendlyEntityTitle, success, error }; - addPlaylistByShareData: function (options) { + // Expects options: { playlistId, success, error }; + copyPlaylist: function(options) { $.ajax({ type: 'POST', - url: Streamus.serverUrl + 'Playlist/CreateCopyByShareCode', + url: Streamus.serverUrl + 'Playlist/Copy', data: { - shortId: options.shortId, - urlFriendlyEntityTitle: options.urlFriendlyEntityTitle, + playlistId: options.playlistId, userId: this.userId }, - success: function (playlistDto) { + success: function(playlistDto) { // Add and convert back from JSON to Backbone object. var playlist = this.add(playlistDto); options.success(playlist); @@ -49,10 +48,10 @@ error: options.error }); }, - - addPlaylistWithSongs: function (playlistTitle, songs) { + + addPlaylistWithSongs: function(playlistTitle, songs) { songs = songs instanceof Backbone.Collection ? songs.models : _.isArray(songs) ? songs : [songs]; - var playlistItems = _.map(songs, function (song) { + var playlistItems = _.map(songs, function(song) { return { title: song.get('title'), song: song @@ -72,7 +71,7 @@ }); }, - addPlaylistByDataSource: function (playlistTitle, dataSource) { + addPlaylistByDataSource: function(playlistTitle, dataSource) { this.create({ title: playlistTitle, userId: this.userId, @@ -82,30 +81,30 @@ // If a playlist is being created with a YouTube Playlist URL then that URL will need to be imported into the playlist. dataSourceLoaded: !dataSource.isYouTubePlaylist() }, { - success: function (playlist) { + success: function(playlist) { if (!playlist.get('dataSourceLoaded')) { playlist.loadDataSource(); } }, - error: function (model) { + error: function(model) { model.trigger('createError'); } }); }, - _deactivateAllExcept: function (changedPlaylist) { - this.each(function (playlist) { + _deactivateAllExcept: function(changedPlaylist) { + this.each(function(playlist) { if (playlist !== changedPlaylist) { playlist.set('active', false); } }); }, - - _setCanDelete: function (canDelete) { + + _setCanDelete: function(canDelete) { // Playlists can only be deleted if there's >1 playlist existing because I don't want to leave the user with 0 playlists. this.invoke('set', 'canDelete', canDelete); }, - + _onReset: function() { // Ensure there is an always active playlist by trying to load from localstorage if (this.length > 0 && _.isUndefined(this.getActivePlaylist())) { @@ -118,8 +117,8 @@ this._setCanDelete(this.length > 1); }, - - _onChromeRuntimeMessage: function (request, sender, sendResponse) { + + _onChromeRuntimeMessage: function(request, sender, sendResponse) { var sendAsynchronousResponse = false; switch (request.method) { @@ -129,20 +128,20 @@ case 'addYouTubeSongByIdToPlaylist': YouTubeV3API.getSong({ songId: request.songId, - success: function (song) { + success: function(song) { this.get(request.playlistId).get('items').addSongs(song); - + // TODO: It would be nice to run this in addSongs not here to keep things more DRY. // But I kind of feel like I need the playlist title when adding > 1 song (5 songs added to playlist XYZ) which forces it back to the playlist. Streamus.channels.backgroundNotification.commands.trigger('show:notification', { title: chrome.i18n.getMessage('songAdded'), message: song.get('title') }); - + // TODO: This responds success after fetching songs but not after the songs were actually added successfully. sendResponse({ result: 'success' }); }.bind(this), - error: function () { + error: function() { Streamus.channels.backgroundNotification.commands.trigger('show:notification', { title: chrome.i18n.getMessage('errorEncountered') }); @@ -158,25 +157,25 @@ // sendResponse becomes invalid when the event listener returns, unless you return true from the event listener to indicate you wish to send a response asynchronously (this will keep the message channel open to the other end until sendResponse is called). return sendAsynchronousResponse; }, - - _onChangeActive: function (changedPlaylist, active) { + + _onChangeActive: function(changedPlaylist, active) { // Ensure only one playlist is active at a time by de-activating all other active playlists. if (active) { this._deactivateAllExcept(changedPlaylist); window.localStorage.setItem('activePlaylistId', changedPlaylist.get('id')); } }, - - _onAdd: function (addedPlaylist, collection, options) { + + _onAdd: function(addedPlaylist, collection, options) { // Add events fire before the playlist is successfully saved to the server so that the UI can show a saving indicator. // This means that addedPlaylist's ID might not be set yet. If that's the case, wait until successful save before relying on it. if (addedPlaylist.isNew()) { - this.listenToOnce(addedPlaylist, 'createError', function () { + this.listenToOnce(addedPlaylist, 'createError', function() { // TODO: Do something with this error. this.stopListening(addedPlaylist, 'change:id'); }); - this.listenToOnce(addedPlaylist, 'change:id', function () { + this.listenToOnce(addedPlaylist, 'change:id', function() { this.stopListening(addedPlaylist, 'createError'); this._onCreateSuccess(addedPlaylist, options); }); @@ -185,7 +184,7 @@ } }, // TODO: added vs created. - _onCreateSuccess: function (addedPlaylist) { + _onCreateSuccess: function(addedPlaylist) { // Notify all open YouTube tabs that a playlist has been added. Streamus.channels.tab.commands.trigger('notify:youTube', { event: SyncActionType.Added, @@ -195,7 +194,7 @@ title: addedPlaylist.get('title') } }); - + Streamus.channels.backgroundNotification.commands.trigger('show:notification', { title: chrome.i18n.getMessage('playlistCreated'), message: addedPlaylist.get('title') @@ -205,7 +204,7 @@ }, // Whenever a playlist is removed, if it was selected, select the next playlist. - _onRemove: function (removedPlaylist, collection, options) { + _onRemove: function(removedPlaylist, collection, options) { if (removedPlaylist.get('active')) { // Clear local storage of the active playlist if it gets removed. window.localStorage.setItem('activePlaylistId', null); @@ -213,11 +212,11 @@ var index = options.index === this.length ? options.index - 1 : options.index; this._activateByIndex(index); } - + if (this.length === 1) { this._setCanDelete(false); } - + // Notify all open YouTube tabs that a playlist has been added. Streamus.channels.tab.commands.trigger('notify:youTube', { event: SyncActionType.Removed, @@ -227,8 +226,8 @@ } }); }, - - _activateByIndex: function (index) { + + _activateByIndex: function(index) { this.at(index).set('active', true); } }); diff --git a/src/js/background/collection/searchResults.js b/src/js/background/collection/searchResults.js index ececc2d9..9b773ea6 100644 --- a/src/js/background/collection/searchResults.js +++ b/src/js/background/collection/searchResults.js @@ -1,31 +1,31 @@ -define(function (require) { +define(function(require) { 'use strict'; var CollectionMultiSelect = require('background/mixin/collectionMultiSelect'); var SearchResult = require('background/model/searchResult'); - + var SearchResults = Backbone.Collection.extend({ model: SearchResult, mixins: [CollectionMultiSelect], // SearchResults are unable to be destroyed by 'Delete' actions because they don't exist in a mutable collection. isImmutable: true, userFriendlyName: chrome.i18n.getMessage('searchResults'), - + addSongs: function(songs) { var searchResults = this._songsAsSearchResults(songs); this.add(searchResults); }, // Returns the collection of SearchResults' underlying songs. - getSongs: function () { - return this.map(function (model) { + getSongs: function() { + return this.map(function(model) { return model.get('song'); }); }, // Reset the collection's values by mapping a single or list of Song objects into // JSON objects which will be instantiated by Backbone. - resetSongs: function (songs) { + resetSongs: function(songs) { var searchResults = this._songsAsSearchResults(songs); this.reset(searchResults); }, @@ -35,14 +35,14 @@ var searchResults = []; if (songs instanceof Backbone.Collection) { - searchResults = songs.map(function (song) { + searchResults = songs.map(function(song) { return { song: song, title: song.get('title') }; }); } else if (_.isArray(songs)) { - searchResults = _.map(songs, function (song) { + searchResults = _.map(songs, function(song) { return { song: song, title: song.get('title') diff --git a/src/js/background/collection/songs.js b/src/js/background/collection/songs.js index 116e7457..78b636ad 100644 --- a/src/js/background/collection/songs.js +++ b/src/js/background/collection/songs.js @@ -1,4 +1,4 @@ -define(function (require) { +define(function(require) { 'use strict'; var Song = require('background/model/song'); diff --git a/src/js/background/collection/streamItems.js b/src/js/background/collection/streamItems.js index 49baaf6b..b7f50dba 100644 --- a/src/js/background/collection/streamItems.js +++ b/src/js/background/collection/streamItems.js @@ -1,4 +1,4 @@ -define(function (require) { +define(function(require) { 'use strict'; var ChromeCommand = require('background/enum/chromeCommand'); @@ -7,46 +7,46 @@ var CollectionUniqueSong = require('background/mixin/collectionUniqueSong'); var StreamItem = require('background/model/streamItem'); var YouTubeV3API = require('background/model/youTubeV3API'); - + var StreamItems = Backbone.Collection.extend({ model: StreamItem, localStorage: new Backbone.LocalStorage('StreamItems'), userFriendlyName: chrome.i18n.getMessage('stream'), mixins: [CollectionMultiSelect, CollectionSequence, CollectionUniqueSong], - initialize: function () { + initialize: function() { this.on('add', this._onAdd); this.on('remove', this._onRemove); this.on('change:playedRecently', this._onChangePlayedRecently); this.on('change:active', this._onChangeActive); chrome.runtime.onMessage.addListener(this._onChromeRuntimeMessage.bind(this)); chrome.commands.onCommand.addListener(this._onChromeCommandsCommand.bind(this)); - + // Load any existing StreamItems from local storage this.fetch(); }, - - clear: function () { + + clear: function() { // Reset and clear instead of going through this.set() as a performance optimization this.reset(); this.localStorage._clear(); }, - - getActiveItem: function () { + + getActiveItem: function() { return this.findWhere({ active: true }); }, - - notPlayedRecently: function () { + + notPlayedRecently: function() { return this.where({ playedRecently: false }); }, - - getBySong: function (song) { - return this.find(function (streamItem) { + + getBySong: function(song) { + return this.find(function(streamItem) { return streamItem.get('song').get('id') === song.get('id'); }); }, - - showActiveNotification: function () { + + showActiveNotification: function() { var activeItem = this.getActiveItem(); var activeSongId = activeItem.get('song').get('id'); @@ -55,19 +55,19 @@ title: activeItem.get('title') }); }, - - getRandomRelatedSong: function () { + + getRandomRelatedSong: function() { var relatedSongs = this._getRelatedSongs(); var relatedSong = relatedSongs[_.random(relatedSongs.length - 1)] || null; if (relatedSong === null) { - throw new Error("No related song found:" + JSON.stringify(this)); + throw new Error('No related song found:' + JSON.stringify(this)); } return relatedSong; }, - - addSongs: function (songs, options) { + + addSongs: function(songs, options) { options = _.isUndefined(options) ? {} : options; songs = songs instanceof Backbone.Collection ? songs.models : _.isArray(songs) ? songs : [songs]; @@ -79,7 +79,7 @@ var index = _.isUndefined(options.index) ? this.length : options.index; var createdStreamItems = []; - _.each(songs, function (song) { + _.each(songs, function(song) { var streamItem = new StreamItem({ song: song, title: song.get('title'), @@ -105,9 +105,9 @@ } else { // TODO: I need to notify the user that this fallback happened. var songToActivate = this.getBySong(songs[0]); - + if (_.isUndefined(songToActivate)) { - throw new Error("songToActivate undefined:" + songs.length + ' ' + JSON.stringify(songs[0])); + throw new Error('songToActivate undefined:' + songs.length + ' ' + JSON.stringify(songs[0])); } if (songToActivate.get('active')) { @@ -120,44 +120,44 @@ return createdStreamItems; }, - - _onAdd: function (model) { + + _onAdd: function(model) { // Ensure a streamItem is always active if (_.isUndefined(this.getActiveItem())) { model.save({ active: true }); } }, - - _onRemove: function (model) { + + _onRemove: function(model) { // Destroy the model so that Backbone.LocalStorage keeps localStorage up-to-date. model.destroy(); - + // The item removed could've been the last one not played recently. Need to ensure that this isn't the case so there are always valid shuffle targets. this._ensureAllNotPlayedRecentlyExcept(); }, - - _onChangeActive: function (model, active) { + + _onChangeActive: function(model, active) { // Ensure only one streamItem is active at a time by deactivating all other active streamItems. if (active) { this._deactivateAllExcept(model); } }, - - _onChangePlayedRecently: function (model, playedRecently) { + + _onChangePlayedRecently: function(model, playedRecently) { if (playedRecently) { this._ensureAllNotPlayedRecentlyExcept(model); } }, // Beatport can send messages to add stream items and play directly if user has clicked on a button. - _onChromeRuntimeMessage: function (request) { + _onChromeRuntimeMessage: function(request) { switch (request.method) { case 'searchAndStreamByQuery': this._searchAndAddByTitle({ title: request.query, playOnAdd: true, error: function(error) { - console.error("Failed to add song by title: " + request.query, error); + console.error('Failed to add song by title: ' + request.query, error); } }); break; @@ -166,30 +166,30 @@ break; } }, - - _addByTitleList: function (playOnAdd, titleList) { + + _addByTitleList: function(playOnAdd, titleList) { if (titleList.length > 0) { var title = titleList.shift(); this._searchAndAddByTitle({ title: title, playOnAdd: playOnAdd, - error: function (error) { - console.error("Failed to add song by title: " + title, error); + error: function(error) { + console.error('Failed to add song by title: ' + title, error); }, complete: this._addByTitleList.bind(this, false, titleList) }); } }, - - _searchAndAddByTitle: function (options) { + + _searchAndAddByTitle: function(options) { YouTubeV3API.getSongByTitle({ title: options.title, - success: function (song) { + success: function(song) { this.addSongs(song, { playOnAdd: !!options.playOnAdd }); - + if (options.success) { options.success(); } @@ -199,8 +199,8 @@ }); }, - _deactivateAllExcept: function (changedStreamItem) { - this.each(function (streamItem) { + _deactivateAllExcept: function(changedStreamItem) { + this.each(function(streamItem) { // Be sure to check if it is active before saving to avoid hammering localStorage. if (streamItem !== changedStreamItem && streamItem.get('active')) { streamItem.save({ active: false }); @@ -210,17 +210,17 @@ // Take each streamItem's array of related songs, pluck them all out into a collection of arrays // then flatten the arrays into a single array of songs. - _getRelatedSongs: function () { + _getRelatedSongs: function() { // TODO: I don't think this should be OK. // Some might not have information. This is OK. Either YouTube hasn't responded yet or responded with no information. Skip these. // Create a list of all the related songs from all of the information of stream items. - var relatedSongs = _.flatten(this.map(function (streamItem) { + var relatedSongs = _.flatten(this.map(function(streamItem) { return streamItem.get('relatedSongs').models; })); // Don't add any songs that are already in the stream. - relatedSongs = _.filter(relatedSongs, function (relatedSong) { - var alreadyExistingItem = this.find(function (streamItem) { + relatedSongs = _.filter(relatedSongs, function(relatedSong) { + var alreadyExistingItem = this.find(function(streamItem) { var sameSongId = streamItem.get('song').get('id') === relatedSong.get('id'); var sameCleanTitle = streamItem.get('song').get('cleanTitle') === relatedSong.get('cleanTitle'); @@ -231,7 +231,7 @@ }, this); // Try to filter out 'playlist' songs, but if they all get filtered out then back out of this assumption. - var tempFilteredRelatedSongs = _.filter(relatedSongs, function (relatedSong) { + var tempFilteredRelatedSongs = _.filter(relatedSongs, function(relatedSong) { // assuming things >8m are playlists. var isJustOneSong = relatedSong.get('duration') < 480; // TODO: I need better detection than this -- this filters out other things with the word live in it. @@ -251,15 +251,15 @@ // Allows for de-prioritization of played streamItems during shuffling. _ensureAllNotPlayedRecentlyExcept: function(model) { if (this.where({ playedRecently: true }).length === this.length) { - this.each(function (streamItem) { + this.each(function(streamItem) { if (streamItem !== model) { streamItem.save({ playedRecently: false }); } }); } }, - - _onChromeCommandsCommand: function (command) { + + _onChromeCommandsCommand: function(command) { // Only respond to a subset of commands because all commands get broadcast, but not all are for this entity. if (command === ChromeCommand.ShowActiveSong || command === ChromeCommand.DeleteSongFromStream || command === ChromeCommand.CopySongUrl || command === ChromeCommand.CopySongTitleAndUrl || command === ChromeCommand.SaveActiveSong) { if (this.length === 0) { @@ -268,7 +268,7 @@ message: chrome.i18n.getMessage('streamEmpty') }); } else { - switch(command) { + switch (command) { case ChromeCommand.ShowActiveSong: this.showActiveNotification(); break; diff --git a/src/js/background/key/youTubeAPIKey.js.example b/src/js/background/key/youTubeAPIKey.js.example index 8ade1c49..92afdb96 100644 --- a/src/js/background/key/youTubeAPIKey.js.example +++ b/src/js/background/key/youTubeAPIKey.js.example @@ -1,4 +1,4 @@ -define(function () { +define(function() { // Google API's Simple API Access Key. // Production key is omitted from GitHub for security. diff --git a/src/js/background/main.js b/src/js/background/main.js index 1bdd10a6..2a24f622 100644 --- a/src/js/background/main.js +++ b/src/js/background/main.js @@ -1,6 +1,21 @@ -require([ - '../common/requireConfig' -], function () { +//define(['../common/requireConfig'], function(requireConfig) { +// 'use strict'; +// // Mix extra properties into requireConfig as necessary. +// requireConfig.paths.cocktail = 'thirdParty/cocktail'; + +// requireConfig.shim = requireConfig.shim || {}; +// requireConfig.shim['https://www.google-analytics.com/analytics.js'] = { +// exports: 'window.ga' +// }; + +// // Setup the configuration needed to use requireJS +// require.config(requireConfig); + +// // Then, load all of the plugins needed: +// require(['background/plugins']); +//}); + +define(['../common/requireConfig'], function() { 'use strict'; // Then, load all of the plugins needed by the background: diff --git a/src/js/background/mixin/collectionMultiSelect.js b/src/js/background/mixin/collectionMultiSelect.js index 4b97f644..b488d3ad 100644 --- a/src/js/background/mixin/collectionMultiSelect.js +++ b/src/js/background/mixin/collectionMultiSelect.js @@ -1,25 +1,25 @@ define({ - initialize: function () { + initialize: function() { this.on('change:selected', this._onChangeSelected); this.on('change:firstSelected', this._onChangeFirstSelected); - + this.listenTo(Streamus.channels.foreground.vent, 'endUnload', this._onForegroundEndUnload.bind(this)); }, - - selectAll: function () { + + selectAll: function() { this.invoke('set', 'selected', true); }, // Just a nicer naming for deselectAll - deselectAll: function () { + deselectAll: function() { this.deselectAllExcept(null); }, // This takes cid not id because it works for models which aren't persisted to the server. - deselectAllExcept: function (selectedModel) { + deselectAllExcept: function(selectedModel) { var selected = this.selected(); - _.each(selected, function (model) { + _.each(selected, function(model) { if (model !== selectedModel) { model.set('selected', false); } @@ -27,23 +27,23 @@ }, // Return a list of selected models. - selected: function () { + selected: function() { return this.where({ selected: true }); }, // Returns the model which was first selected (or selected last if ctrl was pressed) - firstSelected: function () { + firstSelected: function() { return this.findWhere({ firstSelected: true }); }, // Returns the underlying Songs of the collection. - getSelectedSongs: function () { - return _.map(this.selected(), function (selectedItem) { + getSelectedSongs: function() { + return _.map(this.selected(), function(selectedItem) { return selectedItem.get('song'); }); }, - _onChangeSelected: function (model, selected) { + _onChangeSelected: function(model, selected) { // Whenever only one model is selected -- it becomes the first one to be selected. var selectedModels = this.selected(); @@ -58,9 +58,9 @@ }, // Ensure that only 1 item is ever first selected. - _onChangeFirstSelected: function (changedModel, firstSelected) { + _onChangeFirstSelected: function(changedModel, firstSelected) { if (firstSelected) { - this.each(function (model) { + this.each(function(model) { if (model !== changedModel && model.get('firstSelected')) { model.set('firstSelected', false); } @@ -68,7 +68,7 @@ } }, - _onForegroundEndUnload: function () { + _onForegroundEndUnload: function() { this.deselectAll(); } }); \ No newline at end of file diff --git a/src/js/background/mixin/collectionSequence.js b/src/js/background/mixin/collectionSequence.js index 0398b64c..4394ac3b 100644 --- a/src/js/background/mixin/collectionSequence.js +++ b/src/js/background/mixin/collectionSequence.js @@ -1,6 +1,6 @@ // Entities which need to keep track of their sequence in order to be ordered properly // while also maintaing efficiency with CRUD operations. -define(function (require) { +define(function(require) { 'use strict'; var Direction = require('common/enum/direction'); @@ -8,9 +8,9 @@ define(function (require) { return { comparator: 'sequence', - moveToIndex: function (modelId, index, options) { + moveToIndex: function(modelId, index, options) { options = _.isUndefined(options) ? {} : options; - + var model = this.get(modelId); var currentIndex = this.indexOf(model); var moveResult = { @@ -32,7 +32,7 @@ define(function (require) { return moveResult; }, - getSequenceFromIndexDuringMove: function (index, direction) { + getSequenceFromIndexDuringMove: function(index, direction) { var sequenceIncrement = 10000; var lowSequence; var highSequence; @@ -60,7 +60,7 @@ define(function (require) { // Return what sequence number would be necessary to be at the given index // If skippedModelId is given then it will be skipped (useful when moving an item already in collection) - getSequenceFromIndex: function (index) { + getSequenceFromIndex: function(index) { var sequence; var sequenceIncrement = 10000; @@ -72,7 +72,7 @@ define(function (require) { if (index > 0) { lowSequence = this.at(index - 1).get('sequence'); } - + // highSequence is either the next models' sequence or the maximum sequence + (sequenceIncrement * 2) var highSequence; if (index < this.length) { @@ -87,7 +87,7 @@ define(function (require) { return sequence; }, - _getMaxHighSequence: function (sequenceIncrement) { + _getMaxHighSequence: function(sequenceIncrement) { var maxHighSequence = this.at(this.length - 1).get('sequence') + (sequenceIncrement * 2); return maxHighSequence; } diff --git a/src/js/background/mixin/collectionUniqueSong.js b/src/js/background/mixin/collectionUniqueSong.js index 6089e068..b4405744 100644 --- a/src/js/background/mixin/collectionUniqueSong.js +++ b/src/js/background/mixin/collectionUniqueSong.js @@ -1,10 +1,10 @@ define({ - initialize: function () { + initialize: function() { // Stub out the default implementation of add with one which enforces uniqueness based on song id. this.add = this._add; }, - - getDuplicatesInfo: function (songs) { + + getDuplicatesInfo: function(songs) { songs = songs instanceof Backbone.Collection ? songs.models : _.isArray(songs) ? songs : [songs]; var duplicates = _.filter(songs, this._hasSong.bind(this)); @@ -20,8 +20,7 @@ } else { message = chrome.i18n.getMessage('allSongsAlreadyInCollection', [collectionName]); } - } - else if (someDuplicates) { + } else if (someDuplicates) { message = chrome.i18n.getMessage('songsAlreadyInCollection', [duplicates.length, songs.length, collectionName]); } @@ -33,13 +32,12 @@ }, // Prevent models from being added to the collection if the model's song is not unique to the collection. - _add: function (models, options) { + _add: function(models, options) { var preparedModels; if (models instanceof Backbone.Collection) { preparedModels = models.map(this._prepareModelToAdd.bind(this)); - } - else if (_.isArray(models)) { + } else if (_.isArray(models)) { preparedModels = _.map(models, this._prepareModelToAdd.bind(this)); } else if (!_.isNull(models) && !_.isUndefined(models)) { preparedModels = this._prepareModelToAdd(models); @@ -52,7 +50,7 @@ }, // NOTE: The function _prepareModel is reserved by Backbone. - _prepareModelToAdd: function (model) { + _prepareModelToAdd: function(model) { // If an existing model was not found then just use the given reference. var preparedModel = model; var existingModel = this._getExistingModel(model); @@ -67,18 +65,18 @@ }, // Try to find an existing model in the collection based on the given model's song's id. - _getExistingModel: function (model) { + _getExistingModel: function(model) { var songId = model instanceof Backbone.Model ? model.get('song').get('id') : model.song.id; var existingModel = this._getBySongId(songId); return existingModel; }, - _clone: function (model) { + _clone: function(model) { return model instanceof Backbone.Model ? model.clone() : _.clone(model); }, // Set attributes's id or cid to the model's id or cid to prevent attributes from being added to the collection. - _copyId: function (preparedModel, existingModel) { + _copyId: function(preparedModel, existingModel) { if (existingModel.has('id')) { if (preparedModel instanceof Backbone.Model) { preparedModel.set('id', existingModel.get('id'), { silent: true }); @@ -89,14 +87,14 @@ preparedModel.cid = existingModel.cid; } }, - - _getBySongId: function (songId) { - return this.find(function (model) { + + _getBySongId: function(songId) { + return this.find(function(model) { return model.get('song').get('id') === songId; }); }, - - _hasSong: function (song) { + + _hasSong: function(song) { return !_.isUndefined(this._getBySongId(song.get('id'))); } }); \ No newline at end of file diff --git a/src/js/background/model/analyticsManager.js b/src/js/background/model/analyticsManager.js new file mode 100644 index 00000000..51bb4e33 --- /dev/null +++ b/src/js/background/model/analyticsManager.js @@ -0,0 +1,48 @@ +define(function(require) { + 'use strict'; + + var AnalyticsManager = Backbone.Model.extend({ + defaults: { + module: null + }, + + // The Google Analytics code has been slightly modified to work within the extension environment. + // See this link for more information regarding GA and Chrome Extensions: https://developer.chrome.com/extensions/tut_analytics + // See this link for more information regarduing Universal Analytics: https://developers.google.com/analytics/devguides/collection/analyticsjs/ + initialize: function() { + // Setup temporary Google Analytics objects. + window.GoogleAnalyticsObject = 'ga'; + window.ga = function () { + (window.ga.q = window.ga.q || []).push(arguments); + }; + window.ga.l = 1 * new Date(); + + window.ga('create', 'UA-32334126-1', 'auto'); + // Bug: UA doesn't work out of the box with Chrome extensions. + // https://code.google.com/p/analytics-issues/issues/detail?id=312 + // http://stackoverflow.com/questions/16135000/how-do-you-integrate-universal-analytics-in-to-chrome-extensions + window.ga('set', 'checkProtocolTask', function () { + }); + window.ga('require', 'displayfeatures'); + window.ga('require', 'linkid', 'linkid.js'); + + // Create a function that wraps `window.ga`. + // This allows dependant modules to use `window.ga` without knowingly + // programming against a global object. + this.set('module', function() { + window.ga.apply(this, arguments); + }); + + // Asynchronously load Google Analytics, letting it take over our `window.ga` + // object after it loads. This allows us to add events to `window.ga` even + // before the library has fully loaded. + require(['https://www.google-analytics.com/analytics.js']); + }, + + sendPageView: function(url) { + this.get('module')('send', 'pageview', url); + } + }); + + return AnalyticsManager; +}); \ No newline at end of file diff --git a/src/js/background/model/backgroundArea.js b/src/js/background/model/backgroundArea.js index 5e414655..1fd0edf4 100644 --- a/src/js/background/model/backgroundArea.js +++ b/src/js/background/model/backgroundArea.js @@ -1,6 +1,7 @@ -define(function (require) { +define(function(require) { 'use strict'; + var AnalyticsManager = require('background/model/analyticsManager'); var BrowserSettings = require('background/model/browserSettings'); var ChromeContextMenusManager = require('background/model/chromeContextMenusManager'); var ChromeIconManager = require('background/model/chromeIconManager'); @@ -28,17 +29,19 @@ defaults: { youTubePlayer: null, debugManager: null, + analyticsManager: null, foregroundUnloadTimeout: null }, - initialize: function () { + initialize: function() { this.listenTo(Streamus.channels.foreground.vent, 'started', this._onForegroundStarted.bind(this)); this.listenTo(Streamus.channels.foreground.vent, 'beginUnload', this._onForegroundBeginUnload.bind(this)); this.listenTo(Streamus.channels.foreground.vent, 'endUnload', this._onForegroundEndUnload.bind(this)); + chrome.runtime.onMessageExternal.addListener(this._onChromeRuntimeMessageExternal.bind(this)); var debugManager = new DebugManager(); this.set('debugManager', debugManager); - + var browserSettings = new BrowserSettings(); var settings = new Settings(); @@ -50,7 +53,7 @@ youTubePlayer: youTubePlayer, debugManager: debugManager }); - + var radioButton = new RadioButton(); var shuffleButton = new ShuffleButton(); var repeatButton = new RepeatButton(); @@ -74,23 +77,23 @@ signInManager: signInManager, streamItems: stream.get('items') }); - + var chromeIconManager = new ChromeIconManager({ player: player, streamItems: stream.get('items'), settings: settings, tabManager: tabManager }); - + var chromeNotificationsManager = new ChromeNotificationsManager({ tabManager: tabManager, settings: settings }); - + var chromeOmniboxManager = new ChromeOmniboxManager({ streamItems: stream.get('items') }); - + var clientErrorManager = new ClientErrorManager({ signInManager: signInManager }); @@ -117,8 +120,11 @@ var dataSourceManager = new DataSourceManager(); var playlistsViewModel = new PlaylistsViewModel(); + var analyticsManager = new AnalyticsManager(); + this.set('analyticsManager', analyticsManager); // Exposed globally so that the foreground can access the same instance through chrome.extension.getBackgroundPage() + window.analyticsManager = analyticsManager; window.browserSettings = browserSettings; window.debugManager = debugManager; window.tabManager = tabManager; @@ -136,32 +142,41 @@ window.dataSourceManager = dataSourceManager; window.playlistsViewModel = playlistsViewModel; }, - - _onForegroundStarted: function () { + + _onForegroundStarted: function() { if (this.get('foregroundUnloadTimeout') !== null) { Streamus.channels.error.commands.trigger('log:error', new Error('Foreground was re-opened before timeout exceeded!')); } this._clearForegroundUnloadTimeout(); }, - + _onForegroundBeginUnload: function() { var foregroundUnloadTimeout = setTimeout(this._onForegroundUnloadTimeoutExceeded.bind(this), 500); this.set('foregroundUnloadTimeout', foregroundUnloadTimeout); }, - - _onForegroundUnloadTimeoutExceeded: function () { + + _onForegroundUnloadTimeoutExceeded: function() { Streamus.channels.error.commands.trigger('log:error', new Error('Foreground failed to unload properly!')); this._clearForegroundUnloadTimeout(); }, - - _onForegroundEndUnload: function () { + + _onForegroundEndUnload: function() { this._clearForegroundUnloadTimeout(); }, - + _clearForegroundUnloadTimeout: function() { clearTimeout(this.get('foregroundUnloadTimeout')); this.set('foregroundUnloadTimeout', null); + }, + + // Allow external websites to ping the extension to find out whether the extension is installed or not + _onChromeRuntimeMessageExternal: function(request, sender, sendResponse) { + if (request.message === 'isInstalled') { + sendResponse({ + isInstalled: true + }); + } } }); diff --git a/src/js/background/model/browserSettings.js b/src/js/background/model/browserSettings.js index 2a794bff..65844bf8 100644 --- a/src/js/background/model/browserSettings.js +++ b/src/js/background/model/browserSettings.js @@ -1,4 +1,4 @@ -define(function (require) { +define(function(require) { 'use strict'; var SyncActionType = require('background/enum/syncActionType'); @@ -17,16 +17,16 @@ enhanceBeatport: true }, - initialize: function () { + initialize: function() { // Load from Backbone.LocalStorage this.fetch(); - + chrome.runtime.onMessage.addListener(this._onChromeRuntimeMessage.bind(this)); this.on('change:enhanceYouTube', this._onChangeEnhanceYouTube); this.on('change:enhanceBeatport', this._onChangeEnhanceBeatport); }, - - _onChromeRuntimeMessage: function (request, sender, sendResponse) { + + _onChromeRuntimeMessage: function(request, sender, sendResponse) { switch (request.method) { case 'getBeatportInjectData': sendResponse({ @@ -41,14 +41,14 @@ break; } }, - - _onChangeEnhanceYouTube: function (model, enhanceYouTube) { + + _onChangeEnhanceYouTube: function(model, enhanceYouTube) { Streamus.channels.tab.commands.trigger('notify:youTube', { event: enhanceYouTube ? 'enhance-on' : 'enhance-off' }); }, - - _onChangeEnhanceBeatport: function (model, enhanceBeatport) { + + _onChangeEnhanceBeatport: function(model, enhanceBeatport) { Streamus.channels.tab.commands.trigger('notify:beatport', { event: enhanceBeatport ? 'enhance-on' : 'enhance-off' }); diff --git a/src/js/background/model/buttons/nextButton.js b/src/js/background/model/buttons/nextButton.js index a8a92c25..67f2a82c 100644 --- a/src/js/background/model/buttons/nextButton.js +++ b/src/js/background/model/buttons/nextButton.js @@ -1,9 +1,9 @@ -define(function (require) { +define(function(require) { 'use strict'; var ChromeCommand = require('background/enum/chromeCommand'); var RepeatButtonState = require('common/enum/repeatButtonState'); - + var NextButton = Backbone.Model.extend({ defaults: { enabled: false, @@ -13,7 +13,7 @@ repeatButton: null, }, - initialize: function () { + initialize: function() { // TODO: There's a LOT of things to listen to here. Once Stream can tell me its nextItem I should be able to clean this up easier. this.listenTo(this.get('stream').get('items'), 'add remove reset change:active', this._setEnabled); this.listenTo(this.get('radioButton'), 'change:enabled', this._onRadioButtonChangeEnabled); @@ -25,7 +25,7 @@ }, // Prevent spamming by only allowing a next click once every 100ms. - tryActivateNextStreamItem: _.debounce(function () { + tryActivateNextStreamItem: _.debounce(function() { var activatedNextItem = false; if (this.get('enabled')) { @@ -35,8 +35,8 @@ return activatedNextItem; }, 100, true), - - _onChromeCommandsCommand: function (command) { + + _onChromeCommandsCommand: function(command) { if (command === ChromeCommand.NextSong) { var activatedStreamItem = this.tryActivateNextStreamItem(); @@ -48,20 +48,20 @@ } } }, - - _onRepeatButtonChangeState: function () { + + _onRepeatButtonChangeState: function() { this._setEnabled(); }, - + _onShuffleButtonChangeEnabled: function() { this._setEnabled(); }, - + _onRadioButtonChangeEnabled: function() { this._setEnabled(); }, - - _setEnabled: function () { + + _setEnabled: function() { var enabled = false; var streamItems = this.get('stream').get('items'); diff --git a/src/js/background/model/buttons/playPauseButton.js b/src/js/background/model/buttons/playPauseButton.js index fb10e184..09d24d35 100644 --- a/src/js/background/model/buttons/playPauseButton.js +++ b/src/js/background/model/buttons/playPauseButton.js @@ -1,8 +1,8 @@ -define(function (require) { +define(function(require) { 'use strict'; var ChromeCommand = require('background/enum/chromeCommand'); - + var PlayPauseButton = Backbone.Model.extend({ defaults: { enabled: false, @@ -10,7 +10,7 @@ streamItems: null, }, - initialize: function () { + initialize: function() { var streamItems = this.get('streamItems'); this.listenTo(streamItems, 'add', this._onStreamItemsAdd); this.listenTo(streamItems, 'remove', this._onStreamItemsRemove); @@ -21,15 +21,15 @@ }, // Only allow changing once every 100ms to preent spamming. - tryTogglePlayerState: _.debounce(function () { + tryTogglePlayerState: _.debounce(function() { if (this.get('enabled')) { this.get('player').toggleState(); } return this.get('enabled'); }, 100, true), - - _onChromeCommandsCommand: function (command) { + + _onChromeCommandsCommand: function(command) { if (command === ChromeCommand.ToggleSong) { var didTogglePlayerState = this.tryTogglePlayerState(); @@ -41,23 +41,23 @@ } } }, - + _onStreamItemsAdd: function() { this._toggleEnabled(false); }, - + _onStreamItemsRemove: function(model, collection) { this._toggleEnabled(collection.isEmpty()); }, - + _onStreamItemsReset: function(collection) { this._toggleEnabled(collection.isEmpty()); }, - - _toggleEnabled: function (streamItemsEmpty) { + + _toggleEnabled: function(streamItemsEmpty) { this.set('enabled', !streamItemsEmpty); } }); - + return PlayPauseButton; }); \ No newline at end of file diff --git a/src/js/background/model/buttons/previousButton.js b/src/js/background/model/buttons/previousButton.js index 791512bc..c39a93cf 100644 --- a/src/js/background/model/buttons/previousButton.js +++ b/src/js/background/model/buttons/previousButton.js @@ -1,8 +1,8 @@ -define(function (require) { +define(function(require) { 'use strict'; var ChromeCommand = require('background/enum/chromeCommand'); - + var PreviousButton = Backbone.Model.extend({ defaults: { enabled: false, @@ -11,20 +11,20 @@ repeatButton: null, stream: null }, - - initialize: function () { + + initialize: function() { // TODO: There's a LOT of things to listen to here. Once Stream can tell me its previousItem I should be able to clean this up easier. this.listenTo(this.get('stream').get('items'), 'add remove reset change:active sort', this._toggleEnabled); this.listenTo(this.get('player'), 'change:currentTime', this._onPlayerChangeCurrentTime); this.listenTo(this.get('shuffleButton'), 'change:enabled', this._onShuffleButtonChangeState); this.listenTo(this.get('repeatButton'), 'change:state', this._onRepeatButtonChangeState); chrome.commands.onCommand.addListener(this._onChromeCommandsCommand.bind(this)); - + this._toggleEnabled(); }, // TODO: IDK how I feel about this checking enabled vs outsiders checking enabled. // Prevent spamming by only allowing a previous click once every 100ms. - tryDoTimeBasedPrevious: _.debounce(function () { + tryDoTimeBasedPrevious: _.debounce(function() { var enabled = this.get('enabled'); if (enabled) { @@ -38,20 +38,20 @@ return enabled; }, 100, true), - - _onPlayerChangeCurrentTime: function () { + + _onPlayerChangeCurrentTime: function() { this._toggleEnabled(); }, - - _onShuffleButtonChangeState: function () { + + _onShuffleButtonChangeState: function() { this._toggleEnabled(); }, - - _onRepeatButtonChangeState: function () { + + _onRepeatButtonChangeState: function() { this._toggleEnabled(); }, - - _onChromeCommandsCommand: function (command) { + + _onChromeCommandsCommand: function(command) { if (command === ChromeCommand.PreviousSong) { var didPrevious = this.tryDoTimeBasedPrevious(); @@ -63,8 +63,8 @@ } } }, - - _toggleEnabled: function () { + + _toggleEnabled: function() { var previousItem = this.get('stream').getPrevious(); var enabled = previousItem !== null || this._songHasBeenPlaying(); @@ -78,6 +78,6 @@ return this.get('player').get('currentTime') > 3; } }); - + return PreviousButton; }); \ No newline at end of file diff --git a/src/js/background/model/buttons/radioButton.js b/src/js/background/model/buttons/radioButton.js index 62b76ff2..48790313 100644 --- a/src/js/background/model/buttons/radioButton.js +++ b/src/js/background/model/buttons/radioButton.js @@ -1,37 +1,37 @@ -define(function (require) { +define(function(require) { 'use strict'; var ChromeCommand = require('background/enum/chromeCommand'); - + var RadioButton = Backbone.Model.extend({ localStorage: new Backbone.LocalStorage('RadioButton'), - + defaults: { // Need to set the ID for Backbone.LocalStorage id: 'RadioButton', enabled: false }, - - initialize: function () { + + initialize: function() { // Load from Backbone.LocalStorage this.fetch(); - + chrome.commands.onCommand.addListener(this._onChromeCommandsCommand.bind(this)); }, - - toggleEnabled: function () { + + toggleEnabled: function() { this.save({ enabled: !this.get('enabled') }); }, - - getStateMessage: function () { + + getStateMessage: function() { var isEnabled = this.get('enabled'); var stateMessage = chrome.i18n.getMessage(isEnabled ? 'radioOn' : 'radioOff'); return stateMessage; }, - - _onChromeCommandsCommand: function (command) { + + _onChromeCommandsCommand: function(command) { if (command === ChromeCommand.ToggleRadio) { this.toggleEnabled(); diff --git a/src/js/background/model/buttons/repeatButton.js b/src/js/background/model/buttons/repeatButton.js index b06e425a..626567e6 100644 --- a/src/js/background/model/buttons/repeatButton.js +++ b/src/js/background/model/buttons/repeatButton.js @@ -1,4 +1,4 @@ -define(function (require) { +define(function(require) { 'use strict'; var ChromeCommand = require('background/enum/chromeCommand'); @@ -6,55 +6,39 @@ var RepeatButton = Backbone.Model.extend({ localStorage: new Backbone.LocalStorage('RepeatButton'), - + defaults: { // Need to set the ID for Backbone.LocalStorage id: 'RepeatButton', state: RepeatButtonState.Off }, - - initialize: function () { + + initialize: function() { // Load from Backbone.LocalStorage this.fetch(); - + // TODO: Legacy support, remove in a few patches after v0.169 switch (this.get('state')) { case null: case undefined: case 0: - case "0": + case '0': this.save('state', RepeatButtonState.Off); break; case 1: - case "1": + case '1': this.save('state', RepeatButtonState.RepeatSong); break; case 2: - case "2": + case '2': this.save('state', RepeatButtonState.RepeatAll); break; } - - // TODO: Legacy support, remove in a few patches after v0.169 - switch(this.get('state')) { - case 0: - case "0": - this.set('state', RepeatButtonState.Off); - break; - case 1: - case "1": - this.set('state', RepeatButtonState.RepeatSong); - break; - case 2: - case "2": - this.set('state', RepeatButtonState.RepeatAll); - break; - } - + chrome.commands.onCommand.addListener(this._onChromeCommandsCommand.bind(this)); }, - - toggleRepeatState: function () { + + toggleRepeatState: function() { var nextState = null; switch (this.get('state')) { @@ -68,7 +52,7 @@ nextState = RepeatButtonState.Off; break; default: - console.error("Unhandled repeatButtonState:", this.state); + console.error('Unhandled repeatButtonState:', this.state); break; } @@ -76,8 +60,8 @@ state: nextState }); }, - - getStateMessage: function () { + + getStateMessage: function() { var message = ''; switch (this.get('state')) { case RepeatButtonState.Off: @@ -89,11 +73,11 @@ case RepeatButtonState.RepeatAll: message = chrome.i18n.getMessage('repeatAll'); } - + return message; }, - _onChromeCommandsCommand: function (command) { + _onChromeCommandsCommand: function(command) { if (command === ChromeCommand.ToggleRepeat) { this.toggleRepeatState(); diff --git a/src/js/background/model/buttons/shuffleButton.js b/src/js/background/model/buttons/shuffleButton.js index bb74562c..54e3ecbb 100644 --- a/src/js/background/model/buttons/shuffleButton.js +++ b/src/js/background/model/buttons/shuffleButton.js @@ -1,40 +1,40 @@ -define(function (require) { +define(function(require) { 'use strict'; var ChromeCommand = require('background/enum/chromeCommand'); - + var ShuffleButton = Backbone.Model.extend({ localStorage: new Backbone.LocalStorage('ShuffleButton'), - + defaults: { // Need to set the ID for Backbone.LocalStorage id: 'ShuffleButton', enabled: false }, - - initialize: function () { + + initialize: function() { // Load from Backbone.LocalStorage this.fetch(); - + chrome.commands.onCommand.addListener(this._onChromeCommandsCommand.bind(this)); }, - toggleEnabled: function () { + toggleEnabled: function() { this.save({ enabled: !this.get('enabled') }); }, - - getStateMessage: function () { + + getStateMessage: function() { var isEnabled = this.get('enabled'); var stateMessage = chrome.i18n.getMessage(isEnabled ? 'shufflingOn' : 'shufflingOff'); return stateMessage; }, - _onChromeCommandsCommand: function (command) { + _onChromeCommandsCommand: function(command) { if (command === ChromeCommand.ToggleShuffle) { this.toggleEnabled(); - + Streamus.channels.backgroundNotification.commands.trigger('show:notification', { message: this.getStateMessage() }); diff --git a/src/js/background/model/chromeContextMenusManager.js b/src/js/background/model/chromeContextMenusManager.js index 3d04dc9c..1f3ec03c 100644 --- a/src/js/background/model/chromeContextMenusManager.js +++ b/src/js/background/model/chromeContextMenusManager.js @@ -1,6 +1,6 @@ -define(function (require) { +define(function(require) { 'use strict'; - + var DataSource = require('background/model/dataSource'); var YouTubeV3API = require('background/model/youTubeV3API'); @@ -20,11 +20,11 @@ signInManager: null }, - initialize: function () { + initialize: function() { this._setTextSelection(); this._setYouTubeLinks(); this._setYouTubePages(); - + this.listenTo(this.get('browserSettings'), 'change:showTextSelectionContextMenu', this._onBrowserSettingsChangeShowTextSelectionContextMenu); this.listenTo(this.get('browserSettings'), 'change:showYouTubeLinkContextMenu', this._onBrowserSettingsChangeShowYouTubeLinkContextMenu); this.listenTo(this.get('browserSettings'), 'change:showYouTubePageContextMenu', this._onBrowserSettingsChangeShowYouTubePageContextMenu); @@ -35,20 +35,20 @@ this.listenTo(signedInUser.get('playlists'), 'add', this._onPlaylistsAdd); } }, - + _onBrowserSettingsChangeShowTextSelectionContextMenu: function() { this._setTextSelection(); }, - + _onBrowserSettingsChangeShowYouTubeLinkContextMenu: function() { this._setYouTubeLinks(); }, - + _onBrowserSettingsChangeShowYouTubePageContextMenu: function() { this._setYouTubePages(); }, - - _onPlaylistsAdd: function (model) { + + _onPlaylistsAdd: function(model) { if (this.get('browserSettings').get('showYouTubeLinkContextMenu')) { this._createPlaylistContextMenu(this._getContextMenuOptions(true), this.get('youTubeLinkSaveId'), model); } @@ -57,11 +57,11 @@ this._createPlaylistContextMenu(this._getContextMenuOptions(false), this.get('youTubePageSaveId'), model); } }, - - _onSignInManagerChangeSignedInUser: function (model, signedInUser) { + + _onSignInManagerChangeSignedInUser: function(model, signedInUser) { if (signedInUser === null) { this.stopListening(model.previous('signedInUser').get('playlists')); - + this._removeContextMenu('youTubeLinkSaveId'); this._removeContextMenu('youTubePageSaveId'); } else { @@ -76,31 +76,31 @@ } } }, - - _setTextSelection: function () { + + _setTextSelection: function() { if (this.get('browserSettings').get('showTextSelectionContextMenu')) { this._createTextSelection(); } else { this._removeTextSelection(); } }, - - _setYouTubeLinks: function () { + + _setYouTubeLinks: function() { if (this.get('browserSettings').get('showYouTubeLinkContextMenu')) { this._createYouTubeLinks(); } else { this._removeYouTubeLinks(); } }, - - _setYouTubePages: function () { + + _setYouTubePages: function() { if (this.get('browserSettings').get('showYouTubePageContextMenu')) { this._createYouTubePages(); } else { this._removeYouTubePages(); } }, - + _createYouTubeLinks: function() { var contextMenuOptions = this._getContextMenuOptions(true); @@ -112,7 +112,7 @@ } }, - _createYouTubePages: function () { + _createYouTubePages: function() { var contextMenuOptions = this._getContextMenuOptions(false); this.set('youTubePagePlayId', this._createPlayContextMenu(contextMenuOptions)); @@ -123,13 +123,13 @@ } }, - _createTextSelection: function () { + _createTextSelection: function() { var textSelectionPlayId = chrome.contextMenus.create({ 'contexts': ['selection'], 'title': chrome.i18n.getMessage('searchAndPlay') + ' \"%s\"', 'onclick': this._onClickTextSelectionContextMenu.bind(this, true) }); - + var textSelectionAddId = chrome.contextMenus.create({ 'contexts': ['selection'], 'title': chrome.i18n.getMessage('searchAndAdd') + ' \"%s\"', @@ -139,25 +139,25 @@ this.set('textSelectionPlayId', textSelectionPlayId); this.set('textSelectionAddId', textSelectionAddId); }, - + _removeYouTubeLinks: function() { this._removeContextMenu('youTubeLinkPlayId'); this._removeContextMenu('youTubeLinkAddId'); this._removeContextMenu('youTubeLinkSaveId'); }, - - _removeYouTubePages: function () { + + _removeYouTubePages: function() { this._removeContextMenu('youTubePagePlayId'); this._removeContextMenu('youTubePageAddId'); this._removeContextMenu('youTubePageSaveId'); }, - - _removeTextSelection: function () { + + _removeTextSelection: function() { this._removeContextMenu('textSelectionPlayId'); this._removeContextMenu('textSelectionAddId'); }, - - _removeContextMenu: function (contextMenuIdPropertyName) { + + _removeContextMenu: function(contextMenuIdPropertyName) { var contextMenuId = this.get(contextMenuIdPropertyName); if (contextMenuId !== -1) { @@ -165,7 +165,7 @@ this.set(contextMenuIdPropertyName, -1); } }, - + _createPlayContextMenu: function(contextMenuOptions) { var playId = chrome.contextMenus.create(_.extend({}, contextMenuOptions, { 'title': chrome.i18n.getMessage('play'), @@ -174,7 +174,7 @@ return playId; }, - + _createAddContextMenu: function(contextMenuOptions) { var addId = chrome.contextMenus.create(_.extend({}, contextMenuOptions, { 'title': chrome.i18n.getMessage('add'), @@ -183,7 +183,7 @@ return addId; }, - + _createSaveContextMenu: function(contextMenuOptions) { // Create a sub menu item to hold playlists var saveContextMenuId = chrome.contextMenus.create(_.extend({}, contextMenuOptions, { @@ -191,7 +191,7 @@ })); // Create menu items for each playlist - this.get('signInManager').get('signedInUser').get('playlists').each(function (playlist) { + this.get('signInManager').get('signedInUser').get('playlists').each(function(playlist) { this._createPlaylistContextMenu(contextMenuOptions, saveContextMenuId, playlist); }.bind(this)); @@ -199,7 +199,7 @@ }, // Whenever a playlist context menu is clicked -- add the related song to that playlist. - _createPlaylistContextMenu: function (contextMenuOptions, parentId, playlist) { + _createPlaylistContextMenu: function(contextMenuOptions, parentId, playlist) { var playlistContextMenuId = chrome.contextMenus.create(_.extend({}, contextMenuOptions, { 'title': playlist.get('title'), 'parentId': parentId, @@ -207,18 +207,18 @@ })); // Update context menu items whenever the playlist's data changes (renamed or deleted) - this.listenTo(playlist, 'change:title', function () { + this.listenTo(playlist, 'change:title', function() { chrome.contextMenus.update(playlistContextMenuId, { 'title': playlist.get('title') }); }); - this.listenTo(playlist, 'destroy', function () { + this.listenTo(playlist, 'destroy', function() { chrome.contextMenus.remove(playlistContextMenuId); }); }, - _getContextMenuOptions: function (isLink) { + _getContextMenuOptions: function(isLink) { var urlPatterns = this.get('tabManager').get('youTubeUrlPatterns'); var contextMenuOptions = { @@ -229,48 +229,48 @@ return contextMenuOptions; }, - - _onClickTextSelectionContextMenu: function (playOnAdd, onClickData) { + + _onClickTextSelectionContextMenu: function(playOnAdd, onClickData) { YouTubeV3API.getSongByTitle({ title: onClickData.selectionText, - success: function (song) { + success: function(song) { this.get('streamItems').addSongs(song, { playOnAdd: playOnAdd }); }.bind(this) }); }, - + _onClickPlayContextMenu: function(onClickData) { var url = onClickData.linkUrl || onClickData.pageUrl; var dataSource = new DataSource({ url: url }); - + dataSource.getSong({ - success: function (song) { + success: function(song) { this.get('streamItems').addSongs(song, { playOnAdd: true }); }.bind(this) }); }, - + _onClickAddContextMenu: function(onClickData) { var url = onClickData.linkUrl || onClickData.pageUrl; var dataSource = new DataSource({ url: url }); dataSource.getSong({ - success: function (song) { + success: function(song) { this.get('streamItems').addSongs(song); }.bind(this) }); }, - + _onClickSaveContextMenu: function(playlist, onClickData) { var url = onClickData.linkUrl || onClickData.pageUrl; - + var dataSource = new DataSource({ url: url }); dataSource.getSong({ - success: function (song) { + success: function(song) { playlist.get('items').addSongs(song); } }); diff --git a/src/js/background/model/chromeIconManager.js b/src/js/background/model/chromeIconManager.js index cf3cec11..f08eac44 100644 --- a/src/js/background/model/chromeIconManager.js +++ b/src/js/background/model/chromeIconManager.js @@ -1,4 +1,4 @@ -define(function (require) { +define(function(require) { 'use strict'; var PlayerState = require('common/enum/playerState'); @@ -11,7 +11,7 @@ tabManager: null }, - initialize: function () { + initialize: function() { var player = this.get('player'); this.listenTo(player, 'change:muted', this._onPlayerChangeMuted); this.listenTo(player, 'change:state', this._onPlayerChangeState); @@ -31,44 +31,44 @@ }, // This event handler will only run when browserAction's popup is string.empty. - _onChromeBrowserActionClicked: function () { + _onChromeBrowserActionClicked: function() { this.get('tabManager').showStreamusTab(); }, - + _onPlayerChangeMuted: function(model, muted) { this._setIcon(model.get('state'), muted, model.get('volume')); }, - - _onPlayerChangeState: function (model, state) { + + _onPlayerChangeState: function(model, state) { this._setIcon(state, model.get('muted'), model.get('volume')); this._setTitle(this.get('streamItems').getActiveItem(), state, model.get('volume')); }, - + _onPlayerChangeVolume: function(model, volume) { this._setIcon(model.get('state'), model.get('muted'), volume); this._setTitle(this.get('streamItems').getActiveItem(), model.get('state'), volume); }, - - _onStreamItemsChangeActive: function (model, active) { + + _onStreamItemsChangeActive: function(model, active) { if (active) { var player = this.get('player'); this._setTitle(model, player.get('state'), player.get('volume')); } }, - - _onSettingsChangeOpenInTab: function (model, openInTab) { + + _onSettingsChangeOpenInTab: function(model, openInTab) { this._setPopup(openInTab); }, // Disable the popup when opening in a tab so the foreground doesn't flicker as the tab is opening. - _setPopup: function (openInTab) { + _setPopup: function(openInTab) { chrome.browserAction.setPopup({ popup: openInTab ? '' : 'foreground.html' }); }, // TODO: Show 'next up' as well once I am able to calculate that information. - _setTitle: function (activeStreamItem, playerState, volume) { + _setTitle: function(activeStreamItem, playerState, volume) { var title; if (_.isUndefined(activeStreamItem)) { title = chrome.i18n.getMessage('streamEmpty'); @@ -78,17 +78,16 @@ } chrome.browserAction.setTitle({ - title: title + '\n' + chrome.i18n.getMessage('volume')+ ': ' + volume + '%' + title: title + '\n' + chrome.i18n.getMessage('volume') + ': ' + volume + '%' }); }, - - _getPlayerStateMessage: function (playerState) { + + _getPlayerStateMessage: function(playerState) { var playerStateMessage; if (playerState === PlayerState.Playing) { playerStateMessage = chrome.i18n.getMessage('playing') + ': '; - } - else if (playerState === PlayerState.Buffering) { + } else if (playerState === PlayerState.Buffering) { playerStateMessage = chrome.i18n.getMessage('buffering') + ': '; } else { playerStateMessage = chrome.i18n.getMessage('paused') + ': '; @@ -113,24 +112,23 @@ } }); }, - - _getIconColor: function (playerState, isMuted) { + + _getIconColor: function(playerState, isMuted) { var iconColor = 'yellow'; if (isMuted) { iconColor = 'red'; - } - else if (playerState === PlayerState.Playing || playerState === PlayerState.Buffering) { + } else if (playerState === PlayerState.Playing || playerState === PlayerState.Buffering) { iconColor = 'green'; } return iconColor; }, - + _getIconBarCount: function(volume) { return Math.ceil((volume / 25)); } }); - + return ChromeIconManager; }); \ No newline at end of file diff --git a/src/js/background/model/chromeNotificationsManager.js b/src/js/background/model/chromeNotificationsManager.js index f059c0a2..cf84543e 100644 --- a/src/js/background/model/chromeNotificationsManager.js +++ b/src/js/background/model/chromeNotificationsManager.js @@ -1,8 +1,8 @@ -define(function (require) { +define(function(require) { 'use strict'; var DesktopNotificationDuration = require('common/enum/desktopNotificationDuration'); - + // Use the chrome.notifications API to create rich notifications using templates and show these notifications to users in the system tray. // Availability: Stable since Chrome 28, but getPermissionLevel since Chrome 32 // Permissions: "notifications" @@ -24,39 +24,39 @@ tabManager: null, settings: null }, - - initialize: function () { + + initialize: function() { this.listenTo(Streamus.channels.notification.commands, 'show:notification', this._showNotification); // Background notifications will only show up via desktop notification, normal notification commands will be rendered in the UI if it is open. this.listenTo(Streamus.channels.backgroundNotification.commands, 'show:notification', this._showNotification); }, - - _showNotification: function (notificationOptions) { + + _showNotification: function(notificationOptions) { // Pass along the notification to the foreground if it's open. Otherwise, use desktop notifications to notify the user. this._isForegroundActive(this._onIsForegroundActiveResponse.bind(this, notificationOptions)); }, - - _onIsForegroundActiveResponse: function (notificationOptions, foregroundActive) { + + _onIsForegroundActiveResponse: function(notificationOptions, foregroundActive) { if (!foregroundActive) { var chromeNotificationOptions = _.pick(notificationOptions, ['message', 'title', 'iconUrl']); - - if (this._notificationsEnabled() ) { + + if (this._notificationsEnabled()) { chrome.notifications.getPermissionLevel(this._onGetPermissionLevel.bind(this, chromeNotificationOptions)); } } }, - - _onGetPermissionLevel: function (options, permissionLevel) { + + _onGetPermissionLevel: function(options, permissionLevel) { if (permissionLevel === 'granted') { this._createNotification(options); } }, - - _notificationsEnabled: function () { + + _notificationsEnabled: function() { return !_.isUndefined(chrome.notifications) && this.get('settings').get('desktopNotificationsEnabled'); }, - - _createNotification: function (options) { + + _createNotification: function(options) { this._clearCloseNotificationTimeout(); var notificationOptions = _.extend({}, this.get('defaultNotificationOptions'), options); @@ -65,11 +65,11 @@ this._setCloseNotificationTimeout(); }, - + _onNotificationCreate: function(notificationId) { this.set('shownNotificationId', notificationId); }, - + _onCloseNotificationTimeout: function() { var shownNotificationId = this.get('shownNotificationId'); @@ -77,31 +77,31 @@ this._clearNotification(shownNotificationId); } }, - + _onNotificationClear: function() { this.set('shownNotificationId', ''); }, - + _clearNotification: function(notificationId) { chrome.notifications.clear(notificationId, this._onNotificationClear.bind(this)); }, - + _clearCloseNotificationTimeout: function() { clearTimeout(this.get('closeNotificationTimeout')); this.set('closeNotificationTimeout', null); }, - - _setCloseNotificationTimeout: function () { + + _setCloseNotificationTimeout: function() { var notificationDuration = this._getNotificationDuration(this.get('settings').get('desktopNotificationDuration')); var closeNotificationTimeout = setTimeout(this._onCloseNotificationTimeout.bind(this), notificationDuration); this.set('closeNotificationTimeout', closeNotificationTimeout); }, - - _isForegroundActive: function (callback) { - var foreground = chrome.extension.getViews({ type: "popup" }); + + _isForegroundActive: function(callback) { + var foreground = chrome.extension.getViews({ type: 'popup' }); if (foreground.length === 0) { - this.get('tabManager').isStreamusTabActive(function (streamusTabActive) { + this.get('tabManager').isStreamusTabActive(function(streamusTabActive) { callback(streamusTabActive); }); } else { @@ -110,10 +110,10 @@ }, // Converts the DesktopNotificationDuration enum into milliseconds for use in setTimeout. - _getNotificationDuration: function (desktopNotificationDuration) { + _getNotificationDuration: function(desktopNotificationDuration) { var notificationDuration; - switch(desktopNotificationDuration) { + switch (desktopNotificationDuration) { case DesktopNotificationDuration.OneSecond: notificationDuration = 1000; break; diff --git a/src/js/background/model/chromeOmniboxManager.js b/src/js/background/model/chromeOmniboxManager.js index d832c34a..9a88c87e 100644 --- a/src/js/background/model/chromeOmniboxManager.js +++ b/src/js/background/model/chromeOmniboxManager.js @@ -1,4 +1,4 @@ -define(function (require) { +define(function(require) { 'use strict'; var Songs = require('background/collection/songs'); @@ -8,7 +8,7 @@ // Displays streamus search suggestions and allows instant playing in the stream var ChromeOmniboxManager = Backbone.Model.extend({ - defaults: function () { + defaults: function() { return { suggestedSongs: new Songs(), searchRequest: null, @@ -17,19 +17,19 @@ streamItems: null }; }, - - initialize: function () { + + initialize: function() { chrome.omnibox.setDefaultSuggestion({ // TODO: Inform user that @add will cause add to happen. description: chrome.i18n.getMessage('pressEnterToPlay') }); - + // User has started a keyword input session by typing the extension's keyword. This is guaranteed to be sent exactly once per input session, and before any onInputChanged events. chrome.omnibox.onInputChanged.addListener(this._onChromeOmniboxInputChanged.bind(this)); chrome.omnibox.onInputEntered.addListener(this._onChromeOmniboxInputEntered.bind(this)); }, - - _onChromeOmniboxInputChanged: function (text, suggest) { + + _onChromeOmniboxInputChanged: function(text, suggest) { // Clear suggestedSongs this.get('suggestedSongs').reset(); @@ -62,10 +62,10 @@ this.set('searchRequest', searchRequest); } }, - - _onChromeOmniboxInputEntered: function (text) { + + _onChromeOmniboxInputEntered: function(text) { // Find the cached song data by url - var pickedSong = this.get('suggestedSongs').find(function (song) { + var pickedSong = this.get('suggestedSongs').find(function(song) { return song.get('url') === text; }); @@ -89,14 +89,14 @@ }); } }, - - _getModifiers: function (text) { + + _getModifiers: function(text) { var validModifiers = this.get('validModifiers'); var usedModifiers = []; - _.each(validModifiers, function (modifier) { + _.each(validModifiers, function(modifier) { var indexOfModifier = text.indexOf('@' + modifier); - + if (indexOfModifier !== -1) { usedModifiers.push(modifier); } @@ -104,32 +104,32 @@ return usedModifiers; }, - - _trimModifiers: function (text, modifiers) { - _.each(modifiers, function (modifier) { + + _trimModifiers: function(text, modifiers) { + _.each(modifiers, function(modifier) { var regexp = new RegExp('@' + modifier, 'gi'); text = text.replace(regexp, ''); }); return text.trim(); }, - - _onSearchResponse: function (suggest, searchText, searchResponse) { + + _onSearchResponse: function(suggest, searchText, searchResponse) { this.set('searchRequest', null); var suggestions = this._buildSuggestions(searchResponse.songs, searchText); suggest(suggestions); }, - _buildSuggestions: function (songs, text) { - var suggestions = songs.map(function (song) { + _buildSuggestions: function(songs, text) { + var suggestions = songs.map(function(song) { this.get('suggestedSongs').add(song); var safeTitle = _.escape(song.get('title')); - var textStyleRegExp = new RegExp(Utility.escapeRegExp(text), "i"); + var textStyleRegExp = new RegExp(Utility.escapeRegExp(text), 'i'); var styledTitle = safeTitle.replace(textStyleRegExp, '$&'); - var description = '' + song.get('prettyDuration') + " " + styledTitle; + var description = '' + song.get('prettyDuration') + ' ' + styledTitle; return { content: song.get('url'), diff --git a/src/js/background/model/clientError.js b/src/js/background/model/clientError.js index 331978a7..c0c8ece6 100644 --- a/src/js/background/model/clientError.js +++ b/src/js/background/model/clientError.js @@ -1,8 +1,8 @@ -define(function () { +define(function() { 'use strict'; - + var ClientError = Backbone.Model.extend({ - defaults: function () { + defaults: function() { return { instanceId: Streamus.instanceId, message: '', @@ -20,26 +20,26 @@ // Don't save error because stack is a better representation of error. blacklist: ['error'], - toJSON: function () { + toJSON: function() { return this.omit(this.blacklist); }, - - initialize: function () { + + initialize: function() { this._cleanMessage(); this._dropUrlPrefix(); this._setStack(); }, // The first part of the message just tells me an error was thrown, no need to know that. - _cleanMessage: function () { + _cleanMessage: function() { this.set('message', this.get('message').replace('Uncaught Error: ', '').replace('Uncaught TypeError: ', '')); }, // The first part of the URL is always the same and not very interesting. Drop it off. - _dropUrlPrefix: function () { + _dropUrlPrefix: function() { this.set('url', this.get('url').replace('chrome-extension://' + chrome.runtime.id + '/', '')); }, - + _setStack: function() { var stack = ''; var error = this.get('error'); @@ -55,7 +55,7 @@ this.set('stack', stack.replace('Error: ', '').trim()); }, - + _getBrowserVersion: function() { var browserVersion = ''; @@ -68,6 +68,6 @@ return browserVersion; } }); - + return ClientError; }); \ No newline at end of file diff --git a/src/js/background/model/clientErrorManager.js b/src/js/background/model/clientErrorManager.js index f090bd92..a6a21cbf 100644 --- a/src/js/background/model/clientErrorManager.js +++ b/src/js/background/model/clientErrorManager.js @@ -1,15 +1,14 @@ -define(function (require) { +define(function(require) { 'use strict'; var ClientErrors = require('background/collection/clientErrors'); var ClientErrorManager = Backbone.Model.extend({ - defaults: function () { + defaults: function() { return { platformInfo: { os: '', - arch: '', - nacl_arch: '' + arch: '' }, language: '', signInManager: null, @@ -17,7 +16,7 @@ }; }, - initialize: function () { + initialize: function() { chrome.runtime.getPlatformInfo(this._onChromeRuntimeGetPlatformInfo.bind(this)); window.onerror = this._onWindowError.bind(this); this.listenTo(Streamus.channels.error.commands, 'log:error', this._logError); @@ -29,19 +28,19 @@ console.warn('Debugging enabled; Message:' + message); }, - _onChromeRuntimeGetPlatformInfo: function (platformInfo) { + _onChromeRuntimeGetPlatformInfo: function(platformInfo) { this.set('platformInfo', platformInfo); }, - _onWindowError: function (message, url, lineNumber, columnNumber, error) { + _onWindowError: function(message, url, lineNumber, columnNumber, error) { this._createClientError(message, url, lineNumber, error); }, - + _logError: function(error) { this._createClientError(error.message, '', 0, error); }, - - _createClientError: function (message, url, lineNumber, error) { + + _createClientError: function(message, url, lineNumber, error) { if (Streamus.localDebug && !Streamus.testing) { this._warnDebugEnabled(message); return; diff --git a/src/js/background/model/dataSource.js b/src/js/background/model/dataSource.js index e9ce2885..6983da34 100644 --- a/src/js/background/model/dataSource.js +++ b/src/js/background/model/dataSource.js @@ -1,4 +1,4 @@ -define(function (require) { +define(function(require) { 'use strict'; var YouTubeV3API = require('background/model/youTubeV3API'); @@ -20,10 +20,12 @@ // Take the URL given to the dataSource and parse it for relevant information. // If the URL is for a Playlist -- just get the title and set the ID. If it's a Channel, // need to fetch the Channel's Uploads playlist first. - parseUrl: function (options) { + parseUrl: function(options) { var url = this.get('url'); - if (url === '') throw new Error('URL expected to be set'); - + if (url === '') { + throw new Error('URL expected to be set'); + } + var dataSourceId; // URLs could have both video id + playlist id. Use a flag to determine whether video id is important @@ -83,19 +85,21 @@ options.success(); } }, - - getSong: function (options) { + + getSong: function(options) { this.parseUrl({ - success: function () { + success: function() { YouTubeV3API.getSong({ songId: this.get('id'), success: options.success, - error: function () { + error: function() { Streamus.channels.backgroundNotification.commands.trigger('show:notification', { title: chrome.i18n.getMessage('failedToFindSong') }); - if (options.error) options.error(); + if (options.error) { + options.error(); + } } }); }.bind(this) @@ -103,16 +107,16 @@ }, // These dataSourceTypes require going out to a server and collecting a list of information in order to be created. - isYouTubePlaylist: function () { + isYouTubePlaylist: function() { return this.get('type') === DataSourceType.YouTubePlaylist; }, - - isYouTubeVideo: function () { + + isYouTubeVideo: function() { return this.get('type') === DataSourceType.YouTubeVideo; }, // Expects options: { success: function, error: function } - getTitle: function (options) { + getTitle: function(options) { // If the title has already been fetched from the URL -- return the cached one. if (this.get('title') !== '') { options.success(this.get('title')); @@ -122,7 +126,7 @@ YouTubeV3API.getTitle({ serviceType: YouTubeServiceType.Playlists, id: this.get('id'), - success: function (title) { + success: function(title) { this.set('title', title); options.success(title); }.bind(this), @@ -132,7 +136,7 @@ // TODO: I'd much rather use a series of identifiers to try and parse out a video id instead of a regex. // Takes a URL and returns parsed URL information such as schema and song id if found inside of the URL. - _parseYouTubeSongIdFromUrl: function (url) { + _parseYouTubeSongIdFromUrl: function(url) { var songId = ''; var match = url.match(/^.*(youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=|watch\?.*?\&v=)([^#\&\?]*).*/); @@ -144,10 +148,10 @@ }, // Find a YouTube Channel or Playlist ID by looking through the URL for the given identifier. - _parseIdFromUrlWithIdentifiers: function (url, identifiers) { + _parseIdFromUrlWithIdentifiers: function(url, identifiers) { var id = ''; - - _.each(identifiers, function (identifier) { + + _.each(identifiers, function(identifier) { var urlTokens = url.split(identifier); if (urlTokens.length > 1) { @@ -162,7 +166,7 @@ if (indexOfSlash !== -1) { id = id.substring(0, indexOfSlash); } - + var indexOfPound = id.indexOf('#'); if (indexOfPound !== -1) { id = id.substring(0, indexOfPound); @@ -172,7 +176,7 @@ return id; }, - + _idIsUsername: function() { var indexOfUser = this.get('url').indexOf('/user/'); return indexOfUser != -1; diff --git a/src/js/background/model/dataSourceManager.js b/src/js/background/model/dataSourceManager.js index 23b85fdc..582dcea8 100644 --- a/src/js/background/model/dataSourceManager.js +++ b/src/js/background/model/dataSourceManager.js @@ -1,11 +1,11 @@ -define(function (require) { +define(function(require) { 'use strict'; var DataSource = require('background/model/dataSource'); // This is necessary to provide the foreground with a DataSource object which is created from the background's instance of Backbone. var DataSourceManager = Backbone.Model.extend({ - getDataSource: function (options) { + getDataSource: function(options) { return new DataSource(options); } }); diff --git a/src/js/background/model/debugManager.js b/src/js/background/model/debugManager.js index 1a44da76..5a90d521 100644 --- a/src/js/background/model/debugManager.js +++ b/src/js/background/model/debugManager.js @@ -2,9 +2,8 @@ 'use strict'; var DebugManager = Backbone.Model.extend({ - defaults: function () { + defaults: function() { return { - youTubeIFrameReferers: [], flashLoaded: false }; } diff --git a/src/js/background/model/player.js b/src/js/background/model/player.js index 85139de3..3c84a4e3 100644 --- a/src/js/background/model/player.js +++ b/src/js/background/model/player.js @@ -1,4 +1,4 @@ -define(function (require) { +define(function(require) { 'use strict'; var ChromeCommand = require('background/enum/chromeCommand'); @@ -10,7 +10,7 @@ define(function (require) { var Player = Backbone.Model.extend({ localStorage: new Backbone.LocalStorage('Player'), - defaults: function () { + defaults: function() { return { // Need to set the ID for Backbone.LocalStorage id: 'Player', @@ -46,12 +46,12 @@ define(function (require) { // Don't want to save everything to localStorage -- only variables which need to be persisted. whitelist: ['muted', 'volume'], - toJSON: function () { + toJSON: function() { return this.pick(this.whitelist); }, // Initialize the player by creating a YouTube Player IFrame hosting an HTML5 player - initialize: function () { + initialize: function() { this.on('change:volume', this._onChangeVolume); this.on('change:muted', this._onChangeMuted); this.on('change:ready', this._onChangeReady); @@ -72,8 +72,8 @@ define(function (require) { this._ensureInitialState(); }, - - activateSong: function (song, timeInSeconds) { + + activateSong: function(song, timeInSeconds) { if (this.get('ready')) { var playerState = this.get('state'); var playOnActivate = this.get('playOnActivate'); @@ -105,25 +105,24 @@ define(function (require) { this.set('songToActivate', song); } }, - - toggleState: function () { + + toggleState: function() { var playing = this.get('state') === PlayerState.Playing; - + if (playing) { this.pause(); } else { this.play(); } }, - - setVolume: function (volume) { + + setVolume: function(volume) { var maxVolume = this.get('maxVolume'); var minVolume = this.get('minVolume'); - + if (volume > maxVolume) { volume = maxVolume; - } - else if (volume < minVolume) { + } else if (volume < minVolume) { volume = minVolume; } @@ -133,7 +132,7 @@ define(function (require) { }); }, - stop: function () { + stop: function() { this.get('youTubePlayer').stop(); this.set({ @@ -143,11 +142,11 @@ define(function (require) { }); }, - pause: function () { + pause: function() { this.get('youTubePlayer').pause(); }, - - play: function () { + + play: function() { if (this.get('youTubePlayer').get('ready')) { this.get('youTubePlayer').play(); } else { @@ -156,7 +155,7 @@ define(function (require) { } }, - seekTo: function (timeInSeconds) { + seekTo: function(timeInSeconds) { if (this.get('ready')) { var state = this.get('state'); @@ -171,8 +170,8 @@ define(function (require) { this.set('currentTime', timeInSeconds); } }, - - watchInTab: function (song) { + + watchInTab: function(song) { var url = song.get('url'); if (this.get('loadedSong') === song) { @@ -185,8 +184,8 @@ define(function (require) { this.pause(); }, - - refresh: function () { + + refresh: function() { this._clearRefreshAlarm(); var loadedSong = this.get('loadedSong'); @@ -196,7 +195,7 @@ define(function (require) { }, // Ensure that the initial state of the player properly reflects the state of its APIs - _ensureInitialState: function () { + _ensureInitialState: function() { this.set('ready', this.get('youTubePlayer').get('ready')); this.set('loading', this.get('youTubePlayer').get('loading')); // TODO: How will I handle currentLoadAttempt w/ 2+ APIs? If both are loading they could be on separate attempts...? @@ -204,28 +203,28 @@ define(function (require) { }, // Attempt to set playback quality to songQuality or highest possible. - _onChangeSongQuality: function (model, songQuality) { + _onChangeSongQuality: function(model, songQuality) { var youTubeQuality = this._getYouTubeQuality(songQuality); this.get('youTubePlayer').setPlaybackQuality(youTubeQuality); }, // Update the volume whenever the UI modifies the volume property. - _onChangeVolume: function (model, volume) { + _onChangeVolume: function(model, volume) { if (this.get('ready')) { this.get('youTubePlayer').setVolume(volume); } else { this.get('youTubePlayer').preload(); } }, - - _onChangeMuted: function (model, muted) { + + _onChangeMuted: function(model, muted) { if (this.get('ready')) { this.get('youTubePlayer').setMuted(muted); } else { this.get('youTubePlayer').preload(); } }, - + _onChangeState: function(model, state) { if (state === PlayerState.Playing || state === PlayerState.Buffering) { this._clearRefreshAlarm(); @@ -233,15 +232,15 @@ define(function (require) { this._createRefreshAlarm(); } }, - - _onChangeReady: function (model, ready) { + + _onChangeReady: function(model, ready) { if (ready) { // Load from Backbone.LocalStorage this.fetch(); // These values need to be set explicitly because the 'change' event handler won't fire if localStorage value is the same as default. this.get('youTubePlayer').setVolume(this.get('volume')); this.get('youTubePlayer').setMuted(this.get('muted')); - + // If an 'activateSong' command came in while the player was not ready, fulfill it now. var songToActivate = this.get('songToActivate'); if (songToActivate !== null) { @@ -254,7 +253,7 @@ define(function (require) { this._clearRefreshAlarm(); } }, - + _onChangeLoading: function(model, loading) { // Ensure player doesn't start playing a song when recovering from a bad state after a long period of time. // It is OK to start playback again when recovering initially, but not OK if recovering hours later. @@ -263,14 +262,14 @@ define(function (require) { this.set('state', state); } }, - - _onChromeRuntimeConnect: function (port) { + + _onChromeRuntimeConnect: function(port) { if (port.name === 'youTubeIFrameConnectRequest') { port.onMessage.addListener(this._onYouTubeIFrameMessage.bind(this)); } }, - - _onYouTubeIFrameMessage: function (message) { + + _onYouTubeIFrameMessage: function(message) { // It's better to be told when time updates rather than poll YouTube's API for the currentTime. if (!_.isUndefined(message.currentTime)) { this.set('currentTime', message.currentTime); @@ -289,59 +288,58 @@ define(function (require) { } } } - + if (!_.isUndefined(message.error)) { var error = new Error(message.error); Streamus.channels.error.commands.trigger('log:error', error); } - + if (!_.isUndefined(message.flashLoaded)) { this.get('debugManager').set('flashLoaded', message.flashLoaded); } }, - _onChromeCommandsCommand: function (command) { + _onChromeCommandsCommand: function(command) { if (command === ChromeCommand.IncreaseVolume) { var increasedVolume = this.get('volume') + 5; this.setVolume(increasedVolume); - } - else if (command === ChromeCommand.DecreaseVolume) { + } else if (command === ChromeCommand.DecreaseVolume) { var decreasedVolume = this.get('volume') - 5; this.setVolume(decreasedVolume); } }, - - _onChromeAlarmsAlarm: function (alarm) { + + _onChromeAlarmsAlarm: function(alarm) { // Check the alarm name because closing the browser will not clear an alarm, but new alarm name is generated on open. if (alarm.name === this.get('refreshAlarmName')) { this.refresh(); } }, - - _onYouTubePlayerChangeReady: function (model, ready) { + + _onYouTubePlayerChangeReady: function(model, ready) { this.set('ready', ready); }, - - _onYouTubePlayerChangeState: function (model, youTubePlayerState) { + + _onYouTubePlayerChangeState: function(model, youTubePlayerState) { var playerState = this._getPlayerState(youTubePlayerState); this.set('state', playerState); }, - - _onYouTubePlayerChangeLoading: function (model, loading) { + + _onYouTubePlayerChangeLoading: function(model, loading) { this.set('loading', loading); }, - - _onYouTubePlayerChangeCurrentLoadAttempt: function (model, currentLoadAttempt) { + + _onYouTubePlayerChangeCurrentLoadAttempt: function(model, currentLoadAttempt) { this.set('currentLoadAttempt', currentLoadAttempt); }, // TODO: In the future this should probably be generic and just emit an error which isn't tied to YouTube. // Emit errors so the foreground so can notify the user. - _onYouTubePlayerError: function (model, error) { + _onYouTubePlayerError: function(model, error) { this.trigger('youTubeError', this, error); }, - - _createRefreshAlarm: function () { + + _createRefreshAlarm: function() { if (!this.get('refreshAlarmCreated')) { this.set('refreshAlarmCreated', true); chrome.alarms.create(this.get('refreshAlarmName'), { @@ -354,19 +352,19 @@ define(function (require) { // TODO: Reconsider pause logic. It's possible for someone to juggle a single song between playing/not playing for long enough that // it would still expire. It would be better to keep the timer always going as long as the song is loaded and if it pauses with the timer exceeded // or is paused when the timer exceeds, reload. - _clearRefreshAlarm: function () { + _clearRefreshAlarm: function() { if (this.get('refreshAlarmCreated')) { this.set('refreshAlarmCreated', false); chrome.alarms.clear(this.get('refreshAlarmName')); } }, - - _playOnActivate: function (playOnActivate) { + + _playOnActivate: function(playOnActivate) { this.set('playOnActivate', playOnActivate); }, // Maps a SongQuality enumeration value to the corresponding YouTubeQuality enumeration value. - _getYouTubeQuality: function (songQuality) { + _getYouTubeQuality: function(songQuality) { var youTubeQuality = YouTubeQuality.Default; switch (songQuality) { @@ -412,7 +410,7 @@ define(function (require) { playerState = PlayerState.SongCued; break; default: - throw new Error("Unmapped YouTubePlayerState:" + youTubePlayerState); + throw new Error('Unmapped YouTubePlayerState:' + youTubePlayerState); } return playerState; diff --git a/src/js/background/model/playlist.js b/src/js/background/model/playlist.js index e0344b34..dd2cd223 100644 --- a/src/js/background/model/playlist.js +++ b/src/js/background/model/playlist.js @@ -1,4 +1,4 @@ -define(function (require) { +define(function(require) { 'use strict'; var PlaylistItems = require('background/collection/playlistItems'); @@ -6,7 +6,7 @@ define(function (require) { var ShareCode = require('background/model/shareCode'); var YouTubeV3API = require('background/model/youTubeV3API'); var ListItemType = require('common/enum/listItemType'); - + // Playlist holds a collection of PlaylistItems as well as properties pertaining to a playlist. // Provides methods to work with PlaylistItems such as getting, removing, updating, etc.. var Playlist = Backbone.Model.extend({ @@ -27,7 +27,7 @@ define(function (require) { // Convert data which is sent from the server back to a proper Backbone.Model. // Need to recreate submodels as Backbone.Models else they will just be regular Objects. - parse: function (playlistDto) { + parse: function(playlistDto) { // Patch requests do not return information. if (!_.isUndefined(playlistDto)) { // Convert C# Guid.Empty into BackboneJS null @@ -47,22 +47,22 @@ define(function (require) { return playlistDto; }, - - initialize: function () { + + initialize: function() { this._ensureItemsCollection(); this._setActivePlaylistListeners(this.get('active')); - + this.on('change:title', this._onChangeTitle); this.on('change:active', this._onChangeActive); }, - + getShareCode: function(options) { $.ajax({ url: Streamus.serverUrl + 'ShareCode/GetShareCode', data: { playlistId: this.get('id') }, - success: function (shareCodeJson) { + success: function(shareCodeJson) { var shareCode = new ShareCode(shareCodeJson); options.success(shareCode); }, @@ -71,29 +71,28 @@ define(function (require) { }, // Recursively load any potential bulk data from YouTube after the Playlist has saved successfully. - loadDataSource: function () { + loadDataSource: function() { YouTubeV3API.getPlaylistSongs({ playlistId: this.get('dataSource').get('id'), success: this._onGetPlaylistSongsSuccess.bind(this) }); }, - isLoading: function () { + isLoading: function() { return this.has('dataSource') && !this.get('dataSourceLoaded'); }, - - _onGetPlaylistSongsSuccess: function (response) { + + _onGetPlaylistSongsSuccess: function(response) { // Periodicially send bursts of packets to the server and trigger visual update. this.get('items').addSongs(response.songs, { success: this._onAddSongsByDataSourceSuccess.bind(this, response.nextPageToken) }); }, - _onAddSongsByDataSourceSuccess: function (nextPageToken) { + _onAddSongsByDataSourceSuccess: function(nextPageToken) { if (_.isUndefined(nextPageToken)) { this.set('dataSourceLoaded', true); - } - else { + } else { // Request next batch of data by iteration once addItems has succeeded. YouTubeV3API.getPlaylistSongs({ playlistId: this.get('dataSource').get('id'), @@ -102,7 +101,7 @@ define(function (require) { }); } }, - + _onChangeTitle: function(model, title) { this._emitYouTubeTabUpdateEvent({ id: model.get('id'), @@ -111,8 +110,8 @@ define(function (require) { this.save({ title: title }, { patch: true }); }, - - _onChangeActive: function (model, active) { + + _onChangeActive: function(model, active) { this._setActivePlaylistListeners(active); if (!active) { @@ -121,14 +120,14 @@ define(function (require) { }, // Notify all open YouTube tabs that a playlist has been renamed. - _emitYouTubeTabUpdateEvent: function (data) { + _emitYouTubeTabUpdateEvent: function(data) { Streamus.channels.tab.commands.trigger('notify:youTube', { event: SyncActionType.Updated, type: ListItemType.Playlist, data: data }); }, - + _setActivePlaylistListeners: function(active) { if (active) { this.listenTo(Streamus.channels.activePlaylist.commands, 'save:song', this._saveSong); @@ -136,10 +135,10 @@ define(function (require) { this.stopListening(Streamus.channels.activePlaylist.commands); } }, - - _saveSong: function (song) { + + _saveSong: function(song) { var duplicatesInfo = this.get('items').getDuplicatesInfo(song); - + if (duplicatesInfo.allDuplicates) { Streamus.channels.backgroundNotification.commands.trigger('show:notification', { title: duplicatesInfo.message @@ -151,19 +150,19 @@ define(function (require) { }); } }, - - _onSaveSongsSuccess: function (savedSong) { + + _onSaveSongsSuccess: function(savedSong) { Streamus.channels.backgroundNotification.commands.trigger('show:notification', { title: chrome.i18n.getMessage('songSavedToPlaylist', [savedSong.get('title'), this.get('title')]) }); }, - + _onSaveSongsError: function() { Streamus.channels.backgroundNotification.commands.trigger('show:notification', { title: chrome.i18n.getMessage('errorEncountered') }); }, - + _ensureItemsCollection: function() { var items = this.get('items'); diff --git a/src/js/background/model/playlistItem.js b/src/js/background/model/playlistItem.js index 3cae056b..35c3c19f 100644 --- a/src/js/background/model/playlistItem.js +++ b/src/js/background/model/playlistItem.js @@ -1,9 +1,9 @@ -define(function (require) { +define(function(require) { 'use strict'; var Song = require('background/model/song'); var ListItemType = require('common/enum/listItemType'); - + var PlaylistItem = Backbone.Model.extend({ defaults: { id: null, @@ -15,8 +15,8 @@ song: null, listItemType: ListItemType.PlaylistItem }, - - parse: function (playlistItemDto) { + + parse: function(playlistItemDto) { // Patch requests do not return information. if (!_.isUndefined(playlistItemDto)) { // Convert C# Guid.Empty into BackboneJS null @@ -33,18 +33,18 @@ return playlistItemDto; }, - - toJSON: function () { + + toJSON: function() { // Backbone Model's toJSON doesn't automatically send cid across, but I want it for re-mapping collections after server saves. var json = Backbone.Model.prototype.toJSON.apply(this, arguments); json.cid = this.cid; return json; }, - initialize: function () { + initialize: function() { this._ensureSongModel(); }, - + _ensureSongModel: function() { var song = this.get('song'); diff --git a/src/js/background/model/relatedSongsManager.js b/src/js/background/model/relatedSongsManager.js index 7a468be0..1a683c22 100644 --- a/src/js/background/model/relatedSongsManager.js +++ b/src/js/background/model/relatedSongsManager.js @@ -1,4 +1,4 @@ -define(function (require) { +define(function(require) { 'use strict'; var YouTubeV3API = require('background/model/youTubeV3API'); @@ -29,7 +29,7 @@ this.get('requestOptionsQueue').push(options); } }, - + _onGetRelatedSongsSuccess: function(callback, relatedSongs) { this._decrementRequestCount(); callback(relatedSongs); @@ -44,11 +44,11 @@ this.getRelatedSongs(requestOptions); } }, - - _incrementRequestCount: function () { + + _incrementRequestCount: function() { this.set('currentRequestCount', this.get('currentRequestCount') + 1); }, - + _decrementRequestCount: function() { this.set('currentRequestCount', this.get('currentRequestCount') - 1); }, diff --git a/src/js/background/model/search.js b/src/js/background/model/search.js index 1b6d4d78..17fded8c 100644 --- a/src/js/background/model/search.js +++ b/src/js/background/model/search.js @@ -1,4 +1,4 @@ -define(function (require) { +define(function(require) { 'use strict'; var SearchResults = require('background/collection/searchResults'); @@ -6,7 +6,7 @@ var YouTubeV3API = require('background/model/youTubeV3API'); var Search = Backbone.Model.extend({ - defaults: function () { + defaults: function() { return { results: new SearchResults(), maxSearchResults: 200, @@ -17,8 +17,8 @@ clearQueryTimeout: null }; }, - - initialize: function () { + + initialize: function() { this.on('change:query', this._onChangeQuery); this.on('change:searchQueued', this._onSearchQueued); this.on('change:pendingRequest', this._onChangePendingRequest); @@ -26,7 +26,7 @@ }, // The foreground has to be able to call this whenever a view opens. - stopClearQueryTimer: function () { + stopClearQueryTimer: function() { window.clearTimeout(this.get('clearQueryTimeout')); this.set('clearQueryTimeout', null); }, @@ -35,38 +35,38 @@ hasQuery: function() { return this.get('query') !== ''; }, - - _onChangeQuery: function () { + + _onChangeQuery: function() { this._search(); }, - + _onChangePendingRequest: function(model, pendingRequest) { var isSearching = this._isSearching(this.get('searchQueued'), pendingRequest); this.set('searching', isSearching); }, - - _onSearchQueued: function (model, searchQueued) { + + _onSearchQueued: function(model, searchQueued) { var isSearching = this._isSearching(searchQueued, this.get('pendingRequest')); this.set('searching', isSearching); }, - - _startClearQueryTimer: function () { + + _startClearQueryTimer: function() { // Safe-guard against multiple setTimeouts, just incase. this.stopClearQueryTimer(); this.set('clearQueryTimeout', setTimeout(this._clearQuery.bind(this), 10000)); }, // Only search on queries which actually contain text. Different from hasQuery because want to show no search results when they type 'space' - _hasSearchableQuery: function () { + _hasSearchableQuery: function() { return this._getTrimmedQuery() !== ''; }, - - _getTrimmedQuery: function () { + + _getTrimmedQuery: function() { return this.get('query').trim(); }, // Perform a search on the given query or just terminate immediately if nothing to do. - _search: function () { + _search: function() { this._clearResults(); this._abortPendingRequest(); @@ -80,7 +80,7 @@ }, // Set some flags indicating that a search is in progress. - _startSearching: function () { + _startSearching: function() { this.set('searchQueued', true); // Debounce a search request so that when the user stops typing the last request will run. this._doDebounceSearch(this._getTrimmedQuery()); @@ -88,10 +88,10 @@ // Handle the actual search functionality inside of a debounced function. // This is so I can tell when the user starts typing, but not actually run the search logic until they pause. - _doDebounceSearch: _.debounce(function (trimmedQuery) { + _doDebounceSearch: _.debounce(function(trimmedQuery) { // TODO: What happens between now and when parseUrl is running? It will look like no search is being performed? this.set('searchQueued', false); - + // If the user typed 'a' and then hit backspace, debounce search will still be trying to run with 'a' // because no future search query arrived. Prevent this. if (this._getTrimmedQuery() === trimmedQuery) { @@ -101,7 +101,7 @@ }); dataSource.parseUrl({ - success: function () { + success: function() { this._abortPendingRequest(); // If the search query had a valid YouTube Video ID inside of it -- display that result, otherwise search. @@ -116,8 +116,8 @@ }); } }, 350), - - _setResultsBySong: function (songId) { + + _setResultsBySong: function(songId) { var pendingRequest = YouTubeV3API.getSong({ songId: songId, success: this._trySetResults.bind(this), @@ -126,19 +126,19 @@ this.set('pendingRequest', pendingRequest); }, - - _setResultsByPlaylist: function (playlistId) { + + _setResultsByPlaylist: function(playlistId) { // TODO: This is not DRY with how a Playlist loads its songs internally, how can I share the logic? var pendingRequest = YouTubeV3API.getPlaylistSongs({ playlistId: playlistId, success: this._onGetPlaylistSongsSuccess.bind(this, playlistId), error: this._onSearchError.bind(this) }); - + this.set('pendingRequest', pendingRequest); }, - - _setResultsByText: function (trimmedQuery) { + + _setResultsByText: function(trimmedQuery) { var pendingRequest = YouTubeV3API.search({ text: trimmedQuery, success: this._onSearchSuccess.bind(this, trimmedQuery), @@ -164,8 +164,8 @@ this.set('pendingRequest', null); } }, - - _onSearchSuccess: function (trimmedQuery, response) { + + _onSearchSuccess: function(trimmedQuery, response) { this.get('results').addSongs(response.songs); var continueSearching = !_.isUndefined(response.nextPageToken) && this.get('results').length < this.get('maxSearchResults'); @@ -185,25 +185,25 @@ }, // TODO: Should I be notifying the user an error happened here? - _onSearchError: function () { + _onSearchError: function() { this.set('pendingRequest', null); }, - - _trySetResults: function (songs) { + + _trySetResults: function(songs) { this.get('results').resetSongs(songs); this.set('pendingRequest', null); }, - - _clearResults: function () { + + _clearResults: function() { // Might as well not trigger excess reset events if they can be avoided. var results = this.get('results'); - + if (results.length > 0) { results.reset(); } }, - - _abortPendingRequest: function () { + + _abortPendingRequest: function() { var pendingRequest = this.get('pendingRequest'); if (pendingRequest !== null) { @@ -211,15 +211,15 @@ this.set('pendingRequest', null); } }, - - _clearQuery: function () { + + _clearQuery: function() { this.set('query', ''); }, - - _isSearching: function (searchQueued, pendingRequest) { + + _isSearching: function(searchQueued, pendingRequest) { return searchQueued || pendingRequest !== null; }, - + _onForegroundEndUnload: function() { // Remember search query for a bit just in case user closes and re-opens immediately. this._startClearQueryTimer(); diff --git a/src/js/background/model/searchResult.js b/src/js/background/model/searchResult.js index 92564f60..29eef7c2 100644 --- a/src/js/background/model/searchResult.js +++ b/src/js/background/model/searchResult.js @@ -1,4 +1,4 @@ -define(function (require) { +define(function(require) { 'use strict'; var ListItemType = require('common/enum/listItemType'); diff --git a/src/js/background/model/settings.js b/src/js/background/model/settings.js index 5c5a5bca..adf51902 100644 --- a/src/js/background/model/settings.js +++ b/src/js/background/model/settings.js @@ -1,4 +1,4 @@ -define(function (require) { +define(function(require) { 'use strict'; var SongQuality = require('common/enum/songQuality'); @@ -20,12 +20,12 @@ desktopNotificationsEnabled: true, desktopNotificationDuration: DesktopNotificationDuration.ThreeSeconds }, - - initialize: function () { + + initialize: function() { // Load from Backbone.LocalStorage this.fetch(); } }); - + return Settings; }); \ No newline at end of file diff --git a/src/js/background/model/shareCode.js b/src/js/background/model/shareCode.js index 8fa419ed..e36fc865 100644 --- a/src/js/background/model/shareCode.js +++ b/src/js/background/model/shareCode.js @@ -1,23 +1,24 @@ -define(function () { +define(function() { 'use strict'; var ShareCode = Backbone.Model.extend({ defaults: { id: null, + // TODO: Remove this or use it? entityType: -1, entityId: null, shortId: null, urlFriendlyEntityTitle: '' }, - + urlRoot: function() { return Streamus.serverUrl + 'ShareCode/'; }, - + copyUrl: function() { var shortId = this.get('shortId'); var urlFriendlyEntityTitle = this.get('urlFriendlyEntityTitle'); - var shareUrl = 'https://share.streamus.com/playlist/' + shortId + '/' + urlFriendlyEntityTitle; + var shareUrl = 'https://streamus.com/share/playlist/' + shortId + '/' + urlFriendlyEntityTitle; Streamus.channels.clipboard.commands.trigger('copy:text', shareUrl); } diff --git a/src/js/background/model/signInManager.js b/src/js/background/model/signInManager.js index 4a1702f6..93b9f632 100644 --- a/src/js/background/model/signInManager.js +++ b/src/js/background/model/signInManager.js @@ -1,4 +1,4 @@ -define(function (require) { +define(function(require) { 'use strict'; var User = require('background/model/user'); @@ -21,14 +21,15 @@ needGoogleSignIn: false }, - initialize: function () { + initialize: function() { this.on('change:signedInUser', this._onChangeSignedInUser); this.on('change:signInFailed', this._onChangeSignInFailed); chrome.runtime.onMessage.addListener(this._onChromeRuntimeMessage.bind(this)); + chrome.runtime.onMessageExternal.addListener(this._onChromeRuntimeMessageExternal.bind(this)); chrome.identity.onSignInChanged.addListener(this._onChromeIdentitySignInChanged.bind(this)); }, - signInWithGoogle: function () { + signInWithGoogle: function() { if (this._canSignIn()) { if (this._supportsGoogleSignIn()) { this._getGoogleUserInfo(); @@ -38,21 +39,23 @@ } }, - signOut: function () { + signOut: function() { if (this.get('signedInUser') !== null) { localStorage.removeItem('userId'); this.set('signedInUser', null); } }, - - saveGooglePlusId: function () { - chrome.identity.getProfileUserInfo(function (profileUserInfo) { - if (profileUserInfo.id === '') throw new Error('saveGooglePlusId should only be called when a googlePlusId is known to exist'); + + saveGooglePlusId: function() { + chrome.identity.getProfileUserInfo(function(profileUserInfo) { + if (profileUserInfo.id === '') { + throw new Error('saveGooglePlusId should only be called when a googlePlusId is known to exist'); + } var signedInUser = this.get('signedInUser'); signedInUser.set('googlePlusId', profileUserInfo.id); - signedInUser.hasLinkedGoogleAccount(function (hasLinkedGoogleAccount) { + signedInUser.hasLinkedGoogleAccount(function(hasLinkedGoogleAccount) { // If the account is already know to the database -- merge this account with it and then load existing account w/ merged data. if (hasLinkedGoogleAccount) { signedInUser.mergeByGooglePlusId(); @@ -62,15 +65,15 @@ } }.bind(this)); }.bind(this)); - + this.set('needLinkUserId', false); }, - - isSignedIn: function () { + + isSignedIn: function() { return this.get('signedInUser') !== null; }, - _signIn: function (googlePlusId) { + _signIn: function(googlePlusId) { this.set('signingIn', true); var signingInUser = new User({ @@ -94,7 +97,7 @@ signingInUser.tryloadByUserId(); } else { // If the account does have a Google+ ID -- the account might be known to the database or it might not, check. - signingInUser.hasLinkedGoogleAccount(function (hasLinkedGoogleAccount) { + signingInUser.hasLinkedGoogleAccount(function(hasLinkedGoogleAccount) { // If the account is known to the database -- load it. if (hasLinkedGoogleAccount) { signingInUser.loadByGooglePlusId(); @@ -116,41 +119,41 @@ }, // getProfileUserInfo is only supported in Chrome v37 for Win/Macs currently. - _supportsGoogleSignIn: function () { + _supportsGoogleSignIn: function() { // chrome.identity.getProfileUserInfo is defined in Opera, but throws an error if called. I've reported the issue to them. var isOpera = navigator.userAgent.indexOf(' OPR/') >= 0; return !_.isUndefined(chrome.identity.getProfileUserInfo) && !isOpera; }, - _getGoogleUserInfo: function () { + _getGoogleUserInfo: function() { chrome.identity.getProfileUserInfo(this._onGetProfileUserInfo.bind(this)); }, // TODO: It feels weird that this explicitly calls signIn - what if I want to get their info without signing in? // https://developer.chrome.com/extensions/identity#method-getProfileUserInfo - _onGetProfileUserInfo: function (profileUserInfo) { + _onGetProfileUserInfo: function(profileUserInfo) { this._signIn(profileUserInfo.id); }, - _onChangeSignedInUser: function (model, signedInUser) { + _onChangeSignedInUser: function(model, signedInUser) { // Send a message to open YouTube tabs that Streamus has signed in and their HTML needs to update. Streamus.channels.tab.commands.trigger('notify:youTube', { event: signedInUser !== null ? 'signed-in' : 'signed-out' }); }, - _onChangeSignInFailed: function (model, signInFailed) { + _onChangeSignInFailed: function(model, signInFailed) { if (signInFailed) { this._onSignInFailed(); } }, - _onSignInFailed: function () { + _onSignInFailed: function() { clearInterval(this.get('signInRetryInterval')); this.set('signInRetryInterval', setInterval(this._doSignInRetryTimerIntervalTick.bind(this), 1000)); }, - _doSignInRetryTimerIntervalTick: function () { + _doSignInRetryTimerIntervalTick: function() { var signInRetryTimer = this.get('signInRetryTimer'); this.set('signInRetryTimer', signInRetryTimer - 1); @@ -159,20 +162,20 @@ } }, - _resetSignInRetryTimer: function () { + _resetSignInRetryTimer: function() { clearInterval(this.get('signInRetryInterval')); this.set('signInRetryTimer', SIGN_IN_FAILURE_WAIT_TIME); this.set('signInFailed', false); }, - _canSignIn: function () { + _canSignIn: function() { // Signing in is only allowed if no user is currently signed in, not in the process of being signed in and if not waiting for signInFailure timer. var canSignIn = this.get('signedInUser') === null && !this.get('signingIn') && !this.get('signInFailed'); return canSignIn; }, // https://developer.chrome.com/extensions/identity#event-onSignInChanged - _onChromeIdentitySignInChanged: function (account, signedIn) { + _onChromeIdentitySignInChanged: function(account, signedIn) { if (signedIn) { this._signIn(account.id); } else { @@ -180,11 +183,11 @@ } }, - _onSignInSuccess: function (signingInUser) { + _onSignInSuccess: function(signingInUser) { this._setSignedInUser(signingInUser); }, - _onSignInError: function (signingInUser, error) { + _onSignInError: function(signingInUser, error) { this.stopListening(signingInUser); this.set('signingInUser', null); @@ -193,7 +196,7 @@ this.set('signInFailed', true); }, - _setSignedInUser: function (signingInUser) { + _setSignedInUser: function(signingInUser) { this.stopListening(signingInUser); this.set('signedInUser', signingInUser); @@ -202,7 +205,7 @@ // Announce that user has signedIn so managers can use it to fetch data. this.set('signingIn', false); - this._shouldLinkUserId(function (shouldLinkUserId) { + this._shouldLinkUserId(function(shouldLinkUserId) { if (shouldLinkUserId) { this._needLinkUserId(); } @@ -211,7 +214,7 @@ // When the active Chrome user signs out, check to see if it's linked to the current Streamus user. // If so, unload the current Streamus user and re-create as a non-chrome user. - _onChromeSignedOut: function (googlePlusId) { + _onChromeSignedOut: function(googlePlusId) { if (googlePlusId === this.get('signedInUser').get('googlePlusId')) { this.signOut(); this.signInWithGoogle(); @@ -219,8 +222,6 @@ }, _onChromeRuntimeMessage: function (request, sender, sendResponse) { - var sendAsynchronousResponse = false; - switch (request.method) { case 'getSignedInState': sendResponse({ @@ -230,38 +231,43 @@ case 'signIn': this.signInWithGoogle(); break; - case 'addPlaylistByShareData': + } + }, + + _onChromeRuntimeMessageExternal: function (request, sender, sendResponse) { + var sendAsynchronousResponse = false; + + switch (request.method) { + case 'copyPlaylist': if (this._canSignIn()) { // TODO: What if sign in fails? this.once('change:signedInUser', function () { - this._handleAddSharedPlaylistRequest(request, sendResponse); + this._handleCopyPlaylistRequest(request, sendResponse); }); this.signInWithGoogle(); } else { - this._handleAddSharedPlaylistRequest(request, sendResponse); + this._handleCopyPlaylistRequest(request, sendResponse); } sendAsynchronousResponse = true; break; } - + // sendResponse becomes invalid when the event listener returns, unless you return true from the event listener to indicate you wish to send a response asynchronously (this will keep the message channel open to the other end until sendResponse is called). return sendAsynchronousResponse; }, - _handleAddSharedPlaylistRequest: function (request, sendResponse) { + _handleCopyPlaylistRequest: function (request, sendResponse) { // TODO: Probably house this logic on signedInUser or on playlists? - this.get('signedInUser').get('playlists').addPlaylistByShareData({ - shortId: request.shareCodeShortId, - urlFriendlyEntityTitle: request.urlFriendlyEntityTitle, - success: function (playlist) { + this.get('signedInUser').get('playlists').copyPlaylist({ + playlistId: request.playlistId, + success: function() { sendResponse({ - result: 'success', - playlistTitle: playlist.get('title') + result: 'success' }); }, - error: function (error) { + error: function(error) { sendResponse({ result: 'error', error: error @@ -270,9 +276,9 @@ }); }, - _shouldLinkUserId: function (callback) { + _shouldLinkUserId: function(callback) { if (this._supportsGoogleSignIn()) { - chrome.identity.getProfileUserInfo(function (profileUserInfo) { + chrome.identity.getProfileUserInfo(function(profileUserInfo) { var signedInToChrome = profileUserInfo.id !== ''; var accountLinked = this.get('signedInUser').linkedToGoogle(); callback(signedInToChrome && !accountLinked); @@ -282,11 +288,11 @@ } }, // TODO: Just set this properties manually instead of via function. - _needLinkUserId: function () { + _needLinkUserId: function() { this.set('needLinkUserId', true); }, - _needGoogleSignIn: function () { + _needGoogleSignIn: function() { this.set('needGoogleSignIn', true); } }); diff --git a/src/js/background/model/song.js b/src/js/background/model/song.js index 2ebbb7a4..15b2356a 100644 --- a/src/js/background/model/song.js +++ b/src/js/background/model/song.js @@ -1,6 +1,6 @@ -define(function (require) { +define(function(require) { 'use strict'; - + var SongType = require('background/enum/songType'); var Utility = require('common/utility'); @@ -21,62 +21,64 @@ cleanTitle: '' }, + // TODO: Needed? // Song is never saved to the server -- it gets flattened into a PlaylistItem sync: function() { return false; }, - - initialize: function () { + + initialize: function() { this._setPrettyDuration(this.get('duration')); this._setCleanTitle(this.get('title')); this._setUrl(this.get('id')); + // TODO: Why can song values even change? this.on('change:duration', this._onChangeDuration); this.on('change:title', this._onChangeTitle); this.on('change:id', this._onChangeId); }, - - copyUrl: function () { + + copyUrl: function() { var url = this.get('url'); Streamus.channels.clipboard.commands.trigger('copy:text', url); - + Streamus.channels.notification.commands.trigger('show:notification', { message: chrome.i18n.getMessage('urlCopied') }); }, - - copyTitleAndUrl: function () { + + copyTitleAndUrl: function() { var titleAndUrl = this.get('title') + ' - ' + this.get('url'); Streamus.channels.clipboard.commands.trigger('copy:text', titleAndUrl); - + Streamus.channels.notification.commands.trigger('show:notification', { message: chrome.i18n.getMessage('urlCopied') }); }, - - _onChangeId: function (model, id) { + + _onChangeId: function(model, id) { this._setUrl(id); }, - + _onChangeTitle: function(model, title) { this._setCleanTitle(title); }, - + _onChangeDuration: function(model, duration) { this._setPrettyDuration(duration); }, // Calculate this value pre-emptively because when rendering I don't want to incur inefficiency - _setPrettyDuration: function (duration) { + _setPrettyDuration: function(duration) { this.set('prettyDuration', Utility.prettyPrintTime(duration)); }, // Useful for comparisons and other searching. - _setCleanTitle: function (title) { + _setCleanTitle: function(title) { this.set('cleanTitle', Utility.cleanTitle(title)); }, - - _setUrl: function (id) { + + _setUrl: function(id) { this.set('url', 'https://youtu.be/' + id); } }); diff --git a/src/js/background/model/stream.js b/src/js/background/model/stream.js index 30bf4196..994c82b9 100644 --- a/src/js/background/model/stream.js +++ b/src/js/background/model/stream.js @@ -1,4 +1,4 @@ -define(function (require) { +define(function(require) { 'use strict'; var StreamItems = require('background/collection/streamItems'); @@ -9,7 +9,7 @@ var Stream = Backbone.Model.extend({ localStorage: new Backbone.LocalStorage('Stream'), - defaults: function () { + defaults: function() { return { // Need to set the ID for Backbone.LocalStorage id: 'Stream', @@ -26,11 +26,11 @@ // Don't want to save everything to localStorage -- only variables which need to be persisted. whitelist: ['history'], - toJSON: function () { + toJSON: function() { return this.pick(this.whitelist); }, - initialize: function () { + initialize: function() { this.listenTo(this.get('player'), 'change:state', this._onPlayerChangeState); this.listenTo(this.get('player'), 'youTubeError', this._onPlayerYouTubeError); this.listenTo(this.get('items'), 'add', this._onItemsAdd); @@ -55,7 +55,7 @@ // TODO: Function is way too big. // If a streamItem which was active is removed, activateNext will have a removedActiveItemIndex provided - activateNext: function (removedActiveItemIndex) { + activateNext: function(removedActiveItemIndex) { var nextItem = null; var shuffleEnabled = this.get('shuffleButton').get('enabled'); @@ -101,8 +101,7 @@ } else { nextItem.save({ active: true }); } - } - else if (radioEnabled) { + } else if (radioEnabled) { var randomRelatedSong = items.getRandomRelatedSong(); var addedSongs = items.addSongs(randomRelatedSong, { @@ -117,8 +116,7 @@ else if (!_.isUndefined(removedActiveItemIndex)) { items.last().save({ active: true }); this.get('player').pause(); - } - else { + } else { // Otherwise, activate the first item in the playlist and then pause the player because playlist looping shouldn't continue. items.first().save({ active: true }); this.get('player').pause(); @@ -144,7 +142,7 @@ return nextItem; }, - activatePrevious: function () { + activatePrevious: function() { var previousStreamItem = this.getPrevious(); // When repeating a song -- it'll already be active, but still need to trigger a change:active event so program will respond. @@ -161,7 +159,7 @@ }, // Return the previous item or null without affecting the history. - getPrevious: function () { + getPrevious: function() { var previousStreamItem = null; var history = this.get('history'); var items = this.get('items'); @@ -199,11 +197,11 @@ }, // A StreamItem's related song information is used when radio mode is enabled to allow users to discover new music. - _onItemsAdd: function (model) { + _onItemsAdd: function(model) { this._ensureHasRelatedSongs(model); }, - _onGetRelatedSongsSuccess: function (model, relatedSongs) { + _onGetRelatedSongsSuccess: function(model, relatedSongs) { // The model could have been removed from the collection while the request was still in flight. // If this happened, just discard the data instead of trying to update the model. if (this.get('items').get(model)) { @@ -212,7 +210,7 @@ } }, - _onItemsRemove: function (model, collection, options) { + _onItemsRemove: function(model, collection, options) { var history = this.get('history'); history = _.without(history, model.get('id')); this.save('history', history); @@ -228,7 +226,7 @@ } }, - _onItemsReset: function (collection) { + _onItemsReset: function(collection) { this.save('history', []); var isEmpty = collection.isEmpty(); @@ -239,14 +237,14 @@ } }, - _onItemsChangeActive: function (model, active) { + _onItemsChangeActive: function(model, active) { if (active) { this.set('activeItem', model); this._loadActiveItem(model); } }, - _onPlayerChangeState: function (model, state) { + _onPlayerChangeState: function(model, state) { // Since the player's state is dependent on asynchronous actions it's important to ensure that // the Stream is still in a valid state when an event comes in. The user could've removed songs after an event started to arrive. if (!this.get('items').isEmpty()) { @@ -258,8 +256,7 @@ if (nextItem === null) { model.set('playOnActivate', false); } - } - else if (state === PlayerState.Playing) { + } else if (state === PlayerState.Playing) { // Only display notifications if the foreground isn't active -- either through the extension popup or as a URL tab this.get('items').showActiveNotification(); } @@ -267,7 +264,7 @@ }, // TODO: Really needs to be reworked. - _onPlayerYouTubeError: function (model, youTubeError) { + _onPlayerYouTubeError: function(model, youTubeError) { if (this.get('items').length > 0) { //model.set('playOnActivate', false); // TODO: It would be better if I could get the next item instead of having to activate it automatically. @@ -288,11 +285,11 @@ } }, - _loadActiveItem: function (activeItem) { + _loadActiveItem: function(activeItem) { this.get('player').activateSong(activeItem.get('song')); }, - _cleanupOnItemsEmpty: function () { + _cleanupOnItemsEmpty: function() { this.set('activeItem', null); this.get('player').stop(); }, diff --git a/src/js/background/model/streamItem.js b/src/js/background/model/streamItem.js index cd2f9120..ce4400aa 100644 --- a/src/js/background/model/streamItem.js +++ b/src/js/background/model/streamItem.js @@ -1,12 +1,12 @@ -define(function (require) { +define(function(require) { 'use strict'; var Songs = require('background/collection/songs'); var Song = require('background/model/song'); var ListItemType = require('common/enum/listItemType'); - + var StreamItem = Backbone.Model.extend({ - defaults: function () { + defaults: function() { return { id: null, song: null, @@ -24,11 +24,11 @@ // Don't want to save everything to localStorage -- only variables which need to be persisted. blacklist: ['selected', 'firstSelected'], - toJSON: function () { + toJSON: function() { return this.omit(this.blacklist); }, - - initialize: function () { + + initialize: function() { this._ensureSongModel(); this._ensureRelatedSongsCollection(); this.on('change:active', this._onChangeActive); @@ -43,8 +43,8 @@ this.set('song', new Song(song), { silent: true }); } }, - - _ensureRelatedSongsCollection: function () { + + _ensureRelatedSongsCollection: function() { var relatedSongs = this.get('relatedSongs'); // Need to convert relatedSongs array to Backbone.Collection diff --git a/src/js/background/model/tabManager.js b/src/js/background/model/tabManager.js index 3a1a1662..cdd645da 100644 --- a/src/js/background/model/tabManager.js +++ b/src/js/background/model/tabManager.js @@ -1,8 +1,8 @@ -define(function () { +define(function() { 'use strict'; var TabManager = Backbone.Model.extend({ - defaults: function () { + defaults: function() { return { streamusForegroundUrl: 'chrome-extension://' + chrome.runtime.id + '/foreground.html', keyboardShortcutsUrl: 'chrome://extensions/configureCommands', @@ -10,57 +10,57 @@ beatportUrlPatterns: ['*://*.beatport.com/*'] }; }, - - initialize: function () { + + initialize: function() { this.listenTo(Streamus.channels.tab.commands, 'notify:youTube', this._notifyYouTube); this.listenTo(Streamus.channels.tab.commands, 'notify:beatport', this._notifyBeatport); }, - isStreamusTabActive: function (callback) { + isStreamusTabActive: function(callback) { var queryInfo = { url: this.get('streamusForegroundUrl'), lastFocusedWindow: true }; - this._queryTabs(queryInfo, function (tabs) { + this._queryTabs(queryInfo, function(tabs) { callback(tabs.length > 0); }); }, - - isStreamusTabOpen: function (callback) { + + isStreamusTabOpen: function(callback) { var queryInfo = { url: this.get('streamusForegroundUrl'), }; - this._queryTabs(queryInfo, function (tabs) { + this._queryTabs(queryInfo, function(tabs) { callback(tabs.length > 0); }); }, - - showStreamusTab: function () { + + showStreamusTab: function() { this._showTab(this.get('streamusForegroundUrl')); }, showKeyboardShortcutsTab: function() { this._showTab(this.get('keyboardShortcutsUrl')); }, - - _notifyYouTube: function (data) { + + _notifyYouTube: function(data) { this.messageYouTubeTabs(data); }, - - _notifyBeatport: function (data) { + + _notifyBeatport: function(data) { this.messageBeatportTabs(data); }, - - _showTab: function (urlPattern, url) { + + _showTab: function(urlPattern, url) { var queryInfo = { url: urlPattern }; - this._queryTabs(queryInfo, function (tabDetailsList) { + this._queryTabs(queryInfo, function(tabDetailsList) { if (tabDetailsList.length > 0) { - var anyTabHighlighted = _.some(tabDetailsList, function (tabDetails) { + var anyTabHighlighted = _.some(tabDetailsList, function(tabDetails) { return tabDetails.highlighted; }); @@ -85,39 +85,39 @@ }, // This is sufficient to message all tabs as well as popped-out windows which aren't tabs. - messageYouTubeTabs: function (message) { - _.each(this.get('youTubeUrlPatterns'), function (youTubeUrlPattern) { + messageYouTubeTabs: function(message) { + _.each(this.get('youTubeUrlPatterns'), function(youTubeUrlPattern) { var queryInfo = { url: youTubeUrlPattern }; - this._queryTabs(queryInfo, function (tabs) { - _.each(tabs, function (tab) { + this._queryTabs(queryInfo, function(tabs) { + _.each(tabs, function(tab) { chrome.tabs.sendMessage(tab.id, message); }); }); }, this); }, - - messageBeatportTabs: function (message) { - _.each(this.get('beatportUrlPatterns'), function (beatportUrlPattern) { + + messageBeatportTabs: function(message) { + _.each(this.get('beatportUrlPatterns'), function(beatportUrlPattern) { var queryInfo = { url: beatportUrlPattern }; - this._queryTabs(queryInfo, function (tabs) { - _.each(tabs, function (tab) { + this._queryTabs(queryInfo, function(tabs) { + _.each(tabs, function(tab) { chrome.tabs.sendMessage(tab.id, message); }); }); }, this); }, - - _queryTabs: function (queryInfo, callback) { + + _queryTabs: function(queryInfo, callback) { chrome.tabs.query(queryInfo, callback); }, - - _highlightTabs: function (highlightInfo) { + + _highlightTabs: function(highlightInfo) { // TODO: The callback will be optional once Google resolves https://code.google.com/p/chromium/issues/detail?id=417564 chrome.tabs.highlight(highlightInfo, _.noop); } diff --git a/src/js/background/model/user.js b/src/js/background/model/user.js index e4d059da..3714ec48 100644 --- a/src/js/background/model/user.js +++ b/src/js/background/model/user.js @@ -1,4 +1,4 @@ -define(function (require) { +define(function(require) { 'use strict'; var Playlists = require('background/collection/playlists'); @@ -10,12 +10,12 @@ define(function (require) { playlists: null, language: '' }, - + urlRoot: function() { return Streamus.serverUrl + 'User/'; }, - loadByGooglePlusId: function () { + loadByGooglePlusId: function() { $.ajax({ url: Streamus.serverUrl + 'User/GetByGooglePlusId', contentType: 'application/json; charset=utf-8', @@ -40,10 +40,10 @@ define(function (require) { error: this._onLoadError.bind(this) }); }, - - tryloadByUserId: function () { - var userId = this._getLocalUserId(); - + + tryloadByUserId: function() { + var userId = window.localStorage.getItem('userId'); + if (userId === null) { this._create(); } else { @@ -52,10 +52,10 @@ define(function (require) { }, // A user is linked to a Google account if their GooglePlusId is not empty. - linkedToGoogle: function () { + linkedToGoogle: function() { return this.get('googlePlusId') !== ''; }, - + hasLinkedGoogleAccount: function(callback) { $.ajax({ url: Streamus.serverUrl + 'User/HasLinkedGoogleAccount', @@ -68,19 +68,8 @@ define(function (require) { }); }, - _getLocalUserId: function () { - var userId = localStorage.getItem('userId'); - - // NOTE: This is a bit of legacy code. Originally was calling toJSON on all objects written to localStorage, so quotes might exist. - if (userId !== null) { - userId = userId.replace(/"/g, ''); - } - - return userId; - }, - // No stored ID found at any client storage spot. Create a new user and use the returned user object. - _create: function () { + _create: function() { this.save({ // Save the language here upon creation because the user is clearly unknown and it'll save a PATCH request by knowing language on creation. language: chrome.i18n.getUILanguage() @@ -92,7 +81,7 @@ define(function (require) { // Loads user data by ID from the server, writes the ID to client-side storage locations // for future loading and then announces that the user has been loaded. - _loadByUserId: function (userId) { + _loadByUserId: function(userId) { this.set('id', userId); this.fetch({ @@ -102,7 +91,7 @@ define(function (require) { }, // Set playlists as a Backbone.Collection from the JSON received from the server. - _ensurePlaylistsCollection: function () { + _ensurePlaylistsCollection: function() { var playlists = this.get('playlists'); // Need to convert playlists array to Backbone.Collection @@ -118,26 +107,28 @@ define(function (require) { this.get('playlists').reset(playlists); } }, - + _onLoadByGooglePlusIdSuccess: function(userDto) { - if (userDto === null) throw new Error("UserDTO should always be returned here."); + if (userDto === null) { + throw new Error('UserDTO should always be returned here.'); + } this.set(userDto); this._onLoadSuccess(); }, - - _onLoadError: function (error) { + + _onLoadError: function(error) { this.trigger('loadError', error); }, - + _onLoadSuccess: function() { this._ensurePlaylistsCollection(); this._setLanguage(); localStorage.setItem('userId', this.get('id')); this.trigger('loadSuccess'); }, - - _setLanguage: function () { + + _setLanguage: function() { var language = chrome.i18n.getUILanguage(); if (this.get('language') !== language) { this.save({ language: language }, { patch: true }); diff --git a/src/js/background/model/youTubePlayer.js b/src/js/background/model/youTubePlayer.js index b374d096..74928052 100644 --- a/src/js/background/model/youTubePlayer.js +++ b/src/js/background/model/youTubePlayer.js @@ -1,4 +1,4 @@ -define(function (require) { +define(function(require) { 'use strict'; var YouTubePlayerAPI = require('background/model/youTubePlayerAPI'); @@ -6,7 +6,7 @@ // This is the actual YouTube Player API widget housed within the iframe. var youTubePlayerWidget = null; - + // This value is 1 because it is displayed visually. // 'Load attempt: 0' does not make sense to non-programmers. var _initialLoadAttempt = 1; @@ -28,8 +28,8 @@ loadAttemptInterval: null }; }, - - initialize: function () { + + initialize: function() { this.listenTo(this.get('api'), 'change:ready', this._onApiChangeReady); this.listenTo(Streamus.channels.foreground.vent, 'started', this._onForegroundStarted); this.on('change:loading', this._onChangeLoading); @@ -37,7 +37,7 @@ // Preload is used to indicate that an attempt to load YouTube's API is hopefully going to come soon. However, if the iframe // holding YouTube's API fails to load then load will not be called. If the iframe does load successfully then load will be called. - preload: function () { + preload: function() { if (!this.get('loading')) { // Ensure the widget is null for debugging purposes. // Being able to tell the difference between a widget API method failing and the widget itself not being ready is important. @@ -50,9 +50,9 @@ // Loading a widget requires the widget's API be ready first. Ensure that the API is loaded // otherwise defer loading a widget until the API is ready. - load: function () { + load: function() { var api = this.get('api'); - + if (api.get('ready')) { this._loadWidget(); } else { @@ -60,27 +60,27 @@ } }, - stop: function () { + stop: function() { youTubePlayerWidget.stopVideo(); }, - pause: function () { + pause: function() { youTubePlayerWidget.pauseVideo(); }, - play: function () { + play: function() { youTubePlayerWidget.playVideo(); }, - seekTo: function (timeInSeconds) { + seekTo: function(timeInSeconds) { // Always pass allowSeekAhead: true to the seekTo method. // If this value is not provided and the user seeks to the end of a song while paused // the player will enter into a bad state of 'ended -> playing.' // https://developers.google.com/youtube/js_api_reference#seekTo youTubePlayerWidget.seekTo(timeInSeconds, true); }, - - setMuted: function (muted) { + + setMuted: function(muted) { if (muted) { youTubePlayerWidget.mute(); } else { @@ -88,25 +88,25 @@ } }, - setVolume: function (volume) { + setVolume: function(volume) { youTubePlayerWidget.setVolume(volume); }, // The variable is called suggestedQuality because the widget may not have be able to fulfill the request. // If it cannot, it will set its quality to the level most near suggested quality. - setPlaybackQuality: function (suggestedQuality) { + setPlaybackQuality: function(suggestedQuality) { youTubePlayerWidget.setPlaybackQuality(suggestedQuality); }, - loadVideoById: function (videoOptions) { + loadVideoById: function(videoOptions) { youTubePlayerWidget.loadVideoById(videoOptions); }, - cueVideoById: function (videoOptions) { + cueVideoById: function(videoOptions) { youTubePlayerWidget.cueVideoById(videoOptions); }, - _loadWidget: function () { + _loadWidget: function() { // YouTube's API creates the window.YT object with which widgets can be created. // https://developers.google.com/youtube/iframe_api_reference#Loading_a_Video_Player youTubePlayerWidget = new window.YT.Player(this.get('iframeId'), { @@ -118,20 +118,20 @@ }); }, - _onYouTubePlayerReady: function () { + _onYouTubePlayerReady: function() { // TODO: It's apparently possible for youTubePlayerWidget.setVolume to be undefined at this point in time. How can I reproduce? // It's important to set ready to true before loading to false otherwise it looks like YouTubePlayer failed to load properly. this.set('ready', true); this.set('loading', false); }, - _onYouTubePlayerStateChange: function (state) { + _onYouTubePlayerStateChange: function(state) { // Pass 'this' as the first parameter to match the event signature of a Backbone.Model change event. this.trigger('change:state', this, state.data); }, // Emit errors so the foreground so can notify the user. - _onYouTubePlayerError: function (error) { + _onYouTubePlayerError: function(error) { // If the error is really bad then attempt to recover rather than reflecting the error throughout the program. if (error.data === YouTubePlayerError.ReallyBad) { this.preload(); @@ -140,13 +140,13 @@ } }, - _onApiChangeReady: function (model, ready) { + _onApiChangeReady: function(model, ready) { if (ready) { this._loadWidget(); } }, - - _onChangeLoading: function (model, loading) { + + _onChangeLoading: function(model, loading) { this.set('currentLoadAttempt', _initialLoadAttempt); var loadAttemptInterval = null; @@ -156,13 +156,13 @@ } else { clearInterval(this.get('loadAttemptInterval')); } - + this.set('loadAttemptInterval', loadAttemptInterval); }, - - _onLoadAttemptDelayExceeded: function () { + + _onLoadAttemptDelayExceeded: function() { var currentLoadAttempt = this.get('currentLoadAttempt'); - + if (currentLoadAttempt === this.get('maxLoadAttempts')) { this.set('loading', false); } else { @@ -172,7 +172,7 @@ // Streamus could have disconnected from the API and failed to recover automatically. // A good time to try recovering again is when the user is interacting the UI. - _onForegroundStarted: function () { + _onForegroundStarted: function() { if (!this.get('ready')) { this.preload(); } diff --git a/src/js/background/model/youTubePlayerAPI.js b/src/js/background/model/youTubePlayerAPI.js index 58bfb221..850250ed 100644 --- a/src/js/background/model/youTubePlayerAPI.js +++ b/src/js/background/model/youTubePlayerAPI.js @@ -1,1298 +1,1511 @@ -define(function () { - 'use strict'; - - var YouTubePlayerAPI = Backbone.Model.extend({ - defaults: { - ready: false, - inserted: false - }, - - load: function () { - if (this.get('inserted')) { - var error = new Error('API script already inserted'); - Streamus.channels.error.commands.trigger('log:error', error); - return; - } - - // This function will be called when the API is fully loaded. Needs to be exposed globally so YouTube can call it. - window.onYouTubeIframeAPIReady = this._onYouTubeIframeAPIReady.bind(this); - - this._loadIframeAPI(); - this.set('inserted', true); - }, +define(function() { + 'use strict'; + + var YouTubePlayerAPI = Backbone.Model.extend({ + defaults: { + ready: false, + inserted: false + }, + + load: function() { + if (this.get('inserted')) { + var error = new Error('API script already inserted'); + Streamus.channels.error.commands.trigger('log:error', error); + return; + } + + // This function will be called when the API is fully loaded. Needs to be exposed globally so YouTube can call it. + window.onYouTubeIframeAPIReady = this._onYouTubeIframeAPIReady.bind(this); + + this._loadIframeAPI(); + this.set('inserted', true); + }, + + _onYouTubeIframeAPIReady: function() { + this.set('ready', true); + }, - _onYouTubeIframeAPIReady: function () { - this.set('ready', true); - }, + // This code is from https://www.youtube.com/iframe_api. I pull it in locally as to not need to relax content_security_policy. + _loadIframeAPI: function() { + /* jshint ignore:start */ + if (!window['YT']) { + window.YT = { + loading: 0, + loaded: 0 + }; + } + + if (!window['YTConfig']) { + window.YTConfig = { + // NOTE: I modified this from http to https because extensions do not care much for http. + 'host': 'https://www.youtube.com' + }; + } + + if (!YT.loading) { + YT.loading = 1; + var l = []; + + YT.ready = function(f) { + if (YT.loaded) { + f(); + } else { + l.push(f); + } + }; + + window.onYTReady = function() { + YT.loaded = 1; + for (var i = 0; i < l.length; i++) { + try { + l[i](); + } catch(e) { + } + } + }; + + YT.setConfig = function(c) { + for (var k in c) { + if (c.hasOwnProperty(k)) { + YTConfig[k] = c[k]; + } + } + }; + + this._loadWidgetAPI(); + } + /* jshint ignore:end */ + }, - // This code is from https://www.youtube.com/iframe_api. I pull it in locally as to not need to relax content_security_policy. - _loadIframeAPI: function() { - /* jshint ignore:start */ - if (!window['YT']) { - window.YT = { - loading: 0, - loaded: 0 - }; - } - - if (!window['YTConfig']) { - window.YTConfig = { - // NOTE: I modified this from http to https because extensions do not care much for http. - 'host': 'https://www.youtube.com' - }; - } - - if (!YT.loading) { - YT.loading = 1; - var l = []; - - YT.ready = function (f) { - if (YT.loaded) { - f(); - } else { - l.push(f); - } - }; - - window.onYTReady = function () { - YT.loaded = 1; - for (var i = 0; i < l.length; i++) { - try { - l[i](); - } catch (e) { - } - } - }; - - YT.setConfig = function (c) { - for (var k in c) { - if (c.hasOwnProperty(k)) { - YTConfig[k] = c[k]; - } - } - }; - - this._loadWidgetAPI(); - } - /* jshint ignore:end */ - }, - - // This code is from https://s.ytimg.com/yts/jsbin/www-widgetapi-vflBfDu58/www-widgetapi.js. I pull it in locally as to not need to relax content_security_policy. - // NOTE: I have patched a bug in this code. It is documented here: https://code.google.com/p/gdata-issues/issues/detail?id=6402 - _loadWidgetAPI: function() { - /* jshint ignore:start */ - var g, h = window; - - function l(a) { - a = a.split("."); - for (var b = h, c; c = a.shift() ;) - if (null != b[c]) b = b[c]; - else return null; - return b; - } - - function aa() { } - - function m(a) { - var b = typeof a; - if ("object" == b) - if (a) { - if (a instanceof Array) return "array"; - if (a instanceof Object) return b; - var c = Object.prototype.toString.call(a); - if ("[object Window]" == c) return "object"; - if ("[object Array]" == c || "number" == typeof a.length && "undefined" != typeof a.splice && "undefined" != typeof a.propertyIsEnumerable && !a.propertyIsEnumerable("splice")) return "array"; - if ("[object Function]" == c || "undefined" != typeof a.call && "undefined" != typeof a.propertyIsEnumerable && !a.propertyIsEnumerable("call")) return "function"; - } else return "null"; - else if ("function" == b && "undefined" == typeof a.call) return "object"; - return b; - } - - function n(a) { - return "string" == typeof a; - } - - function ba(a) { - var b = typeof a; - return "object" == b && null != a || "function" == b; - } - var p = "closure_uid_" + (1E9 * Math.random() >>> 0), - ca = 0; - - function da(a) { - return a.call.apply(a.bind, arguments); - } - - function ea(a, b) { - if (!a) throw Error(); - if (2 < arguments.length) { - var d = Array.prototype.slice.call(arguments, 2); - return function () { - var c = Array.prototype.slice.call(arguments); - Array.prototype.unshift.apply(c, d); - return a.apply(b, c); - }; - } - return function () { - return a.apply(b, arguments); - }; - } - - function q() { - q = Function.prototype.bind && -1 != Function.prototype.bind.toString().indexOf("native code") ? da : ea; - return q.apply(null, arguments); - } - - function r(a, b) { - var c = a.split("."), - d = h; - c[0] in d || !d.execScript || d.execScript("var " + c[0]); - for (var e; c.length && (e = c.shift()) ;) c.length || void 0 === b ? d[e] ? d = d[e] : d = d[e] = {} : d[e] = b; - } - - function s(a, b) { - function c() { } - c.prototype = b.prototype; - a.I = b.prototype; - a.prototype = new c; - a.base = function (a, c) { - return b.prototype[c].apply(a, Array.prototype.slice.call(arguments, 2)); - }; - } - Function.prototype.bind = Function.prototype.bind || function (a, b) { - if (1 < arguments.length) { - var c = Array.prototype.slice.call(arguments, 1); - c.unshift(this, a); - return q.apply(null, c); - } - return q(this, a); - }; - var fa = String.prototype.trim ? function (a) { - return a.trim(); - } : function (a) { - return a.replace(/^[\s\xa0]+|[\s\xa0]+$/g, ""); - }; - - function t(a, b) { - return a < b ? -1 : a > b ? 1 : 0; - }; - var v = Array.prototype, - ga = v.indexOf ? function (a, b, c) { - return v.indexOf.call(a, b, c); - } : function (a, b, c) { - c = null == c ? 0 : 0 > c ? Math.max(0, a.length + c) : c; - if (n(a)) return n(b) && 1 == b.length ? a.indexOf(b, c) : -1; - for (; c < a.length; c++) - if (c in a && a[c] === b) return c; - return -1; - }, - w = v.forEach ? function (a, b, c) { - v.forEach.call(a, b, c); - } : function (a, b, c) { - for (var d = a.length, e = n(a) ? a.split("") : a, f = 0; f < d; f++) f in e && b.call(c, e[f], f, a); - }; - - function ha(a, b) { - var c; - t: { - c = a.length; - for (var d = n(a) ? a.split("") : a, e = 0; e < c; e++) - if (e in d && b.call(void 0, d[e], e, a)) { - c = e; - break t; - } - c = -1; - } - return 0 > c ? null : n(a) ? a.charAt(c) : a[c]; - } - - function ia() { - return v.concat.apply(v, arguments); - } - - function ja(a) { - var b = a.length; - if (0 < b) { - for (var c = Array(b), d = 0; d < b; d++) c[d] = a[d]; - return c; - } - return []; - } - - function ka(a, b, c) { - return 2 >= arguments.length ? v.slice.call(a, b) : v.slice.call(a, b, c); - }; - - function la(a) { - var b = x, - c; - for (c in b) - if (a.call(void 0, b[c], c, b)) return c; - }; - var y; - t: { - var ma = h.navigator; - if (ma) { - var na = ma.userAgent; - if (na) { - y = na; - break t; - } - } - y = ""; - }; - var z = -1 != y.indexOf("Opera") || -1 != y.indexOf("OPR"), - A = -1 != y.indexOf("Trident") || -1 != y.indexOf("MSIE"), - B = -1 != y.indexOf("Gecko") && -1 == y.toLowerCase().indexOf("webkit") && !(-1 != y.indexOf("Trident") || -1 != y.indexOf("MSIE")), - C = -1 != y.toLowerCase().indexOf("webkit"); - - function oa() { - var a = h.document; - return a ? a.documentMode : void 0; - } - var D = function () { - var a = "", - b; - if (z && h.opera) return a = h.opera.version, "function" == m(a) ? a() : a; - B ? b = /rv\:([^\);]+)(\)|;)/ : A ? b = /\b(?:MSIE|rv)[: ]([^\);]+)(\)|;)/ : C && (b = /WebKit\/(\S+)/); - b && (a = (a = b.exec(y)) ? a[1] : ""); - return A && (b = oa(), b > parseFloat(a)) ? String(b) : a; - }(), - pa = {}; - - function E(a) { - var b; - if (!(b = pa[a])) { - b = 0; - for (var c = fa(String(D)).split("."), d = fa(String(a)).split("."), e = Math.max(c.length, d.length), f = 0; 0 == b && f < e; f++) { - var k = c[f] || "", - u = d[f] || "", - kb = RegExp("(\\d*)(\\D*)", "g"), - lb = RegExp("(\\d*)(\\D*)", "g"); - do { - var M = kb.exec(k) || ["", "", ""], - N = lb.exec(u) || ["", "", ""]; - if (0 == M[0].length && 0 == N[0].length) break; - b = t(0 == M[1].length ? 0 : parseInt(M[1], 10), 0 == N[1].length ? 0 : parseInt(N[1], 10)) || t(0 == M[2].length, 0 == N[2].length) || t(M[2], N[2]); - } while (0 == b); - } - b = pa[a] = 0 <= b; - } - return b; - } - var qa = h.document, - ra = qa && A ? oa() || ("CSS1Compat" == qa.compatMode ? parseInt(D, 10) : 5) : void 0; - !B && !A || A && A && 9 <= ra || B && E("1.9.1"); - A && E("9"); - - function sa(a) { - var b, c, d, e; - b = document; - if (b.querySelectorAll && b.querySelector && a) return b.querySelectorAll("" + (a ? "." + a : "")); - if (a && b.getElementsByClassName) { - var f = b.getElementsByClassName(a); - return f; - } - f = b.getElementsByTagName("*"); - if (a) { - e = {}; - for (c = d = 0; b = f[c]; c++) { - var k = b.className, - u; - if (u = "function" == typeof k.split) u = 0 <= ga(k.split(/\s+/), a); - u && (e[d++] = b); - } - e.length = d; - return e; - } - return f; - } - - function ta(a, b) { - for (var c = 0; a;) { - if (b(a)) return a; - a = a.parentNode; - c++; - } - return null; - }; - - function ua(a) { - a = String(a); - if (/^\s*$/.test(a) ? 0 : /^[\],:{}\s\u2028\u2029]*$/.test(a.replace(/\\["\\\/bfnrtu]/g, "@").replace(/"[^"\\\n\r\u2028\u2029\x00-\x08\x0a-\x1f]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, "]").replace(/(?:^|:|,)(?:[\s\u2028\u2029]*\[)+/g, ""))) try { - return eval("(" + a + ")"); - } catch (b) { } - throw Error("Invalid JSON string: " + a); - } - - function va() { } - - function F(a, b, c) { - switch (typeof b) { - case "string": - wa(b, c); - break; - case "number": - c.push(isFinite(b) && !isNaN(b) ? b : "null"); - break; - case "boolean": - c.push(b); - break; - case "undefined": - c.push("null"); - break; - case "object": - if (null == b) { - c.push("null"); - break; - } - if ("array" == m(b)) { - var d = b.length; - c.push("["); - for (var e = "", f = 0; f < d; f++) c.push(e), F(a, b[f], c), e = ","; - c.push("]"); - break; - } - c.push("{"); - d = ""; - for (e in b) Object.prototype.hasOwnProperty.call(b, e) && (f = b[e], "function" != typeof f && (c.push(d), wa(e, c), c.push(":"), F(a, f, c), d = ",")); - c.push("}"); - break; - case "function": - break; - default: - throw Error("Unknown type: " + typeof b); - } - } - var xa = { - '"': '\\"', - "\\": "\\\\", - "/": "\\/", - "\b": "\\b", - "\f": "\\f", - "\n": "\\n", - "\r": "\\r", - "\t": "\\t", - "\x0B": "\\u000b" - }, - ya = /\uffff/.test("\uffff") ? /[\\\"\x00-\x1f\x7f-\uffff]/g : /[\\\"\x00-\x1f\x7f-\xff]/g; - - function wa(a, b) { - b.push('"', a.replace(ya, function (a) { - if (a in xa) return xa[a]; - var b = a.charCodeAt(0), - e = "\\u"; - 16 > b ? e += "000" : 256 > b ? e += "00" : 4096 > b && (e += "0"); - return xa[a] = e + b.toString(16); - }), '"'); - }; - - function G() { - this.j = this.j; - this.o = this.o; - } - G.prototype.j = !1; - G.prototype.dispose = function () { - this.j || (this.j = !0, this.H()); - }; - G.prototype.H = function () { - if (this.o) - for (; this.o.length;) this.o.shift()(); - }; - - function H() { - G.call(this); - this.d = []; - this.g = {}; - } - s(H, G); - g = H.prototype; - g.O = 1; - g.B = 0; - g.subscribe = function (a, b, c) { - var d = this.g[a]; - d || (d = this.g[a] = []); - var e = this.O; - this.d[e] = a; - this.d[e + 1] = b; - this.d[e + 2] = c; - this.O = e + 3; - d.push(e); - return e; - }; - - function za(a, b, c) { - var d = I; - if (a = d.g[a]) { - var e = d.d; - (a = ha(a, function (a) { - return e[a + 1] == b && e[a + 2] == c; - })) && Aa(d, a); - } - } - - function Aa(a, b) { - if (0 != a.B) a.k || (a.k = []), a.k.push(b); - else { - var c = a.d[b]; - if (c) { - if (c = a.g[c]) { - var d = ga(c, b); - 0 <= d && v.splice.call(c, d, 1); - } - delete a.d[b]; - delete a.d[b + 1]; - delete a.d[b + 2]; - } - } - } - g.R = function (a) { - var c = this.g[a]; - if (c) { - this.B++; - for (var d = ka(arguments, 1), e = 0, f = c.length; e < f; e++) { - var k = c[e]; - this.d[k + 1].apply(this.d[k + 2], d); - } - this.B--; - if (this.k && 0 == this.B) - for (; c = this.k.pop() ;) Aa(this, c); - return 0 != e; - } - return false; - }; - g.H = function () { - H.I.H.call(this); - delete this.d; - delete this.g; - delete this.k; - }; - var Ba = /^(?:([^:/?#.]+):)?(?:\/\/(?:([^/?#]*)@)?([^/#?]*?)(?::([0-9]+))?(?=[/#?]|$))?([^?#]+)?(?:\?([^#]*))?(?:#(.*))?$/; - - function Ca(a) { - if (Da) { - Da = false; - var b = h.location; - if (b) { - var c = b.href; - if (c && (c = (c = Ca(c)[3] || null) ? decodeURI(c) : c) && c != b.hostname) throw Da = !0, Error(); - } - } - return a.match(Ba); - } - var Da = C; - - function Ea(a, b, c) { - if ("array" == m(b)) - for (var d = 0; d < b.length; d++) Ea(a, String(b[d]), c); - else null != b && c.push("&", a, "" === b ? "" : "=", encodeURIComponent(String(b))); - } - var Fa = /#|$/; - var Ga = {}; - - function Ha(a) { - return Ga[a] || (Ga[a] = String(a).replace(/\-([a-z])/g, function (a, c) { - return c.toUpperCase(); - })); - }; - var Ia = l("yt.dom.getNextId_"); - if (!Ia) { - Ia = function () { - return ++Ja; - }; - r("yt.dom.getNextId_", Ia); - var Ja = 0; - }; - var J = window.yt && window.yt.config_ || {}; - r("yt.config_", J); - r("yt.tokens_", window.yt && window.yt.tokens_ || {}); - r("yt.msgs_", window.yt && window.yt.msgs_ || {}); - - function Ka() { - var b = arguments; - if (1 < b.length) { - var c = b[0]; - J[c] = b[1]; - } else - for (c in b = b[0], b) J[c] = b[c]; - } - - function La(a) { - "function" == m(a) && (a = Ma(a)); - return window.setInterval(a, 250); - } - - function Ma(a) { - return a && window.yterr ? function () { - try { - return a.apply(this, arguments); - } catch (b) { - var c = b; - if (window && window.yterr) { - var d = l("yt.www.errors.log"); - d ? d(c, void 0) : (d = ("ERRORS" in J ? J.ERRORS : void 0) || [], d.push([c, void 0]), Ka("ERRORS", d)); - } - throw b; - } - } : a; - }; - - function Na(a) { - if (a = a || window.event) { - for (var b in a) b in Oa || (this[b] = a[b]); - (b = a.target || a.srcElement) && 3 == b.nodeType && (b = b.parentNode); - this.target = b; - if (b = a.relatedTarget) try { - b = b.nodeName ? b : null; - } catch (c) { - b = null; - } else "mouseover" == this.type ? b = a.fromElement : "mouseout" == this.type && (b = a.toElement); - this.relatedTarget = b; - this.clientX = void 0 != a.clientX ? a.clientX : a.pageX; - this.clientY = void 0 != a.clientY ? a.clientY : a.pageY; - this.keyCode = a.keyCode ? a.keyCode : a.which; - this.charCode = a.charCode || ("keypress" == this.type ? - this.keyCode : 0); - this.altKey = a.altKey; - this.ctrlKey = a.ctrlKey; - this.shiftKey = a.shiftKey; - "MozMousePixelScroll" == this.type ? (this.wheelDeltaX = a.axis == a.HORIZONTAL_AXIS ? a.detail : 0, this.wheelDeltaY = a.axis == a.HORIZONTAL_AXIS ? 0 : a.detail) : window.opera ? (this.wheelDeltaX = 0, this.wheelDeltaY = a.detail) : 0 == a.wheelDelta % 120 ? "WebkitTransform" in document.documentElement.style ? window.chrome && 0 == navigator.platform.indexOf("Mac") ? (this.wheelDeltaX = a.wheelDeltaX / -30, this.wheelDeltaY = a.wheelDeltaY / -30) : (this.wheelDeltaX = - a.wheelDeltaX / -1.2, this.wheelDeltaY = a.wheelDeltaY / -1.2) : (this.wheelDeltaX = 0, this.wheelDeltaY = a.wheelDelta / -1.6) : (this.wheelDeltaX = a.wheelDeltaX / -3, this.wheelDeltaY = a.wheelDeltaY / -3); - } - } - g = Na.prototype; - g.type = ""; - g.target = null; - g.relatedTarget = null; - g.currentTarget = null; - g.data = null; - g.keyCode = 0; - g.charCode = 0; - g.altKey = !1; - g.ctrlKey = !1; - g.shiftKey = !1; - g.clientX = 0; - g.clientY = 0; - g.wheelDeltaX = 0; - g.wheelDeltaY = 0; - var Oa = { - stopImmediatePropagation: 1, - stopPropagation: 1, - preventMouseEvent: 1, - preventManipulation: 1, - preventDefault: 1, - layerX: 1, - layerY: 1, - scale: 1, - rotation: 1 - }; - var x = l("yt.events.listeners_") || {}; - r("yt.events.listeners_", x); - var Pa = l("yt.events.counter_") || { - count: 0 - }; - r("yt.events.counter_", Pa); - - function Qa(a, b, c) { - return la(function (d) { - return d[0] == a && d[1] == b && d[2] == c && 0 == d[4]; - }); - } - - function Ra(a, b, c) { - if (a && (a.addEventListener || a.attachEvent)) { - var d = Qa(a, b, c); - if (!d) { - var d = ++Pa.count + "", - e = !("mouseenter" != b && "mouseleave" != b || !a.addEventListener || "onmouseenter" in document), - f; - f = e ? function (d) { - d = new Na(d); - if (!ta(d.relatedTarget, function (b) { - return b == a; - })) return d.currentTarget = a, d.type = b, c.call(a, d); - } : function (b) { - b = new Na(b); - b.currentTarget = a; - return c.call(a, b); - }; - f = Ma(f); - x[d] = [a, b, c, f, !1]; - a.addEventListener ? "mouseenter" == b && e ? a.addEventListener("mouseover", f, !1) : "mouseleave" == b && e ? a.addEventListener("mouseout", - f, !1) : "mousewheel" == b && "MozBoxSizing" in document.documentElement.style ? a.addEventListener("MozMousePixelScroll", f, !1) : a.addEventListener(b, f, !1) : a.attachEvent("on" + b, f); - } - } - } - - function Sa(a) { - a && ("string" == typeof a && (a = [a]), w(a, function (a) { - if (a in x) { - var c = x[a], - d = c[0], - e = c[1], - f = c[3], - c = c[4]; - d.removeEventListener ? d.removeEventListener(e, f, c) : d.detachEvent && d.detachEvent("on" + e, f); - delete x[a]; - } - })); - }; - - function Ta(a) { - var b = [], - c; - for (c in a) Ea(c, a[c], b); - b[0] = ""; - return b.join(""); - }; - var K = {}, - Ua = [], - I = new H, - Va = {}; - - function Wa() { - w(Ua, function (a) { - a(); - }); - } - - function Xa(a) { - var b = ja(document.getElementsByTagName("yt:" + a)); - a = "yt-" + a; - var c = document; - a = c.querySelectorAll && c.querySelector ? c.querySelectorAll("." + a) : sa(a); - a = ja(a); - return ia(b, a); - } - - function L(a, b) { - return "yt:" == a.tagName.toLowerCase().substr(0, 3) ? a.getAttribute(b) : a ? a.dataset ? a.dataset[Ha(b)] : a.getAttribute("data-" + b) : null; - } - - function Ya() { - I.R.apply(I, arguments); - }; - - function O(a, b, c) { - this.g = b; - this.o = this.d = null; - this.G = this[p] || (this[p] = ++ca); - this.j = 0; - this.F = !1; - this.D = []; - this.k = null; - this.L = c; - this.M = {}; - b = document; - if (a = n(a) ? b.getElementById(a) : a) - if ("iframe" != a.tagName.toLowerCase() && (b = Za(this, a), this.o = a, (c = a.parentNode) && c.replaceChild(b, a), a = b), this.d = a, this.d.id || (b = a = this.d, b = b[p] || (b[p] = ++ca), a.id = "widget" + b), K[this.d.id] = this, window.postMessage) { - this.k = new H; - $a(this); - a = P(this.g, "events"); - for (var d in a) a.hasOwnProperty(d) && this.addEventListener(d, a[d]); - for (var e in Va) ab(this, - e); - } - } - g = O.prototype; - g.Y = function (a, b) { - this.d.width = a; - this.d.height = b; - return this; - }; - g.X = function () { - return this.d; - }; - g.P = function (a) { - this.v(a.event, a); - }; - g.addEventListener = function (a, b) { - var c = b; - "string" == typeof b && (c = function () { - window[b].apply(window, arguments); - }); - this.k.subscribe(a, c); - bb(this, a); - return this; - }; - - function ab(a, b) { - var c = b.split("."); - if (2 != !c.length) { - var d = c[1]; - a.L == c[0] && bb(a, d); - } - } - g.destroy = function () { - this.d.id && (K[this.d.id] = null); - var a = this.k; - a && "function" == typeof a.dispose && a.dispose(); - if (this.o) { - var a = this.d, - b = a.parentNode; - b && b.replaceChild(this.o, a); - } else (a = this.d) && a.parentNode && a.parentNode.removeChild(a); - Q && (Q[this.G] = null); - this.g = null; - var a = this.d, - c; - for (c in x) x[c][0] == a && Sa(c); - this.o = this.d = null; - }; - g.C = function () { - return {}; - }; - - function R(a, b, c) { - c = c || []; - c = Array.prototype.slice.call(c); - b = { - event: "command", - func: b, - args: c - }; - a.F ? a.J(b) : a.D.push(b); - } - g.v = function (a, b) { - if (!this.k.j) { - var c = { - target: this, - data: b - }; - this.k.R(a, c); - Ya(this.L + "." + a, c); - } - }; - - function Za(a, b) { - for (var c = document.createElement("iframe"), d = b.attributes, e = 0, f = d.length; e < f; e++) { - var k = d[e].value; - null != k && "" != k && "null" != k && c.setAttribute(d[e].name, k); - } - c.setAttribute("frameBorder", 0); - c.setAttribute("allowfullscreen", 1); - c.setAttribute("title", "YouTube " + P(a.g, "title")); - (d = P(a.g, "width")) && c.setAttribute("width", d); - (d = P(a.g, "height")) && c.setAttribute("height", d); - var u = a.C(); - u.enablejsapi = window.postMessage ? 1 : 0; - window.location.host && (u.origin = window.location.protocol + "//" + window.location.host); - window.location.href && w(["debugjs", "debugcss"], function (a) { - var b; - b = window.location.href; - var c = b.search(Fa), - d; - i: { - d = 0; - for (var e = a.length; 0 <= (d = b.indexOf(a, d)) && d < c;) { - var f = b.charCodeAt(d - 1); - if (38 == f || 63 == f) - if (f = b.charCodeAt(d + e), !f || 61 == f || 38 == f || 35 == f) break i; - d += e + 1; - } - d = -1; - } - if (0 > d) b = null; - else { - e = b.indexOf("&", d); - if (0 > e || e > c) e = c; - d += a.length + 1; - b = decodeURIComponent(b.substr(d, e - d).replace(/\+/g, " ")); - } - null === b || (u[a] = b); - }); - c.src = P(a.g, "host") + a.K() + "?" + Ta(u); - return c; - } - g.N = function () { - this.d && this.d.contentWindow ? this.J({ - event: "listening" - }) : window.clearInterval(this.j); - }; - - function $a(a) { - cb(a.g, a, a.G); - a.j = La(q(a.N, a)); - Ra(a.d, "load", q(function () { - window.clearInterval(this.j); - this.j = La(q(this.N, this)); - if (window.initialDeliveryComplete) { - clearInterval(this.j); - } - }, a)); - } - - function bb(a, b) { - a.M[b] || (a.M[b] = !0, R(a, "addEventListener", [b])); - } - g.J = function (a) { - a.id = this.G; - var b = []; - F(new va, a, b); - a = b.join(""); - var b = this.g, - c, d = Ca(this.d.src); - c = d[1]; - var e = d[2], - f = d[3], - d = d[4], - k = ""; - c && (k += c + ":"); - f && (k += "//", e && (k += e + "@"), k += f, d && (k += ":" + d)); - c = k; - b = 0 == c.indexOf("https:") ? [c] : b.d ? [c.replace("http:", "https:")] : b.j ? [c] : [c, c.replace("http:", "https:")]; - for (c = 0; c < b.length; c++) { - this.d.contentWindow.postMessage(a, b[c]); - } - }; - var db = "StopIteration" in h ? h.StopIteration : Error("StopIteration"); - - function eb() { } - eb.prototype.next = function () { - throw db; - }; - eb.prototype.g = function () { - return this; - }; - A && E("9"); - !C || E("528"); - B && E("1.9b") || A && E("8") || z && E("9.5") || C && E("528"); - B && !E("8") || A && E("9"); - var fb, gb, hb, ib, jb, mb, nb; - nb = mb = jb = ib = hb = gb = fb = !1; - var S = y; - S && (-1 != S.indexOf("Firefox") ? fb = !0 : -1 != S.indexOf("Camino") ? gb = !0 : -1 != S.indexOf("iPhone") || -1 != S.indexOf("iPod") ? hb = !0 : -1 != S.indexOf("iPad") ? ib = !0 : -1 != S.indexOf("Chrome") ? mb = !0 : -1 != S.indexOf("Android") ? jb = !0 : -1 != S.indexOf("Safari") && (nb = !0)); - var ob = fb, - pb = gb, - qb = hb, - rb = ib, - sb = jb, - tb = mb, - ub = nb; - var vb = "corp.google.com googleplex.com youtube.com youtube-nocookie.com youtubeeducation.com prod.google.com sandbox.google.com docs.google.com drive.google.com mail.google.com plus.google.com play.google.com googlevideo.com talkgadget.google.com survey.g.doubleclick.net youtube.googleapis.com vevo.com".split(" "), - wb = ""; - - function xb() { } - new xb; - new xb; - var T = y, - T = T.toLowerCase(); - if (-1 != T.indexOf("android") && !T.match(/android\D*(\d\.\d)[^\;|\)]*[\;\)]/)) { - var yb = { - cupcake: 1.5, - donut: 1.6, - eclair: 2, - froyo: 2.2, - gingerbread: 2.3, - honeycomb: 3, - "ice cream sandwich": 4, - jellybean: 4.1 - }, - zb = [], - Ab = 0, - Bb; - for (Bb in yb) zb[Ab++] = Bb; - T.match("(" + zb.join("|") + ")"); - }; - var Cb = l("yt.net.ping.workerUrl_") || null; - r("yt.net.ping.workerUrl_", Cb); - var U = window.performance || window.mozPerformance || window.msPerformance || window.webkitPerformance || {}; - q(U.clearResourceTimings || U.webkitClearResourceTimings || U.mozClearResourceTimings || U.msClearResourceTimings || U.oClearResourceTimings || aa, U); - var Db; - var Eb = y, - Fb = Eb.match(/\((iPad|iPhone|iPod)( Simulator)?;/); - if (!Fb || 2 > Fb.length) Db = void 0; - else { - var Gb = Eb.match(/\((iPad|iPhone|iPod)( Simulator)?; (U; )?CPU (iPhone )?OS (\d_\d)[_ ]/); - Db = Gb && 6 == Gb.length ? Number(Gb[5].replace("_", ".")) : 0; - } - 0 <= Db && 0 <= y.search("Safari") && y.search("Version"); - - function V(a) { - return (a = a.exec(y)) ? a[1] : ""; - } (function () { - if (ob) return V(/Firefox\/([0-9.]+)/); - if (A || z) return D; - if (tb) return V(/Chrome\/([0-9.]+)/); - if (ub) return V(/Version\/([0-9.]+)/); - if (qb || rb) { - var a; - if (a = /Version\/(\S+).*Mobile\/(\S+)/.exec(y)) return a[1] + "." + a[2]; - } else { - if (sb) return (a = V(/Android\s+([0-9.]+)/)) ? a : V(/Version\/([0-9.]+)/); - if (pb) return V(/Camino\/([0-9.]+)/); - } - return ""; - })(); - - function Hb() { }; - - function Ib() { } - s(Ib, Hb); - - function W(a) { - this.d = a; - } - s(W, Ib); - W.prototype.isAvailable = function () { - if (!this.d) return !1; - try { - return this.d.setItem("__sak", "1"), this.d.removeItem("__sak"), !0; - } catch (a) { - return !1; - } - }; - W.prototype.g = function (a) { - var b = 0, - c = this.d, - d = new eb; - d.next = function () { - if (b >= c.length) throw db; - var d; - d = c.key(b++); - if (a) return d; - d = c.getItem(d); - if (!n(d)) throw "Storage mechanism: Invalid value was encountered"; - return d; - }; - return d; - }; - W.prototype.key = function (a) { - return this.d.key(a); - }; - - function Jb() { - var a = null; - try { - a = window.localStorage || null; - } catch (b) { } - this.d = a; - } - s(Jb, W); - - function Kb() { - var a = null; - try { - a = window.sessionStorage || null; - } catch (b) { } - this.d = a; - } - s(Kb, W); - (new Jb).isAvailable(); - (new Kb).isAvailable(); - - function Lb(a) { - return (0 == a.search("cue") || 0 == a.search("load")) && "loadModule" != a; - } - - function Mb(a) { - return 0 == a.search("get") || 0 == a.search("is"); - }; - - function X(a) { - this.g = a || {}; - this.defaults = {}; - this.defaults.host = "http://www.youtube.com"; - this.defaults.title = ""; - this.j = this.d = !1; - a = document.getElementById("www-widgetapi-script"); - if (this.d = !!("https:" == document.location.protocol || a && 0 == a.src.indexOf("https:"))) { - a = [this.g, window.YTConfig || {}, this.defaults]; - for (var b = 0; b < a.length; b++) a[b].host && (a[b].host = a[b].host.replace("http://", "https://")); - } - } - var Q = null; - - function P(a, b) { - for (var c = [a.g, window.YTConfig || {}, a.defaults], d = 0; d < c.length; d++) { - var e = c[d][b]; - if (void 0 != e) return e; - } - return null; - } - - function cb(a, b, c) { - Q || (Q = {}, Ra(window, "message", q(a.k, a))); - Q[c] = b; - } - X.prototype.k = function (a) { - var b; - (b = a.origin == P(this, "host")) || ((b = a.origin) && b == wb ? b = true : (new RegExp("^(https?:)?//([a-z0-9-]{1,63}\\.)*(" + vb.join("|").replace(/\./g, ".") + ")(:[0-9]+)?([/?#]|$)", "i")).test(b) ? (wb = b, b = !0) : b = !1); - if (b) { - var c; - try { - c = ua(a.data); - } catch (d) { - return; - } - this.j = !0; - this.d || 0 != a.origin.indexOf("https:") || (this.d = true); - if (a = Q[c.id]) a.F = !0, a.F && (w(a.D, a.J, a), a.D.length = 0), a.P(c); - } - }; - - function Nb(a) { - X.call(this, a); - this.defaults.title = "video player"; - this.defaults.videoId = ""; - this.defaults.width = 640; - this.defaults.height = 360; - } - s(Nb, X); - - function Y(a, b) { - var c = new Nb(b); - O.call(this, a, c, "player"); - this.A = {}; - this.t = {}; - } - s(Y, O); - - function Ob(a) { - if ("iframe" != a.tagName.toLowerCase()) { - var b = L(a, "videoid"); - if (b) { - var c = L(a, "width"), - d = L(a, "height"); - new Y(a, { - videoId: b, - width: c, - height: d - }); - } - } - } - g = Y.prototype; - g.K = function () { - return "/embed/" + P(this.g, "videoId"); - }; - g.C = function () { - var a; - if (P(this.g, "playerVars")) { - a = P(this.g, "playerVars"); - var b = {}, - c; - for (c in a) b[c] = a[c]; - a = b; - } else a = {}; - return a; - }; - g.P = function (a) { - var b = a.event; - a = a.info; - switch (b) { - case "apiInfoDelivery": - if (ba(a)) - for (var c in a) this.t[c] = a[c]; - break; - case "infoDelivery": - Pb(this, a); - break; - case "initialDelivery": - window.clearInterval(this.j); - window.initialDeliveryComplete = true; - this.A = {}; - this.t = {}; - Qb(this, a.apiInterface); - Pb(this, a); - break; - default: - this.v(b, a); - } - }; - - function Pb(a, b) { - if (ba(b)) - for (var c in b) a.A[c] = b[c]; - } - - function Qb(a, b) { - w(b, function (a) { - this[a] || (Lb(a) ? this[a] = function () { - this.A = {}; - this.t = {}; - R(this, a, arguments); - return this; - } : Mb(a) ? this[a] = function () { - var b = 0; - 0 == a.search("get") ? b = 3 : 0 == a.search("is") && (b = 2); - return this.A[a.charAt(b).toLowerCase() + a.substr(b + 1)]; - } : this[a] = function () { - R(this, a, arguments); - return this; - }); - }, a); - } - g.aa = function () { - var a = this.d.cloneNode(false), - b = this.A.videoData, - c = P(this.g, "host"); - a.src = b && b.video_id ? c + "/embed/" + b.video_id : a.src; - b = document.createElement("div"); - b.appendChild(a); - return b.innerHTML; - }; - g.$ = function (a) { - return this.t.namespaces ? a ? this.t[a].options || [] : this.t.namespaces || [] : []; - }; - g.Z = function (a, b) { - if (this.t.namespaces && a && b) return this.t[a][b]; - }; - - function Rb(a) { - X.call(this, a); - this.defaults.title = "Thumbnail"; - this.defaults.videoId = ""; - this.defaults.width = 120; - this.defaults.height = 68; - } - s(Rb, X); - - function Z(a, b) { - var c = new Rb(b); - O.call(this, a, c, "thumbnail"); - } - s(Z, O); - - function Sb(a) { - if ("iframe" != a.tagName.toLowerCase()) { - var b = L(a, "videoid"); - if (b) { - b = { - videoId: b, - events: {} - }; - b.width = L(a, "width"); - b.height = L(a, "height"); - b.thumbWidth = L(a, "thumb-width"); - b.thumbHeight = L(a, "thumb-height"); - b.thumbAlign = L(a, "thumb-align"); - var c = L(a, "onclick"); - c && (b.events.onClick = c); - new Z(a, b); - } - } - } - Z.prototype.K = function () { - return "/embed/" + P(this.g, "videoId"); - }; - Z.prototype.C = function () { - return { - player: 0, - thumb_width: P(this.g, "thumbWidth"), - thumb_height: P(this.g, "thumbHeight"), - thumb_align: P(this.g, "thumbAlign") - }; - }; - Z.prototype.v = function (a, b) { - Z.I.v.call(this, a, b ? b.info : void 0); - }; - - function Tb(a) { - X.call(this, a); - this.defaults.host = "https://www.youtube.com"; - this.defaults.title = "upload widget"; - this.defaults.width = 640; - this.defaults.height = .67 * P(this, "width"); - } - s(Tb, X); - - function $(a, b) { - var c = new Tb(b); - O.call(this, a, c, "upload"); - } - s($, O); - g = $.prototype; - g.K = function () { - return "/upload_embed"; - }; - g.C = function () { - var a = {}, - b = P(this.g, "webcamOnly"); - null != b && (a.webcam_only = b); - return a; - }; - g.v = function (a, b) { - $.I.v.call(this, a, b); - "onApiReady" == a && R(this, "hostWindowReady"); - }; - g.S = function () { - R(this, "setVideoDescription", arguments); - }; - g.U = function () { - R(this, "setVideoKeywords", arguments); - }; - g.V = function () { - R(this, "setVideoPrivacy", arguments); - }; - g.T = function () { - R(this, "setVideoDraftPrivacy", arguments); - }; - g.W = function () { - R(this, "setVideoTitle", arguments); - }; - r("YT.PlayerState.UNSTARTED", -1); - r("YT.PlayerState.ENDED", 0); - r("YT.PlayerState.PLAYING", 1); - r("YT.PlayerState.PAUSED", 2); - r("YT.PlayerState.BUFFERING", 3); - r("YT.PlayerState.CUED", 5); - r("YT.UploadWidgetEvent.API_READY", "onApiReady"); - r("YT.UploadWidgetEvent.UPLOAD_SUCCESS", "onUploadSuccess"); - r("YT.UploadWidgetEvent.PROCESSING_COMPLETE", "onProcessingComplete"); - r("YT.UploadWidgetEvent.STATE_CHANGE", "onStateChange"); - r("YT.UploadWidgetState.IDLE", 0); - r("YT.UploadWidgetState.PENDING", 1); - r("YT.UploadWidgetState.ERROR", 2); - r("YT.UploadWidgetState.PLAYBACK", 3); - r("YT.UploadWidgetState.RECORDING", 4); - r("YT.UploadWidgetState.STOPPED", 5); - r("YT.get", function (a) { - return K[a]; - }); - r("YT.scan", Wa); - r("YT.subscribe", function (a, b, c) { - I.subscribe(a, b, c); - Va[a] = true; - for (var d in K) ab(K[d], a); - }); - r("YT.unsubscribe", function (a, b, c) { - za(a, b, c); - }); - r("YT.Player", Y); - r("YT.Thumbnail", Z); - r("YT.UploadWidget", $); - O.prototype.destroy = O.prototype.destroy; - O.prototype.setSize = O.prototype.Y; - O.prototype.getIframe = O.prototype.X; - O.prototype.addEventListener = O.prototype.addEventListener; - Y.prototype.getVideoEmbedCode = Y.prototype.aa; - Y.prototype.getOptions = Y.prototype.$; - Y.prototype.getOption = Y.prototype.Z; - $.prototype.setVideoDescription = $.prototype.S; - $.prototype.setVideoKeywords = $.prototype.U; - $.prototype.setVideoPrivacy = $.prototype.V; - $.prototype.setVideoTitle = $.prototype.W; - $.prototype.setVideoDraftPrivacy = $.prototype.T; - Ua.push(function () { - var a = Xa("player"); - w(a, Ob); - }); - Ua.push(function () { - var a = Xa("thumbnail"); - w(a, Sb); - }); - YTConfig.parsetags && "onload" != YTConfig.parsetags || Wa(); - var Ub = l("onYTReady"); - Ub && Ub(); - var Vb = l("onYouTubeIframeAPIReady"); - Vb && Vb(); - var Wb = l("onYouTubePlayerAPIReady"); - Wb && Wb(); - - /* jshint ignore:end */ - } - }); - - return YouTubePlayerAPI; + // This code is from https://s.ytimg.com/yts/jsbin/www-widgetapi-vflBfDu58/www-widgetapi.js. I pull it in locally as to not need to relax content_security_policy. + // NOTE: I have patched a bug in this code. It is documented here: https://code.google.com/p/gdata-issues/issues/detail?id=6402 + _loadWidgetAPI: function() { + /* jshint ignore:start */ + var g, h = window; + + function l(a) { + a = a.split("."); + for (var b = h, c; c = a.shift();) { + if (null != b[c]) { + b = b[c]; + } else { + return null; + } + } + return b; + } + + function aa() { + } + + function m(a) { + var b = typeof a; + if ("object" == b) { + if (a) { + if (a instanceof Array) { + return "array"; + } + if (a instanceof Object) { + return b; + } + var c = Object.prototype.toString.call(a); + if ("[object Window]" == c) { + return "object"; + } + if ("[object Array]" == c || "number" == typeof a.length && "undefined" != typeof a.splice && "undefined" != typeof a.propertyIsEnumerable && !a.propertyIsEnumerable("splice")) { + return "array"; + } + if ("[object Function]" == c || "undefined" != typeof a.call && "undefined" != typeof a.propertyIsEnumerable && !a.propertyIsEnumerable("call")) { + return "function"; + } + } else { + return "null"; + } + } else if ("function" == b && "undefined" == typeof a.call) { + return "object"; + } + return b; + } + + function n(a) { + return "string" == typeof a; + } + + function ba(a) { + var b = typeof a; + return "object" == b && null != a || "function" == b; + } + + var p = "closure_uid_" + (1E9 * Math.random() >>> 0), + ca = 0; + + function da(a) { + return a.call.apply(a.bind, arguments); + } + + function ea(a, b) { + if (!a) { + throw Error(); + } + if (2 < arguments.length) { + var d = Array.prototype.slice.call(arguments, 2); + return function() { + var c = Array.prototype.slice.call(arguments); + Array.prototype.unshift.apply(c, d); + return a.apply(b, c); + }; + } + return function() { + return a.apply(b, arguments); + }; + } + + function q() { + q = Function.prototype.bind && -1 != Function.prototype.bind.toString().indexOf("native code") ? da : ea; + return q.apply(null, arguments); + } + + function r(a, b) { + var c = a.split("."), + d = h; + c[0] in d || !d.execScript || d.execScript("var " + c[0]); + for (var e; c.length && (e = c.shift());) { + c.length || void 0 === b ? d[e] ? d = d[e] : d = d[e] = {} : d[e] = b; + } + } + + function s(a, b) { + + function c() { + } + + c.prototype = b.prototype; + a.I = b.prototype; + a.prototype = new c; + a.base = function(a, c) { + return b.prototype[c].apply(a, Array.prototype.slice.call(arguments, 2)); + }; + } + + Function.prototype.bind = Function.prototype.bind || function(a, b) { + if (1 < arguments.length) { + var c = Array.prototype.slice.call(arguments, 1); + c.unshift(this, a); + return q.apply(null, c); + } + return q(this, a); + }; + var fa = String.prototype.trim ? function(a) { + return a.trim(); + } : function(a) { + return a.replace(/^[\s\xa0]+|[\s\xa0]+$/g, ""); + }; + + function t(a, b) { + return a < b ? -1 : a > b ? 1 : 0; + } + + ; + var v = Array.prototype, + ga = v.indexOf ? function(a, b, c) { + return v.indexOf.call(a, b, c); + } : function(a, b, c) { + c = null == c ? 0 : 0 > c ? Math.max(0, a.length + c) : c; + if (n(a)) { + return n(b) && 1 == b.length ? a.indexOf(b, c) : -1; + } + for (; c < a.length; c++) { + if (c in a && a[c] === b) { + return c; + } + } + return -1; + }, + w = v.forEach ? function(a, b, c) { + v.forEach.call(a, b, c); + } : function(a, b, c) { + for (var d = a.length, e = n(a) ? a.split("") : a, f = 0; f < d; f++) { + f in e && b.call(c, e[f], f, a); + } + }; + + function ha(a, b) { + var c; + t: { + c = a.length; + for (var d = n(a) ? a.split("") : a, e = 0; e < c; e++) { + if (e in d && b.call(void 0, d[e], e, a)) { + c = e; + break t; + } + } + c = -1; + } + return 0 > c ? null : n(a) ? a.charAt(c) : a[c]; + } + + function ia() { + return v.concat.apply(v, arguments); + } + + function ja(a) { + var b = a.length; + if (0 < b) { + for (var c = Array(b), d = 0; d < b; d++) { + c[d] = a[d]; + } + return c; + } + return []; + } + + function ka(a, b, c) { + return 2 >= arguments.length ? v.slice.call(a, b) : v.slice.call(a, b, c); + } + + ; + + function la(a) { + var b = x, + c; + for (c in b) { + if (a.call(void 0, b[c], c, b)) { + return c; + } + } + } + + ; + var y; + t: { + var ma = h.navigator; + if (ma) { + var na = ma.userAgent; + if (na) { + y = na; + break t; + } + } + y = ""; + } + ; + var z = -1 != y.indexOf("Opera") || -1 != y.indexOf("OPR"), + A = -1 != y.indexOf("Trident") || -1 != y.indexOf("MSIE"), + B = -1 != y.indexOf("Gecko") && -1 == y.toLowerCase().indexOf("webkit") && !(-1 != y.indexOf("Trident") || -1 != y.indexOf("MSIE")), + C = -1 != y.toLowerCase().indexOf("webkit"); + + function oa() { + var a = h.document; + return a ? a.documentMode : void 0; + } + + var D = function() { + var a = "", + b; + if (z && h.opera) { + return a = h.opera.version, "function" == m(a) ? a() : a; + } + B ? b = /rv\:([^\);]+)(\)|;)/ : A ? b = /\b(?:MSIE|rv)[: ]([^\);]+)(\)|;)/ : C && (b = /WebKit\/(\S+)/); + b && (a = (a = b.exec(y)) ? a[1] : ""); + return A && (b = oa(), b > parseFloat(a)) ? String(b) : a; + }(), + pa = {}; + + function E(a) { + var b; + if (!(b = pa[a])) { + b = 0; + for (var c = fa(String(D)).split("."), d = fa(String(a)).split("."), e = Math.max(c.length, d.length), f = 0; 0 == b && f < e; f++) { + var k = c[f] || "", + u = d[f] || "", + kb = RegExp("(\\d*)(\\D*)", "g"), + lb = RegExp("(\\d*)(\\D*)", "g"); + do { + var M = kb.exec(k) || ["", "", ""], + N = lb.exec(u) || ["", "", ""]; + if (0 == M[0].length && 0 == N[0].length) { + break; + } + b = t(0 == M[1].length ? 0 : parseInt(M[1], 10), 0 == N[1].length ? 0 : parseInt(N[1], 10)) || t(0 == M[2].length, 0 == N[2].length) || t(M[2], N[2]); + } while (0 == b); + } + b = pa[a] = 0 <= b; + } + return b; + } + + var qa = h.document, + ra = qa && A ? oa() || ("CSS1Compat" == qa.compatMode ? parseInt(D, 10) : 5) : void 0; + !B && !A || A && A && 9 <= ra || B && E("1.9.1"); + A && E("9"); + + function sa(a) { + var b, c, d, e; + b = document; + if (b.querySelectorAll && b.querySelector && a) { + return b.querySelectorAll("" + (a ? "." + a : "")); + } + if (a && b.getElementsByClassName) { + var f = b.getElementsByClassName(a); + return f; + } + f = b.getElementsByTagName("*"); + if (a) { + e = {}; + for (c = d = 0; b = f[c]; c++) { + var k = b.className, + u; + if (u = "function" == typeof k.split) { + u = 0 <= ga(k.split(/\s+/), a); + } + u && (e[d++] = b); + } + e.length = d; + return e; + } + return f; + } + + function ta(a, b) { + for (var c = 0; a;) { + if (b(a)) { + return a; + } + a = a.parentNode; + c++; + } + return null; + } + + ; + + function ua(a) { + a = String(a); + if (/^\s*$/.test(a) ? 0 : /^[\],:{}\s\u2028\u2029]*$/.test(a.replace(/\\["\\\/bfnrtu]/g, "@").replace(/"[^"\\\n\r\u2028\u2029\x00-\x08\x0a-\x1f]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, "]").replace(/(?:^|:|,)(?:[\s\u2028\u2029]*\[)+/g, ""))) { + try { + return eval("(" + a + ")"); + } catch(b) { + } + } + throw Error("Invalid JSON string: " + a); + } + + function va() { + } + + function F(a, b, c) { + switch (typeof b) { + case "string": + wa(b, c); + break; + case "number": + c.push(isFinite(b) && !isNaN(b) ? b : "null"); + break; + case "boolean": + c.push(b); + break; + case "undefined": + c.push("null"); + break; + case "object": + if (null == b) { + c.push("null"); + break; + } + if ("array" == m(b)) { + var d = b.length; + c.push("["); + for (var e = "", f = 0; f < d; f++) { + c.push(e), F(a, b[f], c), e = ","; + } + c.push("]"); + break; + } + c.push("{"); + d = ""; + for (e in b) { + Object.prototype.hasOwnProperty.call(b, e) && (f = b[e], "function" != typeof f && (c.push(d), wa(e, c), c.push(":"), F(a, f, c), d = ",")); + } + c.push("}"); + break; + case "function": + break; + default: + throw Error("Unknown type: " + typeof b); + } + } + + var xa = { + '"': '\\"', + "\\": "\\\\", + "/": "\\/", + "\b": "\\b", + "\f": "\\f", + "\n": "\\n", + "\r": "\\r", + "\t": "\\t", + "\x0B": "\\u000b" + }, + ya = /\uffff/.test("\uffff") ? /[\\\"\x00-\x1f\x7f-\uffff]/g : /[\\\"\x00-\x1f\x7f-\xff]/g; + + function wa(a, b) { + b.push('"', a.replace(ya, function(a) { + if (a in xa) { + return xa[a]; + } + var b = a.charCodeAt(0), + e = "\\u"; + 16 > b ? e += "000" : 256 > b ? e += "00" : 4096 > b && (e += "0"); + return xa[a] = e + b.toString(16); + }), '"'); + } + + ; + + function G() { + this.j = this.j; + this.o = this.o; + } + + G.prototype.j = !1; + G.prototype.dispose = function() { + this.j || (this.j = !0, this.H()); + }; + G.prototype.H = function() { + if (this.o) { + for (; this.o.length;) { + this.o.shift()(); + } + } + }; + + function H() { + G.call(this); + this.d = []; + this.g = {}; + } + + s(H, G); + g = H.prototype; + g.O = 1; + g.B = 0; + g.subscribe = function(a, b, c) { + var d = this.g[a]; + d || (d = this.g[a] = []); + var e = this.O; + this.d[e] = a; + this.d[e + 1] = b; + this.d[e + 2] = c; + this.O = e + 3; + d.push(e); + return e; + }; + + function za(a, b, c) { + var d = I; + if (a = d.g[a]) { + var e = d.d; + (a = ha(a, function(a) { + return e[a + 1] == b && e[a + 2] == c; + })) && Aa(d, a); + } + } + + function Aa(a, b) { + if (0 != a.B) { + a.k || (a.k = []), a.k.push(b); + } else { + var c = a.d[b]; + if (c) { + if (c = a.g[c]) { + var d = ga(c, b); + 0 <= d && v.splice.call(c, d, 1); + } + delete a.d[b]; + delete a.d[b + 1]; + delete a.d[b + 2]; + } + } + } + + g.R = function(a) { + var c = this.g[a]; + if (c) { + this.B++; + for (var d = ka(arguments, 1), e = 0, f = c.length; e < f; e++) { + var k = c[e]; + this.d[k + 1].apply(this.d[k + 2], d); + } + this.B--; + if (this.k && 0 == this.B) { + for (; c = this.k.pop();) { + Aa(this, c); + } + } + return 0 != e; + } + return false; + }; + g.H = function() { + H.I.H.call(this); + delete this.d; + delete this.g; + delete this.k; + }; + var Ba = /^(?:([^:/?#.]+):)?(?:\/\/(?:([^/?#]*)@)?([^/#?]*?)(?::([0-9]+))?(?=[/#?]|$))?([^?#]+)?(?:\?([^#]*))?(?:#(.*))?$/; + + function Ca(a) { + if (Da) { + Da = false; + var b = h.location; + if (b) { + var c = b.href; + if (c && (c = (c = Ca(c)[3] || null) ? decodeURI(c) : c) && c != b.hostname) { + throw Da = !0, Error(); + } + } + } + return a.match(Ba); + } + + var Da = C; + + function Ea(a, b, c) { + if ("array" == m(b)) { + for (var d = 0; d < b.length; d++) { + Ea(a, String(b[d]), c); + } + } else { + null != b && c.push("&", a, "" === b ? "" : "=", encodeURIComponent(String(b))); + } + } + + var Fa = /#|$/; + var Ga = {}; + + function Ha(a) { + return Ga[a] || (Ga[a] = String(a).replace(/\-([a-z])/g, function(a, c) { + return c.toUpperCase(); + })); + } + + ; + var Ia = l("yt.dom.getNextId_"); + if (!Ia) { + Ia = function() { + return ++Ja; + }; + r("yt.dom.getNextId_", Ia); + var Ja = 0; + } + ; + var J = window.yt && window.yt.config_ || {}; + r("yt.config_", J); + r("yt.tokens_", window.yt && window.yt.tokens_ || {}); + r("yt.msgs_", window.yt && window.yt.msgs_ || {}); + + function Ka() { + var b = arguments; + if (1 < b.length) { + var c = b[0]; + J[c] = b[1]; + } else { + for (c in b = b[0], b) { + J[c] = b[c]; + } + } + } + + function La(a) { + "function" == m(a) && (a = Ma(a)); + return window.setInterval(a, 250); + } + + function Ma(a) { + return a && window.yterr ? function() { + try { + return a.apply(this, arguments); + } catch(b) { + var c = b; + if (window && window.yterr) { + var d = l("yt.www.errors.log"); + d ? d(c, void 0) : (d = ("ERRORS" in J ? J.ERRORS : void 0) || [], d.push([c, void 0]), Ka("ERRORS", d)); + } + throw b; + } + } : a; + } + + ; + + function Na(a) { + if (a = a || window.event) { + for (var b in a) { + b in Oa || (this[b] = a[b]); + } + (b = a.target || a.srcElement) && 3 == b.nodeType && (b = b.parentNode); + this.target = b; + if (b = a.relatedTarget) { + try { + b = b.nodeName ? b : null; + } catch(c) { + b = null; + } + } else { + "mouseover" == this.type ? b = a.fromElement : "mouseout" == this.type && (b = a.toElement); + } + this.relatedTarget = b; + this.clientX = void 0 != a.clientX ? a.clientX : a.pageX; + this.clientY = void 0 != a.clientY ? a.clientY : a.pageY; + this.keyCode = a.keyCode ? a.keyCode : a.which; + this.charCode = a.charCode || ("keypress" == this.type ? + this.keyCode : 0); + this.altKey = a.altKey; + this.ctrlKey = a.ctrlKey; + this.shiftKey = a.shiftKey; + "MozMousePixelScroll" == this.type ? (this.wheelDeltaX = a.axis == a.HORIZONTAL_AXIS ? a.detail : 0, this.wheelDeltaY = a.axis == a.HORIZONTAL_AXIS ? 0 : a.detail) : window.opera ? (this.wheelDeltaX = 0, this.wheelDeltaY = a.detail) : 0 == a.wheelDelta % 120 ? "WebkitTransform" in document.documentElement.style ? window.chrome && 0 == navigator.platform.indexOf("Mac") ? (this.wheelDeltaX = a.wheelDeltaX / -30, this.wheelDeltaY = a.wheelDeltaY / -30) : (this.wheelDeltaX = + a.wheelDeltaX / -1.2, this.wheelDeltaY = a.wheelDeltaY / -1.2) : (this.wheelDeltaX = 0, this.wheelDeltaY = a.wheelDelta / -1.6) : (this.wheelDeltaX = a.wheelDeltaX / -3, this.wheelDeltaY = a.wheelDeltaY / -3); + } + } + + g = Na.prototype; + g.type = ""; + g.target = null; + g.relatedTarget = null; + g.currentTarget = null; + g.data = null; + g.keyCode = 0; + g.charCode = 0; + g.altKey = !1; + g.ctrlKey = !1; + g.shiftKey = !1; + g.clientX = 0; + g.clientY = 0; + g.wheelDeltaX = 0; + g.wheelDeltaY = 0; + var Oa = { + stopImmediatePropagation: 1, + stopPropagation: 1, + preventMouseEvent: 1, + preventManipulation: 1, + preventDefault: 1, + layerX: 1, + layerY: 1, + scale: 1, + rotation: 1 + }; + var x = l("yt.events.listeners_") || {}; + r("yt.events.listeners_", x); + var Pa = l("yt.events.counter_") || { + count: 0 + }; + r("yt.events.counter_", Pa); + + function Qa(a, b, c) { + return la(function(d) { + return d[0] == a && d[1] == b && d[2] == c && 0 == d[4]; + }); + } + + function Ra(a, b, c) { + if (a && (a.addEventListener || a.attachEvent)) { + var d = Qa(a, b, c); + if (!d) { + var d = ++Pa.count + "", + e = !("mouseenter" != b && "mouseleave" != b || !a.addEventListener || "onmouseenter" in document), + f; + f = e ? function(d) { + d = new Na(d); + if (!ta(d.relatedTarget, function(b) { + return b == a; + })) { + return d.currentTarget = a, d.type = b, c.call(a, d); + } + } : function(b) { + b = new Na(b); + b.currentTarget = a; + return c.call(a, b); + }; + f = Ma(f); + x[d] = [a, b, c, f, !1]; + a.addEventListener ? "mouseenter" == b && e ? a.addEventListener("mouseover", f, !1) : "mouseleave" == b && e ? a.addEventListener("mouseout", + f, !1) : "mousewheel" == b && "MozBoxSizing" in document.documentElement.style ? a.addEventListener("MozMousePixelScroll", f, !1) : a.addEventListener(b, f, !1) : a.attachEvent("on" + b, f); + } + } + } + + function Sa(a) { + a && ("string" == typeof a && (a = [a]), w(a, function(a) { + if (a in x) { + var c = x[a], + d = c[0], + e = c[1], + f = c[3], + c = c[4]; + d.removeEventListener ? d.removeEventListener(e, f, c) : d.detachEvent && d.detachEvent("on" + e, f); + delete x[a]; + } + })); + } + + ; + + function Ta(a) { + var b = [], + c; + for (c in a) { + Ea(c, a[c], b); + } + b[0] = ""; + return b.join(""); + } + + ; + var K = {}, + Ua = [], + I = new H, + Va = {}; + + function Wa() { + w(Ua, function(a) { + a(); + }); + } + + function Xa(a) { + var b = ja(document.getElementsByTagName("yt:" + a)); + a = "yt-" + a; + var c = document; + a = c.querySelectorAll && c.querySelector ? c.querySelectorAll("." + a) : sa(a); + a = ja(a); + return ia(b, a); + } + + function L(a, b) { + return "yt:" == a.tagName.toLowerCase().substr(0, 3) ? a.getAttribute(b) : a ? a.dataset ? a.dataset[Ha(b)] : a.getAttribute("data-" + b) : null; + } + + function Ya() { + I.R.apply(I, arguments); + } + + ; + + function O(a, b, c) { + this.g = b; + this.o = this.d = null; + this.G = this[p] || (this[p] = ++ca); + this.j = 0; + this.F = !1; + this.D = []; + this.k = null; + this.L = c; + this.M = {}; + b = document; + if (a = n(a) ? b.getElementById(a) : a) { + if ("iframe" != a.tagName.toLowerCase() && (b = Za(this, a), this.o = a, (c = a.parentNode) && c.replaceChild(b, a), a = b), this.d = a, this.d.id || (b = a = this.d, b = b[p] || (b[p] = ++ca), a.id = "widget" + b), K[this.d.id] = this, window.postMessage) { + this.k = new H; + $a(this); + a = P(this.g, "events"); + for (var d in a) { + a.hasOwnProperty(d) && this.addEventListener(d, a[d]); + } + for (var e in Va) { + ab(this, + e); + } + } + } + } + + g = O.prototype; + g.Y = function(a, b) { + this.d.width = a; + this.d.height = b; + return this; + }; + g.X = function() { + return this.d; + }; + g.P = function(a) { + this.v(a.event, a); + }; + g.addEventListener = function(a, b) { + var c = b; + "string" == typeof b && (c = function() { + window[b].apply(window, arguments); + }); + this.k.subscribe(a, c); + bb(this, a); + return this; + }; + + function ab(a, b) { + var c = b.split("."); + if (2 != !c.length) { + var d = c[1]; + a.L == c[0] && bb(a, d); + } + } + + g.destroy = function() { + this.d.id && (K[this.d.id] = null); + var a = this.k; + a && "function" == typeof a.dispose && a.dispose(); + if (this.o) { + var a = this.d, + b = a.parentNode; + b && b.replaceChild(this.o, a); + } else { + (a = this.d) && a.parentNode && a.parentNode.removeChild(a); + } + Q && (Q[this.G] = null); + this.g = null; + var a = this.d, + c; + for (c in x) { + x[c][0] == a && Sa(c); + } + this.o = this.d = null; + }; + g.C = function() { + return {}; + }; + + function R(a, b, c) { + c = c || []; + c = Array.prototype.slice.call(c); + b = { + event: "command", + func: b, + args: c + }; + a.F ? a.J(b) : a.D.push(b); + } + + g.v = function(a, b) { + if (!this.k.j) { + var c = { + target: this, + data: b + }; + this.k.R(a, c); + Ya(this.L + "." + a, c); + } + }; + + function Za(a, b) { + for (var c = document.createElement("iframe"), d = b.attributes, e = 0, f = d.length; e < f; e++) { + var k = d[e].value; + null != k && "" != k && "null" != k && c.setAttribute(d[e].name, k); + } + c.setAttribute("frameBorder", 0); + c.setAttribute("allowfullscreen", 1); + c.setAttribute("title", "YouTube " + P(a.g, "title")); + (d = P(a.g, "width")) && c.setAttribute("width", d); + (d = P(a.g, "height")) && c.setAttribute("height", d); + var u = a.C(); + u.enablejsapi = window.postMessage ? 1 : 0; + window.location.host && (u.origin = window.location.protocol + "//" + window.location.host); + window.location.href && w(["debugjs", "debugcss"], function(a) { + var b; + b = window.location.href; + var c = b.search(Fa), + d; + i: { + d = 0; + for (var e = a.length; 0 <= (d = b.indexOf(a, d)) && d < c;) { + var f = b.charCodeAt(d - 1); + if (38 == f || 63 == f) { + if (f = b.charCodeAt(d + e), !f || 61 == f || 38 == f || 35 == f) { + break i; + } + } + d += e + 1; + } + d = -1; + } + if (0 > d) { + b = null; + } else { + e = b.indexOf("&", d); + if (0 > e || e > c) { + e = c; + } + d += a.length + 1; + b = decodeURIComponent(b.substr(d, e - d).replace(/\+/g, " ")); + } + null === b || (u[a] = b); + }); + c.src = P(a.g, "host") + a.K() + "?" + Ta(u); + return c; + } + + g.N = function() { + this.d && this.d.contentWindow ? this.J({ + event: "listening" + }) : window.clearInterval(this.j); + }; + + function $a(a) { + cb(a.g, a, a.G); + a.j = La(q(a.N, a)); + Ra(a.d, "load", q(function() { + window.clearInterval(this.j); + this.j = La(q(this.N, this)); + if (window.initialDeliveryComplete) { + clearInterval(this.j); + } + }, a)); + } + + function bb(a, b) { + a.M[b] || (a.M[b] = !0, R(a, "addEventListener", [b])); + } + + g.J = function(a) { + a.id = this.G; + var b = []; + F(new va, a, b); + a = b.join(""); + var b = this.g, + c, d = Ca(this.d.src); + c = d[1]; + var e = d[2], + f = d[3], + d = d[4], + k = ""; + c && (k += c + ":"); + f && (k += "//", e && (k += e + "@"), k += f, d && (k += ":" + d)); + c = k; + b = 0 == c.indexOf("https:") ? [c] : b.d ? [c.replace("http:", "https:")] : b.j ? [c] : [c, c.replace("http:", "https:")]; + for (c = 0; c < b.length; c++) { + this.d.contentWindow.postMessage(a, b[c]); + } + }; + var db = "StopIteration" in h ? h.StopIteration : Error("StopIteration"); + + function eb() { + } + + eb.prototype.next = function() { + throw db; + }; + eb.prototype.g = function() { + return this; + }; + A && E("9"); + !C || E("528"); + B && E("1.9b") || A && E("8") || z && E("9.5") || C && E("528"); + B && !E("8") || A && E("9"); + var fb, gb, hb, ib, jb, mb, nb; + nb = mb = jb = ib = hb = gb = fb = !1; + var S = y; + S && (-1 != S.indexOf("Firefox") ? fb = !0 : -1 != S.indexOf("Camino") ? gb = !0 : -1 != S.indexOf("iPhone") || -1 != S.indexOf("iPod") ? hb = !0 : -1 != S.indexOf("iPad") ? ib = !0 : -1 != S.indexOf("Chrome") ? mb = !0 : -1 != S.indexOf("Android") ? jb = !0 : -1 != S.indexOf("Safari") && (nb = !0)); + var ob = fb, + pb = gb, + qb = hb, + rb = ib, + sb = jb, + tb = mb, + ub = nb; + var vb = "corp.google.com googleplex.com youtube.com youtube-nocookie.com youtubeeducation.com prod.google.com sandbox.google.com docs.google.com drive.google.com mail.google.com plus.google.com play.google.com googlevideo.com talkgadget.google.com survey.g.doubleclick.net youtube.googleapis.com vevo.com".split(" "), + wb = ""; + + function xb() { + } + + new xb; + new xb; + var T = y, + T = T.toLowerCase(); + if (-1 != T.indexOf("android") && !T.match(/android\D*(\d\.\d)[^\;|\)]*[\;\)]/)) { + var yb = { + cupcake: 1.5, + donut: 1.6, + eclair: 2, + froyo: 2.2, + gingerbread: 2.3, + honeycomb: 3, + "ice cream sandwich": 4, + jellybean: 4.1 + }, + zb = [], + Ab = 0, + Bb; + for (Bb in yb) { + zb[Ab++] = Bb; + } + T.match("(" + zb.join("|") + ")"); + } + ; + var Cb = l("yt.net.ping.workerUrl_") || null; + r("yt.net.ping.workerUrl_", Cb); + var U = window.performance || window.mozPerformance || window.msPerformance || window.webkitPerformance || {}; + q(U.clearResourceTimings || U.webkitClearResourceTimings || U.mozClearResourceTimings || U.msClearResourceTimings || U.oClearResourceTimings || aa, U); + var Db; + var Eb = y, + Fb = Eb.match(/\((iPad|iPhone|iPod)( Simulator)?;/); + if (!Fb || 2 > Fb.length) { + Db = void 0; + } else { + var Gb = Eb.match(/\((iPad|iPhone|iPod)( Simulator)?; (U; )?CPU (iPhone )?OS (\d_\d)[_ ]/); + Db = Gb && 6 == Gb.length ? Number(Gb[5].replace("_", ".")) : 0; + } + 0 <= Db && 0 <= y.search("Safari") && y.search("Version"); + + function V(a) { + return (a = a.exec(y)) ? a[1] : ""; + } + + (function() { + if (ob) { + return V(/Firefox\/([0-9.]+)/); + } + if (A || z) { + return D; + } + if (tb) { + return V(/Chrome\/([0-9.]+)/); + } + if (ub) { + return V(/Version\/([0-9.]+)/); + } + if (qb || rb) { + var a; + if (a = /Version\/(\S+).*Mobile\/(\S+)/.exec(y)) { + return a[1] + "." + a[2]; + } + } else { + if (sb) { + return (a = V(/Android\s+([0-9.]+)/)) ? a : V(/Version\/([0-9.]+)/); + } + if (pb) { + return V(/Camino\/([0-9.]+)/); + } + } + return ""; + })(); + + function Hb() { + } + + ; + + function Ib() { + } + + s(Ib, Hb); + + function W(a) { + this.d = a; + } + + s(W, Ib); + W.prototype.isAvailable = function() { + if (!this.d) { + return !1; + } + try { + return this.d.setItem("__sak", "1"), this.d.removeItem("__sak"), !0; + } catch(a) { + return !1; + } + }; + W.prototype.g = function(a) { + var b = 0, + c = this.d, + d = new eb; + d.next = function() { + if (b >= c.length) { + throw db; + } + var d; + d = c.key(b++); + if (a) { + return d; + } + d = c.getItem(d); + if (!n(d)) { + throw "Storage mechanism: Invalid value was encountered"; + } + return d; + }; + return d; + }; + W.prototype.key = function(a) { + return this.d.key(a); + }; + + function Jb() { + var a = null; + try { + a = window.localStorage || null; + } catch(b) { + } + this.d = a; + } + + s(Jb, W); + + function Kb() { + var a = null; + try { + a = window.sessionStorage || null; + } catch(b) { + } + this.d = a; + } + + s(Kb, W); + (new Jb).isAvailable(); + (new Kb).isAvailable(); + + function Lb(a) { + return (0 == a.search("cue") || 0 == a.search("load")) && "loadModule" != a; + } + + function Mb(a) { + return 0 == a.search("get") || 0 == a.search("is"); + } + + ; + + function X(a) { + this.g = a || {}; + this.defaults = {}; + this.defaults.host = "http://www.youtube.com"; + this.defaults.title = ""; + this.j = this.d = !1; + a = document.getElementById("www-widgetapi-script"); + if (this.d = !!("https:" == document.location.protocol || a && 0 == a.src.indexOf("https:"))) { + a = [this.g, window.YTConfig || {}, this.defaults]; + for (var b = 0; b < a.length; b++) { + a[b].host && (a[b].host = a[b].host.replace("http://", "https://")); + } + } + } + + var Q = null; + + function P(a, b) { + for (var c = [a.g, window.YTConfig || {}, a.defaults], d = 0; d < c.length; d++) { + var e = c[d][b]; + if (void 0 != e) { + return e; + } + } + return null; + } + + function cb(a, b, c) { + Q || (Q = {}, Ra(window, "message", q(a.k, a))); + Q[c] = b; + } + + X.prototype.k = function(a) { + var b; + (b = a.origin == P(this, "host")) || ((b = a.origin) && b == wb ? b = true : (new RegExp("^(https?:)?//([a-z0-9-]{1,63}\\.)*(" + vb.join("|").replace(/\./g, ".") + ")(:[0-9]+)?([/?#]|$)", "i")).test(b) ? (wb = b, b = !0) : b = !1); + if (b) { + var c; + try { + c = ua(a.data); + } catch(d) { + return; + } + this.j = !0; + this.d || 0 != a.origin.indexOf("https:") || (this.d = true); + if (a = Q[c.id]) { + a.F = !0, a.F && (w(a.D, a.J, a), a.D.length = 0), a.P(c); + } + } + }; + + function Nb(a) { + X.call(this, a); + this.defaults.title = "video player"; + this.defaults.videoId = ""; + this.defaults.width = 640; + this.defaults.height = 360; + } + + s(Nb, X); + + function Y(a, b) { + var c = new Nb(b); + O.call(this, a, c, "player"); + this.A = {}; + this.t = {}; + } + + s(Y, O); + + function Ob(a) { + if ("iframe" != a.tagName.toLowerCase()) { + var b = L(a, "videoid"); + if (b) { + var c = L(a, "width"), + d = L(a, "height"); + new Y(a, { + videoId: b, + width: c, + height: d + }); + } + } + } + + g = Y.prototype; + g.K = function() { + return "/embed/" + P(this.g, "videoId"); + }; + g.C = function() { + var a; + if (P(this.g, "playerVars")) { + a = P(this.g, "playerVars"); + var b = {}, + c; + for (c in a) { + b[c] = a[c]; + } + a = b; + } else { + a = {}; + } + return a; + }; + g.P = function(a) { + var b = a.event; + a = a.info; + switch (b) { + case "apiInfoDelivery": + if (ba(a)) { + for (var c in a) { + this.t[c] = a[c]; + } + } + break; + case "infoDelivery": + Pb(this, a); + break; + case "initialDelivery": + window.clearInterval(this.j); + window.initialDeliveryComplete = true; + this.A = {}; + this.t = {}; + Qb(this, a.apiInterface); + Pb(this, a); + break; + default: + this.v(b, a); + } + }; + + function Pb(a, b) { + if (ba(b)) { + for (var c in b) { + a.A[c] = b[c]; + } + } + } + + function Qb(a, b) { + w(b, function(a) { + this[a] || (Lb(a) ? this[a] = function() { + this.A = {}; + this.t = {}; + R(this, a, arguments); + return this; + } : Mb(a) ? this[a] = function() { + var b = 0; + 0 == a.search("get") ? b = 3 : 0 == a.search("is") && (b = 2); + return this.A[a.charAt(b).toLowerCase() + a.substr(b + 1)]; + } : this[a] = function() { + R(this, a, arguments); + return this; + }); + }, a); + } + + g.aa = function() { + var a = this.d.cloneNode(false), + b = this.A.videoData, + c = P(this.g, "host"); + a.src = b && b.video_id ? c + "/embed/" + b.video_id : a.src; + b = document.createElement("div"); + b.appendChild(a); + return b.innerHTML; + }; + g.$ = function(a) { + return this.t.namespaces ? a ? this.t[a].options || [] : this.t.namespaces || [] : []; + }; + g.Z = function(a, b) { + if (this.t.namespaces && a && b) { + return this.t[a][b]; + } + }; + + function Rb(a) { + X.call(this, a); + this.defaults.title = "Thumbnail"; + this.defaults.videoId = ""; + this.defaults.width = 120; + this.defaults.height = 68; + } + + s(Rb, X); + + function Z(a, b) { + var c = new Rb(b); + O.call(this, a, c, "thumbnail"); + } + + s(Z, O); + + function Sb(a) { + if ("iframe" != a.tagName.toLowerCase()) { + var b = L(a, "videoid"); + if (b) { + b = { + videoId: b, + events: {} + }; + b.width = L(a, "width"); + b.height = L(a, "height"); + b.thumbWidth = L(a, "thumb-width"); + b.thumbHeight = L(a, "thumb-height"); + b.thumbAlign = L(a, "thumb-align"); + var c = L(a, "onclick"); + c && (b.events.onClick = c); + new Z(a, b); + } + } + } + + Z.prototype.K = function() { + return "/embed/" + P(this.g, "videoId"); + }; + Z.prototype.C = function() { + return { + player: 0, + thumb_width: P(this.g, "thumbWidth"), + thumb_height: P(this.g, "thumbHeight"), + thumb_align: P(this.g, "thumbAlign") + }; + }; + Z.prototype.v = function(a, b) { + Z.I.v.call(this, a, b ? b.info : void 0); + }; + + function Tb(a) { + X.call(this, a); + this.defaults.host = "https://www.youtube.com"; + this.defaults.title = "upload widget"; + this.defaults.width = 640; + this.defaults.height = .67 * P(this, "width"); + } + + s(Tb, X); + + function $(a, b) { + var c = new Tb(b); + O.call(this, a, c, "upload"); + } + + s($, O); + g = $.prototype; + g.K = function() { + return "/upload_embed"; + }; + g.C = function() { + var a = {}, + b = P(this.g, "webcamOnly"); + null != b && (a.webcam_only = b); + return a; + }; + g.v = function(a, b) { + $.I.v.call(this, a, b); + "onApiReady" == a && R(this, "hostWindowReady"); + }; + g.S = function() { + R(this, "setVideoDescription", arguments); + }; + g.U = function() { + R(this, "setVideoKeywords", arguments); + }; + g.V = function() { + R(this, "setVideoPrivacy", arguments); + }; + g.T = function() { + R(this, "setVideoDraftPrivacy", arguments); + }; + g.W = function() { + R(this, "setVideoTitle", arguments); + }; + r("YT.PlayerState.UNSTARTED", -1); + r("YT.PlayerState.ENDED", 0); + r("YT.PlayerState.PLAYING", 1); + r("YT.PlayerState.PAUSED", 2); + r("YT.PlayerState.BUFFERING", 3); + r("YT.PlayerState.CUED", 5); + r("YT.UploadWidgetEvent.API_READY", "onApiReady"); + r("YT.UploadWidgetEvent.UPLOAD_SUCCESS", "onUploadSuccess"); + r("YT.UploadWidgetEvent.PROCESSING_COMPLETE", "onProcessingComplete"); + r("YT.UploadWidgetEvent.STATE_CHANGE", "onStateChange"); + r("YT.UploadWidgetState.IDLE", 0); + r("YT.UploadWidgetState.PENDING", 1); + r("YT.UploadWidgetState.ERROR", 2); + r("YT.UploadWidgetState.PLAYBACK", 3); + r("YT.UploadWidgetState.RECORDING", 4); + r("YT.UploadWidgetState.STOPPED", 5); + r("YT.get", function(a) { + return K[a]; + }); + r("YT.scan", Wa); + r("YT.subscribe", function(a, b, c) { + I.subscribe(a, b, c); + Va[a] = true; + for (var d in K) { + ab(K[d], a); + } + }); + r("YT.unsubscribe", function(a, b, c) { + za(a, b, c); + }); + r("YT.Player", Y); + r("YT.Thumbnail", Z); + r("YT.UploadWidget", $); + O.prototype.destroy = O.prototype.destroy; + O.prototype.setSize = O.prototype.Y; + O.prototype.getIframe = O.prototype.X; + O.prototype.addEventListener = O.prototype.addEventListener; + Y.prototype.getVideoEmbedCode = Y.prototype.aa; + Y.prototype.getOptions = Y.prototype.$; + Y.prototype.getOption = Y.prototype.Z; + $.prototype.setVideoDescription = $.prototype.S; + $.prototype.setVideoKeywords = $.prototype.U; + $.prototype.setVideoPrivacy = $.prototype.V; + $.prototype.setVideoTitle = $.prototype.W; + $.prototype.setVideoDraftPrivacy = $.prototype.T; + Ua.push(function() { + var a = Xa("player"); + w(a, Ob); + }); + Ua.push(function() { + var a = Xa("thumbnail"); + w(a, Sb); + }); + YTConfig.parsetags && "onload" != YTConfig.parsetags || Wa(); + var Ub = l("onYTReady"); + Ub && Ub(); + var Vb = l("onYouTubeIframeAPIReady"); + Vb && Vb(); + var Wb = l("onYouTubePlayerAPIReady"); + Wb && Wb(); + + /* jshint ignore:end */ + } + }); + + return YouTubePlayerAPI; }); \ No newline at end of file diff --git a/src/js/background/model/youTubeV3API.js b/src/js/background/model/youTubeV3API.js index 2b9ff64a..0b687261 100644 --- a/src/js/background/model/youTubeV3API.js +++ b/src/js/background/model/youTubeV3API.js @@ -1,4 +1,4 @@ -define(function (require) { +define(function(require) { 'use strict'; var Songs = require('background/collection/songs'); @@ -10,14 +10,16 @@ var YouTubeV3API = Backbone.Model.extend({ // Performs a search and then grabs the first item and returns its title // Expects options: { title: string, success: function, error: function } - getSongByTitle: function (options) { + getSongByTitle: function(options) { return this.search({ text: options.title, // Expect to find a playable song within the first 10 -- don't need the default 50 items maxResults: 10, - success: function (searchResponse) { + success: function(searchResponse) { if (searchResponse.songs.length === 0) { - if (options.error) options.error(chrome.i18n.getMessage('failedToFindSong')); + if (options.error) { + options.error(chrome.i18n.getMessage('failedToFindSong')); + } } else { options.success(searchResponse.songs.first()); } @@ -29,20 +31,20 @@ // Performs a search of YouTube with the provided text and returns a list of playable songs (<= max-results) // Expects options: { maxResults: integer, text: string, fields: string, success: function, error: function } - search: function (options) { + search: function(options) { var activeJqXHR = this._doRequest(YouTubeServiceType.Search, { - success: function (response) { - var songIds = _.map(response.items, function (item) { + success: function(response) { + var songIds = _.map(response.items, function(item) { return item.id.videoId; }); - + activeJqXHR = this.getSongs({ songIds: songIds, - success: function (songs) { + success: function(songs) { activeJqXHR = null; - + options.success({ - songs: songs, + songs: songs, nextPageToken: response.nextPageToken, }); }, @@ -51,8 +53,12 @@ }); }.bind(this), error: function(error) { - if (options.error) options.error(error); - if (options.complete) options.complete(); + if (options.error) { + options.error(error); + } + if (options.complete) { + options.complete(); + } } }, { part: 'id', @@ -68,25 +74,25 @@ return { promise: activeJqXHR, - abort: function () { + abort: function() { if (activeJqXHR !== null) { activeJqXHR.abort(); } } }; }, - - getChannelUploadsPlaylistId: function (options) { + + getChannelUploadsPlaylistId: function(options) { var listOptions = _.extend({ part: 'contentDetails', fields: 'items/contentDetails/relatedPlaylists/uploads' }, _.pick(options, ['id', 'forUsername'])); - + return this._doRequest('channels', { - success: function (response) { + success: function(response) { if (_.isUndefined(response.items[0])) { options.error(); - throw new Error("No response.items found for options:" + JSON.stringify(options)); + throw new Error('No response.items found for options:' + JSON.stringify(options)); } options.success({ @@ -97,11 +103,11 @@ complete: options.complete }, listOptions); }, - - getSong: function (options) { + + getSong: function(options) { return this.getSongs({ songIds: [options.songId], - success: function (songs) { + success: function(songs) { if (songs.length === 0) { options.error(chrome.i18n.getMessage('failedToFindSong') + ' ' + options.songId); } else { @@ -114,16 +120,16 @@ }, // Returns the results of a request for a segment of a channel, playlist, or other dataSource. - getPlaylistSongs: function (options) { + getPlaylistSongs: function(options) { var activeJqXHR = this._doRequest(YouTubeServiceType.PlaylistItems, { - success: function (response) { - var songIds = _.map(response.items, function (item) { + success: function(response) { + var songIds = _.map(response.items, function(item) { return item.contentDetails.videoId; }); activeJqXHR = this.getSongs({ songIds: songIds, - success: function (songs) { + success: function(songs) { activeJqXHR = null; options.success({ @@ -136,8 +142,12 @@ }); }.bind(this), error: function(error) { - if (options.error) options.error(error); - if (options.complete) options.complete(); + if (options.error) { + options.error(error); + } + if (options.complete) { + options.complete(); + } } }, { part: 'contentDetails', @@ -146,10 +156,10 @@ pageToken: options.pageToken || '', fields: 'nextPageToken, items/contentDetails/videoId' }); - + return { promise: activeJqXHR, - abort: function () { + abort: function() { if (activeJqXHR !== null) { activeJqXHR.abort(); } @@ -157,21 +167,21 @@ }; }, - getRelatedSongs: function (options) { + getRelatedSongs: function(options) { var activeJqXHR = this._doRequest(YouTubeServiceType.Search, { - success: function (response) { + success: function(response) { // It is possible to receive no response if a song was removed from YouTube but is still known to Streamus. if (!response) { - throw new Error("No response for: " + JSON.stringify(options)); + throw new Error('No response for: ' + JSON.stringify(options)); } - var songIds = _.map(response.items, function (item) { + var songIds = _.map(response.items, function(item) { return item.id.videoId; }); activeJqXHR = this.getSongs({ songIds: songIds, - success: function (songs) { + success: function(songs) { activeJqXHR = null; options.success(songs); }, @@ -179,9 +189,13 @@ complete: options.complete }); }.bind(this), - error: function (error) { - if (options.error) options.error(error); - if (options.complete) options.complete(); + error: function(error) { + if (options.error) { + options.error(error); + } + if (options.complete) { + options.complete(); + } } }, { part: 'id', @@ -192,10 +206,10 @@ fields: 'items/id/videoId', videoEmbeddable: 'true' }); - + return { promise: activeJqXHR, - abort: function () { + abort: function() { if (activeJqXHR !== null) { activeJqXHR.abort(); } @@ -204,12 +218,14 @@ }, // Converts a list of YouTube song ids into actual video information by querying YouTube with the list of ids. - getSongs: function (options) { + getSongs: function(options) { return this._doRequest(YouTubeServiceType.Videos, { - success: function (response) { + success: function(response) { if (_.isUndefined(response)) { - if (options.error) options.error(); - throw new Error("No response found for options:" + JSON.stringify(options)); + if (options.error) { + options.error(); + } + throw new Error('No response found for options: ' + JSON.stringify(options)); } if (_.isUndefined(response.items)) { @@ -221,7 +237,7 @@ } else { // Filter out videos which have marked themselves as not able to be embedded since they won't be able to be played in Streamus. // TODO: Notify the user that this has happened. - var embeddableItems = _.filter(response.items, function (item) { + var embeddableItems = _.filter(response.items, function(item) { return item.status.embeddable; }); @@ -238,15 +254,15 @@ fields: 'items/id, items/contentDetails/duration, items/snippet/title, items/snippet/channelTitle, items/status/embeddable' }); }, - - getTitle: function (options) { + + getTitle: function(options) { var ajaxDataOptions = _.extend({ part: 'snippet', fields: 'items/snippet/title' }, _.pick(options, ['id', 'forUsername'])); return this._doRequest(options.serviceType, { - success: function (response) { + success: function(response) { if (response.items.length === 0) { options.error(chrome.i18n.getMessage('errorLoadingTitle')); } else { @@ -258,7 +274,7 @@ }, ajaxDataOptions); }, - _doRequest: function (serviceType, ajaxOptions, ajaxDataOptions) { + _doRequest: function(serviceType, ajaxOptions, ajaxDataOptions) { return $.ajax(_.extend(ajaxOptions, { url: 'https://www.googleapis.com/youtube/v3/' + serviceType, data: _.extend({ @@ -266,9 +282,9 @@ }, ajaxDataOptions) })); }, - + _itemListToSongs: function(itemList) { - return new Songs(_.map(itemList, function (item) { + return new Songs(_.map(itemList, function(item) { return { id: item.id, duration: Utility.iso8061DurationToSeconds(item.contentDetails.duration), diff --git a/src/js/background/plugins.js b/src/js/background/plugins.js index b37781a2..42d1306a 100644 --- a/src/js/background/plugins.js +++ b/src/js/background/plugins.js @@ -1,20 +1,18 @@ -define(function (require) { +define(function(require) { 'use strict'; - - // TODO: Assign these to variables? + require('backbone.marionette'); require('backbone.localStorage'); - require('googleAnalytics'); var Cocktail = require('cocktail'); Cocktail.patch(Backbone); - ga('send', 'pageview', '/background.html'); - + // Some sensitive data is not committed to GitHub. Use an example file to help others and provide detection of incomplete setup. - requirejs.onError = function (error) { + requirejs.onError = function(error) { var headerWritten = false; + console.log('error:', error); - error.requireModules.forEach(function (requireModule) { + error.requireModules.forEach(function(requireModule) { if (requireModule.indexOf('background/key/') !== -1) { if (!headerWritten) { console.warn('%c ATTENTION! Additional configuration is required', 'color: rgb(66,133,244); font-size: 18px; font-weight: bold;'); @@ -25,7 +23,7 @@ console.warn('%cKey not found. \n Rename "' + requireModule + '.js.example" to "' + requireModule + '.js".\n Then, follow the instructions in the file.', 'color: red'); } }); - + if (headerWritten) { console.warn('%c -----------------------------------------------', 'color: rgb(66,133,244); font-size: 18px; font-weight: bold;'); } diff --git a/src/js/background/view/backgroundAreaView.js b/src/js/background/view/backgroundAreaView.js index c5d20883..c58ddc38 100644 --- a/src/js/background/view/backgroundAreaView.js +++ b/src/js/background/view/backgroundAreaView.js @@ -1,4 +1,4 @@ -define(function (require) { +define(function(require) { 'use strict'; var YouTubePlayerRegion = require('background/view/youTubePlayerRegion'); @@ -8,8 +8,8 @@ var BackgroundAreaView = Marionette.LayoutView.extend({ id: 'backgroundArea', template: _.template(BackgroundAreaTemplate), - - regions: function (options) { + + regions: function(options) { return { youTubePlayerRegion: { el: '#' + this.id + '-youTubePlayerRegion', @@ -19,11 +19,15 @@ clipboardRegion: { el: '#' + this.id + '-clipboardRegion', regionClass: ClipboardRegion - } + } }; }, + + initialize: function() { + this.model.get('analyticsManager').sendPageView('/background.html'); + }, - onAttach: function () { + onAttach: function() { Streamus.channels.backgroundArea.vent.trigger('attached'); } }); diff --git a/src/js/background/view/clipboardRegion.js b/src/js/background/view/clipboardRegion.js index 6994cb93..fa317b8b 100644 --- a/src/js/background/view/clipboardRegion.js +++ b/src/js/background/view/clipboardRegion.js @@ -1,18 +1,18 @@ -define(function (require) { +define(function(require) { 'use strict'; var ClipboardView = require('background/view/clipboardView'); var ClipboardRegion = Marionette.Region.extend({ - initialize: function () { + initialize: function() { this.listenTo(Streamus.channels.backgroundArea.vent, 'attached', this._onBackgroundAreaAttached); }, - _onBackgroundAreaAttached: function () { + _onBackgroundAreaAttached: function() { this._showClipboardView(); }, - _showClipboardView: function () { + _showClipboardView: function() { var clipboardView = new ClipboardView(); this.show(clipboardView); } diff --git a/src/js/background/view/clipboardView.js b/src/js/background/view/clipboardView.js index 1bfd8a1d..5fc50c17 100644 --- a/src/js/background/view/clipboardView.js +++ b/src/js/background/view/clipboardView.js @@ -1,4 +1,4 @@ -define(function () { +define(function() { 'use strict'; var ClipboardView = Marionette.ItemView.extend({ @@ -6,7 +6,7 @@ tagName: 'textarea', template: false, - initialize: function () { + initialize: function() { this.listenTo(Streamus.channels.clipboard.commands, 'copy:text', this._copyText); }, diff --git a/src/js/background/view/youTubePlayerRegion.js b/src/js/background/view/youTubePlayerRegion.js index 84d08a40..d6e25012 100644 --- a/src/js/background/view/youTubePlayerRegion.js +++ b/src/js/background/view/youTubePlayerRegion.js @@ -1,4 +1,4 @@ -define(function (require) { +define(function(require) { 'use strict'; var YouTubePlayerView = require('background/view/youTubePlayerView'); @@ -6,15 +6,15 @@ var YouTubePlayerRegion = Marionette.Region.extend({ youTubePlayer: null, - initialize: function (options) { + initialize: function(options) { this.youTubePlayer = options.youTubePlayer; this.listenTo(Streamus.channels.backgroundArea.vent, 'attached', this._onBackgroundAreaAttached); this.listenTo(this.youTubePlayer, 'change:loading', this._onYouTubePlayerChangeLoading); this.listenTo(this.youTubePlayer, 'change:currentLoadAttempt', this._onYouTubePlayerChangeCurrentLoadAttempt); }, - - _onBackgroundAreaAttached: function () { + + _onBackgroundAreaAttached: function() { if (this.youTubePlayer.get('loading')) { this._showYouTubePlayerView(); } else { @@ -22,21 +22,21 @@ } }, - _showYouTubePlayerView: function () { + _showYouTubePlayerView: function() { var youTubePlayerView = new YouTubePlayerView({ model: this.youTubePlayer }); - + this.show(youTubePlayerView); }, - _onYouTubePlayerChangeLoading: function (model, loading) { + _onYouTubePlayerChangeLoading: function(model, loading) { if (loading) { this._showYouTubePlayerView(); } }, - _onYouTubePlayerChangeCurrentLoadAttempt: function (model) { + _onYouTubePlayerChangeCurrentLoadAttempt: function(model) { if (model.get('loading')) { this._showYouTubePlayerView(); } diff --git a/src/js/background/view/youTubePlayerView.js b/src/js/background/view/youTubePlayerView.js index d899a1fa..15760374 100644 --- a/src/js/background/view/youTubePlayerView.js +++ b/src/js/background/view/youTubePlayerView.js @@ -1,4 +1,4 @@ -define(function () { +define(function() { 'use strict'; var YouTubePlayerView = Marionette.ItemView.extend({ @@ -10,7 +10,7 @@ // loaded is set to true when the iframes contentWindow is ready loaded: false, - attributes: function () { + attributes: function() { return { name: 'youtube-player', frameborder: 0, @@ -21,12 +21,12 @@ src: 'https://www.youtube.com/embed/J1Ol6M0d9sg?enablejsapi=1&origin=chrome-extension://' + chrome.runtime.id }; }, - + events: { 'load': '_onLoad' }, - - initialize: function () { + + initialize: function() { this.model.set('iframeId', this.el.id); // IMPORTANT: I need to bind like this and not just use .bind(this) inline because bind returns a new, anonymous function @@ -39,21 +39,21 @@ chrome.webRequest.onBeforeSendHeaders.addListener(this._onChromeWebRequestBeforeSendHeaders, { urls: [iframeUrlPattern] }, ['blocking', 'requestHeaders']); - + chrome.webRequest.onCompleted.addListener(this._onChromeWebRequestCompleted, { urls: [iframeUrlPattern], types: ['sub_frame'] }); }, - - onBeforeDestroy: function () { + + onBeforeDestroy: function() { chrome.webRequest.onBeforeSendHeaders.removeListener(this._onChromeWebRequestBeforeSendHeaders); chrome.webRequest.onCompleted.removeListener(this._onChromeWebRequestCompleted); }, // Add a Referer to requests because Chrome extensions don't implicitly have one. // Without a Referer - YouTube will reject most requests to play music. - _onChromeWebRequestBeforeSendHeaders: function (info) { + _onChromeWebRequestBeforeSendHeaders: function(info) { var refererRequestHeader = this._getHeader(info.requestHeaders, 'Referer'); var referer = 'https://www.youtube.com/'; @@ -65,7 +65,7 @@ } else { refererRequestHeader.value = referer; } - + // Opera does not default to using the HTML5 player because of lack of MSE support. // Modify user's preferences being sent to YouTube to imply that the user wants HTML5 only // This will cause preferences to go from looking like PREF=al=en&f1=50000000&f5=30; to PREF=al=en&f1=50000000&f5=30&f2=40000000; @@ -73,7 +73,7 @@ if (isOpera) { var html5PrefValue = 'f2=40000000'; var cookieRequestHeader = this._getHeader(info.requestHeaders, 'Cookie'); - + if (_.isUndefined(cookieRequestHeader)) { info.requestHeaders.push({ name: 'Cookie', @@ -83,13 +83,13 @@ // Try to find PREF: var cookieValue = cookieRequestHeader.value; var prefStartIndex = cookieValue.indexOf('PREF'); - + // Failed to find any preferences, so provide full pref string if (prefStartIndex === -1) { - cookieRequestHeader.value = cookieValue + ' PREF=' + html5PrefValue + ';'; + cookieRequestHeader.value = cookieValue + '; PREF=' + html5PrefValue + ';'; } else { var prefEndIndex = cookieValue.indexOf(';', prefStartIndex); - + // Don't try to handle malformed preferences, too difficult. if (prefEndIndex !== -1) { // Inject custom preference value @@ -99,32 +99,32 @@ } } } - + return { requestHeaders: info.requestHeaders }; }, // Only load YouTube's API once the iframe has been built successfully. // If Internet is lagging or disconnected then _onWebRequestCompleted will not fire. // Even if the Internet is working properly, it's possible to try and load the API before CORS is ready to allow postMessages. - _onChromeWebRequestCompleted: function () { + _onChromeWebRequestCompleted: function() { chrome.webRequest.onCompleted.removeListener(this._onWebRequestCompleted); this.webRequestCompleted = true; this._checkLoadModel(); }, - - _checkLoadModel: function () { + + _checkLoadModel: function() { if (this.loaded && this.webRequestCompleted) { this.model.load(); } }, - _onLoad: function () { + _onLoad: function() { this.loaded = true; this._checkLoadModel(); }, - + _getHeader: function(requestHeaders, headerName) { - var refererRequestHeader = _.find(requestHeaders, function (requestHeader) { + var refererRequestHeader = _.find(requestHeaders, function(requestHeader) { return requestHeader.name === headerName; }); diff --git a/src/js/common/requireConfig.js b/src/js/common/requireConfig.js index ed9f0886..9da20e3f 100644 --- a/src/js/common/requireConfig.js +++ b/src/js/common/requireConfig.js @@ -1,37 +1,56 @@ -define(function () { - 'use strict'; +//define({ +// baseUrl: 'js/', +// enforceDefine: true, - require.config({ - baseUrl: 'js/', - enforceDefine: true, +// paths: { +// // Paths: +// 'template': '../template', - paths: { - // Paths: - 'template': '../template', +// // Third Party: +// 'backbone': 'thirdParty/backbone', +// 'backbone.localStorage': 'thirdParty/backbone.localStorage', +// 'backbone.marionette': 'thirdParty/backbone.marionette', +// 'jquery': 'thirdParty/jquery', +// // Rename lodash to underscore since functionally equivilant but underscore is expected by other third party libraries. +// 'underscore': 'thirdParty/lodash', +// 'text': 'thirdParty/text' +// } +//}); - // Third Party: - 'backbone': 'thirdParty/backbone', - 'backbone.localStorage': 'thirdParty/backbone.localStorage', - 'backbone.marionette': 'thirdParty/backbone.marionette', - 'cocktail': 'thirdParty/cocktail', - 'googleAnalytics': 'thirdParty/googleAnalytics', - 'jquery': 'thirdParty/jquery', - 'jquery.perfectScrollbar': 'thirdParty/jquery.perfectScrollbar', - 'jquery.qtip': 'thirdParty/jquery.qtip', - 'jquery-ui': 'thirdParty/jquery-ui', - 'less': 'thirdParty/less', - // Rename lodash to underscore since functionally equivilant but underscore is expected by other third party libraries. - 'underscore': 'thirdParty/lodash', - 'text': 'thirdParty/text' - }, +define(function() { + 'use strict'; - shim: { - 'googleAnalytics': { - exports: 'window.ga' - }, - 'less': { - exports: 'window.less' - } - } - }); + require.config({ + baseUrl: 'js/', + enforceDefine: true, + + paths: { + // Paths: + 'template': '../template', + + // Third Party: + 'backbone': 'thirdParty/backbone', + 'backbone.localStorage': 'thirdParty/backbone.localStorage', + 'backbone.marionette': 'thirdParty/backbone.marionette', + 'cocktail': 'thirdParty/cocktail', + 'jquery': 'thirdParty/jquery', + 'jquery.perfectScrollbar': 'thirdParty/jquery.perfectScrollbar', + 'jquery.qtip': 'thirdParty/jquery.qtip', + 'jquery-ui': 'thirdParty/jquery-ui', + 'lodash': 'thirdParty/lodash', + 'text': 'thirdParty/text' + }, + + shim: { + 'https://www.google-analytics.com/analytics.js': { + exports: 'window.ga' + } + }, + + map: { + '*': { + 'underscore': 'lodash' + } + } + }); }); \ No newline at end of file diff --git a/src/js/common/utility.js b/src/js/common/utility.js index e3af70d1..316415bc 100644 --- a/src/js/common/utility.js +++ b/src/js/common/utility.js @@ -13,11 +13,11 @@ // Ensure two-digits for small numbers if (minutes < 10) { - minutes = "0" + minutes; + minutes = '0' + minutes; } if (remainingSeconds < 10) { - remainingSeconds = "0" + remainingSeconds; + remainingSeconds = '0' + remainingSeconds; } var timeString = minutes + ':' + remainingSeconds; @@ -80,7 +80,7 @@ // http://stackoverflow.com/questions/3446170/escape-string-for-use-in-javascript-regex escapeRegExp: function(string) { - var escapedString = string.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&"); + var escapedString = string.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&'); return escapedString; }, @@ -93,7 +93,9 @@ var line = ''; for (var index in array[i]) { var csval = array[i][index]; - if (line !== '') line += ','; + if (line !== '') { + line += ','; + } if (/("|,|\n)/.test(csval)) { // enclose value in double quotes, escaping any pre-existing @@ -110,7 +112,7 @@ }, // Converts an ISO8061 format (i.e: PT1H3M52S) to numeric representation in seconds. - iso8061DurationToSeconds: function (isoDuration) { + iso8061DurationToSeconds: function(isoDuration) { var hoursMatch = isoDuration.match(/(\d+)H/); var hours = parseInt(hoursMatch ? hoursMatch[1] : 0); diff --git a/src/js/foreground/application.js b/src/js/foreground/application.js index 32ead64e..b742dbbe 100644 --- a/src/js/foreground/application.js +++ b/src/js/foreground/application.js @@ -1,15 +1,15 @@ -define(function (require) { +define(function(require) { 'use strict'; var ForegroundAreaView = require('foreground/view/foregroundAreaView'); var Application = Marionette.Application.extend({ backgroundPage: null, - + regions: { foregroundAreaRegion: '#foregroundAreaRegion' }, - + channels: { global: Backbone.Wreqr.radio.channel('global'), dialog: Backbone.Wreqr.radio.channel('dialog'), @@ -26,20 +26,20 @@ listItem: Backbone.Wreqr.radio.channel('listItem'), simpleMenu: Backbone.Wreqr.radio.channel('simpleMenu') }, - + backgroundChannels: null, - - initialize: function () { + + initialize: function() { this._setBackgroundPage(); this._setBackgroundChannels(); this.on('start', this._onStart); }, - - _setBackgroundPage: function () { + + _setBackgroundPage: function() { this.backgroundPage = chrome.extension.getBackgroundPage(); }, - - _setBackgroundChannels: function () { + + _setBackgroundChannels: function() { this.backgroundChannels = { error: this.backgroundPage.Backbone.Wreqr.radio.channel('error'), notification: this.backgroundPage.Backbone.Wreqr.radio.channel('notification'), @@ -47,16 +47,16 @@ }; }, - _onStart: function () { + _onStart: function() { Streamus.backgroundChannels.foreground.vent.trigger('started'); this._showForegroundArea(); }, - - _showForegroundArea: function () { + + _showForegroundArea: function() { this.foregroundAreaRegion.show(new ForegroundAreaView()); } }); - + $(function() { var streamus = new Application(); window.Streamus = streamus; diff --git a/src/js/foreground/main.js b/src/js/foreground/main.js index 6bf337f3..dd815db3 100644 --- a/src/js/foreground/main.js +++ b/src/js/foreground/main.js @@ -1,13 +1,19 @@ -require([ - '../common/requireConfig' -], function () { +//define(['../common/requireConfig'], function(requireConfig) { +// 'use strict'; +// // Mix extra properties into requireConfig as necessary. +// requireConfig.paths['jquery.perfectScrollbar'] = 'thirdParty/jquery.perfectScrollbar'; +// requireConfig.paths['jquery.qtip'] = 'thirdParty/jquery.qtip'; +// requireConfig.paths['jquery-ui'] = 'thirdParty/jquery-ui'; + +// // Setup the configuration needed to use requireJS +// require.config(requireConfig); + +// // Then, load all of the plugins needed: +// require(['foreground/plugins']); +//}); + +define(['../common/requireConfig'], function() { 'use strict'; - - // Only log errors with less. - // http://lesscss.org/usage/ - window.less = { - logLevel: 1 - }; // Load all of the plugins needed by the foreground: require(['foreground/plugins']); diff --git a/src/js/foreground/plugins.js b/src/js/foreground/plugins.js index 06068997..cc17b9df 100644 --- a/src/js/foreground/plugins.js +++ b/src/js/foreground/plugins.js @@ -1,17 +1,12 @@ -define(function (require) { +define(function(require) { 'use strict'; - - // TODO: Assign these to variables? + require('backbone.marionette'); require('backbone.localStorage'); - require('googleAnalytics'); require('jquery.perfectScrollbar'); require('jquery.qtip'); require('jquery-ui'); - require('less'); - - ga('send', 'pageview', '/foreground.html'); - + // Finally, load the application which will initialize the foreground: require(['foreground/application']); }); \ No newline at end of file diff --git a/src/js/foreground/view/appBar/adminMenuAreaView.js b/src/js/foreground/view/appBar/adminMenuAreaView.js index 86ebafc9..1a6492d8 100644 --- a/src/js/foreground/view/appBar/adminMenuAreaView.js +++ b/src/js/foreground/view/appBar/adminMenuAreaView.js @@ -1,15 +1,15 @@ -define(function (require) { +define(function(require) { 'use strict'; var SettingsDialogView = require('foreground/view/dialog/settingsDialogView'); var BrowserSettingsDialogView = require('foreground/view/dialog/browserSettingsDialogView'); var AdminMenuAreaTemplate = require('text!template/appBar/adminMenuArea.html'); var SettingsIcon = require('text!template/icon/settingsIcon_24.svg'); - + var AdminMenuAreaView = Marionette.ItemView.extend({ id: 'adminMenuArea', template: _.template(AdminMenuAreaTemplate), - + templateHelpers: { settingsMessage: chrome.i18n.getMessage('settings'), browserSettingsMessage: chrome.i18n.getMessage('browserSettings'), @@ -18,8 +18,8 @@ reloadMessage: chrome.i18n.getMessage('reload'), settingsIcon: SettingsIcon }, - - ui: function () { + + ui: function() { return { menuButton: '#' + this.id + '-menuButton', menu: '#' + this.id + '-menu', @@ -39,71 +39,71 @@ 'click @ui.openInTabMenuItem': '_onClickOpenInTabMenuItem', 'click @ui.restartMenuItem': '_onClickRestartMenuItem' }, - + modelEvents: { 'change:menuShown': '_onChangeMenuShown' }, tabManager: null, - + elementEvents: { 'drag': '_onElementDrag', 'click': '_onElementClick' }, - - initialize: function () { + + initialize: function() { this.tabManager = Streamus.backgroundPage.tabManager; this.bindEntityEvents(Streamus.channels.element.vent, this.elementEvents); }, - - _onClickMenuButton: function () { + + _onClickMenuButton: function() { this.model.set('menuShown', !this.model.get('menuShown')); }, - - _onClickSettingsMenuItem: function () { + + _onClickSettingsMenuItem: function() { Streamus.channels.dialog.commands.trigger('show:dialog', SettingsDialogView); }, - + _onClickBrowserSettingsMenuItem: function() { Streamus.channels.dialog.commands.trigger('show:dialog', BrowserSettingsDialogView); }, - + _onClickKeyboardShortcutsMenuItem: function() { this.tabManager.showKeyboardShortcutsTab(); }, - - _onClickOpenInTabMenuItem: function () { + + _onClickOpenInTabMenuItem: function() { this.tabManager.showStreamusTab(); }, - - _onClickRestartMenuItem: function () { + + _onClickRestartMenuItem: function() { Streamus.backgroundPage.chrome.runtime.reload(); }, - - _onElementDrag: function () { + + _onElementDrag: function() { this.model.set('menuShown', false); }, - - _onElementClick: function (event) { + + _onElementClick: function(event) { // If the user clicks anywhere on the page except for this menu button -- hide the menu. if ($(event.target).closest(this.ui.menuButton.selector).length === 0) { this.model.set('menuShown', false); } }, - - _onChangeMenuShown: function (model, menuShown) { + + _onChangeMenuShown: function(model, menuShown) { if (menuShown) { this._showMenu(); } else { this._hideMenu(); } }, - - _showMenu: function () { + + _showMenu: function() { this.ui.menu.addClass('is-visible'); }, - - _hideMenu: function () { + + _hideMenu: function() { this.ui.menu.removeClass('is-visible'); } }); diff --git a/src/js/foreground/view/appBar/appBarRegion.js b/src/js/foreground/view/appBar/appBarRegion.js index e3d64b0a..ce676824 100644 --- a/src/js/foreground/view/appBar/appBarRegion.js +++ b/src/js/foreground/view/appBar/appBarRegion.js @@ -1,14 +1,14 @@ -define(function (require) { +define(function(require) { 'use strict'; var AppBarView = require('foreground/view/appBar/appBarView'); var AppBarRegion = Marionette.Region.extend({ - initialize: function () { + initialize: function() { this.listenTo(Streamus.channels.foregroundArea.vent, 'rendered', this._onForegroundAreaRendered); }, - - _onForegroundAreaRendered: function () { + + _onForegroundAreaRendered: function() { this.show(new AppBarView()); } }); diff --git a/src/js/foreground/view/appBar/appBarView.js b/src/js/foreground/view/appBar/appBarView.js index 12be53c3..7be22316 100644 --- a/src/js/foreground/view/appBar/appBarView.js +++ b/src/js/foreground/view/appBar/appBarView.js @@ -1,4 +1,4 @@ -define(function (require) { +define(function(require) { 'use strict'; var AdminMenuArea = require('foreground/model/adminMenuArea'); @@ -17,8 +17,8 @@ var AppBarView = Marionette.LayoutView.extend({ id: 'appBar', template: _.template(AppBarTemplate), - - templateHelpers: function () { + + templateHelpers: function() { return { searchQuery: this.search.get('query'), showSearchMessage: chrome.i18n.getMessage('showSearch'), @@ -28,8 +28,8 @@ closeIcon: _.template(CloseIconTemplate)() }; }, - - regions: function () { + + regions: function() { return { // TODO: Maybe move into its own region because it's getting complicated to tell whether user is signed in or not to load playlist title. playlistTitleRegion: '#' + this.id + '-playlistTitleRegion', @@ -40,8 +40,8 @@ nextButtonRegion: '#' + this.id + '-nextButtonRegion' }; }, - - ui: function () { + + ui: function() { return { searchInput: '#' + this.id + '-searchInput', showSearchButton: '#' + this.id + '-showSearchButton', @@ -54,7 +54,7 @@ searchInputRegion: '#' + this.id + '-searchInputRegion' }; }, - + events: { 'click @ui.showSearchButton': '_onClickShowSearchButton', 'click @ui.hideSearchButton': '_onClickHideSearchButton', @@ -63,7 +63,7 @@ 'click @ui.showPlaylistsAreaButton:not(.is-disabled)': '_onClickShowPlaylistsAreaButton', 'click @ui.hidePlaylistsAreaButton': '_onClickHidePlaylistsAreaButton' }, - + behaviors: { // Needed for the 'not signed in' message on nav button. Tooltip: { @@ -74,7 +74,7 @@ signInManager: null, search: null, - initialize: function () { + initialize: function() { this.signInManager = Streamus.backgroundPage.signInManager; this.search = Streamus.backgroundPage.search; @@ -91,8 +91,8 @@ this.listenTo(signedInUser.get('playlists'), 'change:active', this._onPlaylistsChangeActive); } }, - - onRender: function () { + + onRender: function() { var signedInUser = this.signInManager.get('signedInUser'); this._setShowPlaylistsAreaButtonState(signedInUser); @@ -118,26 +118,26 @@ model: Streamus.backgroundPage.nextButton })); }, - - onAttach: function () { + + onAttach: function() { // TODO: It would be better to read this state from a viewmodel rather than hitting the DOM. // Needs to be ran in onAttach as well as when the search is showing because 'showing' event can trigger when view is rendering rather than attached. if (this.ui.searchInput.is(':visible')) { this._focusSearchInput(); } }, - - _onSearchChangeQuery: function (model, query) { + + _onSearchChangeQuery: function(model, query) { var searchInputElement = this.ui.searchInput[0]; var selectionStart = searchInputElement.selectionStart; var selectionEnd = searchInputElement.selectionEnd; this.ui.searchInput.val(query); - + // Preserve the selection range which is lost after modifying val searchInputElement.setSelectionRange(selectionStart, selectionEnd); }, - - _onSignInManagerChangeSignedInUser: function (model, signedInUser) { + + _onSignInManagerChangeSignedInUser: function(model, signedInUser) { if (signedInUser === null) { this.stopListening(model.previous('signedInUser').get('playlists')); this.playlistTitleRegion.empty(); @@ -148,50 +148,50 @@ this._setShowPlaylistsAreaButtonState(signedInUser); }, - - _onPlaylistsChangeActive: function (model, active) { + + _onPlaylistsChangeActive: function(model, active) { if (active) { this.playlistTitleRegion.show(new PlaylistTitleView({ model: model })); } }, - - _onClickShowSearchButton: function () { + + _onClickShowSearchButton: function() { Streamus.channels.searchArea.commands.trigger('show:search'); Streamus.channels.playlistsArea.commands.trigger('hide:playlistsArea'); }, - - _onClickHideSearchButton: function () { + + _onClickHideSearchButton: function() { Streamus.channels.searchArea.commands.trigger('hide:search'); }, - + _onInputSearchInput: function() { Streamus.channels.searchArea.commands.trigger('search', { query: this.ui.searchInput.val() }); }, - _onClickShowPlaylistsAreaButton: function () { + _onClickShowPlaylistsAreaButton: function() { Streamus.channels.playlistsArea.commands.trigger('show:playlistsArea'); Streamus.channels.searchArea.commands.trigger('hide:search'); }, - - _onClickHidePlaylistsAreaButton: function () { + + _onClickHidePlaylistsAreaButton: function() { Streamus.channels.playlistsArea.commands.trigger('hide:playlistsArea'); }, - + _onPlaylistsAreaShowing: function() { this.ui.showPlaylistsAreaButton.addClass('is-hidden'); this.ui.hidePlaylistsAreaButton.removeClass('is-hidden'); }, - - _onPlaylistsAreaHiding: function () { + + _onPlaylistsAreaHiding: function() { this.ui.showPlaylistsAreaButton.removeClass('is-hidden'); this.ui.hidePlaylistsAreaButton.addClass('is-hidden'); }, - - _onSearchAreaShowing: function () { + + _onSearchAreaShowing: function() { this.ui.showSearchButton.addClass('is-hidden'); this.ui.hideSearchButton.removeClass('is-hidden'); this.ui.playlistTitleRegion.addClass('is-hidden'); @@ -199,26 +199,26 @@ this._focusSearchInput(); }, - + _onSearchAreaHiding: function() { this.ui.showSearchButton.removeClass('is-hidden'); this.ui.hideSearchButton.addClass('is-hidden'); this.ui.playlistTitleRegion.removeClass('is-hidden'); this.ui.searchInputRegion.addClass('is-hidden'); }, - - _setPlaylistTitleRegion: function (signedInUser) { + + _setPlaylistTitleRegion: function(signedInUser) { this.playlistTitleRegion.show(new PlaylistTitleView({ model: signedInUser.get('playlists').getActivePlaylist() })); }, - _setShowPlaylistsAreaButtonState: function (signedInUser) { + _setShowPlaylistsAreaButtonState: function(signedInUser) { var signedOut = signedInUser === null; var title = signedOut ? chrome.i18n.getMessage('notSignedIn') : ''; this.ui.showPlaylistsAreaButton.toggleClass('is-disabled', signedOut).attr('title', title); }, - + _focusSearchInput: function() { // Reset val after focusing to prevent selecting the text while maintaining focus. // This needs to be ran after makign the region visible because you can't focus an element which isn't visible. diff --git a/src/js/foreground/view/appBar/nextButtonView.js b/src/js/foreground/view/appBar/nextButtonView.js index e3469e94..8cc9f1ae 100644 --- a/src/js/foreground/view/appBar/nextButtonView.js +++ b/src/js/foreground/view/appBar/nextButtonView.js @@ -1,4 +1,4 @@ -define(function (require) { +define(function(require) { 'use strict'; var NextButtonTemplate = require('text!template/appBar/nextButton.html'); @@ -19,20 +19,20 @@ modelEvents: { 'change:enabled': '_onChangeEnabled' }, - - onRender: function () { + + onRender: function() { this._setState(this.model.get('enabled')); }, - _onClick: function () { + _onClick: function() { this.model.tryActivateNextStreamItem(); }, - _onChangeEnabled: function (model, enabled) { + _onChangeEnabled: function(model, enabled) { this._setState(enabled); }, - _setState: function (enabled) { + _setState: function(enabled) { this.$el.toggleClass('is-disabled', !enabled); } }); diff --git a/src/js/foreground/view/appBar/playPauseButtonView.js b/src/js/foreground/view/appBar/playPauseButtonView.js index a548bbbb..00a11106 100644 --- a/src/js/foreground/view/appBar/playPauseButtonView.js +++ b/src/js/foreground/view/appBar/playPauseButtonView.js @@ -1,4 +1,4 @@ -define(function (require) { +define(function(require) { 'use strict'; var PlayerState = require('common/enum/playerState'); @@ -10,19 +10,19 @@ id: 'playPauseButton', className: 'button button--icon button--icon--primary button--large', template: _.template(PlayPauseButtonTemplate), - + templateHelpers: { pauseIcon: _.template(PauseIconTemplate)(), playIcon: _.template(PlayIconTemplate)() }, - - ui: function () { + + ui: function() { return { playIcon: '#' + this.id + '-playIcon', pauseIcon: '#' + this.id + '-pauseIcon' }; }, - + events: { 'click': '_onClick' }, @@ -30,31 +30,31 @@ modelEvents: { 'change:enabled': '_onChangeEnabled' }, - + player: null, - - initialize: function () { + + initialize: function() { this.player = Streamus.backgroundPage.player; this.listenTo(this.player, 'change:state', this._onPlayerChangeState); }, - onRender: function () { + onRender: function() { this._setState(this.model.get('enabled'), this.player.get('state')); }, - _onClick: function () { + _onClick: function() { this.model.tryTogglePlayerState(); }, - _onChangeEnabled: function (model, enabled) { + _onChangeEnabled: function(model, enabled) { this._setState(enabled, this.player.get('state')); }, - - _onPlayerChangeState: function (model, state) { + + _onPlayerChangeState: function(model, state) { this._setState(this.model.get('enabled'), state); }, - _setState: function (enabled, playerState) { + _setState: function(enabled, playerState) { this.$el.toggleClass('is-disabled', !enabled); // TODO: There's a difference between buffering-->play and buffering-->paused. Don't want to change button when buffering-->paused. How to tell the difference? diff --git a/src/js/foreground/view/appBar/playlistTitleView.js b/src/js/foreground/view/appBar/playlistTitleView.js index f10d4628..5e5695d1 100644 --- a/src/js/foreground/view/appBar/playlistTitleView.js +++ b/src/js/foreground/view/appBar/playlistTitleView.js @@ -1,4 +1,4 @@ -define(function (require) { +define(function(require) { 'use strict'; var Tooltip = require('foreground/view/behavior/tooltip'); @@ -11,22 +11,22 @@ modelEvents: { 'change:title': '_onChangeTitle' }, - + behaviors: { Tooltip: { behaviorClass: Tooltip } }, - - onRender: function () { + + onRender: function() { this._setTitle(this.model.get('title')); }, - - _onChangeTitle: function () { + + _onChangeTitle: function() { this.render(); }, - - _setTitle: function (title) { + + _setTitle: function(title) { this.$el.attr('title', title); } }); diff --git a/src/js/foreground/view/appBar/previousButtonView.js b/src/js/foreground/view/appBar/previousButtonView.js index d81bbe63..5d21a0ef 100644 --- a/src/js/foreground/view/appBar/previousButtonView.js +++ b/src/js/foreground/view/appBar/previousButtonView.js @@ -1,4 +1,4 @@ -define(function (require) { +define(function(require) { 'use strict'; var PreviousButtonTemplate = require('text!template/appBar/previousButton.html'); @@ -11,28 +11,28 @@ templateHelpers: { previousIcon: _.template(PreviousIconTemplate)() }, - + events: { 'click': '_onClick' }, - + modelEvents: { 'change:enabled': '_onChangeEnabled' }, - - onRender: function () { + + onRender: function() { this._setState(this.model.get('enabled')); }, - - _onClick: function () { + + _onClick: function() { this.model.tryDoTimeBasedPrevious(); }, - - _onChangeEnabled: function (model, enabled) { + + _onChangeEnabled: function(model, enabled) { this._setState(enabled); }, - - _setState: function (enabled) { + + _setState: function(enabled) { this.$el.toggleClass('is-disabled', !enabled); } }); diff --git a/src/js/foreground/view/appBar/volumeAreaView.js b/src/js/foreground/view/appBar/volumeAreaView.js index 4ff1d84c..fc8766c5 100644 --- a/src/js/foreground/view/appBar/volumeAreaView.js +++ b/src/js/foreground/view/appBar/volumeAreaView.js @@ -1,4 +1,4 @@ -define(function (require) { +define(function(require) { 'use strict'; var VolumeAreaTemplate = require('text!template/appBar/volumeArea.html'); @@ -11,15 +11,15 @@ define(function (require) { id: 'volumeArea', className: 'volumeArea', template: _.template(VolumeAreaTemplate), - + templateHelpers: { volumeUpIcon: _.template(VolumeUpIconTemplate)(), volumeDownIcon: _.template(VolumeDownIconTemplate)(), volumeOffIcon: _.template(VolumeOffIconTemplate)(), volumeMuteIcon: _.template(VolumeMuteIconTemplate)() }, - - ui: function () { + + ui: function() { return { volumeProgress: '#' + this.id + '-volumeProgress', volumeRange: '#' + this.id + '-volumeRange', @@ -39,46 +39,46 @@ define(function (require) { }, player: null, - - initialize: function () { + + initialize: function() { this.player = Streamus.backgroundPage.player; this.listenTo(this.player, 'change:muted', this._onPlayerChangeMuted); this.listenTo(this.player, 'change:volume', this._onPlayerChangeVolume); }, - onRender: function () { + onRender: function() { var volume = this.player.get('volume'); this._setVolumeProgress(volume); - + var muted = this.player.get('muted'); this._setVolumeIcon(volume, muted); }, - + _onInputVolumeRange: function() { this._setVolume(); }, - + _onClickVolumeButton: function() { this._toggleMute(); }, - - _onWheel: function (event) { + + _onWheel: function(event) { var delta = event.originalEvent.wheelDeltaY / 120; this._scrollVolume(delta); }, - _setVolume: function () { + _setVolume: function() { var volume = parseInt(this.ui.volumeRange.val()); this.player.setVolume(volume); }, - - _setVolumeProgress: function (volume) { + + _setVolumeProgress: function(volume) { this.ui.volumeRange.val(volume); this.ui.volumeProgress.height(volume + '%'); }, - - _setVolumeIcon: function (volume, muted) { + + _setVolumeIcon: function(volume, muted) { this.ui.volumeIconUp.toggleClass('is-hidden', muted || volume <= 50); this.ui.volumeIconDown.toggleClass('is-hidden', muted || volume > 50 || volume === 0); this.ui.volumeIconOff.toggleClass('is-hidden', muted || volume !== 0); @@ -86,7 +86,7 @@ define(function (require) { }, // Adjust volume when user scrolls wheel while hovering over volume. - _scrollVolume: function (delta) { + _scrollVolume: function(delta) { var volume = parseInt(this.ui.volumeRange.val()) + (delta * 3); if (volume > 100) { @@ -100,19 +100,19 @@ define(function (require) { this.player.setVolume(volume); }, - _toggleMute: function () { + _toggleMute: function() { var isMuted = this.player.get('muted'); this.player.save({ muted: !isMuted }); }, - _onPlayerChangeVolume: function (model, volume) { + _onPlayerChangeVolume: function(model, volume) { this._setVolumeProgress(volume); this._setVolumeIcon(volume, model.get('muted')); }, - - _onPlayerChangeMuted: function (model, muted) { + + _onPlayerChangeMuted: function(model, muted) { this._setVolumeIcon(model.get('volume'), muted); } }); diff --git a/src/js/foreground/view/behavior/collectionViewMultiSelect.js b/src/js/foreground/view/behavior/collectionViewMultiSelect.js index a83cb436..3c2bc823 100644 --- a/src/js/foreground/view/behavior/collectionViewMultiSelect.js +++ b/src/js/foreground/view/behavior/collectionViewMultiSelect.js @@ -1,4 +1,4 @@ -define(function () { +define(function() { 'use strict'; var CollectionViewMultiSelect = Marionette.Behavior.extend({ @@ -11,36 +11,36 @@ // Whenever an item is dragged - ensure it is selected because click event doesn't happen // when performing a drag operation. It doesn't feel right to use mousedown instead of click. - onItemDragged: function (options) { + onItemDragged: function(options) { this._setSelected({ modelToSelect: options.item, shiftKey: event.shiftKey, drag: true }); }, - - onDeselectCollection: function () { + + onDeselectCollection: function() { this._deselectCollection(); }, - - _onListItemSelected: function (options) { + + _onListItemSelected: function(options) { if (this.view.childViewType !== options.listItemType) { this._deselectCollection(); } }, - - _onElementDrop: function () { + + _onElementDrop: function() { this._deselectCollection(); }, - - _deselectCollection: function () { + + _deselectCollection: function() { this.view.collection.deselectAll(); }, // TODO: This function name sucks. Waiting on issues from Marionette: // - https://github.com/marionettejs/backbone.marionette/issues/2235 // - https://github.com/marionettejs/backbone.marionette/issues/2236 - onChildviewClickLeftContent: function (childView, options) { + onChildviewClickLeftContent: function(childView, options) { this._setSelected({ shiftKey: options.shiftKey, modelToSelect: options.model @@ -51,8 +51,8 @@ // Don't allow to bubble up since handling click at this level. return false; }, - - _setSelected: function (options) { + + _setSelected: function(options) { var modelToSelect = options.modelToSelect; var shiftKeyPressed = options.shiftKey || false; @@ -74,8 +74,8 @@ listItemType: modelToSelect.get('listItemType') }); }, - - _selectGroup: function (selectedIndex) { + + _selectGroup: function(selectedIndex) { var firstSelectedIndex = 0; var collection = this.view.collection; @@ -88,7 +88,7 @@ } // Select all items between the selected item and the firstSelected item. - collection.each(function (model, index) { + collection.each(function(model, index) { var isBetweenAbove = index <= selectedIndex && index >= firstSelectedIndex; var isBetweenBelow = index >= selectedIndex && index <= firstSelectedIndex; diff --git a/src/js/foreground/view/behavior/itemViewMultiSelect.js b/src/js/foreground/view/behavior/itemViewMultiSelect.js index 006b5291..f42adff0 100644 --- a/src/js/foreground/view/behavior/itemViewMultiSelect.js +++ b/src/js/foreground/view/behavior/itemViewMultiSelect.js @@ -1,4 +1,4 @@ -define(function (require) { +define(function(require) { 'use strict'; var Checkbox = require('foreground/model/checkbox'); @@ -8,34 +8,34 @@ ui: { leftContent: '.listItem-leftContent' }, - + events: { 'click @ui.leftContent': '_onClickLeftContent', 'dblclick @ui.leftContent': '_onDblClickLeftContent', 'mouseenter @ui.leftContent': '_onMouseEnterLeftContent', 'mouseleave @ui.leftContent': '_onMouseLeaveLeftContent' }, - + modelEvents: { 'change:selected': '_onChangeSelected' }, - + isMouseOver: false, checkbox: null, - - initialize: function () { + + initialize: function() { this.checkbox = new Checkbox({ // TODO: I cannot access this.view.model in initialize from a behavior. https://github.com/marionettejs/backbone.marionette/issues/1579 checked: this.view.options.model.get('selected') }); }, - - onRender: function () { + + onRender: function() { this.$el.addClass('js-listItem--multiSelect'); this._setSelectedClass(this.view.model.get('selected')); }, - - _onClickLeftContent: function (event) { + + _onClickLeftContent: function(event) { this.view.trigger('click:leftContent', { shiftKey: event.shiftKey, model: this.view.model @@ -43,29 +43,29 @@ }, // Don't propagate dblClick event up to the list item because that will run an action on the item. - _onDblClickLeftContent: function () { + _onDblClickLeftContent: function() { // Since returning false, need to announce the event happened here since root level won't know about it. Streamus.channels.element.vent.trigger('click', event); // Don't allow to bubble up since handling click at this level. return false; }, - - _onMouseEnterLeftContent: function () { + + _onMouseEnterLeftContent: function() { this.isMouseOver = true; - + if (!this.view.model.get('selected')) { this.ui.leftContent.addClass('is-showingCheckbox'); this.ui.leftContent.removeClass('is-showingThumbnail'); - + this.view.checkboxRegion.show(new CheckboxView({ model: this.checkbox })); } }, - - _onMouseLeaveLeftContent: function () { + + _onMouseLeaveLeftContent: function() { this.isMouseOver = false; - + if (!this.view.model.get('selected')) { this.ui.leftContent.removeClass('is-showingCheckbox'); this.ui.leftContent.addClass('is-showingThumbnail'); @@ -73,19 +73,19 @@ this.view.checkboxRegion.empty(); } }, - - _onChangeSelected: function (model, selected) { + + _onChangeSelected: function(model, selected) { this._setSelectedClass(selected); this.checkbox.set('checked', selected); }, - _setSelectedClass: function (selected) { + _setSelectedClass: function(selected) { this.$el.toggleClass('is-selected', selected); - + if (!this.isMouseOver) { this.ui.leftContent.toggleClass('is-showingCheckbox', selected); this.ui.leftContent.toggleClass('is-showingThumbnail', !selected); - + if (selected) { this.view.checkboxRegion.show(new CheckboxView({ model: this.checkbox diff --git a/src/js/foreground/view/behavior/scrollable.js b/src/js/foreground/view/behavior/scrollable.js index f40e5a78..70c6f204 100644 --- a/src/js/foreground/view/behavior/scrollable.js +++ b/src/js/foreground/view/behavior/scrollable.js @@ -1,4 +1,4 @@ -define(function () { +define(function() { 'use strict'; var Scrollable = Marionette.Behavior.extend({ @@ -8,51 +8,51 @@ 'reset': '_onCollectionReset', 'remove': '_onCollectionRemove' }, - - initialize: function () { + + initialize: function() { // Throttle updating the scrollbar because many events can come in at once, but only need to reflect // the final state / potentially small updates while events coming in. // Bound in initialize because if bound on the class then all views which implement Scrollable will // share the throttle timer. this._throttleUpdateScrollbar = _.throttle(this._updateScrollbar.bind(this), 20); }, - - onAttach: function () { + + onAttach: function() { // More info: https://github.com/noraesae/perfect-scrollbar - // This needs to be ran during onShow for perfectScrollbar to do its math properly. + // This needs to be ran during onAttach for perfectScrollbar to do its math properly. this.$el.perfectScrollbar({ suppressScrollX: true, // 56px because that is the height of 1 listItem--medium minScrollbarLength: 56, includePadding: true }); - + // When showing a SlidingRender collection which has an initial set of items, // need to call update after setting up perfectScrollbar to ensure that initial load of items is parsed. _.defer(this._throttleUpdateScrollbar.bind(this)); }, - - onUpdateScrollbar: function () { + + onUpdateScrollbar: function() { this._throttleUpdateScrollbar(); }, - - _onCollectionAdd: function () { + + _onCollectionAdd: function() { this._throttleUpdateScrollbar(); }, - - _onCollectionReset: function () { + + _onCollectionReset: function() { this._throttleUpdateScrollbar(); }, - - _onCollectionRemove: function () { + + _onCollectionRemove: function() { // TODO: This is poor design. How can I fix this? // This needs to be deferred because SlidingRender's onCollectionRemove logic is deferred. _.defer(this._throttleUpdateScrollbar.bind(this)); }, - + _throttleUpdateScrollbar: null, - - _updateScrollbar: function () { + + _updateScrollbar: function() { // When the CollectionView is first initializing _updateScrollbar can fire before perfectScrollbar has been initialized. if (this.view._isShown) { // This needs to be deferred because _onCollection events fire before the view is added or removed. @@ -60,7 +60,7 @@ // once an ItemView exceeds the threshold, but the scrollbar height still needs to be updated. // Additionally, SlidingRender fires onChildAdd events when scrolling which causes lag when // the scrollbar does not need to be updated. - _.defer(function () { + _.defer(function() { this.$el.perfectScrollbar('update'); }.bind(this)); } diff --git a/src/js/foreground/view/behavior/slidingRender.js b/src/js/foreground/view/behavior/slidingRender.js index 59d9356a..a17e818b 100644 --- a/src/js/foreground/view/behavior/slidingRender.js +++ b/src/js/foreground/view/behavior/slidingRender.js @@ -1,4 +1,4 @@ -define(function (require) { +define(function(require) { 'use strict'; var Direction = require('common/enum/direction'); @@ -26,22 +26,22 @@ // Keep track of where user is scrolling from to determine direction and amount changed. lastScrollTop: 0, - - initialize: function () { + + initialize: function() { // IMPORTANT: Stub out the view's implementation of addChild with the slidingRender version. this.view.addChild = this._addChild.bind(this); this.view.showCollection = this._showCollection.bind(this); - + this.listenTo(Streamus.channels.window.vent, 'resize', this._onWindowResize); }, - - onRender: function () { + + onRender: function() { // It's important to set minRenderIndex before onAttach because if a view triggers ListHeightUpdated during its // onAttach then SlidingRender will call _setViewportHeight before minRenderIndex has been set. this.minRenderIndex = this._getMinRenderIndex(0); }, - - onAttach: function () { + + onAttach: function() { // Allow N items to be rendered initially where N is how many items need to cover the viewport. this._setViewportHeight(); @@ -54,29 +54,29 @@ var self = this; // Throttle the scroll event because scrolls can happen a lot and don't need to re-calculate very often. - this.$el.scroll(_.throttle(function () { + this.$el.scroll(_.throttle(function() { self._setRenderedElements(this.scrollTop); }, 20)); }, // jQuery UI's sortable needs to be able to know the minimum rendered index. Whenever an external // event requests the min render index -- return it! - onGetMinRenderIndex: function () { + onGetMinRenderIndex: function() { this.view.triggerMethod('GetMinRenderIndexResponse', { minRenderIndex: this.minRenderIndex }); }, - - onListHeightUpdated: function () { + + onListHeightUpdated: function() { this._setViewportHeight(); }, - - _onWindowResize: function () { + + _onWindowResize: function() { this._setViewportHeight(); }, // Whenever the viewport height is changed -- adjust the items which are currently rendered to match - _setViewportHeight: function () { + _setViewportHeight: function() { this.viewportHeight = this.$el.height(); // Unload or load N items where N is the difference in viewport height. @@ -93,20 +93,19 @@ if (this.view.collection.length > currentMaxRenderIndex) { this._removeItemsByIndex(currentMaxRenderIndex, indexDifference); } - } - else if (indexDifference < 0) { + } else if (indexDifference < 0) { // Load N items - for (var count = 0; count < Math.abs(indexDifference) ; count++) { + for (var count = 0; count < Math.abs(indexDifference); count++) { this._renderElementAtIndex(currentMaxRenderIndex + 1 + count); } } - + this._setHeightPaddingTop(); }, // When deleting an element from a list it's important to render the next element (if any) since // positions change when removing. - _renderElementAtIndex: function (index) { + _renderElementAtIndex: function(index) { var rendered = false; if (this.view.collection.length > index) { @@ -121,11 +120,11 @@ return rendered; }, - _setRenderedElements: function (scrollTop) { + _setRenderedElements: function(scrollTop) { // Figure out the range of items currently rendered: var currentMinRenderIndex = this.minRenderIndex; var currentMaxRenderIndex = this.maxRenderIndex; - + // Figure out the range of items which need to be rendered: var minRenderIndex = this._getMinRenderIndex(scrollTop); var maxRenderIndex = this._getMaxRenderIndex(scrollTop); @@ -186,8 +185,8 @@ this.lastScrollTop = scrollTop; }, - - _setHeightPaddingTop: function () { + + _setHeightPaddingTop: function() { this._setPaddingTop(); this._setHeight(); }, @@ -197,13 +196,13 @@ this.view.ui.childContainer.css('padding-top', this._getPaddingTop()); }, - _getPaddingTop: function () { + _getPaddingTop: function() { return this.minRenderIndex * this.childViewHeight; }, // Set the elements height calculated from the number of potential items rendered into it. // Necessary because items are lazy-appended for performance, but scrollbar size changing not desired. - _setHeight: function () { + _setHeight: function() { // Subtracting minRenderIndex is important because of how CSS renders the element. If you don't subtract minRenderIndex // then the rendered items will push up the height of the element by minRenderIndex * childViewHeight. var height = (this.view.collection.length - this.minRenderIndex) * this.childViewHeight; @@ -216,13 +215,13 @@ this.view.ui.childContainer.height(height); }, - _addItems: function (models, indexOffset, currentTotalRendered, isAddingToEnd) { + _addItems: function(models, indexOffset, currentTotalRendered, isAddingToEnd) { var skippedCount = 0; var ChildView; - _.each(models, function (model, index) { + _.each(models, function(model, index) { ChildView = this.view.getChildView(model); - + var shouldAdd = this._indexWithinRenderRange(index + indexOffset); if (shouldAdd) { @@ -241,7 +240,7 @@ }, // Remove N items from the end of the render item list. - _removeItemsByIndex: function (startIndex, countToRemove) { + _removeItemsByIndex: function(startIndex, countToRemove) { for (var index = 0; index < countToRemove; index++) { var item = this.view.collection.at(startIndex - index); var childView = this.view.children.findByModel(item); @@ -249,8 +248,8 @@ } }, - _removeItems: function (models) { - _.each(models, function (model) { + _removeItems: function(models) { + _.each(models, function(model) { var childView = this.view.children.findByModel(model); this.view.removeChildView(childView); @@ -259,10 +258,10 @@ // Overridden Marionette's internal method to loop through collection and show each child view. // BUG: https://github.com/marionettejs/backbone.marionette/issues/2021 - _showCollection: function () { + _showCollection: function() { var viewIndex = 0; var ChildView; - this.view.collection.each(function (child, index) { + this.view.collection.each(function(child, index) { ChildView = this.view.getChildView(child); if (this._indexWithinRenderRange(index)) { @@ -275,21 +274,21 @@ // The bypass flag is set when shouldAdd has already been determined elsewhere. // This is necessary because sometimes the view's model's index in its collection is different than the view's index in the collectionview. // In this scenario the index has already been corrected before _addChild is called so the index isn't a valid indicator of whether the view should be added. - _addChild: function (child, ChildView, index, bypass) { + _addChild: function(child, ChildView, index, bypass) { var shouldAdd = false; if (this.minRenderIndex > -1 && this.maxRenderIndex > -1) { shouldAdd = bypass || this._indexWithinRenderRange(index); } - + if (shouldAdd) { return Marionette.CompositeView.prototype.addChild.apply(this.view, arguments); } }, - _getMinRenderIndex: function (scrollTop) { + _getMinRenderIndex: function(scrollTop) { var minRenderIndex = Math.floor(scrollTop / this.childViewHeight) - this.threshold; - + if (minRenderIndex < 0) { minRenderIndex = 0; } @@ -297,7 +296,7 @@ return minRenderIndex; }, - _getMaxRenderIndex: function (scrollTop) { + _getMaxRenderIndex: function(scrollTop) { // Subtract 1 to make math 'inclusive' instead of 'exclusive' var maxRenderIndex = Math.ceil((scrollTop / this.childViewHeight) + (this.viewportHeight / this.childViewHeight)) - 1 + this.threshold; @@ -305,7 +304,7 @@ }, // Returns true if an childView at the given index would not be fully visible -- part of it rendering out of the top of the viewport. - _indexOverflowsTop: function (index) { + _indexOverflowsTop: function(index) { var position = index * this.childViewHeight; var scrollPosition = this.$el.scrollTop(); @@ -314,7 +313,7 @@ return overflowsTop; }, - _indexOverflowsBottom: function (index) { + _indexOverflowsBottom: function(index) { // Add one to index because want to get the bottom of the element and not the top. var position = (index + 1) * this.childViewHeight; var scrollPosition = this.$el.scrollTop() + this.viewportHeight; @@ -324,13 +323,13 @@ return overflowsBottom; }, - _indexWithinRenderRange: function (index) { + _indexWithinRenderRange: function(index) { return index >= this.minRenderIndex && index <= this.maxRenderIndex; }, // TODO: An animation on this would be nice. // Ensure that the active item is visible by setting the container's scrollTop to a position which allows it to be seen. - _scrollToItem: function (item) { + _scrollToItem: function(item) { var itemIndex = this.view.collection.indexOf(item); var overflowsTop = this._indexOverflowsTop(itemIndex); @@ -344,8 +343,7 @@ if (overflowsBottom) { // Add 1 to index because want the bottom of the element and not the top. scrollTop = (itemIndex + 1) * this.childViewHeight - this.viewportHeight; - } - else if (overflowsTop) { + } else if (overflowsTop) { scrollTop = itemIndex * this.childViewHeight; } @@ -354,7 +352,7 @@ }, // Reset min/max, scrollTop, paddingTop and height to their default values. - _onCollectionReset: function () { + _onCollectionReset: function() { this.$el.scrollTop(0); this.lastScrollTop = 0; @@ -363,12 +361,12 @@ this._setHeightPaddingTop(); }, - - _onCollectionRemove: function (item, collection, options) { + + _onCollectionRemove: function(item, collection, options) { // TODO: just modify the index in anticipation of the view being removed rather than wrapping in a setTimeout. // I've wrapped this in a setTimeout because the CollectionView has yet to remove the model which is being removed from the collection. // Because of this, _renderElementAtIndex has an off-by-one error due to the presence of the view whose model is being removed. - setTimeout(function () { + setTimeout(function() { // When a rendered view is lost - render the next one since there's a spot in the viewport if (this._indexWithinRenderRange(options.index)) { var rendered = this._renderElementAtIndex(this.maxRenderIndex); @@ -382,8 +380,8 @@ this._setHeightPaddingTop(); }.bind(this)); }, - - _onCollectionAdd: function (item, collection) { + + _onCollectionAdd: function(item, collection) { var index = collection.indexOf(item); var indexWithinRenderRange = this._indexWithinRenderRange(index); @@ -401,8 +399,8 @@ this._setHeightPaddingTop(); }, - - _onCollectionChangeActive: function (item, active) { + + _onCollectionChangeActive: function(item, active) { if (active) { this._scrollToItem(item); } diff --git a/src/js/foreground/view/behavior/sortable.js b/src/js/foreground/view/behavior/sortable.js index 8e30a068..150610af 100644 --- a/src/js/foreground/view/behavior/sortable.js +++ b/src/js/foreground/view/behavior/sortable.js @@ -1,4 +1,4 @@ -define(function (require) { +define(function(require) { 'use strict'; var ListItemType = require('common/enum/listItemType'); @@ -7,15 +7,15 @@ placeholderClass: 'sortable-placeholder', isDraggingClass: 'is-dragging', childViewHeight: 56, - - onRender: function () { - this.view.$el.scroll(_.throttle(function () { + + onRender: function() { + this.view.$el.scroll(_.throttle(function() { this.view.ui.childContainer.sortable('refresh'); }.bind(this), 20)); this.view.ui.childContainer.sortable(this._getSortableOptions()); }, - + _getSortableOptions: function() { var sortableOptions = { // Append to body so that the placeholder appears above all other elements instead of under when dragging between regions. @@ -43,14 +43,14 @@ return sortableOptions; }, - - _helper: function () { + + _helper: function() { return $('', { 'class': 'sortable-selectedItemsCount' }); }, - - _change: function (event, ui) { + + _change: function(event, ui) { var placeholderAdjacent = false; // When dragging an element up/down its own list -- hide the sortable helper around the element being dragged. var draggedItems = this.view.collection.selected(); @@ -59,7 +59,7 @@ var draggedModelId = draggedItems[0].get('id'); placeholderAdjacent = ui.placeholder.next().data('id') === draggedModelId || ui.placeholder.prev().data('id') === draggedModelId; } - + $('.' + this.placeholderClass).toggleClass('is-hidden', placeholderAdjacent); this.view.ui.childContainer.sortable('refresh'); @@ -67,19 +67,19 @@ // Hiding or removing the placeholder modifies the height of the child container which can cause a scrollbar to appear/disappear. So, need to notify. this.view.triggerMethod('UpdateScrollbar'); }, - - _start: function (event, ui) { + + _start: function(event, ui) { Streamus.channels.element.vent.trigger('drag'); this.view.triggerMethod('ItemDragged', { item: this.view.collection.get(ui.item.data('id')), shiftKey: event.shiftKey }); - + // Set helper text here, not in helper, because dragStart may select a search result. var selectedItems = this.view.collection.selected(); ui.helper.text(selectedItems.length); - var draggedSongs = _.map(selectedItems, function (item) { + var draggedSongs = _.map(selectedItems, function(item) { return item.get('song'); }); @@ -91,23 +91,23 @@ }, // Placeholder stops being accessible once beforeStop finishes, so store its index here for use later. - _beforeStop: function (event, ui) { + _beforeStop: function(event, ui) { // Subtract one from placeholderIndex when parentNode exists because jQuery UI moves the HTML element above the placeholder. this.view.ui.childContainer.data({ placeholderIndex: ui.placeholder.index() - 1 }); }, - - _stop: function (event, ui) { + + _stop: function(event, ui) { var childContainer = this.view.ui.childContainer; var isParentNodeLost = ui.item[0].parentNode === null; - + // TODO: Check collection isImmutable instead of ListItemType. // The SearchResult view is not able to be moved so disable move logic for it. // If the mouse dropped the items not over the given list don't run move logic. var allowMove = ui.item.data('type') !== ListItemType.SearchResult && childContainer.is(':hover'); if (allowMove) { - this.view.once('GetMinRenderIndexResponse', function (response) { + this.view.once('GetMinRenderIndexResponse', function(response) { var dropIndex = childContainer.data('placeholderIndex') + response.minRenderIndex; this._moveItems(this.view.collection.selected(), dropIndex, isParentNodeLost); this._cleanup(); @@ -122,13 +122,13 @@ var removeHtmlElement = allowMove || isParentNodeLost; return removeHtmlElement; }, - - _cleanup: function () { + + _cleanup: function() { this.view.ui.childContainer.removeData('draggedSongs placeholderIndex').removeClass(this.isDraggingClass); Streamus.channels.element.vent.trigger('drop'); }, - - _receive: function (event, ui) { + + _receive: function(event, ui) { // If the parentNode does not exist then slidingRender has removed the HTML element which means the HTML element is not above the placeholder and I need to +1. // This only applies for receiving and not for sorting elements within the parent list, so don't do this logic in onBeforeStop because it's not clear // if sort or receive is happening. @@ -138,11 +138,11 @@ placeholderIndex += 1; } - this.view.once('GetMinRenderIndexResponse', function (response) { + this.view.once('GetMinRenderIndexResponse', function(response) { this.view.collection.addSongs(ui.sender.data('draggedSongs'), { index: placeholderIndex + response.minRenderIndex }); - + // TODO: Since I provided the index that the item would be inserted at in the collection, the collection does not resort. // But, the index in the collection does not correspond to the index in the CollectionView -- that's simply the placeholderIndex. Not sure how to handle that. // I'd need to intercept the _onCollectionAdd event of Marionette, calculate the proper index, and pass bypass: true in, but not going to do that for now. @@ -150,35 +150,35 @@ }.bind(this)); this.view.triggerMethod('GetMinRenderIndex'); }, - - _over: function (event, ui) { + + _over: function(event, ui) { this._overrideSortableItem(ui); this._decoratePlaceholder(ui); - + // Hiding or removing the placeholder modifies the height of the child container which can cause a scrollbar to appear/disappear. So, need to notify. this.view.triggerMethod('UpdateScrollbar'); }, - + _out: function() { // Hiding or removing the placeholder modifies the height of the child container which can cause a scrollbar to appear/disappear. So, need to notify. this.view.triggerMethod('UpdateScrollbar'); }, - _moveItems: function (items, dropIndex, isParentNodeLost) { + _moveItems: function(items, dropIndex, isParentNodeLost) { var moved = false; var itemsHandledBelowOrAtDropIndex = 0; var itemsHandled = 0; // Capture the indices of the items being moved before actually moving them because sorts on the collection will // change indices during each iteration. - var dropInfoList = _.map(items, function (item) { + var dropInfoList = _.map(items, function(item) { return { itemId: item.get('id'), itemIndex: this.view.collection.indexOf(item) }; }, this); - _.each(dropInfoList, function (dropInfo) { + _.each(dropInfoList, function(dropInfo) { var index = dropIndex; var aboveDropIndex = dropInfo.itemIndex > dropIndex; @@ -195,7 +195,7 @@ index -= (itemsHandledBelowOrAtDropIndex - 1); } } - + // Pass silent: true to moveToIndex because we might be looping over many items in which case I don't want to refresh the view repeatedly. var moveResult = this.view.collection.moveToIndex(dropInfo.itemId, index, { silent: true @@ -205,14 +205,13 @@ if (moveResult.moved) { moved = true; } - + if (!aboveDropIndex) { itemsHandledBelowOrAtDropIndex += 1; } itemsHandled++; - }, this); - + }, this); if (moved) { // If a move happened call sort without silent so that views can update accordingly. @@ -223,12 +222,12 @@ this.view._behaviors[1]._updateScrollbar(); } }, - - _decoratePlaceholder: function (ui) { + + _decoratePlaceholder: function(ui) { var notDroppable = false; var warnDroppable = false; var placeholderText = ''; - + var overOtherCollection = this.view.childViewType !== ui.item.data('type'); if (overOtherCollection) { // Decorate the placeholder to indicate songs can't be copied. @@ -254,10 +253,10 @@ // Override jQuery UI's sortableItem to allow a dragged item to scroll another sortable collection. // Need to re-call method on start to ensure that dragging still works inside normal parent collection, too. // http://stackoverflow.com/questions/11025470/jquery-ui-sortable-scrolling-jsfiddle-example - _overrideSortableItem: function (ui) { + _overrideSortableItem: function(ui) { var placeholderParent = ui.placeholder.parent().parent(); var sortableItem = ui.item.data('sortableItem'); - + // If the item being sorted has been unloaded by slidingRender behavior then sortableItem will be unavailable. // In this scenario, fall back to the more expensive query of getting a reference to the sortable instance via its parent's ID. if (_.isUndefined(sortableItem)) { diff --git a/src/js/foreground/view/behavior/tooltip.js b/src/js/foreground/view/behavior/tooltip.js index 9730a022..ce447dc2 100644 --- a/src/js/foreground/view/behavior/tooltip.js +++ b/src/js/foreground/view/behavior/tooltip.js @@ -2,9 +2,9 @@ // if it has a title. It will also apply tooltips to child elements if they have the js-tooltipable or // js-textTooltipable class. The js-textTooltipable class assumes that the element's text is to be conditionally // shown as a tooltip. The text tooltip will only be shown if the text is overflows the element. -define(function () { +define(function() { 'use strict'; - + // TODO: There might be a more elegant way to initialize these, but I definitely don't want to query for the window every time. $.extend($.fn.qtip.defaults.position, { viewport: $(window), @@ -27,24 +27,24 @@ define(function () { // Children which need tooltips, but also need to take into account overflowing, are decorated with the js-textTooltipable class. textTooltipable: '.js-textTooltipable' }, - - initialize: function () { + + initialize: function() { // Give Tooltip an array property of titleMutationObservers: https://github.com/jashkenas/backbone/issues/1442 // Mutation observers are used to track changes to js-textTooltipable elements. If the text // is modified then its overflowing state needs to be re-evaluated. _.extend(this, { - titleMutationObservers: [] + titleMutationObservers: [] }); }, // TODO: There's a bug in Marionette where onAttach doesn't fire for CollectionView items on re-render: https://github.com/marionettejs/backbone.marionette/issues/2209 - onShow: function () { + onShow: function() { // Defer this because can't measure until onAttach has fired which isn't guaranteed with onShow. _.defer(this._setTooltips.bind(this)); }, - - onBeforeDestroy: function () { - _.each(this.titleMutationObservers, function (titleMutationObserver) { + + onBeforeDestroy: function() { + _.each(this.titleMutationObservers, function(titleMutationObserver) { titleMutationObserver.disconnect(); }); this.titleMutationObservers.length = 0; @@ -53,8 +53,8 @@ define(function () { this._destroy(this.ui.tooltipable); this._destroy(this.ui.textTooltipable); }, - - _setTooltips: function () { + + _setTooltips: function() { // Important to check since _setTooltips can be called through defer. if (!this.view.isDestroyed) { // Decorate the view itself @@ -66,24 +66,24 @@ define(function () { // Decorate child views if (this.ui.tooltipable.length > 0) { - _.each(this.ui.tooltipable, function (tooltipable) { + _.each(this.ui.tooltipable, function(tooltipable) { this._decorateTooltipable($(tooltipable)); }, this); } if (this.ui.textTooltipable.length > 0) { - _.each(this.ui.textTooltipable, function (textTooltipable) { + _.each(this.ui.textTooltipable, function(textTooltipable) { this._decorateTextTooltipable($(textTooltipable)); }, this); } } }, - + _decorateTooltipable: function(tooltipableElement) { this._applyQtip(tooltipableElement); this._setTitleMutationObserver(tooltipableElement, false); }, - + _decorateTextTooltipable: function(textTooltipableElement) { this._setTitleTooltip(textTooltipableElement, true); this._setTitleMutationObserver(textTooltipableElement, true); @@ -93,15 +93,15 @@ define(function () { _isTextTooltipableElement: function(element) { return element.hasClass('js-textTooltipable'); }, - - _isTooltipableElement: function (element) { + + _isTooltipableElement: function(element) { return element.hasClass('js-tooltipable'); }, // Whenever an element's title changes -- need to re-check to see if a title exists / if the element is overflowing and apply/remove the tooltip accordingly. - _setTitleMutationObserver: function (element, checkOverflow) { - var titleMutationObserver = new window.MutationObserver(function (mutations) { - mutations.forEach(function (mutation) { + _setTitleMutationObserver: function(element, checkOverflow) { + var titleMutationObserver = new window.MutationObserver(function(mutations) { + mutations.forEach(function(mutation) { var oldTitle = mutation.attributeName === 'title' ? mutation.oldValue : undefined; this._setTitleTooltip(element, checkOverflow, oldTitle); }.bind(this)); @@ -117,8 +117,8 @@ define(function () { this.titleMutationObservers.push(titleMutationObserver); }, - - _setTitleTooltip: function (element, checkOverflow, oldTitle) { + + _setTitleTooltip: function(element, checkOverflow, oldTitle) { if (checkOverflow) { // Only show the tooltip if the title is overflowing. var textOverflows = element[0].offsetWidth < element[0].scrollWidth; @@ -145,14 +145,14 @@ define(function () { } } }, - + _applyQtip: function(element) { element.qtip(); }, // Unbind qTip to allow the GC to clean-up everything. // Memory leak will happen if this is not called. - _destroy: function (element) { + _destroy: function(element) { element.qtip('destroy', true); } }); diff --git a/src/js/foreground/view/contextMenu/contextMenuItemView.js b/src/js/foreground/view/contextMenu/contextMenuItemView.js index 7a4d418d..03b94f3f 100644 --- a/src/js/foreground/view/contextMenu/contextMenuItemView.js +++ b/src/js/foreground/view/contextMenu/contextMenuItemView.js @@ -1,4 +1,4 @@ -define(function (require) { +define(function(require) { 'use strict'; var Tooltip = require('foreground/view/behavior/tooltip'); @@ -6,7 +6,7 @@ var ContextMenuItemView = Marionette.ItemView.extend({ tagName: 'li', - className: function () { + className: function() { var className = 'listItem listItem--small js-tooltipable'; className += this.model.get('disabled') ? ' is-disabled' : ''; return className; @@ -17,25 +17,25 @@ 'click': '_onClick', }, - attributes: function () { + attributes: function() { return { title: this.model.get('title') }; }, - + behaviors: { Tooltip: { behaviorClass: Tooltip } }, - _onClick: function () { + _onClick: function() { var enabled = !this.model.get('disabled'); if (enabled) { this.model.get('onClick')(); } - + // Return false to prevent the view from closing which emulates how native, disabled contextMenu views work when clicked. return enabled; } diff --git a/src/js/foreground/view/contextMenu/contextMenuRegion.js b/src/js/foreground/view/contextMenu/contextMenuRegion.js index f713d6bb..db1b4714 100644 --- a/src/js/foreground/view/contextMenu/contextMenuRegion.js +++ b/src/js/foreground/view/contextMenu/contextMenuRegion.js @@ -1,4 +1,4 @@ -define(function (require) { +define(function(require) { 'use strict'; var ContextMenu = require('foreground/model/contextMenu'); @@ -7,19 +7,19 @@ var ContextMenuRegion = Marionette.Region.extend({ contextMenu: null, - initialize: function () { + initialize: function() { this.contextMenu = new ContextMenu(); - + this.listenTo(Streamus.channels.element.vent, 'drag', this._onElementDrag); this.listenTo(Streamus.channels.element.vent, 'click', this._onElementClick); this.listenTo(Streamus.channels.element.vent, 'contextMenu', this._onElementContextMenu); }, - - _onElementDrag: function () { + + _onElementDrag: function() { this._hideContextMenu(); }, - - _onElementClick: function () { + + _onElementClick: function() { this._hideContextMenu(); }, @@ -33,8 +33,8 @@ this._hideContextMenu(); } }, - - _showContextMenu: function (top, left) { + + _showContextMenu: function(top, left) { // TODO: A bug in Marionette causes this.$el.height/width to be null on first use, https://github.com/marionettejs/backbone.marionette/issues/1971. var $this = $(this.el); @@ -50,8 +50,8 @@ containerWidth: $this.width() })); }, - - _hideContextMenu: function () { + + _hideContextMenu: function() { if (this.currentView) { this.currentView.hide(); } diff --git a/src/js/foreground/view/contextMenu/contextMenuView.js b/src/js/foreground/view/contextMenu/contextMenuView.js index e390c44b..7f28e246 100644 --- a/src/js/foreground/view/contextMenu/contextMenuView.js +++ b/src/js/foreground/view/contextMenu/contextMenuView.js @@ -1,4 +1,4 @@ -define(function (require) { +define(function(require) { 'use strict'; var ContextMenuItemView = require('foreground/view/contextMenu/contextMenuItemView'); @@ -11,7 +11,7 @@ childView: ContextMenuItemView, childViewContainer: '@ui.contextMenuItems', template: _.template(ContextMenuTemplate), - templateHelpers: function () { + templateHelpers: function() { return { viewId: this.id }; @@ -20,18 +20,18 @@ containerHeight: 0, containerWidth: 0, - ui: function () { + ui: function() { return { contextMenuItems: '#' + this.id + '-contextMenuItems' }; }, - + initialize: function(options) { this.containerHeight = options.containerHeight; this.containerWidth = options.containerWidth; }, - onAttach: function () { + onAttach: function() { var offsetTop = this._ensureOffset(this.model.get('top'), this.$el.outerHeight(), this.containerHeight); var offsetLeft = this._ensureOffset(this.model.get('left'), this.$el.outerWidth(), this.containerWidth); @@ -39,16 +39,16 @@ top: offsetTop, left: offsetLeft }); - + this.$el.addClass('is-visible'); }, // TODO: Move this logic to the ContextMenuRegion and use the same logic as in NotificationRegion. - hide: function () { + hide: function() { this.$el.off('webkitTransitionEnd').one('webkitTransitionEnd', this._onTransitionOutComplete.bind(this)); this.$el.removeClass('is-visible'); }, - - _onTransitionOutComplete: function () { + + _onTransitionOutComplete: function() { this.model.get('items').reset(); this.destroy(); }, diff --git a/src/js/foreground/view/dialog/browserSettingsDialogView.js b/src/js/foreground/view/dialog/browserSettingsDialogView.js index c068cad8..13c58fbc 100644 --- a/src/js/foreground/view/dialog/browserSettingsDialogView.js +++ b/src/js/foreground/view/dialog/browserSettingsDialogView.js @@ -1,4 +1,4 @@ -define(function (require) { +define(function(require) { 'use strict'; var Dialog = require('foreground/model/dialog'); @@ -8,19 +8,19 @@ var BrowserSettingsDialogView = DialogView.extend({ id: 'browserSettingsDialog', - initialize: function () { + initialize: function() { this.model = new Dialog({ submitButtonText: chrome.i18n.getMessage('save') }); this.contentView = new BrowserSettingsView({ model: Streamus.backgroundPage.browserSettings - }); + }); DialogView.prototype.initialize.apply(this, arguments); }, - onSubmit: function () { + onSubmit: function() { this.contentView.save(); } }); diff --git a/src/js/foreground/view/dialog/browserSettingsView.js b/src/js/foreground/view/dialog/browserSettingsView.js index 3a17812b..8c1036d9 100644 --- a/src/js/foreground/view/dialog/browserSettingsView.js +++ b/src/js/foreground/view/dialog/browserSettingsView.js @@ -1,4 +1,4 @@ -define(function (require) { +define(function(require) { 'use strict'; var Checkboxes = require('foreground/collection/checkboxes'); @@ -9,13 +9,13 @@ var BrowserSettingsView = DialogContentView.extend({ id: 'browserSettings', template: _.template(BrowserSettingsTemplate), - + templateHelpers: { contextMenusMessage: chrome.i18n.getMessage('contextMenus'), websiteEnhancementsMessage: chrome.i18n.getMessage('websiteEnhancements') }, - - regions: function () { + + regions: function() { return { showTextSelectionContextMenuRegion: '#' + this.id + '-showTextSelectionContextMenuRegion', showYouTubeLinkContextMenuRegion: '#' + this.id + '-showYouTubeLinkContextMenuRegion', @@ -24,22 +24,22 @@ enhanceBeatportRegion: '#' + this.id + '-enhanceBeatportRegion', }; }, - - initialize: function () { + + initialize: function() { this.checkboxes = new Checkboxes(); }, - + save: function() { var currentValues = {}; - this.checkboxes.each(function (checkbox) { + this.checkboxes.each(function(checkbox) { currentValues[checkbox.get('property')] = checkbox.get('checked'); }); this.model.save(currentValues); }, - - onRender: function () { + + onRender: function() { // TODO: It would be sweet to render some CollectionViews which are able to render radios, selects or checkboxes... but not just yet. this._showCheckbox('showTextSelectionContextMenu', 'textSelection'); this._showCheckbox('showYouTubeLinkContextMenu', 'youTubeLinks'); @@ -47,8 +47,8 @@ this._showCheckbox('enhanceYouTube', 'youTube'); this._showCheckbox('enhanceBeatport', 'beatport'); }, - - _showCheckbox: function (propertyName, message) { + + _showCheckbox: function(propertyName, message) { var checkbox = this.checkboxes.add({ labelText: chrome.i18n.getMessage(message), checked: this.model.get(propertyName), diff --git a/src/js/foreground/view/dialog/clearStreamDialogView.js b/src/js/foreground/view/dialog/clearStreamDialogView.js index 6a346a8c..f62486ce 100644 --- a/src/js/foreground/view/dialog/clearStreamDialogView.js +++ b/src/js/foreground/view/dialog/clearStreamDialogView.js @@ -1,15 +1,15 @@ -define(function (require) { +define(function(require) { 'use strict'; var Dialog = require('foreground/model/dialog'); var DialogContentView = require('foreground/view/dialog/dialogContentView'); var DialogView = require('foreground/view/dialog/dialogView'); - + var ClearStreamDialogView = DialogView.extend({ id: 'clearStreamDialog', stream: null, - - initialize: function () { + + initialize: function() { this.model = new Dialog({ reminderProperty: 'remindClearStream' }); @@ -22,8 +22,8 @@ DialogView.prototype.initialize.apply(this, arguments); }, - - onSubmit: function () { + + onSubmit: function() { this.stream.get('items').clear(); } }); diff --git a/src/js/foreground/view/dialog/createPlaylistDialogView.js b/src/js/foreground/view/dialog/createPlaylistDialogView.js index edfcb528..e6594d30 100644 --- a/src/js/foreground/view/dialog/createPlaylistDialogView.js +++ b/src/js/foreground/view/dialog/createPlaylistDialogView.js @@ -1,14 +1,14 @@ -define(function (require) { +define(function(require) { 'use strict'; var Dialog = require('foreground/model/dialog'); var CreatePlaylistView = require('foreground/view/dialog/createPlaylistView'); var DialogView = require('foreground/view/dialog/dialogView'); - + var CreatePlaylistDialogView = DialogView.extend({ id: 'createPlaylistDialog', - initialize: function (options) { + initialize: function(options) { this.model = new Dialog({ submitButtonText: chrome.i18n.getMessage('create') }); @@ -16,11 +16,11 @@ this.contentView = new CreatePlaylistView({ songs: options && options.songs ? options.songs : [] }); - + DialogView.prototype.initialize.apply(this, arguments); - }, - - onSubmit: function () { + }, + + onSubmit: function() { this.contentView.createPlaylist(); } }); diff --git a/src/js/foreground/view/dialog/createPlaylistView.js b/src/js/foreground/view/dialog/createPlaylistView.js index a903cfda..9b81995e 100644 --- a/src/js/foreground/view/dialog/createPlaylistView.js +++ b/src/js/foreground/view/dialog/createPlaylistView.js @@ -1,4 +1,4 @@ -define(function (require) { +define(function(require) { 'use strict'; var DataSourceType = require('common/enum/dataSourceType'); @@ -10,8 +10,8 @@ template: _.template(CreatePlaylistTemplate), // TODO: Would also be nice to pull this from the DB instead, need to truncate DB column to 150. titleMaxLength: 150, - - templateHelpers: function () { + + templateHelpers: function() { return { titleMessage: chrome.i18n.getMessage('title'), playlistUrlMessage: chrome.i18n.getMessage('playlistUrl'), @@ -20,8 +20,8 @@ showDataSource: this.songs.length === 0 }; }, - - ui: function () { + + ui: function() { return { title: '#' + this.id + '-title', titleCharacterCount: '#' + this.id + '-title-characterCount', @@ -34,31 +34,31 @@ 'input @ui.title': '_onInputTitle', 'input @ui.dataSource': '_onInputDataSource' }, - + playlists: null, dataSourceManager: null, songs: [], - - initialize: function (options) { + + initialize: function(options) { this.songs = options && options.songs ? options.songs : this.songs; this.playlists = Streamus.backgroundPage.signInManager.get('signedInUser').get('playlists'); this.dataSourceManager = Streamus.backgroundPage.dataSourceManager; }, - onRender: function () { + onRender: function() { this._setDataSourceAsUserInput(); this._setTitleCharacterCount(); this._validateTitle(); }, - onAttach: function () { + onAttach: function() { // Reset the value after focusing to focus without selecting. this.ui.title.focus().val(this.ui.title.val()); }, - - createPlaylist: function () { + + createPlaylist: function() { var trimmedTitle = this._getTrimmedTitle(); - + if (this.songs.length > 0) { this.playlists.addPlaylistWithSongs(trimmedTitle, this.songs); } else { @@ -66,32 +66,32 @@ this.playlists.addPlaylistByDataSource(trimmedTitle, dataSource); } }, - + _onInputDataSource: function() { this._debounceParseInput(); }, - - _onInputTitle: function () { + + _onInputTitle: function() { this._setTitleCharacterCount(); this._validateTitle(); }, - - _setTitleCharacterCount: function () { + + _setTitleCharacterCount: function() { var trimmedTitle = this._getTrimmedTitle(); this.ui.titleCharacterCount.text(trimmedTitle.length); }, // Throttle for typing support so I don't continuously validate while typing - _debounceParseInput: _.debounce(function () { + _debounceParseInput: _.debounce(function() { // Wrap in a setTimeout to let drop event finish (no real noticeable lag but keeps things DRY easier) setTimeout(this._parseInput.bind(this)); }, 100), - - _getTrimmedTitle: function () { + + _getTrimmedTitle: function() { return this.ui.title.val().trim(); }, - - _parseInput: function () { + + _parseInput: function() { var youTubeUrl = this.ui.dataSource.val().trim(); if (youTubeUrl !== '') { @@ -100,21 +100,21 @@ this._resetInputState(); } }, - - _validateTitle: function () { + + _validateTitle: function() { // When the user submits - check to see if they provided a playlist name var title = this._getTrimmedTitle(); this.ui.title.toggleClass('is-invalid', title.length === 0 || title.length > this.titleMaxLength); }, - _setDataSourceAsUserInput: function () { + _setDataSourceAsUserInput: function() { var dataSource = this.dataSourceManager.getDataSource({ type: DataSourceType.UserInput }); this.ui.dataSource.data('datasource', dataSource); }, - + _setDataSourceViaUrl: function(url) { // Check validity of URL and represent validity via invalid class. var dataSource = this.dataSourceManager.getDataSource({ @@ -129,8 +129,8 @@ error: this._setErrorState.bind(this) }); }, - - _onParseUrlSuccess: function (dataSource) { + + _onParseUrlSuccess: function(dataSource) { if (!this.isDestroyed) { this.ui.dataSource.data('datasource', dataSource); @@ -140,8 +140,8 @@ }); } }, - - _onGetTitleSuccess: function (title) { + + _onGetTitleSuccess: function(title) { if (!this.isDestroyed) { this.ui.title.val(title); this._validateTitle(); @@ -149,14 +149,14 @@ this.ui.dataSourceHint.text(chrome.i18n.getMessage('playlistLoaded')); } }, - - _setErrorState: function () { + + _setErrorState: function() { if (!this.isDestroyed) { this.ui.dataSourceHint.text(chrome.i18n.getMessage('errorLoadingUrl')); this.ui.dataSource.removeClass('is-valid').addClass('is-invalid'); } }, - + _resetInputState: function() { this.ui.dataSource.removeClass('is-invalid is-valid').removeData('datasource'); this.ui.dataSourceHint.text(''); diff --git a/src/js/foreground/view/dialog/deletePlaylistDialogView.js b/src/js/foreground/view/dialog/deletePlaylistDialogView.js index 89a445ea..4effff08 100644 --- a/src/js/foreground/view/dialog/deletePlaylistDialogView.js +++ b/src/js/foreground/view/dialog/deletePlaylistDialogView.js @@ -1,14 +1,14 @@ -define(function (require) { +define(function(require) { 'use strict'; var Dialog = require('foreground/model/dialog'); var DeletePlaylistView = require('foreground/view/dialog/deletePlaylistView'); var DialogView = require('foreground/view/dialog/dialogView'); - + var DeletePlaylistDialogView = DialogView.extend({ id: 'deletePlaylistDialog', - initialize: function (options) { + initialize: function(options) { this.model = new Dialog({ submitButtonText: chrome.i18n.getMessage('delete'), reminderProperty: 'remindDeletePlaylist' @@ -20,8 +20,8 @@ DialogView.prototype.initialize.apply(this, arguments); }, - - onSubmit: function () { + + onSubmit: function() { this.contentView.deletePlaylist(); } }); diff --git a/src/js/foreground/view/dialog/deletePlaylistView.js b/src/js/foreground/view/dialog/deletePlaylistView.js index 9361b323..361d0e83 100644 --- a/src/js/foreground/view/dialog/deletePlaylistView.js +++ b/src/js/foreground/view/dialog/deletePlaylistView.js @@ -1,4 +1,4 @@ -define(function (require) { +define(function(require) { 'use strict'; var DialogContentView = require('foreground/view/dialog/dialogContentView'); @@ -6,11 +6,11 @@ var DeletePlaylistView = DialogContentView.extend({ template: _.template(DeletePlaylistTemplate), - + templateHelpers: { deleteMessage: chrome.i18n.getMessage('delete') }, - + deletePlaylist: function() { this.model.destroy(); } diff --git a/src/js/foreground/view/dialog/dialogContentView.js b/src/js/foreground/view/dialog/dialogContentView.js index fd457720..fed224be 100644 --- a/src/js/foreground/view/dialog/dialogContentView.js +++ b/src/js/foreground/view/dialog/dialogContentView.js @@ -1,4 +1,4 @@ -define(function () { +define(function() { 'use strict'; // TODO: Prefer using a Behavior instead of inheritance. diff --git a/src/js/foreground/view/dialog/dialogRegion.js b/src/js/foreground/view/dialog/dialogRegion.js index 4b0bfc2f..f1e8648b 100644 --- a/src/js/foreground/view/dialog/dialogRegion.js +++ b/src/js/foreground/view/dialog/dialogRegion.js @@ -1,4 +1,4 @@ -define(function (require) { +define(function(require) { 'use strict'; var YouTubePlayerError = require('common/enum/youTubePlayerError'); @@ -12,8 +12,8 @@ player: null, signInManager: null, debugManager: null, - - initialize: function () { + + initialize: function() { this.player = Streamus.backgroundPage.player; this.signInManager = Streamus.backgroundPage.signInManager; this.debugManager = Streamus.backgroundPage.debugManager; @@ -25,8 +25,8 @@ this.listenTo(this.signInManager, 'change:needGoogleSignIn', this._onSignInManagerChangeNeedGoogleSignIn); chrome.runtime.onUpdateAvailable.addListener(this._onChromeRuntimeUpdateAvailable.bind(this)); }, - - _onForegroundAreaRendered: function () { + + _onForegroundAreaRendered: function() { this._showDialogIfNeedGoogleSignIn(); this._showDialogIfNeedLinkUserId(); this._showDialogIfUpdateAvailable(); @@ -35,7 +35,7 @@ // Make sure Streamus stays up to date because if my Server de-syncs people won't be able to save properly. // http://developer.chrome.com/extensions/runtime#method-requestUpdateCheck - _showDialogIfUpdateAvailable: function () { + _showDialogIfUpdateAvailable: function() { // Don't need to handle the update check -- just need to call it so that onUpdateAvailable will fire. // TODO: The callback will be optional once Google resolves https://code.google.com/p/chromium/issues/detail?id=417564 chrome.runtime.requestUpdateCheck(_.noop); @@ -48,46 +48,46 @@ this._showLinkUserIdDialog(); } }, - + _showDialogIfNeedGoogleSignIn: function() { if (this.signInManager.get('needGoogleSignIn')) { this._showGoogleSignInDialog(); } }, - - _showDialogIfFlashLoaded: function () { + + _showDialogIfFlashLoaded: function() { if (this.debugManager.get('flashLoaded')) { this._showDialog(FlashLoadedDialogView); } }, // Notify user that they should restart Streamus because an update has been downloaded. - _onChromeRuntimeUpdateAvailable: function () { + _onChromeRuntimeUpdateAvailable: function() { this._showDialog(UpdateStreamusDialogView); }, - - _onSignInManagerChangeNeedLinkUserId: function (model, needLinkUserId) { + + _onSignInManagerChangeNeedLinkUserId: function(model, needLinkUserId) { if (needLinkUserId) { this._showLinkUserIdDialog(); } }, - - _onSignInManagerChangeNeedGoogleSignIn: function (model, needGoogleSignIn) { + + _onSignInManagerChangeNeedGoogleSignIn: function(model, needGoogleSignIn) { if (needGoogleSignIn) { this._showGoogleSignInDialog(); } }, // Ask the user to confirm linking their Google+ ID to the currently signed in Chrome account. - _showLinkUserIdDialog: function () { + _showLinkUserIdDialog: function() { this._showDialog(LinkUserIdDialogView); }, - - _showGoogleSignInDialog: function () { + + _showGoogleSignInDialog: function() { this._showDialog(GoogleSignInDialogView); }, - - _showDialog: function (DialogView, options) { + + _showDialog: function(DialogView, options) { var dialogView = new DialogView(options); // Sometimes checkbox reminders are in place which would indicate the view's onSubmit event should run immediately. @@ -99,13 +99,13 @@ dialogView.onSubmit(); } }, - - _onPlayerYouTubeError: function (model, youTubeError) { + + _onPlayerYouTubeError: function(model, youTubeError) { this._showYouTubeErrorDialog(youTubeError); }, // Notify user that YouTube's API has emitted an error - _showYouTubeErrorDialog: function (youTubeError) { + _showYouTubeErrorDialog: function(youTubeError) { var text = chrome.i18n.getMessage('errorEncountered'); switch (youTubeError) { diff --git a/src/js/foreground/view/dialog/dialogView.js b/src/js/foreground/view/dialog/dialogView.js index ed019689..637aac8c 100644 --- a/src/js/foreground/view/dialog/dialogView.js +++ b/src/js/foreground/view/dialog/dialogView.js @@ -1,4 +1,4 @@ -define(function (require) { +define(function(require) { 'use strict'; var Checkbox = require('foreground/model/checkbox'); @@ -8,22 +8,22 @@ var DialogView = Marionette.LayoutView.extend({ className: 'dialog overlay overlay--faded u-transitionable transition--veryFast', template: _.template(DialogTemplate), - templateHelpers: function () { + templateHelpers: function() { return { viewId: this.id }; }, contentView: null, - - regions: function () { + + regions: function() { return { reminderRegion: '#' + this.id + '-reminderRegion', contentRegion: '#' + this.id + '-contentRegion' }; }, - - ui: function () { + + ui: function() { return { panel: '#' + this.id + '-panel', submitButton: '#' + this.id + '-submitButton', @@ -46,20 +46,20 @@ settings: null, reminderCheckbox: null, mouseDownTarget: null, - - initialize: function () { + + initialize: function() { this.settings = Streamus.backgroundPage.settings; }, - + onRender: function() { this.contentRegion.show(this.contentView); - + if (this.model.hasReminder()) { this._showReminder(); } }, - onAttach: function () { + onAttach: function() { // TODO: Keep DRY w/ scrollable. // More info: https://github.com/noraesae/perfect-scrollbar // This needs to be ran during onShow for perfectScrollbar to do its math properly. @@ -74,7 +74,7 @@ }, // Unless a dialog specifically implements reminderProperty it is assumed that reminder is enabled and the dialog will be shown when asked. - isReminderEnabled: function () { + isReminderEnabled: function() { var isReminderEnabled = true; if (this.model.hasReminder()) { @@ -84,45 +84,45 @@ return isReminderEnabled; }, - + onSubmit: _.noop, // TODO: Propagate this logic throughout application. It's more complex, but it's correct UX. // If the user clicks on the darkened area then close the dialog. // Use onMouseDown and onMouseUp because the user can click down on the scrollbar and drag their mouse such that it is over // the darkened panel. This will cause an incorrect close because they didn't click on the dark panel. - _onMouseDown: function (event) { + _onMouseDown: function(event) { this.mouseDownTarget = event.target; }, - - _onMouseUp: function (event) { + + _onMouseUp: function(event) { if (this.mouseDownTarget === event.currentTarget && event.currentTarget === event.target) { this._hide(); } this.mouseDownTarget = null; }, - - _onClickCloseButton: function () { + + _onClickCloseButton: function() { this._hide(); }, - - _onClickCancelButton: function () { + + _onClickCancelButton: function() { this._hide(); }, - - _onClickSubmitButton: function () { + + _onClickSubmitButton: function() { this._submit(); }, // If the enter key is pressed on a js-submittable element, treat as if user pressed OK button. - _onKeyPressSubmittable: function (event) { + _onKeyPressSubmittable: function(event) { if (event.which === 13) { this._submit(); } }, - - _transitionIn: function () { + + _transitionIn: function() { if (!this.isDestroyed) { this.$el.addClass('is-visible'); this.ui.panel.addClass('is-visible'); @@ -131,21 +131,21 @@ this.triggerMethod('visible'); } }, - - _transitionOut: function () { + + _transitionOut: function() { this.$el.on('webkitTransitionEnd', this._onTransitionOutComplete.bind(this)); this.$el.removeClass('is-visible'); this.ui.panel.removeClass('is-visible'); }, // Destroy the view only after it has transitioned out fully otherwise it will disappear without a transition. - _onTransitionOutComplete: function (event) { + _onTransitionOutComplete: function(event) { // webkitTransition bubbles so check the event target to ensure webkitTransitionEnd is running for this element and not a child's transition. if (event.target === event.currentTarget) { this.destroy(); } }, - + _showReminder: function() { this.reminderCheckbox = new Checkbox({ primary: false, @@ -161,17 +161,17 @@ model: this.reminderCheckbox })); }, - + _saveReminderState: function() { var property = this.reminderCheckbox.get('property'); var isChecked = this.reminderCheckbox.get('checked'); this.settings.save(property, isChecked); }, - - _submit: function () { + + _submit: function() { if (this._isValid()) { this.onSubmit(); - + if (this.model.hasReminder()) { this._saveReminderState(); } @@ -179,14 +179,14 @@ this._hide(); } }, - - _isValid: function () { + + _isValid: function() { // TODO: Derive this from dialog's ViewModel state instead of asking the DOM. // Don't use UI here because is-invalid is appended dynamically and so I can't rely on the cache. return this.$el.find('.js-submittable.is-invalid').length === 0; }, - - _hide: function () { + + _hide: function() { // TODO: This is just a patch for now. Some dialogs you don't want to run onSubmit for but you also don't want to always be reminded. if (this.model.get('alwaysSaveReminder')) { this._saveReminderState(); diff --git a/src/js/foreground/view/dialog/editPlaylistDialogView.js b/src/js/foreground/view/dialog/editPlaylistDialogView.js index febe322f..18551c40 100644 --- a/src/js/foreground/view/dialog/editPlaylistDialogView.js +++ b/src/js/foreground/view/dialog/editPlaylistDialogView.js @@ -1,14 +1,14 @@ -define(function (require) { +define(function(require) { 'use strict'; var Dialog = require('foreground/model/dialog'); var EditPlaylistView = require('foreground/view/dialog/editPlaylistView'); var DialogView = require('foreground/view/dialog/dialogView'); - + var EditPlaylistDialogView = DialogView.extend({ id: 'editPlaylistDialog', - initialize: function (options) { + initialize: function(options) { this.model = new Dialog({ submitButtonText: chrome.i18n.getMessage('update') }); @@ -16,10 +16,10 @@ this.contentView = new EditPlaylistView({ model: options.playlist }); - + DialogView.prototype.initialize.apply(this, arguments); }, - + onSubmit: function() { this.contentView.editPlaylist(); } diff --git a/src/js/foreground/view/dialog/editPlaylistView.js b/src/js/foreground/view/dialog/editPlaylistView.js index 6bfb0919..22425446 100644 --- a/src/js/foreground/view/dialog/editPlaylistView.js +++ b/src/js/foreground/view/dialog/editPlaylistView.js @@ -1,4 +1,4 @@ -define(function (require) { +define(function(require) { 'use strict'; var DialogContentView = require('foreground/view/dialog/dialogContentView'); @@ -10,14 +10,14 @@ // TODO: Not DRY w/ CreatePlaylistView -- pull from DB? titleMaxLength: 150, - templateHelpers: function () { + templateHelpers: function() { return { titleMessage: chrome.i18n.getMessage('title'), titleMaxLength: this.titleMaxLength }; }, - - ui: function () { + + ui: function() { return { title: '#' + this.id + '-title', titleCharacterCount: '#' + this.id + '-title-characterCount' @@ -27,41 +27,41 @@ events: { 'input @ui.title': '_onInputTitle' }, - - onRender: function () { + + onRender: function() { this._setTitleCharacterCount(); }, - - onAttach: function () { + + onAttach: function() { this._focusInput(); }, - - editPlaylist: function () { + + editPlaylist: function() { var trimmedTitle = this._getTrimmedTitle(); this.model.set('title', trimmedTitle); }, - - _setTitleCharacterCount: function () { + + _setTitleCharacterCount: function() { var trimmedTitle = this._getTrimmedTitle(); this.ui.titleCharacterCount.text(trimmedTitle.length); }, - - _onInputTitle: function () { + + _onInputTitle: function() { this._setTitleCharacterCount(); this._validateTitle(); }, - - _focusInput: function () { + + _focusInput: function() { // Reset val to prevent text from becoming highlighted. this.ui.title.focus().val(this.ui.title.val()); }, - - _validateTitle: function () { + + _validateTitle: function() { // When the user submits - check to see if they provided a playlist name var trimmedTitle = this._getTrimmedTitle(); this.ui.title.toggleClass('is-invalid', trimmedTitle === ''); }, - + _getTrimmedTitle: function() { return this.ui.title.val().trim(); } diff --git a/src/js/foreground/view/dialog/errorDialogView.js b/src/js/foreground/view/dialog/errorDialogView.js index 87117d81..bd712529 100644 --- a/src/js/foreground/view/dialog/errorDialogView.js +++ b/src/js/foreground/view/dialog/errorDialogView.js @@ -1,19 +1,19 @@ -define(function (require) { +define(function(require) { 'use strict'; var Dialog = require('foreground/model/dialog'); var DialogContentView = require('foreground/view/dialog/dialogContentView'); var DialogView = require('foreground/view/dialog/dialogView'); - + var ErrorDialogView = DialogView.extend({ id: 'errorDialog', player: null, debugManager: null, - initialize: function (options) { + initialize: function(options) { this.debugManager = Streamus.backgroundPage.debugManager; this.player = Streamus.backgroundPage.player; - + this.model = new Dialog({ showCancelButton: false }); @@ -23,13 +23,12 @@ }); DialogView.prototype.initialize.apply(this, arguments); - + // If another extension forced Streamus to load Flash then there's no need to report errors because it quite clearly won't work and the user has been notified. if (!this.debugManager.get('flashLoaded')) { var loadedSong = this.player.get('loadedSong'); var loadedSongId = loadedSong ? loadedSong.get('id') : ''; - var referers = JSON.stringify(this.debugManager.get('youTubeIFrameReferers')); - var error = new Error("Error: " + options.error + ", loadedSongId:" + loadedSongId + ' headers: ' + referers); + var error = new Error('Error: ' + options.error + ', loadedSongId:' + loadedSongId); Streamus.backgroundChannels.error.commands.trigger('log:error', error); } } diff --git a/src/js/foreground/view/dialog/exportPlaylistDialogView.js b/src/js/foreground/view/dialog/exportPlaylistDialogView.js index d9122054..259eeed3 100644 --- a/src/js/foreground/view/dialog/exportPlaylistDialogView.js +++ b/src/js/foreground/view/dialog/exportPlaylistDialogView.js @@ -1,15 +1,15 @@ -define(function (require) { +define(function(require) { 'use strict'; var ExportPlaylist = require('foreground/model/exportPlaylist'); var Dialog = require('foreground/model/dialog'); var ExportPlaylistView = require('foreground/view/dialog/exportPlaylistView'); var DialogView = require('foreground/view/dialog/dialogView'); - + var ExportPlaylistDialogView = DialogView.extend({ id: 'exportPlaylistDialog', - initialize: function (options) { + initialize: function(options) { this.model = new Dialog({ submitButtonText: chrome.i18n.getMessage('export') }); @@ -23,7 +23,7 @@ DialogView.prototype.initialize.apply(this, arguments); }, - onSubmit: function () { + onSubmit: function() { this.contentView.saveAndExport(); } }); diff --git a/src/js/foreground/view/dialog/exportPlaylistView.js b/src/js/foreground/view/dialog/exportPlaylistView.js index ffc96d25..7f5a17f4 100644 --- a/src/js/foreground/view/dialog/exportPlaylistView.js +++ b/src/js/foreground/view/dialog/exportPlaylistView.js @@ -1,4 +1,4 @@ -define(function (require) { +define(function(require) { 'use strict'; var Utility = require('common/utility'); @@ -11,43 +11,43 @@ var ExportPlaylistView = DialogContentView.extend({ id: 'exportPlaylist', template: _.template(ExportPlaylistTemplate), - + templateHelpers: { fileTypeMessage: chrome.i18n.getMessage('fileType'), csvMessage: chrome.i18n.getMessage('csv'), jsonMessage: chrome.i18n.getMessage('json') }, - - regions: function () { + + regions: function() { return { fileTypeRegion: '#' + this.id + '-fileTypeRegion' }; }, - + ui: function() { return { exportCsvRadio: '#' + this.id + '-exportCsvRadio', exportJsonRadio: '#' + this.id + '-exportJsonRadio' }; }, - + radioGroups: null, - initialize: function () { + initialize: function() { this.radioGroups = new RadioGroups(); }, - - onRender: function () { + + onRender: function() { this._showRadioGroup('fileType', ExportFileType); }, - - saveAndExport: function () { + + saveAndExport: function() { this._save(); this._export(); }, - - _showRadioGroup: function (propertyName, options) { - var buttons = _.map(options, function (value, key) { + + _showRadioGroup: function(propertyName, options) { + var buttons = _.map(options, function(value, key) { return { checked: this.model.get(propertyName) === value, value: value, @@ -65,17 +65,17 @@ collection: radioGroup.get('buttons') })); }, - + _save: function() { var currentValues = {}; - this.radioGroups.each(function (radioGroup) { + this.radioGroups.each(function(radioGroup) { currentValues[radioGroup.get('property')] = radioGroup.get('buttons').getChecked().get('value'); }); this.model.save(currentValues); }, - + _export: function() { var downloadableElement = document.createElement('a'); downloadableElement.setAttribute('href', 'data:' + this._getMimeType() + ';charset=utf-8,' + encodeURIComponent(this._getFileText())); @@ -91,7 +91,7 @@ var itemsToExport = this.model.get('playlist').get('items').map(this._mapAsExportedItem.bind(this)); var json = JSON.stringify(itemsToExport); var fileText; - + if (this._isExportingAsCsv()) { fileText = Utility.jsonToCsv(json); } else { @@ -100,8 +100,8 @@ return fileText; }, - - _mapAsExportedItem: function (item) { + + _mapAsExportedItem: function(item) { var song = item.get('song'); var exportedItem = { @@ -115,21 +115,21 @@ return exportedItem; }, - - _getFileName: function () { + + _getFileName: function() { var fileName = this.model.get('playlist').get('title'); fileName += this._isExportingAsJson() ? '.json' : '.txt'; return fileName; }, - - _getMimeType: function () { + + _getMimeType: function() { return this._isExportingAsJson() ? 'application/json' : 'text/plain'; }, - _isExportingAsJson: function () { + _isExportingAsJson: function() { return this.radioGroups.getByProperty('fileType').getCheckedValue() === ExportFileType.Json; }, - + _isExportingAsCsv: function() { return this.radioGroups.getByProperty('fileType').getCheckedValue() === ExportFileType.Csv; } diff --git a/src/js/foreground/view/dialog/flashLoadedDialogView.js b/src/js/foreground/view/dialog/flashLoadedDialogView.js index 6bf0f6ad..0d35b561 100644 --- a/src/js/foreground/view/dialog/flashLoadedDialogView.js +++ b/src/js/foreground/view/dialog/flashLoadedDialogView.js @@ -1,4 +1,4 @@ -define(function (require) { +define(function(require) { 'use strict'; var Dialog = require('foreground/model/dialog'); @@ -8,7 +8,7 @@ var FlashLoadedDialogView = DialogView.extend({ id: 'flashLoadedDialog', - initialize: function () { + initialize: function() { this.model = new Dialog({ showCancelButton: false }); diff --git a/src/js/foreground/view/dialog/googleSignInDialogView.js b/src/js/foreground/view/dialog/googleSignInDialogView.js index 78c3baad..389304cc 100644 --- a/src/js/foreground/view/dialog/googleSignInDialogView.js +++ b/src/js/foreground/view/dialog/googleSignInDialogView.js @@ -1,4 +1,4 @@ -define(function (require) { +define(function(require) { 'use strict'; var Dialog = require('foreground/model/dialog'); @@ -8,13 +8,13 @@ var GoogleSignInDialogView = DialogView.extend({ id: 'googleSignInDialog', signInManager: null, - - initialize: function () { - this.model = new Dialog({ + + initialize: function() { + this.model = new Dialog({ reminderProperty: 'remindGoogleSignIn', alwaysSaveReminder: true }); - + this.contentView = new DialogContentView({ template: _.template(chrome.i18n.getMessage('googleSignInMessage')) }); @@ -23,8 +23,8 @@ DialogView.prototype.initialize.apply(this, arguments); }, - - onSubmit: function () { + + onSubmit: function() { this.signInManager.set('needGoogleSignIn', false); } }); diff --git a/src/js/foreground/view/dialog/linkUserIdDialogView.js b/src/js/foreground/view/dialog/linkUserIdDialogView.js index aa8aa9a4..765ebc98 100644 --- a/src/js/foreground/view/dialog/linkUserIdDialogView.js +++ b/src/js/foreground/view/dialog/linkUserIdDialogView.js @@ -1,4 +1,4 @@ -define(function (require) { +define(function(require) { 'use strict'; var Dialog = require('foreground/model/dialog'); @@ -9,23 +9,23 @@ id: 'linkUserIdDialog', signInManager: null, - initialize: function () { + initialize: function() { this.model = new Dialog({ reminderProperty: 'remindLinkUserId', submitButtonText: chrome.i18n.getMessage('link'), alwaysSaveReminder: true }); - + this.contentView = new DialogContentView({ template: _.template(chrome.i18n.getMessage('linkAccountsMessage')) }); this.signInManager = Streamus.backgroundPage.signInManager; - + DialogView.prototype.initialize.apply(this, arguments); }, - onSubmit: function () { + onSubmit: function() { this.signInManager.saveGooglePlusId(); } }); diff --git a/src/js/foreground/view/dialog/settingsDialogView.js b/src/js/foreground/view/dialog/settingsDialogView.js index 38fa1e14..2590557e 100644 --- a/src/js/foreground/view/dialog/settingsDialogView.js +++ b/src/js/foreground/view/dialog/settingsDialogView.js @@ -1,14 +1,14 @@ -define(function (require) { +define(function(require) { 'use strict'; var Dialog = require('foreground/model/dialog'); var DialogView = require('foreground/view/dialog/dialogView'); var SettingsView = require('foreground/view/dialog/settingsView'); - + var SettingsDialogView = DialogView.extend({ id: 'settingsDialog', - initialize: function () { + initialize: function() { this.model = new Dialog({ submitButtonText: chrome.i18n.getMessage('save') }); @@ -16,10 +16,10 @@ this.contentView = new SettingsView({ model: Streamus.backgroundPage.settings }); - + DialogView.prototype.initialize.apply(this, arguments); }, - + onSubmit: function() { this.contentView.save(); } diff --git a/src/js/foreground/view/dialog/settingsView.js b/src/js/foreground/view/dialog/settingsView.js index ddffbc5e..500ce0f5 100644 --- a/src/js/foreground/view/dialog/settingsView.js +++ b/src/js/foreground/view/dialog/settingsView.js @@ -1,4 +1,4 @@ -define(function (require) { +define(function(require) { 'use strict'; var SongQuality = require('common/enum/songQuality'); @@ -17,8 +17,8 @@ var SettingsView = DialogContentView.extend({ id: 'settings', template: _.template(SettingsTemplate), - - templateHelpers: function () { + + templateHelpers: function() { return { viewId: this.id, generalMessage: chrome.i18n.getMessage('general'), @@ -27,8 +27,8 @@ desktopNotificationsMessage: chrome.i18n.getMessage('desktopNotifications') }; }, - - regions: function () { + + regions: function() { return { songQualityRegion: '#' + this.id + '-songQualityRegion', openToSearchRegion: '#' + this.id + '-openToSearchRegion', @@ -41,14 +41,14 @@ desktopNotificationDurationRegion: '#' + this.id + '-desktopNotificationDurationRegion' }; }, - + checkboxes: null, radioGroups: null, switches: null, simpleListItems: null, signInManager: null, - - initialize: function () { + + initialize: function() { this.checkboxes = new Checkboxes(); this.radioGroups = new RadioGroups(); this.switches = new Switches(); @@ -56,11 +56,11 @@ this.signInManager = Streamus.backgroundPage.signInManager; }, - - onRender: function () { + + onRender: function() { // TODO: It would be sweet to render some CollectionViews which are able to render radios, selects or checkboxes... but not just yet. this._showSimpleListItem({ - propertyName: 'songQuality', + propertyName: 'songQuality', options: _.values(SongQuality) }); @@ -68,7 +68,7 @@ this._showSwitch('openInTab'); this._showCheckbox('remindClearStream'); this._showCheckbox('remindDeletePlaylist'); - + // Once some states have been fulfilled there is no need to allow their reminders to be toggled because // the dialogs which correspond to the reminders will not be shown. if (this.signInManager.get('needLinkUserId')) { @@ -86,24 +86,24 @@ options: _.values(DesktopNotificationDurations) }); }, - - _showSimpleListItem: function (options) { + + _showSimpleListItem: function(options) { var propertyName = options.propertyName; var simpleListItem = this.simpleListItems.add({ property: options.propertyName, - labelKey: _.isUndefined(options.labelKey) ? propertyName: options.labelKey, + labelKey: _.isUndefined(options.labelKey) ? propertyName : options.labelKey, value: this.model.get(propertyName), options: options.options }); - + this[propertyName + 'Region'].show(new SimpleListItemView({ model: simpleListItem })); }, - - _showRadioGroup: function (propertyName, options) { - var buttons = _.map(options, function (value, key) { + + _showRadioGroup: function(propertyName, options) { + var buttons = _.map(options, function(value, key) { return { checked: this.model.get(propertyName) === value, value: value, @@ -121,8 +121,8 @@ collection: radioGroup.get('buttons') })); }, - - _showCheckbox: function (propertyName) { + + _showCheckbox: function(propertyName) { var checkbox = this.checkboxes.add({ labelText: chrome.i18n.getMessage(propertyName), checked: this.model.get(propertyName), @@ -133,8 +133,8 @@ model: checkbox })); }, - - _showSwitch: function (propertyName, labelKey) { + + _showSwitch: function(propertyName, labelKey) { // switch is a reserved keyword so suffix with model. var switchModel = this.switches.add({ labelText: chrome.i18n.getMessage(_.isUndefined(labelKey) ? propertyName : labelKey), @@ -146,23 +146,23 @@ model: switchModel })); }, - - save: function () { + + save: function() { var currentValues = {}; - this.checkboxes.each(function (checkbox) { + this.checkboxes.each(function(checkbox) { currentValues[checkbox.get('property')] = checkbox.get('checked'); }); - - this.radioGroups.each(function (radioGroup) { + + this.radioGroups.each(function(radioGroup) { currentValues[radioGroup.get('property')] = radioGroup.get('buttons').getChecked().get('value'); }); - this.switches.each(function (switchModel) { + this.switches.each(function(switchModel) { currentValues[switchModel.get('property')] = switchModel.get('checked'); }); - this.simpleListItems.each(function (simpleListItem) { + this.simpleListItems.each(function(simpleListItem) { currentValues[simpleListItem.get('property')] = simpleListItem.get('value'); }); diff --git a/src/js/foreground/view/dialog/updateStreamusDialogView.js b/src/js/foreground/view/dialog/updateStreamusDialogView.js index 7ba113b3..e8f01f6a 100644 --- a/src/js/foreground/view/dialog/updateStreamusDialogView.js +++ b/src/js/foreground/view/dialog/updateStreamusDialogView.js @@ -1,4 +1,4 @@ -define(function (require) { +define(function(require) { 'use strict'; var Dialog = require('foreground/model/dialog'); @@ -8,7 +8,7 @@ var UpdateStreamusDialogView = DialogView.extend({ id: 'updateStreamusDialog', - initialize: function () { + initialize: function() { this.model = new Dialog({ submitButtonText: chrome.i18n.getMessage('update') }); @@ -20,7 +20,7 @@ DialogView.prototype.initialize.apply(this, arguments); }, - onSubmit: function () { + onSubmit: function() { chrome.runtime.reload(); } }); diff --git a/src/js/foreground/view/element/checkboxView.js b/src/js/foreground/view/element/checkboxView.js index 19c7b9db..faf75046 100644 --- a/src/js/foreground/view/element/checkboxView.js +++ b/src/js/foreground/view/element/checkboxView.js @@ -1,17 +1,17 @@ -define(function (require) { +define(function(require) { 'use strict'; var CheckboxTemplate = require('text!template/element/checkbox.html'); var CheckboxView = Marionette.ItemView.extend({ tagName: 'checkbox', - className: function () { + className: function() { var className = this.model.get('primary') ? 'checkbox--primary' : 'checkbox--secondary'; className += this.model.get('iconOnLeft') ? ' checkbox--leftIcon' : ' checkbox--rightIcon'; return className; }, template: _.template(CheckboxTemplate), - + ui: { icon: '.checkbox-icon' }, @@ -20,42 +20,40 @@ 'click': '_onClick', 'webkitAnimationEnd @ui.icon': '_onIconWebkitAnimationEnd' }, - + modelEvents: { 'change:checked': '_onChangeChecked' }, - - onRender: function () { + + onRender: function() { var checked = this.model.get('checked'); this._setCheckedState(checked); }, - - _onIconWebkitAnimationEnd: function () { + + _onIconWebkitAnimationEnd: function() { // TODO: Prefer not to use hasClass. if (this.$el.hasClass('is-checking')) { this.$el.removeClass('is-checking'); this.$el.addClass('is-checked'); - } - else if (this.$el.hasClass('is-unchecking')) { + } else if (this.$el.hasClass('is-unchecking')) { this.$el.removeClass('is-unchecking'); this.$el.addClass('is-unchecked'); } }, - - _onClick: function () { + + _onClick: function() { this.model.set('checked', !this.model.get('checked')); }, - - _onChangeChecked: function (model, checked) { + + _onChangeChecked: function(model, checked) { if (checked) { this.$el.addClass('is-checking').removeClass('is-unchecked is-unchecking'); - } - else { + } else { this.$el.addClass('is-unchecking').removeClass('is-checked is-checking'); } }, - - _setCheckedState: function (checked) { + + _setCheckedState: function(checked) { this.$el.toggleClass('is-checked', checked); this.$el.toggleClass('is-unchecked', !checked); } diff --git a/src/js/foreground/view/element/radioButtonView.js b/src/js/foreground/view/element/radioButtonView.js index c09563e2..83267454 100644 --- a/src/js/foreground/view/element/radioButtonView.js +++ b/src/js/foreground/view/element/radioButtonView.js @@ -1,4 +1,4 @@ -define(function (require) { +define(function(require) { 'use strict'; var RadioButtonTemplate = require('text!template/element/radioButton.html'); @@ -7,11 +7,11 @@ var RadioButtonView = Marionette.ItemView.extend({ tagName: 'radio-button', template: _.template(RadioButtonTemplate), - + events: { 'click': '_onClick' }, - + modelEvents: { 'change:checked': '_onChangeChecked' }, @@ -19,16 +19,16 @@ onRender: function() { this._setCheckedState(this.model.get('checked')); }, - - _onClick: function () { + + _onClick: function() { this.model.set('checked', true); }, - - _onChangeChecked: function (model, checked) { + + _onChangeChecked: function(model, checked) { this._setCheckedState(checked); }, - _setCheckedState: function (checked) { + _setCheckedState: function(checked) { this.$el.toggleClass('is-checked', checked); this.$el.toggleClass('is-unchecked', !checked); } diff --git a/src/js/foreground/view/element/radioGroupView.js b/src/js/foreground/view/element/radioGroupView.js index 0d5646d5..c2dfac6c 100644 --- a/src/js/foreground/view/element/radioGroupView.js +++ b/src/js/foreground/view/element/radioGroupView.js @@ -1,4 +1,4 @@ -define(function (require) { +define(function(require) { 'use strict'; var RadioButtonView = require('foreground/view/element/radioButtonView'); @@ -9,27 +9,26 @@ template: _.template(RadioGroupTemplate), childViewContainer: '@ui.buttons', childView: RadioButtonView, - + attributes: { tabIndex: 0 }, - - ui: function () { + + ui: function() { return { // TODO: Naming isn't dynamic. buttons: '.radio-buttons' }; }, - + events: { 'keydown': '_onKeyDown' }, - - _onKeyDown: function (event) { + + _onKeyDown: function(event) { if (event.keyCode === 37 || event.keyCode === 38) { this.collection.checkPrevious(); - } - else if (event.keyCode === 39 || event.keyCode === 40) { + } else if (event.keyCode === 39 || event.keyCode === 40) { this.collection.checkNext(); } } diff --git a/src/js/foreground/view/element/simpleListItemView.js b/src/js/foreground/view/element/simpleListItemView.js index 56a0d5ea..7e47160d 100644 --- a/src/js/foreground/view/element/simpleListItemView.js +++ b/src/js/foreground/view/element/simpleListItemView.js @@ -1,5 +1,5 @@ // TODO: I'd like to figure out better naming conventions for this. -define(function (require) { +define(function(require) { 'use strict'; var SimpleMenuItems = require('foreground/collection/simpleMenuItems'); @@ -13,56 +13,56 @@ define(function (require) { //}, className: 'simpleListItem listItem listItem--medium is-clickable', template: _.template(SimpleListItemTemplate), - - templateHelpers: function () { + + templateHelpers: function() { return { title: chrome.i18n.getMessage(this.model.get('labelKey')), viewId: 'simpleListItem' }; }, - regions: function () { + regions: function() { return { // TODO: This isn't a unique identifier. simpleMenuRegion: '#' + 'simpleListItem' + '-simpleMenuRegion' }; }, - - ui: function () { + + ui: function() { return { prettyValue: '#' + 'simpleListItem' + '-prettyValue' }; }, - + events: { 'click': '_onClick' }, - + modelEvents: { 'change:value': '_onChangeValue' }, - - onRender: function () { + + onRender: function() { this._setPrettyValue(this.model.get('value')); }, - + _onClick: function() { this._openSimpleMenu(); }, - - _onChangeValue: function (model, value) { + + _onChangeValue: function(model, value) { this._setPrettyValue(value); }, - - _setPrettyValue: function (value) { + + _setPrettyValue: function(value) { this.ui.prettyValue.html(chrome.i18n.getMessage(value)); }, - - _openSimpleMenu: function () { + + _openSimpleMenu: function() { // If the list item is clicked while the menu is open do not re-open it. if (_.isUndefined(this.simpleMenuRegion.currentView)) { var options = this.model.get('options'); - var simpleMenuItems = new SimpleMenuItems(_.map(options, function (option) { + var simpleMenuItems = new SimpleMenuItems(_.map(options, function(option) { return { active: this.model.get('value') === option, text: chrome.i18n.getMessage(option), @@ -72,7 +72,7 @@ define(function (require) { // Since I'm building this inside of a click event and click events can close the menu I need to let the event finish before showing the menu // otherwise it'll close immediately. - _.defer(function () { + _.defer(function() { var simpleMenuView = new SimpleMenuView({ collection: simpleMenuItems, model: new SimpleMenu(), @@ -86,8 +86,8 @@ define(function (require) { }.bind(this)); } }, - - _onClickSimpleMenuItem: function (eventArgs) { + + _onClickSimpleMenuItem: function(eventArgs) { var activeItem = eventArgs.collection.getActive(); this.model.set('value', activeItem.get('value')); } diff --git a/src/js/foreground/view/element/simpleMenuItemView.js b/src/js/foreground/view/element/simpleMenuItemView.js index 96064b2a..9fa16488 100644 --- a/src/js/foreground/view/element/simpleMenuItemView.js +++ b/src/js/foreground/view/element/simpleMenuItemView.js @@ -1,4 +1,4 @@ -define(function (require) { +define(function(require) { 'use strict'; var SimpleMenuItemTemplate = require('text!template/element/simpleMenuItem.html'); @@ -6,24 +6,24 @@ var SimpleMenuItemView = Marionette.LayoutView.extend({ className: 'listItem listItem--small listItem--selectable', template: _.template(SimpleMenuItemTemplate), - + events: { 'click': '_onClick' }, - + onRender: function() { this._setState(this.model.get('active')); }, - + _onClick: function() { this.model.set('active', true); }, - - _onChangeActive: function (model, active) { + + _onChangeActive: function(model, active) { this._setState(active); }, - - _setState: function (active) { + + _setState: function(active) { this.$el.toggleClass('is-active', active); } }); diff --git a/src/js/foreground/view/element/simpleMenuView.js b/src/js/foreground/view/element/simpleMenuView.js index add9236e..96515a41 100644 --- a/src/js/foreground/view/element/simpleMenuView.js +++ b/src/js/foreground/view/element/simpleMenuView.js @@ -1,4 +1,4 @@ -define(function (require) { +define(function(require) { 'use strict'; var SimpleMenuItemView = require('foreground/view/element/simpleMenuItemView'); @@ -8,7 +8,7 @@ id: 'simpleMenu', className: 'panel menu', template: _.template(SimpleMenuTemplate), - templateHelpers: function () { + templateHelpers: function() { return { viewId: this.id }; @@ -16,30 +16,30 @@ childView: SimpleMenuItemView, childViewContainer: '@ui.simpleMenuItems', - - ui: function () { + + ui: function() { return { simpleMenuItems: '#' + this.id + '-simpleMenuItems', fixedMenuItem: '#' + this.id + '-fixedMenuItem', panelContent: '#' + this.id + '-panelContent' }; }, - + events: { 'click @ui.simpleMenuItems': '_onClickSimpleMenuItems', 'click @ui.fixedMenuItem': '_onClickFixedMenuItem' }, - + listItemHeight: 0, - + initialize: function(options) { this.listItemHeight = options && options.listItemHeight ? options.listItemHeight : this.listItemHeight; this.listenTo(Streamus.channels.element.vent, 'click', this._onElementClick); }, - - onAttach: function () { + + onAttach: function() { this._ensureActiveIsVisible(); - + if (this.listItemHeight > 0) { this._centerActive(); } @@ -47,7 +47,7 @@ _.defer(function() { this.$el.addClass('is-visible'); }.bind(this)); - + // TODO: Keep DRY w/ scrollable. // More info: https://github.com/noraesae/perfect-scrollbar // This needs to be ran during onShow for perfectScrollbar to do its math properly. @@ -58,14 +58,14 @@ includePadding: true }); }, - - hide: function () { + + hide: function() { Streamus.channels.simpleMenu.vent.trigger('hidden'); this.ui.panelContent.off('webkitTransitionEnd').one('webkitTransitionEnd', this._onTransitionOutComplete.bind(this)); this.$el.removeClass('is-visible'); }, - - _onClickSimpleMenuItems: function () { + + _onClickSimpleMenuItems: function() { this.triggerMethod('click:simpleMenuItem', { view: this, model: this.model, @@ -74,8 +74,8 @@ Streamus.channels.simpleMenu.vent.trigger('clicked:item'); this.hide(); }, - - _onClickFixedMenuItem: function () { + + _onClickFixedMenuItem: function() { this.triggerMethod('click:fixedMenuItem', { view: this, model: this.model, @@ -84,12 +84,12 @@ Streamus.channels.simpleMenu.vent.trigger('clicked:item'); this.hide(); }, - - _onTransitionOutComplete: function () { + + _onTransitionOutComplete: function() { this.destroy(); }, - - _onElementClick: function (event) { + + _onElementClick: function(event) { // This target will show up when dragging the scrollbar and it's weird to close when interacting with scrollbar. if (event.target !== this.ui.panelContent[0]) { this.hide(); @@ -97,11 +97,11 @@ }, // Adjust the scrollTop of the view to ensure that the active item is shown. - _ensureActiveIsVisible: function () { + _ensureActiveIsVisible: function() { var activeItem = this.collection.getActive(); - + if (!_.isUndefined(activeItem)) { - var activeView = this.children.find(function (child) { + var activeView = this.children.find(function(child) { return child.model === activeItem; }); @@ -115,17 +115,17 @@ // TODO: This should also take into account overflow. If overflow would happen, abandon trying to perfectly center and keep the menu within the viewport. // When showing this view over a ListItem, center the view's active item over the ListItem. - _centerActive: function () { + _centerActive: function() { // Adjust the top position of the view based on which item is active. var index = this.collection.indexOf(this.collection.getActive()); - + var childHeight = this.children.first().$el.height(); var offset = -1 * index * childHeight; - + // Account for the fact that the view could be scrolling to show the child so that an offset derived just by index is insufficient. var scrollTop = this.ui.simpleMenuItems[0].scrollTop; offset += scrollTop; - + // Now center the item over its ListItem var paddingTop = parseInt(this.ui.panelContent.css('padding-top')); var centering = (this.listItemHeight - childHeight - paddingTop) / 2; diff --git a/src/js/foreground/view/element/spinnerView.js b/src/js/foreground/view/element/spinnerView.js index e7cac51a..149882f7 100644 --- a/src/js/foreground/view/element/spinnerView.js +++ b/src/js/foreground/view/element/spinnerView.js @@ -1,4 +1,4 @@ -define(function (require) { +define(function(require) { 'use strict'; var SpinnerTemplate = require('text!template/element/spinner.html'); diff --git a/src/js/foreground/view/element/switchView.js b/src/js/foreground/view/element/switchView.js index aed15507..73ef3982 100644 --- a/src/js/foreground/view/element/switchView.js +++ b/src/js/foreground/view/element/switchView.js @@ -1,4 +1,4 @@ -define(function (require) { +define(function(require) { 'use strict'; var SwitchTemplate = require('text!template/element/switch.html'); @@ -7,33 +7,33 @@ var SwitchView = Marionette.ItemView.extend({ tagName: 'switch', template: _.template(SwitchTemplate), - + ui: { icon: '.switch-icon' }, - + events: { 'click': '_onClick' }, - + modelEvents: { 'change:checked': '_onChangeChecked' }, - - onRender: function () { + + onRender: function() { var checked = this.model.get('checked'); this._setCheckedState(checked); }, - - _onClick: function () { + + _onClick: function() { this.model.set('checked', !this.model.get('checked')); }, - + _onChangeChecked: function(model, checked) { this._setCheckedState(checked); }, - - _setCheckedState: function (checked) { + + _setCheckedState: function(checked) { this.$el.toggleClass('is-checked', checked); this.$el.toggleClass('is-unchecked', !checked); this.ui.icon.toggleClass('is-checked', checked); diff --git a/src/js/foreground/view/foregroundAreaView.js b/src/js/foreground/view/foregroundAreaView.js index 60c4c316..4a52972b 100644 --- a/src/js/foreground/view/foregroundAreaView.js +++ b/src/js/foreground/view/foregroundAreaView.js @@ -1,6 +1,6 @@ -define(function (require) { +define(function(require) { 'use strict'; - + var AppBarRegion = require('foreground/view/appBar/appBarRegion'); var ContextMenuRegion = require('foreground/view/contextMenu/contextMenuRegion'); var DialogRegion = require('foreground/view/dialog/dialogRegion'); @@ -18,8 +18,8 @@ id: 'foregroundArea', className: 'flexColumn u-fullHeight', template: _.template(ForegroundAreaTemplate), - - templateHelpers: function () { + + templateHelpers: function() { return { loadingYouTubeMessage: chrome.i18n.getMessage('loadingYouTube'), loadingYouTubeFailedMessage: chrome.i18n.getMessage('loadingYouTubeFailed'), @@ -34,8 +34,8 @@ 'contextmenu': '_onContextMenu', 'click @ui.reloadLink': '_onClickReloadLink' }, - - ui: function () { + + ui: function() { return { loadingMessage: '#' + this.id + '-loadingMessage', loadingFailedMessage: '#' + this.id + '-loadingFailedMessage', @@ -44,7 +44,7 @@ }; }, - regions: function () { + regions: function() { return { spinnerRegion: '#' + this.id + '-spinnerRegion', appBarRegion: { @@ -89,50 +89,52 @@ } }; }, - + player: null, settings: null, - + playerEvents: { 'change:loading': '_onPlayerChangeLoading', 'change:currentLoadAttempt': '_onPlayerChangeCurrentLoadAttempt' }, - initialize: function () { + initialize: function() { this.player = Streamus.backgroundPage.player; this.settings = Streamus.backgroundPage.settings; this.bindEntityEvents(this.player, this.playerEvents); - + window.onunload = this._onWindowUnload.bind(this); window.onresize = this._onWindowResize.bind(this); window.onerror = this._onWindowError.bind(this); + + Streamus.backgroundPage.analyticsManager.sendPageView('/foreground.html'); }, - - onRender: function () { + + onRender: function() { this.spinnerRegion.show(new SpinnerView()); this._checkPlayerLoading(); - + Streamus.channels.foregroundArea.vent.trigger('rendered'); }, // Announce the jQuery target of element clicked so multi-select collections can decide if they should de-select their child views - _onClick: function (event) { + _onClick: function(event) { Streamus.channels.element.vent.trigger('click', event); }, - _onContextMenu: function (event) { + _onContextMenu: function(event) { Streamus.channels.element.vent.trigger('contextMenu', event); }, - _onMouseDown: function () { + _onMouseDown: function() { Streamus.channels.element.vent.trigger('mouseDown', event); }, - _onClickReloadLink: function () { + _onClickReloadLink: function() { chrome.runtime.reload(); }, - - _onWindowResize: function () { + + _onWindowResize: function() { Streamus.channels.window.vent.trigger('resize', { height: this.$el.height(), width: this.$el.width() @@ -140,7 +142,7 @@ }, // Destroy the foreground to perform memory management / unbind event listeners. Memory leaks will be introduced if this doesn't happen. - _onWindowUnload: function () { + _onWindowUnload: function() { Streamus.channels.foreground.vent.trigger('beginUnload'); Streamus.backgroundChannels.foreground.vent.trigger('beginUnload'); this.destroy(); @@ -148,11 +150,11 @@ Streamus.backgroundChannels.foreground.vent.trigger('endUnload'); }, - _onWindowError: function (message, url, lineNumber, columnNumber, error) { + _onWindowError: function(message, url, lineNumber, columnNumber, error) { Streamus.backgroundChannels.error.vent.trigger('windowError', message, url, lineNumber, columnNumber, error); }, - _onPlayerChangeLoading: function (model, loading) { + _onPlayerChangeLoading: function(model, loading) { if (loading) { this._startLoading(); } else { @@ -160,18 +162,18 @@ } }, - _onPlayerChangeCurrentLoadAttempt: function () { + _onPlayerChangeCurrentLoadAttempt: function() { this.ui.loadAttemptMessage.text(this._getLoadAttemptMessage()); }, - - _startLoading: function () { + + _startLoading: function() { this.$el.addClass('is-showingSpinner'); this.ui.loadingFailedMessage.addClass('is-hidden'); this.ui.loadingMessage.removeClass('is-hidden'); }, // Set the foreground's view state to indicate that user interactions are OK once the player is ready. - _stopLoading: function () { + _stopLoading: function() { this.ui.loadingMessage.addClass('is-hidden'); if (this.player.get('ready')) { @@ -182,12 +184,12 @@ } }, - _checkPlayerLoading: function () { + _checkPlayerLoading: function() { if (this.player.get('loading')) { this._startLoading(); } }, - + _getLoadAttemptMessage: function() { return chrome.i18n.getMessage('loadAttempt', [this.player.get('currentLoadAttempt'), this.player.get('maxLoadAttempts')]); } diff --git a/src/js/foreground/view/leftPane/activePlaylistAreaView.js b/src/js/foreground/view/leftPane/activePlaylistAreaView.js index 8a35c568..87b2eaf8 100644 --- a/src/js/foreground/view/leftPane/activePlaylistAreaView.js +++ b/src/js/foreground/view/leftPane/activePlaylistAreaView.js @@ -1,4 +1,4 @@ -define(function (require) { +define(function(require) { 'use strict'; var Tooltip = require('foreground/view/behavior/tooltip'); @@ -9,8 +9,8 @@ id: 'activePlaylistArea', className: 'flexColumn', template: _.template(ActivePlaylistAreaTemplate), - - templateHelpers: function () { + + templateHelpers: function() { return { viewId: this.id, addAllMessage: chrome.i18n.getMessage('addAll'), @@ -21,14 +21,14 @@ wouldYouLikeToMessage: chrome.i18n.getMessage('wouldYouLikeTo') }; }, - - regions: function () { + + regions: function() { return { playlistItemsRegion: '#' + this.id + '-playlistItemsRegion' }; }, - ui: function () { + ui: function() { return { playlistEmptyMessage: '#' + this.id + '-playlistEmptyMessage', showSearchLink: '#' + this.id + '-showSearchLink', @@ -43,15 +43,15 @@ 'click @ui.addAllButton:not(.is-disabled)': '_onClickAddAllButton', 'click @ui.playAllButton:not(.is-disabled)': '_onClickPlayAllButton' }, - + behaviors: { Tooltip: { behaviorClass: Tooltip } }, - + streamItems: null, - + initialize: function() { this.streamItems = Streamus.backgroundPage.stream.get('items'); this.listenTo(this.streamItems, 'add', this._onStreamItemsAdd); @@ -64,55 +64,55 @@ this.listenTo(playlistItems, 'reset', this._onPlaylistItemsReset); }, - onRender: function () { + onRender: function() { this._toggleButtons(); this._updatePlaylistDetails(this.model.get('items').getDisplayInfo()); this._toggleInstructions(this.model.get('items').isEmpty()); - + this.playlistItemsRegion.show(new PlaylistItemsView({ collection: this.model.get('items') })); }, - - _onClickShowSearchLink: function () { + + _onClickShowSearchLink: function() { Streamus.channels.searchArea.commands.trigger('show:search'); }, - - _onStreamItemsAdd: function () { + + _onStreamItemsAdd: function() { this._toggleButtons(); }, - + _onStreamItemsRemove: function() { this._toggleButtons(); }, - - _onStreamItemsReset: function () { + + _onStreamItemsReset: function() { this._toggleButtons(); }, - + _onPlaylistItemsAdd: function(model, collection) { this._toggleButtons(); this._updatePlaylistDetails(collection.getDisplayInfo()); this._toggleInstructions(false); }, - - _onPlaylistItemsRemove: function (model, collection) { + + _onPlaylistItemsRemove: function(model, collection) { this._toggleButtons(); this._updatePlaylistDetails(collection.getDisplayInfo()); this._toggleInstructions(collection.isEmpty()); }, - - _onPlaylistItemsReset: function (collection) { + + _onPlaylistItemsReset: function(collection) { this._toggleButtons(); this._updatePlaylistDetails(collection.getDisplayInfo()); this._toggleInstructions(collection.isEmpty()); }, - - _toggleInstructions: function (collectionEmpty) { + + _toggleInstructions: function(collectionEmpty) { this.ui.playlistEmptyMessage.toggleClass('is-hidden', !collectionEmpty); }, - - _toggleButtons: function () { + + _toggleButtons: function() { var isEmpty = this.model.get('items').isEmpty(); this.ui.playAllButton.toggleClass('is-disabled', isEmpty); @@ -120,15 +120,15 @@ this.ui.addAllButton.toggleClass('is-disabled', isEmpty || duplicatesInfo.allDuplicates).attr('title', isEmpty ? '' : duplicatesInfo.message); }, - _updatePlaylistDetails: function (displayInfo) { + _updatePlaylistDetails: function(displayInfo) { this.ui.playlistDetails.text(displayInfo).attr('title', displayInfo); }, - _onClickAddAllButton: function () { + _onClickAddAllButton: function() { this.streamItems.addSongs(this.model.get('items').pluck('song')); }, - - _onClickPlayAllButton: function () { + + _onClickPlayAllButton: function() { this.streamItems.addSongs(this.model.get('items').pluck('song'), { playOnAdd: true }); diff --git a/src/js/foreground/view/leftPane/leftPaneRegion.js b/src/js/foreground/view/leftPane/leftPaneRegion.js index 0e9c174f..a8e91b39 100644 --- a/src/js/foreground/view/leftPane/leftPaneRegion.js +++ b/src/js/foreground/view/leftPane/leftPaneRegion.js @@ -1,4 +1,4 @@ -define(function (require) { +define(function(require) { 'use strict'; var LeftPaneView = require('foreground/view/leftPane/leftPaneView'); @@ -7,8 +7,8 @@ initialize: function() { this.listenTo(Streamus.channels.foregroundArea.vent, 'rendered', this._onForegroundAreaRendered); }, - - _onForegroundAreaRendered: function () { + + _onForegroundAreaRendered: function() { this.show(new LeftPaneView()); } }); diff --git a/src/js/foreground/view/leftPane/leftPaneView.js b/src/js/foreground/view/leftPane/leftPaneView.js index ac951044..e1e6b6e7 100644 --- a/src/js/foreground/view/leftPane/leftPaneView.js +++ b/src/js/foreground/view/leftPane/leftPaneView.js @@ -1,4 +1,4 @@ -define(function (require) { +define(function(require) { 'use strict'; var ActivePlaylistAreaView = require('foreground/view/leftPane/activePlaylistAreaView'); @@ -10,40 +10,40 @@ className: 'leftPane flexColumn', template: _.template(LeftPaneTemplate), - regions: function () { + regions: function() { return { contentRegion: '#' + this.id + '-contentRegion' }; }, signInManager: null, - - initialize: function () { + + initialize: function() { this.signInManager = Streamus.backgroundPage.signInManager; this.listenTo(this.signInManager, 'change:signedInUser', this._onSignInManagerChangeSignedInUser); - + var signedInUser = this.signInManager.get('signedInUser'); if (signedInUser !== null) { this.listenTo(signedInUser.get('playlists'), 'change:active', this._onPlaylistsChangeActive); } }, - - onRender: function () { + + onRender: function() { var signedInUser = this.signInManager.get('signedInUser'); this._updateRegions(signedInUser); }, - - _onSignInManagerChangeSignedInUser: function (model, signedInUser) { + + _onSignInManagerChangeSignedInUser: function(model, signedInUser) { if (signedInUser === null) { this.stopListening(model.previous('signedInUser').get('playlists')); } else { this.listenTo(signedInUser.get('playlists'), 'change:active', this._onPlaylistsChangeActive); } - + this._updateRegions(signedInUser); }, - - _updateRegions: function (signedInUser) { + + _updateRegions: function(signedInUser) { if (signedInUser !== null) { var activePlaylist = signedInUser.get('playlists').getActivePlaylist(); this._showActivePlaylistContent(activePlaylist); @@ -53,13 +53,13 @@ }, // If the user is signed in -- show the user's active playlist items / information. - _showActivePlaylistContent: function (activePlaylist) { + _showActivePlaylistContent: function(activePlaylist) { this.contentRegion.show(new ActivePlaylistAreaView({ model: activePlaylist, collection: activePlaylist.get('items') })); }, - + _showSignInContent: function() { // Don't continously generate the signIn view if it's already visible because the view itself is trying to update its state // and if you rip out the view while it's trying to update -- Marionette will throw errors saying elements don't have events/methods. @@ -70,8 +70,8 @@ })); } }, - - _onPlaylistsChangeActive: function (model, active) { + + _onPlaylistsChangeActive: function(model, active) { // Don't call updateRegions when a playlist is de-activated because don't want to redraw twice -- expensive! if (active) { this._updateRegions(this.signInManager.get('signedInUser')); diff --git a/src/js/foreground/view/leftPane/playlistItemView.js b/src/js/foreground/view/leftPane/playlistItemView.js index 1c58a481..db19ee05 100644 --- a/src/js/foreground/view/leftPane/playlistItemView.js +++ b/src/js/foreground/view/leftPane/playlistItemView.js @@ -1,4 +1,4 @@ -define(function (require) { +define(function(require) { 'use strict'; var ListItemView = require('foreground/view/listItemView'); @@ -17,11 +17,11 @@ events: _.extend({}, ListItemView.prototype.events, { 'dblclick': '_onDblClick' }), - + modelEvents: { 'change:id': '_onChangeId' }, - + behaviors: _.extend({}, ListItemView.prototype.behaviors, { ListItemMultiSelect: { behaviorClass: ListItemMultiSelect @@ -30,9 +30,9 @@ behaviorClass: Tooltip } }), - + buttonViews: [PlaySongButtonView, AddSongButtonView, DeleteSongButtonView], - + streamItems: null, player: null, @@ -40,8 +40,8 @@ this.streamItems = Streamus.backgroundPage.stream.get('items'); this.player = Streamus.backgroundPage.player; }, - - onRender: function () { + + onRender: function() { var spinnerView = new SpinnerView({ className: 'overlay u-marginAuto' }); @@ -49,8 +49,8 @@ this._setShowingSpinnerClass(); }, - - showContextMenu: function () { + + showContextMenu: function() { Streamus.channels.contextMenu.commands.trigger('reset:items', [{ text: chrome.i18n.getMessage('copyUrl'), onClick: this._copyUrl.bind(this) @@ -62,41 +62,41 @@ onClick: this._watchOnYouTube.bind(this) }]); }, - - _onDblClick: function () { + + _onDblClick: function() { this._playInStream(); }, - - _onChangeId: function (model, id) { + + _onChangeId: function(model, id) { this._setDataId(id); this._setShowingSpinnerClass(); }, // If the playlistItem hasn't been successfully saved to the server -- show a spinner over the UI. - _setShowingSpinnerClass: function () { + _setShowingSpinnerClass: function() { this.$el.toggleClass('is-showingSpinner', this.model.isNew()); }, - - _setDataId: function (id) { + + _setDataId: function(id) { // I'm not 100% positive I need to set both here, but .data() is cached in jQuery and .attr() is on the view, so seems good to keep both up to date. this.$el.data('id', id).attr('id', id); }, - - _copyUrl: function () { + + _copyUrl: function() { this.model.get('song').copyUrl(); }, - - _copyTitleAndUrl: function () { + + _copyTitleAndUrl: function() { this.model.get('song').copyTitleAndUrl(); }, - - _playInStream: function () { + + _playInStream: function() { this.streamItems.addSongs(this.model.get('song'), { playOnAdd: true }); }, - - _watchOnYouTube: function () { + + _watchOnYouTube: function() { this.player.watchInTab(this.model.get('song')); } }); diff --git a/src/js/foreground/view/leftPane/playlistItemsView.js b/src/js/foreground/view/leftPane/playlistItemsView.js index 25c6b51d..37bd48cd 100644 --- a/src/js/foreground/view/leftPane/playlistItemsView.js +++ b/src/js/foreground/view/leftPane/playlistItemsView.js @@ -1,4 +1,4 @@ -define(function (require) { +define(function(require) { 'use strict'; var ListItemType = require('common/enum/listItemType'); @@ -14,8 +14,8 @@ className: 'list u-flex--full', childViewContainer: '@ui.childContainer', childView: PlaylistItemView, - childViewType: ListItemType.PlaylistItem, - childViewOptions: function () { + childViewType: ListItemType.PlaylistItem, + childViewOptions: function() { return { type: this.childViewType, parentId: this.ui.childContainer[0].id @@ -23,13 +23,13 @@ }, // Overwrite resortView to only render children as expected - resortView: function () { + resortView: function() { this._renderChildren(); }, - + template: _.template(PlaylistItemsTemplate), - - ui: function () { + + ui: function() { return { childContainer: '#' + this.id + '-listItems' }; @@ -49,15 +49,19 @@ behaviorClass: Sortable } }, - - initialize: function () { + + initialize: function() { this.viewModel = Streamus.backgroundPage.playlistsViewModel; this.listenTo(Streamus.channels.searchArea.vent, 'showing', this._onSearchAreaShowing); + + setTimeout(function () { + console.log('hallo', this.$childViewContainer); + }.bind(this), 3000); }, // Don't maintain selected results after showing SearchArea because this view won't be visible. - _onSearchAreaShowing: function () { + _onSearchAreaShowing: function() { this.triggerMethod('DeselectCollection'); } }); diff --git a/src/js/foreground/view/leftPane/signInView.js b/src/js/foreground/view/leftPane/signInView.js index 9242cad0..c5b4c286 100644 --- a/src/js/foreground/view/leftPane/signInView.js +++ b/src/js/foreground/view/leftPane/signInView.js @@ -1,4 +1,4 @@ -define(function (require) { +define(function(require) { 'use strict'; var SpinnerView = require('foreground/view/element/spinnerView'); @@ -8,21 +8,21 @@ id: 'signIn', className: 'flexColumn', template: _.template(SignInTemplate), - + templateHelpers: { signingInMessage: chrome.i18n.getMessage('signingIn'), signInMessage: chrome.i18n.getMessage('signIn'), signInFailedMessage: chrome.i18n.getMessage('signInFailed'), pleaseWaitMessage: chrome.i18n.getMessage('pleaseWait') }, - - regions: function () { + + regions: function() { return { spinnerRegion: '#' + this.id + '-spinnerRegion' }; }, - ui: function () { + ui: function() { return { signingInMessage: '#' + this.id + '-signingInMessage', signInMessage: '#' + this.id + '-signInMessage', @@ -41,40 +41,40 @@ 'change:signingIn': '_onChangeSigningIn', 'change:signInRetryTimer': '_onChangeSignInRetryTimer' }, - - onRender: function () { + + onRender: function() { this._toggleBigText(this.model.get('signingIn'), this.model.get('signInFailed')); this.spinnerRegion.show(new SpinnerView()); }, - - _onClickSignInLink: function () { + + _onClickSignInLink: function() { this._signIn(); }, - + _onChangeSignInFailed: function(model, signInFailed) { this._toggleBigText(model.get('signingIn'), signInFailed); }, - - _onChangeSigningIn: function (model, signingIn) { + + _onChangeSigningIn: function(model, signingIn) { this._toggleBigText(signingIn, model.get('signInFailed')); }, - - _onChangeSignInRetryTimer: function (model, signInRetryTimer) { + + _onChangeSignInRetryTimer: function(model, signInRetryTimer) { this._setSignInRetryTimer(signInRetryTimer); }, - _setSignInRetryTimer: function (signInRetryTimer) { + _setSignInRetryTimer: function(signInRetryTimer) { this.ui.signInRetryTimer.text(signInRetryTimer); }, // Set the visibility of any visible text messages. - _toggleBigText: function (signingIn, signInFailed) { + _toggleBigText: function(signingIn, signInFailed) { this.ui.signInFailedMessage.toggleClass('is-hidden', !signInFailed); this.ui.signingInMessage.toggleClass('is-hidden', !signingIn); this.ui.signInMessage.toggleClass('is-hidden', signingIn || signInFailed); }, - _signIn: function () { + _signIn: function() { this.model.signInWithGoogle(); } }); diff --git a/src/js/foreground/view/listItemButton/addPlaylistButtonView.js b/src/js/foreground/view/listItemButton/addPlaylistButtonView.js index 869afd07..63e3acf2 100644 --- a/src/js/foreground/view/listItemButton/addPlaylistButtonView.js +++ b/src/js/foreground/view/listItemButton/addPlaylistButtonView.js @@ -1,4 +1,4 @@ -define(function (require) { +define(function(require) { 'use strict'; var ListItemButtonView = require('foreground/view/listItemButton/listItemButtonView'); @@ -10,22 +10,22 @@ templateHelpers: { addIcon: _.template(AddIconTemplate)() }, - + streamItems: null, - + streamItemsEvents: { 'add': '_onStreamItemsAdd', 'remove': '_onStreamItemsRemove', 'reset': '_onStreamItemsReset' }, - + playlistItemsEvents: { 'add': '_onPlaylistItemsAdd', 'remove': '_onPlaylistItemsRemove', 'reset': '_onPlaylistItemsReset' }, - initialize: function () { + initialize: function() { this.streamItems = Streamus.backgroundPage.stream.get('items'); this.bindEntityEvents(this.streamItems, this.streamItemsEvents); this.bindEntityEvents(this.model.get('items'), this.playlistItemsEvents); @@ -33,35 +33,35 @@ ListItemButtonView.prototype.initialize.apply(this, arguments); }, - onRender: function () { + onRender: function() { this._setState(); }, - - _onPlaylistItemsAdd: function () { + + _onPlaylistItemsAdd: function() { this._setState(); }, - _onPlaylistItemsRemove: function () { + _onPlaylistItemsRemove: function() { this._setState(); }, - _onPlaylistItemsReset: function () { + _onPlaylistItemsReset: function() { this._setState(); }, - - _onStreamItemsAdd: function () { + + _onStreamItemsAdd: function() { this._setState(); }, - _onStreamItemsRemove: function () { + _onStreamItemsRemove: function() { this._setState(); }, - _onStreamItemsReset: function () { + _onStreamItemsReset: function() { this._setState(); }, - - _setState: function () { + + _setState: function() { var playlistItems = this.model.get('items'); var empty = playlistItems.length === 0; var duplicatesInfo = this.streamItems.getDuplicatesInfo(playlistItems.pluck('song')); @@ -69,18 +69,17 @@ this.$el.toggleClass('is-disabled', empty || duplicatesInfo.allDuplicates); var title = chrome.i18n.getMessage('add'); - + if (empty) { title = chrome.i18n.getMessage('playlistEmpty'); - } - else if (duplicatesInfo.message !== '') { + } else if (duplicatesInfo.message !== '') { title = duplicatesInfo.message; } this.$el.attr('title', title); }, - - doOnClickAction: function () { + + doOnClickAction: function() { var songs = this.model.get('items').pluck('song'); this.streamItems.addSongs(songs); } diff --git a/src/js/foreground/view/listItemButton/addSongButtonView.js b/src/js/foreground/view/listItemButton/addSongButtonView.js index 4f24e5be..eabd074e 100644 --- a/src/js/foreground/view/listItemButton/addSongButtonView.js +++ b/src/js/foreground/view/listItemButton/addSongButtonView.js @@ -1,6 +1,6 @@ -define(function (require) { +define(function(require) { 'use strict'; - + var ListItemButtonView = require('foreground/view/listItemButton/listItemButtonView'); var AddListItemButtonTemplate = require('text!template/listItemButton/addListItemButton.html'); var AddIconTemplate = require('text!template/icon/addIcon_18.svg'); @@ -10,45 +10,45 @@ templateHelpers: { addIcon: _.template(AddIconTemplate)() }, - + streamItems: null, streamItemsEvents: { 'add': '_onStreamItemsAdd', 'remove': '_onStreamItemsRemove', 'reset': '_onStreamItemsReset' }, - - initialize: function () { + + initialize: function() { this.streamItems = Streamus.backgroundPage.stream.get('items'); this.bindEntityEvents(this.streamItems, this.streamItemsEvents); - + ListItemButtonView.prototype.initialize.apply(this, arguments); }, - - onRender: function () { + + onRender: function() { this._setState(); }, - - doOnClickAction: function () { + + doOnClickAction: function() { var song = this.model.get('song'); this.streamItems.addSongs(song); }, - - _onStreamItemsAdd: function () { + + _onStreamItemsAdd: function() { this._setState(); }, - _onStreamItemsRemove: function () { + _onStreamItemsRemove: function() { this._setState(); }, - _onStreamItemsReset: function () { + _onStreamItemsReset: function() { this._setState(); }, - _setState: function () { + _setState: function() { var duplicatesInfo = this.streamItems.getDuplicatesInfo(this.model.get('song')); - + this.$el.toggleClass('is-disabled', duplicatesInfo.allDuplicates); var title = duplicatesInfo.allDuplicates ? duplicatesInfo.message : chrome.i18n.getMessage('add'); diff --git a/src/js/foreground/view/listItemButton/deletePlaylistButtonView.js b/src/js/foreground/view/listItemButton/deletePlaylistButtonView.js index 83bce887..d8a04f03 100644 --- a/src/js/foreground/view/listItemButton/deletePlaylistButtonView.js +++ b/src/js/foreground/view/listItemButton/deletePlaylistButtonView.js @@ -1,4 +1,4 @@ -define(function (require) { +define(function(require) { 'use strict'; var PlaylistAction = require('foreground/model/playlistAction'); @@ -11,29 +11,29 @@ templateHelpers: { deleteIcon: _.template(DeleteIconTemplate)() }, - + attributes: { title: chrome.i18n.getMessage('delete') }, - initialize: function () { + initialize: function() { this._setState(); - + ListItemButtonView.prototype.initialize.apply(this, arguments); - + // Ensure that the user isn't able to destroy the model more than once. this.doOnClickAction = _.once(this.doOnClickAction); }, - - doOnClickAction: function () { + + doOnClickAction: function() { var playlistAction = new PlaylistAction({ playlist: this.model }); playlistAction.deletePlaylist(); }, - - _setState: function () { + + _setState: function() { var canDelete = this.model.get('canDelete'); var title; diff --git a/src/js/foreground/view/listItemButton/deleteSongButtonView.js b/src/js/foreground/view/listItemButton/deleteSongButtonView.js index 199789d5..ff5c3700 100644 --- a/src/js/foreground/view/listItemButton/deleteSongButtonView.js +++ b/src/js/foreground/view/listItemButton/deleteSongButtonView.js @@ -1,4 +1,4 @@ -define(function (require) { +define(function(require) { 'use strict'; var ListItemButtonView = require('foreground/view/listItemButton/listItemButtonView'); @@ -10,19 +10,19 @@ templateHelpers: { deleteIcon: _.template(DeleteIconTemplate)() }, - + attributes: { title: chrome.i18n.getMessage('delete') }, - + initialize: function() { ListItemButtonView.prototype.initialize.apply(this, arguments); // Ensure that the user isn't able to destroy the model more than once. this.doOnClickAction = _.once(this.doOnClickAction); }, - - doOnClickAction: function () { + + doOnClickAction: function() { this.model.destroy(); } }); diff --git a/src/js/foreground/view/listItemButton/listItemButtonView.js b/src/js/foreground/view/listItemButton/listItemButtonView.js index 6257b1a6..b0e58221 100644 --- a/src/js/foreground/view/listItemButton/listItemButtonView.js +++ b/src/js/foreground/view/listItemButton/listItemButtonView.js @@ -1,47 +1,47 @@ -define(function (require) { +define(function(require) { 'use strict'; var Tooltip = require('foreground/view/behavior/tooltip'); var ListItemButtonView = Marionette.ItemView.extend({ - className: function () { + className: function() { return 'js-tooltipable listItem-button button button--icon button--icon--secondary button--medium'; }, - + events: { 'click': '_onClick', 'dblclick': '_onDblClick' }, - + behaviors: { Tooltip: { behaviorClass: Tooltip } }, - - initialize: function () { + + initialize: function() { // Debounced to defend against accidental/spam clicking. Bound in initialize because // the debounce timer will be shared between all ListItemButtonViews if bound before initialize. this._debounceOnClickAction = _.debounce(this._doOnClickAction.bind(this), 1000, true); }, // TODO: I actually do need to have these bubble up because global events don't fire. - _onClick: function () { + _onClick: function() { this._debounceOnClickAction(); // Since returning false, need to announce the event happened here since root level won't know about it. Streamus.channels.element.vent.trigger('click', event); // Don't allow click to bubble up since handling click at this level. return false; }, - - _onDblClick: function () { + + _onDblClick: function() { this._debounceOnClickAction(); // Since returning false, need to announce the event happened here since root level won't know about it. Streamus.channels.element.vent.trigger('click', event); // Don't allow dblClick to bubble up since handling click at this level. return false; }, - + _debounceOnClickAction: null, _doOnClickAction: function() { diff --git a/src/js/foreground/view/listItemButton/listItemButtonsView.js b/src/js/foreground/view/listItemButton/listItemButtonsView.js index 261fd0dc..d7e4e170 100644 --- a/src/js/foreground/view/listItemButton/listItemButtonsView.js +++ b/src/js/foreground/view/listItemButton/listItemButtonsView.js @@ -1,13 +1,13 @@ define(function() { 'use strict'; - + // TODO: Can this become a CollectionView? var ListItemButtonsView = Marionette.ItemView.extend({ className: 'listItem-buttons', template: false, // Render a collection of button views to keep things DRY between various types of list-items: - onRender: function () { + onRender: function() { var documentFragment = document.createDocumentFragment(); this.shownButtonViews = []; @@ -23,9 +23,9 @@ this.$el.append(documentFragment); }, - - onBeforeDestroy: function () { - _.each(this.shownButtonViews, function (shownButtonView) { + + onBeforeDestroy: function() { + _.each(this.shownButtonViews, function(shownButtonView) { shownButtonView.destroy(); }); this.shownButtonViews.length = 0; diff --git a/src/js/foreground/view/listItemButton/playPlaylistButtonView.js b/src/js/foreground/view/listItemButton/playPlaylistButtonView.js index 575ff05d..7b489a95 100644 --- a/src/js/foreground/view/listItemButton/playPlaylistButtonView.js +++ b/src/js/foreground/view/listItemButton/playPlaylistButtonView.js @@ -1,4 +1,4 @@ -define(function (require) { +define(function(require) { 'use strict'; var ListItemButtonView = require('foreground/view/listItemButton/listItemButtonView'); @@ -10,47 +10,47 @@ templateHelpers: { playIcon: _.template(PlayIconTemplate)() }, - + streamItems: null, - + playlistItemsEvents: { 'add': '_onPlaylistItemsAdd', 'remove': '_onPlaylistItemsRemove', 'reset': '_onPlaylistItemsReset' }, - initialize: function () { + initialize: function() { this.streamItems = Streamus.backgroundPage.stream.get('items'); this.bindEntityEvents(this.model.get('items'), this.playlistItemsEvents); ListItemButtonView.prototype.initialize.apply(this, arguments); }, - + onRender: function() { this._setState(this.model.get('items').isEmpty()); }, - - doOnClickAction: function () { + + doOnClickAction: function() { var songs = this.model.get('items').pluck('song'); this.streamItems.addSongs(songs, { playOnAdd: true }); }, - - _onPlaylistItemsAdd: function () { + + _onPlaylistItemsAdd: function() { this._setState(false); }, - _onPlaylistItemsRemove: function (model, collection) { + _onPlaylistItemsRemove: function(model, collection) { this._setState(collection.isEmpty()); }, - _onPlaylistItemsReset: function (collection) { + _onPlaylistItemsReset: function(collection) { this._setState(collection.isEmpty()); }, - - _setState: function (isEmpty) { + + _setState: function(isEmpty) { this.$el.toggleClass('is-disabled', isEmpty); var title = isEmpty ? chrome.i18n.getMessage('playlistEmpty') : chrome.i18n.getMessage('play'); diff --git a/src/js/foreground/view/listItemButton/playSongButtonView.js b/src/js/foreground/view/listItemButton/playSongButtonView.js index 089d78be..b5b1c148 100644 --- a/src/js/foreground/view/listItemButton/playSongButtonView.js +++ b/src/js/foreground/view/listItemButton/playSongButtonView.js @@ -1,40 +1,40 @@ -define(function (require) { +define(function(require) { 'use strict'; var ListItemButtonView = require('foreground/view/listItemButton/listItemButtonView'); var PlayListItemButtonTemplate = require('text!template/listItemButton/playListItemButton.html'); var PlayIconTemplate = require('text!template/icon/playIcon_18.svg'); - + var PlayInStreamButtonView = ListItemButtonView.extend({ template: _.template(PlayListItemButtonTemplate), templateHelpers: { playIcon: _.template(PlayIconTemplate)() }, - + attributes: { title: chrome.i18n.getMessage('play') }, - + streamItems: null, player: null, - + initialize: function() { this.streamItems = Streamus.backgroundPage.stream.get('items'); this.player = Streamus.backgroundPage.player; - + ListItemButtonView.prototype.initialize.apply(this, arguments); }, - + doOnClickAction: function() { this._playSong(); }, - _playSong: function () { + _playSong: function() { var song = this.model.get('song'); - + // If there's only one song to be played - check if it's already in the stream. var streamItem = this.streamItems.getBySong(song); - + if (_.isUndefined(streamItem)) { this.streamItems.addSongs(song, { playOnAdd: true @@ -43,8 +43,8 @@ this._playStreamItem(streamItem); } }, - - _playStreamItem: function (streamItem) { + + _playStreamItem: function(streamItem) { if (streamItem.get('active')) { this.player.play(); } else { diff --git a/src/js/foreground/view/listItemButton/saveSongButtonView.js b/src/js/foreground/view/listItemButton/saveSongButtonView.js index abf3e3ee..d90dab9a 100644 --- a/src/js/foreground/view/listItemButton/saveSongButtonView.js +++ b/src/js/foreground/view/listItemButton/saveSongButtonView.js @@ -1,4 +1,4 @@ -define(function (require) { +define(function(require) { 'use strict'; var ListItemButtonView = require('foreground/view/listItemButton/listItemButtonView'); @@ -13,10 +13,10 @@ signInManager: null, - initialize: function () { + initialize: function() { this.signInManager = Streamus.backgroundPage.signInManager; this.listenTo(this.signInManager, 'change:signedInUser', this._onSignInManagerChangeSignedInUser); - + ListItemButtonView.prototype.initialize.apply(this, arguments); }, @@ -24,7 +24,7 @@ this._setState(); }, - doOnClickAction: function () { + doOnClickAction: function() { var offset = this.$el.offset(); Streamus.channels.saveSongs.commands.trigger('show:simpleMenu', { @@ -33,12 +33,12 @@ left: offset.left }); }, - + _onSignInManagerChangeSignedInUser: function() { this._setState(); }, - - _setState: function () { + + _setState: function() { var signedIn = this.signInManager.get('signedInUser') !== null; var title = signedIn ? chrome.i18n.getMessage('save') : chrome.i18n.getMessage('notSignedIn'); diff --git a/src/js/foreground/view/listItemView.js b/src/js/foreground/view/listItemView.js index 2c768da1..4a057a81 100644 --- a/src/js/foreground/view/listItemView.js +++ b/src/js/foreground/view/listItemView.js @@ -1,4 +1,4 @@ -define(function (require) { +define(function(require) { 'use strict'; var Tooltip = require('foreground/view/behavior/tooltip'); @@ -7,8 +7,8 @@ var ListItemView = Marionette.LayoutView.extend({ tagName: 'li', className: 'listItem', - - attributes: function () { + + attributes: function() { // Store the clientId on the view until the model has been saved successfully. var id = this.model.isNew() ? this.model.cid : this.model.get('id'); @@ -25,34 +25,34 @@ 'mouseenter': '_onMouseEnter', 'mouseleave': '_onMouseLeave' }, - - regions: function () { + + regions: function() { return { buttonsRegion: '.' + ListItemView.prototype.className + '-buttonsRegion', spinnerRegion: '.' + ListItemView.prototype.className + '-spinnerRegion', checkboxRegion: '.' + ListItemView.prototype.className + '-checkboxRegion' }; }, - + behaviors: { Tooltip: { behaviorClass: Tooltip } }, - _onContextMenu: function (event) { + _onContextMenu: function(event) { event.preventDefault(); this.showContextMenu(); }, - - _onMouseEnter: function () { + + _onMouseEnter: function() { this.buttonsRegion.show(new ListItemButtonsView({ model: this.model, buttonViews: this.buttonViews })); }, - _onMouseLeave: function () { + _onMouseLeave: function() { this.buttonsRegion.empty(); } }); diff --git a/src/js/foreground/view/notification/notificationRegion.js b/src/js/foreground/view/notification/notificationRegion.js index def33b43..e3dfbc03 100644 --- a/src/js/foreground/view/notification/notificationRegion.js +++ b/src/js/foreground/view/notification/notificationRegion.js @@ -1,64 +1,64 @@ -define(function (require) { +define(function(require) { 'use strict'; var Notification = require('foreground/model/notification'); var NotificationView = require('foreground/view/notification/notificationView'); - var NotificationRegion = Marionette.Region.extend({ - el: '#foregroundArea-notificationRegion', - hideTimeout: null, - hideTimeoutDelay: 3000, + var NotificationRegion = Marionette.Region.extend({ + el: '#foregroundArea-notificationRegion', + hideTimeout: null, + hideTimeoutDelay: 3000, - initialize: function () { - this.listenTo(Streamus.channels.notification.commands, 'show:notification', this._showNotification); - this.listenTo(Streamus.channels.notification.commands, 'hide:notification', this._hideNotification); - this.listenTo(Streamus.channels.element.vent, 'click', this._onElementClick); - this.listenTo(Streamus.backgroundChannels.notification.commands, 'show:notification', this._showNotification); - }, - - _onElementClick: function () { - if (!_.isUndefined(this.currentView)) { - this._hideNotification(); - } - }, + initialize: function() { + this.listenTo(Streamus.channels.notification.commands, 'show:notification', this._showNotification); + this.listenTo(Streamus.channels.notification.commands, 'hide:notification', this._hideNotification); + this.listenTo(Streamus.channels.element.vent, 'click', this._onElementClick); + this.listenTo(Streamus.backgroundChannels.notification.commands, 'show:notification', this._showNotification); + }, - _showNotification: function (notificationOptions) { - // It's important to defer showing notification because they're often shown via user click events. - // Since a click with a notification shown will cause the notification to hide -- need to wait until after click event - // finishes propagating before showing a notification. - _.defer(function() { - this.show(new NotificationView({ - model: new Notification(notificationOptions) - })); + _onElementClick: function() { + if (!_.isUndefined(this.currentView)) { + this._hideNotification(); + } + }, - this._setHideTimeout(); - this.$el.addClass('is-visible'); - }.bind(this)); - }, - - onSwap: function () { - // Timeout will go rogue when swapping views because empty is called without _hideNotification being called. - this._clearHideTimeout(); - }, - - _setHideTimeout: function () { - this.hideTimeout = setTimeout(this._hideNotification.bind(this), this.hideTimeoutDelay); - }, - - _clearHideTimeout: function () { - clearTimeout(this.hideTimeout); - }, - - _hideNotification: function () { - this._clearHideTimeout(); - this.$el.off('webkitTransitionEnd').one('webkitTransitionEnd', this._onTransitionOutComplete.bind(this)); - this.$el.removeClass('is-visible'); - }, - - _onTransitionOutComplete: function () { - this.empty(); - } - }); + _showNotification: function(notificationOptions) { + // It's important to defer showing notification because they're often shown via user click events. + // Since a click with a notification shown will cause the notification to hide -- need to wait until after click event + // finishes propagating before showing a notification. + _.defer(function() { + this.show(new NotificationView({ + model: new Notification(notificationOptions) + })); - return NotificationRegion; + this._setHideTimeout(); + this.$el.addClass('is-visible'); + }.bind(this)); + }, + + onSwap: function() { + // Timeout will go rogue when swapping views because empty is called without _hideNotification being called. + this._clearHideTimeout(); + }, + + _setHideTimeout: function() { + this.hideTimeout = setTimeout(this._hideNotification.bind(this), this.hideTimeoutDelay); + }, + + _clearHideTimeout: function() { + clearTimeout(this.hideTimeout); + }, + + _hideNotification: function() { + this._clearHideTimeout(); + this.$el.off('webkitTransitionEnd').one('webkitTransitionEnd', this._onTransitionOutComplete.bind(this)); + this.$el.removeClass('is-visible'); + }, + + _onTransitionOutComplete: function() { + this.empty(); + } + }); + + return NotificationRegion; }); \ No newline at end of file diff --git a/src/js/foreground/view/notification/notificationView.js b/src/js/foreground/view/notification/notificationView.js index 17e219ce..49f295e4 100644 --- a/src/js/foreground/view/notification/notificationView.js +++ b/src/js/foreground/view/notification/notificationView.js @@ -1,27 +1,27 @@ -define(function (require) { +define(function(require) { 'use strict'; var NotificationTemplate = require('text!template/notification/notification.html'); - var NotificationView = Marionette.ItemView.extend({ - id: 'notification', - className: 'notification panel-content panel-content--fadeInOut', - template: _.template(NotificationTemplate), + var NotificationView = Marionette.ItemView.extend({ + id: 'notification', + className: 'notification panel-content panel-content--fadeInOut', + template: _.template(NotificationTemplate), - ui: function () { - return { - hideButton: '#' + this.id + '-hideButton' - }; - }, + ui: function() { + return { + hideButton: '#' + this.id + '-hideButton' + }; + }, - events: { - 'click @ui.hideButton': '_onClickHideButton' - }, - - _onClickHideButton: function() { - Streamus.channels.notification.commands.trigger('hide:notification'); - } - }); + events: { + 'click @ui.hideButton': '_onClickHideButton' + }, - return NotificationView; + _onClickHideButton: function() { + Streamus.channels.notification.commands.trigger('hide:notification'); + } + }); + + return NotificationView; }); \ No newline at end of file diff --git a/src/js/foreground/view/playlist/playlistView.js b/src/js/foreground/view/playlist/playlistView.js index b7016491..9f7fe4f2 100644 --- a/src/js/foreground/view/playlist/playlistView.js +++ b/src/js/foreground/view/playlist/playlistView.js @@ -1,4 +1,4 @@ -define(function (require) { +define(function(require) { 'use strict'; var SpinnerView = require('foreground/view/element/spinnerView'); @@ -13,10 +13,10 @@ var PlaylistView = ListItemView.extend({ className: ListItemView.prototype.className + ' playlist listItem--small listItem--hasButtons listItem--selectable', template: _.template(PlaylistTemplate), - + ui: _.extend({}, ListItemView.prototype.ui, { title: '.listItem-title', - itemCount: '.listItem-itemCount' + itemCount: '.listItem-itemCount' }), events: _.extend({}, ListItemView.prototype.events, { @@ -30,20 +30,20 @@ 'change:active': '_onChangeActive', 'change:id': '_onChangeId' }, - + buttonViews: [PlayPlaylistButtonView, AddPlaylistButtonView, DeletePlaylistButtonView], - + playlistItemsEvents: { 'add': '_onPlaylistItemsAdd', 'remove': '_onPlaylistItemsRemove', 'reset': '_onPlaylistItemsReset' }, - - initialize: function () { + + initialize: function() { this.bindEntityEvents(this.model.get('items'), this.playlistItemsEvents); }, - - onRender: function () { + + onRender: function() { this.spinnerRegion.show(new SpinnerView({ className: 'overlay u-marginAuto' })); @@ -52,8 +52,8 @@ this._setActiveClass(this.model.get('active')); this._setItemCount(this.model.get('items').length); }, - - showContextMenu: function () { + + showContextMenu: function() { var isEmpty = this.model.get('items').isEmpty(); Streamus.channels.contextMenu.commands.trigger('reset:items', [{ @@ -71,98 +71,98 @@ onClick: this._showExportPlaylistDialog.bind(this) }]); }, - - _onChangeTitle: function (model, title) { + + _onChangeTitle: function(model, title) { this.ui.title.text(title).attr('title', title); }, - - _onChangeDataSourceLoaded: function () { + + _onChangeDataSourceLoaded: function() { this._setShowingSpinnerClass(); }, - - _onChangeId: function () { + + _onChangeId: function() { this._setShowingSpinnerClass(); }, - - _onChangeActive: function (model, active) { + + _onChangeActive: function(model, active) { this._setActiveClass(active); }, - - _setShowingSpinnerClass: function () { + + _setShowingSpinnerClass: function() { var loading = this.model.isLoading(); var saving = this.model.isNew(); this.$el.toggleClass('is-showingSpinner', loading || saving); }, - - _setActiveClass: function (active) { + + _setActiveClass: function(active) { this.$el.toggleClass('is-active', active); }, - - _onPlaylistItemsAdd: function (model, collection) { + + _onPlaylistItemsAdd: function(model, collection) { this._setItemCount(collection.length); }, - _onPlaylistItemsRemove: function (model, collection) { + _onPlaylistItemsRemove: function(model, collection) { this._setItemCount(collection.length); }, - _onPlaylistItemsReset: function (collection) { + _onPlaylistItemsReset: function(collection) { this._setItemCount(collection.length); }, - - _setItemCount: function (itemCount) { + + _setItemCount: function(itemCount) { // Format the number if it is too large. if (itemCount >= 1000) { itemCount = Math.floor(itemCount / 1000) + 'K'; } - + this.ui.itemCount.text(itemCount); }, - - _activate: function () { + + _activate: function() { this.model.set('active', true); }, - + _copyPlaylistUrl: function() { this.model.getShareCode({ success: this._onGetShareCodeSuccess, error: this._onGetShareCodeError }); }, - - _onGetShareCodeSuccess: function (shareCode) { + + _onGetShareCodeSuccess: function(shareCode) { shareCode.copyUrl(); - + Streamus.channels.notification.commands.trigger('show:notification', { message: chrome.i18n.getMessage('urlCopied') }); }, - - _onGetShareCodeError: function () { + + _onGetShareCodeError: function() { Streamus.channels.notification.commands.trigger('show:notification', { message: chrome.i18n.getMessage('copyFailed') }); Streamus.backgroundChannels.error.commands.trigger('log:error', new Error('Failed to get sharecode; ' + ' playlist: ' + this.model.get('id'))); }, - + _showEditPlaylistDialog: function() { Streamus.channels.dialog.commands.trigger('show:dialog', EditPlaylistDialogView, { playlist: this.model }); }, - + _showExportPlaylistDialog: function() { Streamus.channels.dialog.commands.trigger('show:dialog', ExportPlaylistDialogView, { playlist: this.model }); }, - - _onClick: function () { + + _onClick: function() { this._activate(); }, - - _onDblClick: function () { + + _onDblClick: function() { this._activate(); } }); diff --git a/src/js/foreground/view/playlist/playlistsAreaRegion.js b/src/js/foreground/view/playlist/playlistsAreaRegion.js index cca0ca0e..19f1fc9a 100644 --- a/src/js/foreground/view/playlist/playlistsAreaRegion.js +++ b/src/js/foreground/view/playlist/playlistsAreaRegion.js @@ -1,12 +1,12 @@ -define(function (require) { +define(function(require) { 'use strict'; var PlaylistsAreaView = require('foreground/view/playlist/playlistsAreaView'); - + var PlaylistsAreaRegion = Marionette.Region.extend({ signInManager: null, - - initialize: function () { + + initialize: function() { this.signInManager = Streamus.backgroundPage.signInManager; this.listenTo(Streamus.channels.playlistsArea.commands, 'show:playlistsArea', this._showPlaylistsArea); @@ -14,19 +14,19 @@ this.listenTo(Streamus.channels.foregroundArea.vent, 'rendered', this._onForegroundAreaRendered); this.listenTo(this.signInManager, 'change:signedInUser', this._onSignInManagerChangeSignedInUser); }, - - _onForegroundAreaRendered: function () { + + _onForegroundAreaRendered: function() { var signedInUser = this.signInManager.get('signedInUser'); if (signedInUser !== null) { this._createPlaylistsAreaView(signedInUser.get('playlists')); } }, - - _showPlaylistsArea: function () { + + _showPlaylistsArea: function() { this.currentView.show(); }, - - _hidePlaylistsArea: function () { + + _hidePlaylistsArea: function() { // A hide command can be emitted by the application when the user is not signed in. In this scenario, currentView doesn't exist. if (this._playlistsAreaViewExists()) { this.currentView.hide(); @@ -34,11 +34,11 @@ }, // Returns true if PlaylistsAreaView is currently shown - _playlistsAreaViewExists: function () { + _playlistsAreaViewExists: function() { return !_.isUndefined(this.currentView) && this.currentView instanceof PlaylistsAreaView; }, - - _createPlaylistsAreaView: function (playlists) { + + _createPlaylistsAreaView: function(playlists) { if (!this._playlistsAreaViewExists()) { var playlistsAreaView = new PlaylistsAreaView({ playlists: playlists @@ -49,7 +49,7 @@ }, // Don't allow this view to be shown if the user is not signed in. - _onSignInManagerChangeSignedInUser: function (model, signedInUser) { + _onSignInManagerChangeSignedInUser: function(model, signedInUser) { if (signedInUser !== null) { this.empty(); this._createPlaylistsAreaView(signedInUser.get('playlists')); diff --git a/src/js/foreground/view/playlist/playlistsAreaView.js b/src/js/foreground/view/playlist/playlistsAreaView.js index e87012f9..5a026601 100644 --- a/src/js/foreground/view/playlist/playlistsAreaView.js +++ b/src/js/foreground/view/playlist/playlistsAreaView.js @@ -1,4 +1,4 @@ -define(function (require) { +define(function(require) { 'use strict'; var PlaylistsView = require('foreground/view/playlist/playlistsView'); @@ -9,20 +9,20 @@ id: 'playlistsArea', className: 'flexColumn', template: _.template(PlaylistsAreaTemplate), - templateHelpers: function () { + templateHelpers: function() { return { viewId: this.id, createPlaylist: chrome.i18n.getMessage('createPlaylist') }; }, - - regions: function () { + + regions: function() { return { playlistsRegion: '#' + this.id + '-playlistsRegion' }; }, - ui: function () { + ui: function() { return { transitionable: '.u-transitionable', overlay: '#' + this.id + '-overlay', @@ -36,15 +36,15 @@ 'click @ui.hideButton': '_onClickHideButton', 'click @ui.createPlaylistButton': '_onClickCreatePlaylistButton' }, - + playlists: null, // TODO: This feels weird... - initialize: function (options) { + initialize: function(options) { this.playlists = options.playlists; }, - - onRender: function () { + + onRender: function() { var playlistsView = new PlaylistsView({ collection: this.playlists }); @@ -53,13 +53,13 @@ // TODO: This also feels kind of weird. this.listenTo(playlistsView, 'click:childContainer', this._onClickChildContainer); }, - - show: function () { + + show: function() { Streamus.channels.playlistsArea.vent.trigger('showing'); this.ui.transitionable.addClass('is-visible'); }, - - hide: function () { + + hide: function() { Streamus.channels.playlistsArea.vent.trigger('hiding'); this.ui.transitionable.removeClass('is-visible'); }, @@ -68,16 +68,16 @@ this.hide(); }, - _onClickOverlay: function () { + _onClickOverlay: function() { this.hide(); }, - - _onClickCreatePlaylistButton: function () { + + _onClickCreatePlaylistButton: function() { Streamus.channels.dialog.commands.trigger('show:dialog', CreatePlaylistDialogView); }, // Whenever a playlist is clicked it will become active and the menu should hide itself. - _onClickChildContainer: function () { + _onClickChildContainer: function() { this.hide(); } }); diff --git a/src/js/foreground/view/playlist/playlistsView.js b/src/js/foreground/view/playlist/playlistsView.js index 85fead6f..12bab3ff 100644 --- a/src/js/foreground/view/playlist/playlistsView.js +++ b/src/js/foreground/view/playlist/playlistsView.js @@ -1,4 +1,4 @@ -define(function (require) { +define(function(require) { 'use strict'; var ListItemType = require('common/enum/listItemType'); @@ -11,11 +11,11 @@ id: 'playlists', className: 'list u-flex--full', template: _.template(PlaylistsTemplate), - + childView: PlaylistView, childViewContainer: '@ui.childContainer', childViewType: ListItemType.Playlist, - childViewOptions: function () { + childViewOptions: function() { return { type: this.childViewType, parentId: this.ui.childContainer[0].id @@ -23,20 +23,20 @@ }, // Overwrite resortView to only render children as expected - resortView: function () { + resortView: function() { this._renderChildren(); }, - - ui: function () { + + ui: function() { return { childContainer: '#' + this.id + '-listItems' }; }, - + events: { 'scroll': '_onScroll' }, - + triggers: { 'click @ui.childContainer': 'click:childContainer' }, @@ -49,26 +49,26 @@ behaviorClass: Scrollable } }, - + viewModel: null, - - initialize: function () { + + initialize: function() { this.viewModel = Streamus.backgroundPage.playlistsViewModel; }, - - onRender: function () { + + onRender: function() { this.ui.childContainer.sortable(this._getSortableOptions()); }, - + onAttach: function() { this.el.scrollTop = this.viewModel.get('scrollTop'); }, - - _onScroll: function () { + + _onScroll: function() { this.viewModel.set('scrollTop', this.el.scrollTop); }, - - _getSortableOptions: function () { + + _getSortableOptions: function() { var sortableOptions = { axis: 'y', delay: 100, @@ -81,12 +81,12 @@ return sortableOptions; }, - _onSortableStart: function () { + _onSortableStart: function() { Streamus.channels.element.vent.trigger('drag'); }, // Whenever a playlist is moved visually -- update corresponding model with new information. - _onSortableUpdate: function (event, ui) { + _onSortableUpdate: function(event, ui) { this.collection.moveToIndex(ui.item.data('id'), ui.item.index()); } }); diff --git a/src/js/foreground/view/saveSongs/saveSongsRegion.js b/src/js/foreground/view/saveSongs/saveSongsRegion.js index 19f8166a..3177508d 100644 --- a/src/js/foreground/view/saveSongs/saveSongsRegion.js +++ b/src/js/foreground/view/saveSongs/saveSongsRegion.js @@ -1,5 +1,5 @@ // TODO: This is too specific of a usecase, I'd like to expand upon it in the future and make it generic, maybe combine it with ContextMenu if possible. -define(function (require) { +define(function(require) { 'use strict'; var SimpleMenuItems = require('foreground/collection/simpleMenuItems'); @@ -10,18 +10,18 @@ define(function (require) { var SaveSongsRegion = Marionette.Region.extend({ signInManager: null, - initialize: function () { + initialize: function() { this.signInManager = Streamus.backgroundPage.signInManager; this.listenTo(Streamus.channels.saveSongs.commands, 'show:simpleMenu', this._showSimpleMenu); }, - - _showSimpleMenu: function (options) { + + _showSimpleMenu: function(options) { // Wrap the logic for showing a simpleMenu in defer to allow 'click' event to fully propagate before showing the view. // This ensure that a click event which spawned the simpleMenu does not also trigger the closing of the menu - _.defer(function () { + _.defer(function() { var playlists = this.signInManager.get('signedInUser').get('playlists'); - var simpleMenuItems = new SimpleMenuItems(playlists.map(function (playlist) { + var simpleMenuItems = new SimpleMenuItems(playlists.map(function(playlist) { return { active: playlist.get('active'), text: playlist.get('title'), @@ -51,14 +51,14 @@ define(function (require) { }); }.bind(this)); }, - - _onClickSimpleMenuItem: function (playlists, songs, eventArgs) { + + _onClickSimpleMenuItem: function(playlists, songs, eventArgs) { var activeItem = eventArgs.collection.getActive(); var playlist = playlists.get(activeItem.get('value')); playlist.get('items').addSongs(songs); }, - _onClickFixedMenuItem: function (songs) { + _onClickFixedMenuItem: function(songs) { Streamus.channels.dialog.commands.trigger('show:dialog', CreatePlaylistDialogView, { songs: songs }); @@ -66,7 +66,7 @@ define(function (require) { // TODO: Keep DRY w/ contextmenu // Prevent displaying ContextMenu outside of viewport by ensuring its offsets are valid. - _ensureOffset: function (offset, elementDimension, containerDimension) { + _ensureOffset: function(offset, elementDimension, containerDimension) { var ensuredOffset = offset; var needsFlip = offset + elementDimension > containerDimension; diff --git a/src/js/foreground/view/search/searchAreaRegion.js b/src/js/foreground/view/search/searchAreaRegion.js index 359357bc..4c4f075e 100644 --- a/src/js/foreground/view/search/searchAreaRegion.js +++ b/src/js/foreground/view/search/searchAreaRegion.js @@ -1,4 +1,4 @@ -define(function (require) { +define(function(require) { 'use strict'; var SearchView = require('foreground/view/search/searchView'); @@ -6,16 +6,16 @@ // TODO: SearchAreaRegion vs SearchView... var SearchAreaRegion = Marionette.Region.extend({ settings: null, - - initialize: function () { + + initialize: function() { this.settings = Streamus.backgroundPage.settings; this.listenTo(Streamus.channels.searchArea.commands, 'show:search', this._showSearch); this.listenTo(Streamus.channels.searchArea.commands, 'hide:search', this._hideSearch); this.listenTo(Streamus.channels.foregroundArea.vent, 'rendered', this._onForegroundAreaRendered); }, - - _onForegroundAreaRendered: function () { + + _onForegroundAreaRendered: function() { this._createSearchView(); if (this.settings.get('openToSearch')) { @@ -24,8 +24,8 @@ }); } }, - - _createSearchView: function () { + + _createSearchView: function() { var searchView = new SearchView({ model: Streamus.backgroundPage.search, collection: Streamus.backgroundPage.search.get('results') @@ -35,19 +35,19 @@ this.listenTo(searchView, 'hide:search', this._hideSearch); }, - _showSearch: function (options) { + _showSearch: function(options) { Streamus.channels.searchArea.vent.trigger('showing'); // If the view should be visible when UI first loads then do not transition. if (options && options.instant) { this.$el.addClass('is-instant'); } - + this.$el.addClass('is-visible'); this.currentView.triggerMethod('visible'); }, - _hideSearch: function () { + _hideSearch: function() { Streamus.channels.searchArea.vent.trigger('hiding'); this.$el.removeClass('is-instant is-visible'); } diff --git a/src/js/foreground/view/search/searchResultView.js b/src/js/foreground/view/search/searchResultView.js index e5271eef..74cc4a3d 100644 --- a/src/js/foreground/view/search/searchResultView.js +++ b/src/js/foreground/view/search/searchResultView.js @@ -1,4 +1,4 @@ -define(function (require) { +define(function(require) { 'use strict'; var ListItemView = require('foreground/view/listItemView'); @@ -7,32 +7,32 @@ var PlaySongButtonView = require('foreground/view/listItemButton/playSongButtonView'); var SaveSongButtonView = require('foreground/view/listItemButton/saveSongButtonView'); var SearchResultTemplate = require('text!template/search/searchResult.html'); - + var SearchResultView = ListItemView.extend({ className: ListItemView.prototype.className + ' search-result listItem--medium listItem--hasButtons listItem--selectable', template: _.template(SearchResultTemplate), buttonViews: [PlaySongButtonView, AddSongButtonView, SaveSongButtonView], - + events: _.extend({}, ListItemView.prototype.events, { 'dblclick': '_onDblClick' }), - + behaviors: _.extend({}, ListItemView.prototype.behaviors, { ListItemMultiSelect: { behaviorClass: ListItemMultiSelect } }), - + streamItems: null, player: null, - - initialize: function () { + + initialize: function() { this.streamItems = Streamus.backgroundPage.stream.get('items'); this.player = Streamus.backgroundPage.player; }, - - showContextMenu: function () { + + showContextMenu: function() { Streamus.channels.contextMenu.commands.trigger('reset:items', [{ text: chrome.i18n.getMessage('copyUrl'), onClick: this._copyUrl.bind(this) @@ -44,26 +44,26 @@ onClick: this._watchOnYouTube.bind(this) }]); }, - - _onDblClick: function () { + + _onDblClick: function() { this._playInStream(); }, - _playInStream: function () { + _playInStream: function() { this.streamItems.addSongs(this.model.get('song'), { playOnAdd: true }); }, - - _copyUrl: function () { + + _copyUrl: function() { this.model.get('song').copyUrl(); }, - _copyTitleAndUrl: function () { + _copyTitleAndUrl: function() { this.model.get('song').copyTitleAndUrl(); }, - _watchOnYouTube: function () { + _watchOnYouTube: function() { this.player.watchInTab(this.model.get('song')); } }); diff --git a/src/js/foreground/view/search/searchResultsView.js b/src/js/foreground/view/search/searchResultsView.js index c985b2ce..120032af 100644 --- a/src/js/foreground/view/search/searchResultsView.js +++ b/src/js/foreground/view/search/searchResultsView.js @@ -1,4 +1,4 @@ -define(function (require) { +define(function(require) { 'use strict'; var ListItemType = require('common/enum/listItemType'); @@ -16,7 +16,7 @@ childViewContainer: '@ui.childContainer', childView: SearchResultView, childViewType: ListItemType.SearchResult, - childViewOptions: function () { + childViewOptions: function() { return { type: this.childViewType, parentId: this.ui.childContainer[0].id @@ -24,18 +24,18 @@ }, // Overwrite resortView to only render children as expected - resortView: function () { + resortView: function() { this._renderChildren(); }, - + template: _.template(SearchResultsTemplate), - ui: function () { + ui: function() { return { childContainer: '#' + this.id + '-listItems' }; }, - + behaviors: { CollectionViewMultiSelect: { behaviorClass: CollectionViewMultiSelect @@ -54,14 +54,14 @@ } }, - initialize: function () { + initialize: function() { this.viewModel = Streamus.backgroundPage.playlistsViewModel; this.listenTo(Streamus.channels.searchArea.vent, 'hiding', this._onSearchAreaHiding); }, // Don't maintain selected results after closing the view because the view won't be visible. - _onSearchAreaHiding: function () { + _onSearchAreaHiding: function() { this.triggerMethod('DeselectCollection'); } }); diff --git a/src/js/foreground/view/search/searchView.js b/src/js/foreground/view/search/searchView.js index c6bcc9a1..9ac28301 100644 --- a/src/js/foreground/view/search/searchView.js +++ b/src/js/foreground/view/search/searchView.js @@ -1,4 +1,4 @@ -define(function (require) { +define(function(require) { 'use strict'; var SpinnerView = require('foreground/view/element/spinnerView'); @@ -10,8 +10,8 @@ id: 'search', className: 'leftPane flexColumn panel-content panel-content--uncolored u-fullHeight', template: _.template(SearchTemplate), - - templateHelpers: function () { + + templateHelpers: function() { return { viewId: this.id, searchMessage: chrome.i18n.getMessage('search'), @@ -27,14 +27,14 @@ }; }, - regions: function () { + regions: function() { return { searchResultsRegion: '#' + this.id + '-searchResultsRegion', spinnerRegion: '#' + this.id + '-spinnerRegion' }; }, - - ui: function () { + + ui: function() { return { playAllButton: '#' + this.id + '-playAllButton', saveAllButton: '#' + this.id + '-saveAllButton', @@ -44,7 +44,7 @@ noResultsMessage: '#' + this.id + '-noResultsMessage' }; }, - + events: { // TODO: Quit checking class like this. 'click @ui.playAllButton:not(.is-disabled)': '_onClickPlayAllButton', @@ -62,94 +62,94 @@ 'remove': '_onSearchResultsRemove', 'reset': '_onSearchResultsReset' }, - + transitionDuration: 4000, streamItems: null, signInManager: null, - initialize: function () { + initialize: function() { this.streamItems = Streamus.backgroundPage.stream.get('items'); this.signInManager = Streamus.backgroundPage.signInManager; this.listenTo(this.signInManager, 'change:signedInUser', this._onSignInManagerChangeSignedInUser); this.listenTo(Streamus.channels.searchArea.commands, 'search', this._search); }, - - onRender: function () { + + onRender: function() { this._setButtonStates(); this._toggleInstructions(); - + this.searchResultsRegion.show(new SearchResultsView({ collection: this.model.get('results') })); - + this.spinnerRegion.show(new SpinnerView()); }, // onVisible is triggered when the element begins to transition into the viewport. - onVisible: function () { + onVisible: function() { this.model.stopClearQueryTimer(); }, _onClickSaveAllButton: function() { this._showSaveSelectedSimpleMenu(); }, - - _onClickAddAllButton: function () { + + _onClickAddAllButton: function() { this.streamItems.addSongs(this.collection.getSongs()); }, - - _onClickPlayAllButton: function () { + + _onClickPlayAllButton: function() { this.streamItems.addSongs(this.collection.getSongs(), { playOnAdd: true }); }, - + _onSignInManagerChangeSignedInUser: function() { this._setSaveAllButtonState(); }, - _onChangeSearching: function () { + _onChangeSearching: function() { this._toggleInstructions(); }, - _onChangeQuery: function () { + _onChangeQuery: function() { this._toggleInstructions(); }, - _onSearchResultsReset: function () { + _onSearchResultsReset: function() { this._toggleInstructions(); this._setButtonStates(); }, - - _onSearchResultsAdd: function () { + + _onSearchResultsAdd: function() { this._toggleInstructions(); this._setButtonStates(); }, - - _onSearchResultsRemove: function () { + + _onSearchResultsRemove: function() { this._toggleInstructions(); this._setButtonStates(); }, // Searches youtube for song results based on the given text. - _search: function (options) { + _search: function(options) { this.model.set('query', options.query); }, - _setSaveAllButtonState: function () { + _setSaveAllButtonState: function() { var canSave = this._canSave(); this.ui.saveAllButton.toggleClass('is-disabled', !canSave); }, - _setButtonStates: function () { + _setButtonStates: function() { this._setSaveAllButtonState(); var isEmpty = this.collection.isEmpty(); this.ui.playAllButton.toggleClass('is-disabled', isEmpty); this.ui.addAllButton.toggleClass('is-disabled', isEmpty); }, - _showSaveSelectedSimpleMenu: function () { + _showSaveSelectedSimpleMenu: function() { var canSave = this._canSave(); if (canSave) { @@ -162,8 +162,8 @@ }); } }, - - _canSave: function () { + + _canSave: function() { var signedIn = this.signInManager.get('signedInUser') !== null; var isEmpty = this.collection.isEmpty(); @@ -171,7 +171,7 @@ }, // Set the visibility of any visible text messages. - _toggleInstructions: function () { + _toggleInstructions: function() { var hasSearchResults = this.collection.length > 0; // Hide the search message when there is no search in progress diff --git a/src/js/foreground/view/selectionBar/selectionBarRegion.js b/src/js/foreground/view/selectionBar/selectionBarRegion.js index 1ddb6331..634ad9da 100644 --- a/src/js/foreground/view/selectionBar/selectionBarRegion.js +++ b/src/js/foreground/view/selectionBar/selectionBarRegion.js @@ -1,4 +1,4 @@ -define(function (require) { +define(function(require) { 'use strict'; var SelectionBar = require('foreground/model/selectionBar'); @@ -8,8 +8,8 @@ initialize: function() { this.listenTo(Streamus.channels.foregroundArea.vent, 'rendered', this._onForegroundAreaRendered); }, - - _onForegroundAreaRendered: function () { + + _onForegroundAreaRendered: function() { var selectionBar = new SelectionBar(); this.show(new SelectionBarView({ model: selectionBar @@ -17,8 +17,8 @@ this.listenTo(selectionBar, 'change:activeCollection', this._onSelectionBarChangeActiveCollection); }, - - _onSelectionBarChangeActiveCollection: function (model, activeCollection) { + + _onSelectionBarChangeActiveCollection: function(model, activeCollection) { this.$el.toggleClass('is-visible', activeCollection !== null); } }); diff --git a/src/js/foreground/view/selectionBar/selectionBarView.js b/src/js/foreground/view/selectionBar/selectionBarView.js index 97eac9f2..1947e85e 100644 --- a/src/js/foreground/view/selectionBar/selectionBarView.js +++ b/src/js/foreground/view/selectionBar/selectionBarView.js @@ -1,6 +1,6 @@ -define(function (require) { +define(function(require) { 'use strict'; - + var Tooltip = require('foreground/view/behavior/tooltip'); var SelectionBarTemplate = require('text!template/selectionBar/selectionBar.html'); var CloseIconTemplate = require('text!template/icon/closeIcon_24.svg'); @@ -9,8 +9,8 @@ id: 'selectionBar', className: 'selectionBar panel-content panel-content--uncolored', template: _.template(SelectionBarTemplate), - - templateHelpers: function () { + + templateHelpers: function() { return { viewId: this.id, playMessage: chrome.i18n.getMessage('play'), @@ -20,10 +20,10 @@ closeIcon: _.template(CloseIconTemplate)() }; }, - - ui: function () { + + ui: function() { return { - playButton: '#'+ this.id + '-playButton', + playButton: '#' + this.id + '-playButton', addButton: '#' + this.id + '-addButton', saveButton: '#' + this.id + '-saveButton', deleteButton: '#' + this.id + '-deleteButton', @@ -31,7 +31,7 @@ clearButton: '#' + this.id + '-clearButton' }; }, - + events: { 'click @ui.playButton': '_onClickPlayButton', 'click @ui.addButton': '_onClickAddButton', @@ -39,32 +39,32 @@ 'click @ui.deleteButton': '_onClickDeleteButton', 'click @ui.clearButton': '_onClickClearButton' }, - + behaviors: { Tooltip: { behaviorClass: Tooltip } }, - + streamItems: null, searchResults: null, signInManager: null, - + multiSelectCollectionEvents: { 'change:selected': '_onMultiSelectCollectionChangeSelected', 'remove': '_onMultiSelectCollectionRemove' }, - + streamItemsEvents: { 'add': '_onStreamItemsAdd', 'remove': '_onStreamItemsRemove', 'reset': '_onStreamItemsReset' }, - + playlistsEvents: { 'change:active': '_onPlaylistsChangeActive' }, - + initialize: function() { this.streamItems = Streamus.backgroundPage.stream.get('items'); this.searchResults = Streamus.backgroundPage.search.get('results'); @@ -79,44 +79,44 @@ if (signedInUser !== null) { this._setUserBindings(signedInUser, true); } - + this.listenTo(this.signInManager, 'change:signedInUser', this._onSignInManagerChangeSignedInUser); }, - - onRender: function () { + + onRender: function() { this._setSelectionCountText(); this._setButtonStates(); }, - - _onClickClearButton: function () { + + _onClickClearButton: function() { Streamus.channels.listItem.commands.trigger('deselect:collection'); }, - - _onClickPlayButton: function () { + + _onClickPlayButton: function() { var canPlay = this._canPlay(); - + if (canPlay) { var selectedSongs = this.model.get('activeCollection').getSelectedSongs(); this.streamItems.addSongs(selectedSongs, { playOnAdd: true }); - + Streamus.channels.listItem.commands.trigger('deselect:collection'); } }, - - _onClickAddButton: function () { + + _onClickAddButton: function() { var canAdd = this._canAdd(); - + if (canAdd) { var selectedSongs = this.model.get('activeCollection').getSelectedSongs(); this.streamItems.addSongs(selectedSongs); - + Streamus.channels.listItem.commands.trigger('deselect:collection'); } }, - + _onClickSaveButton: function() { var canSave = this._canSave(); @@ -135,55 +135,55 @@ this.listenTo(Streamus.channels.simpleMenu.vent, 'hidden', this._onSimpleMenuHidden); } }, - + _onClickDeleteButton: function() { // TODO: Implement undo since I've opted to not show a dialog. var canDelete = this._canDelete(); - + if (canDelete) { var selectedModels = this.model.get('activeCollection').selected(); _.invoke(selectedModels, 'destroy'); - + Streamus.channels.listItem.commands.trigger('deselect:collection'); } }, - - _onSimpleMenuClickedItem: function () { + + _onSimpleMenuClickedItem: function() { Streamus.channels.listItem.commands.trigger('deselect:collection'); }, - _onSimpleMenuHidden: function () { + _onSimpleMenuHidden: function() { this.stopListening(Streamus.channels.simpleMenu.vent); }, // Keep track of which multi-select collection is currently holding selected items - _onMultiSelectCollectionChangeSelected: function (model, selected) { + _onMultiSelectCollectionChangeSelected: function(model, selected) { this._setActiveCollection(model.collection, selected); this._setSelectionCountText(); this._setButtonStates(); }, // If a selected model is removed from a collection then a 'change:selected' event does not fire. - _onMultiSelectCollectionRemove: function (model, collection) { + _onMultiSelectCollectionRemove: function(model, collection) { this._setActiveCollection(collection, false); this._setSelectionCountText(); this._setButtonStates(); }, - - _onStreamItemsAdd: function () { + + _onStreamItemsAdd: function() { this._setAddButtonState(this.model.get('activeCollection')); }, - + _onStreamItemsRemove: function() { this._setAddButtonState(this.model.get('activeCollection')); }, - - _onStreamItemsReset: function () { + + _onStreamItemsReset: function() { this._setAddButtonState(this.model.get('activeCollection')); }, // Bind/unbind listeners as appropriate whenever the active playlist changes. - _onPlaylistsChangeActive: function (model, active) { + _onPlaylistsChangeActive: function(model, active) { if (active) { this.bindEntityEvents(model.get('items'), this.multiSelectCollectionEvents); } else { @@ -192,7 +192,7 @@ }, // Bind/unbind listeners as appropriate whenver the signedInUser changes. - _onSignInManagerChangeSignedInUser: function (model, signedInUser) { + _onSignInManagerChangeSignedInUser: function(model, signedInUser) { if (signedInUser === null) { this._setUserBindings(model.previous('signedInUser'), false); } else { @@ -201,7 +201,7 @@ }, // Keep track of which collection currently has selected songs by handling selection & deselection events. - _setActiveCollection: function (collection, isSelecting) { + _setActiveCollection: function(collection, isSelecting) { var hasSelectedItems = collection.selected().length > 0; if (hasSelectedItems) { @@ -212,15 +212,14 @@ if (isSelecting) { this.model.set('activeCollection', collection); } - } - else if (this.model.get('activeCollection') === collection) { + } else if (this.model.get('activeCollection') === collection) { this.model.set('activeCollection', null); } }, // Bind or unbind entity events to a user's playlists and activePlaylist's items. // Useful for when a user is signing in/out. - _setUserBindings: function (user, isBinding) { + _setUserBindings: function(user, isBinding) { var bindingAction = isBinding ? this.bindEntityEvents : this.unbindEntityEvents; var playlists = user.get('playlists'); @@ -231,17 +230,17 @@ }, // Update the text which shows how many songs are currently selected - _setSelectionCountText: function () { + _setSelectionCountText: function() { var activeCollection = this.model.get('activeCollection'); var activeCollectionExists = activeCollection !== null; var songCount = activeCollectionExists ? activeCollection.selected().length : 0; - + var selectionCountText = chrome.i18n.getMessage('collectionSelected', [songCount, chrome.i18n.getMessage(songCount === 1 ? 'song' : 'songs')]); this.ui.selectionCountText.html(selectionCountText); }, // Set buttons to disabled when transitioning the view out as well as handle specific scenarios for each button - _setButtonStates: function () { + _setButtonStates: function() { var activeCollection = this.model.get('activeCollection'); var activeCollectionExists = activeCollection !== null; @@ -253,8 +252,8 @@ this._setDeleteButtonState(activeCollection); this._setAddButtonState(activeCollection); }, - - _setDeleteButtonState: function (activeCollection) { + + _setDeleteButtonState: function(activeCollection) { var activeCollectionExists = activeCollection !== null; // Delete is disabled if the user is selecting search results @@ -267,8 +266,8 @@ this.ui.deleteButton.toggleClass('is-disabled', !canDelete).attr('title', deleteTitle); }, - - _setAddButtonState: function (activeCollection) { + + _setAddButtonState: function(activeCollection) { var activeCollectionExists = activeCollection !== null; // Add is disabled if all selected songs are already in the stream. @@ -284,8 +283,8 @@ this.ui.addButton.toggleClass('is-disabled', !canAdd).attr('title', addTitle); }, - - _canPlay: function () { + + _canPlay: function() { var activeCollection = this.model.get('activeCollection'); var activeCollectionExists = activeCollection !== null; @@ -293,19 +292,19 @@ }, // TODO: Super messy, prefer to store on model, but tricky due dependencies on signInManager/streamItems. - _canDelete: function () { + _canDelete: function() { var activeCollection = this.model.get('activeCollection'); var activeCollectionExists = activeCollection !== null; return activeCollectionExists && !activeCollection.isImmutable; }, - - _canSave: function () { + + _canSave: function() { var isSignedIn = this.signInManager.get('signedInUser') !== null; var activeCollectionExists = this.model.get('activeCollection') !== null; - + return isSignedIn && activeCollectionExists; }, - + _canAdd: function() { var activeCollection = this.model.get('activeCollection'); var activeCollectionExists = activeCollection !== null; diff --git a/src/js/foreground/view/stream/activeStreamItemView.js b/src/js/foreground/view/stream/activeStreamItemView.js index c5d10dc9..52acb1a1 100644 --- a/src/js/foreground/view/stream/activeStreamItemView.js +++ b/src/js/foreground/view/stream/activeStreamItemView.js @@ -1,4 +1,4 @@ -define(function (require) { +define(function(require) { 'use strict'; var TimeArea = require('foreground/model/timeArea'); @@ -11,7 +11,7 @@ className: 'activeStreamItem', template: _.template(ActiveStreamItemTemplate), - regions: function () { + regions: function() { return { timeAreaRegion: '#' + this.id + '-timeAreaRegion' }; @@ -25,17 +25,17 @@ instant: false, - initialize: function (options) { + initialize: function(options) { this.instant = options.instant; }, - onRender: function () { + onRender: function() { if (this.instant) { this.$el.addClass('is-instant'); } else { this.$el.on('webkitTransitionEnd', this._onTransitionInComplete.bind(this)); } - + this.timeAreaRegion.show(new TimeAreaView({ model: new TimeArea({ totalTime: this.model.get('song').get('duration') @@ -43,13 +43,13 @@ })); }, - onAttach: function () { + onAttach: function() { // If the view is shown instantly then there is no transition to wait for, so announce shown immediately. if (this.instant) { this.$el.addClass('is-visible'); Streamus.channels.activeStreamItemArea.vent.trigger('visible'); } else { - _.defer(function () { + _.defer(function() { this.$el.addClass('is-visible'); }.bind(this)); @@ -57,19 +57,19 @@ } }, - hide: function () { + hide: function() { this.$el.on('webkitTransitionEnd', this._onTransitionOutComplete.bind(this)); this.$el.removeClass('is-instant is-visible'); }, - _onTransitionInComplete: function (event) { + _onTransitionInComplete: function(event) { if (event.target === event.currentTarget) { this.$el.off('webkitTransitionEnd'); Streamus.channels.activeStreamItemArea.vent.trigger('visible'); } }, - _onTransitionOutComplete: function (event) { + _onTransitionOutComplete: function(event) { if (event.target === event.currentTarget) { this.$el.off('webkitTransitionEnd'); Streamus.channels.activeStreamItemArea.vent.trigger('is-hidden'); diff --git a/src/js/foreground/view/stream/clearStreamButtonView.js b/src/js/foreground/view/stream/clearStreamButtonView.js index a8d850fa..a93121b9 100644 --- a/src/js/foreground/view/stream/clearStreamButtonView.js +++ b/src/js/foreground/view/stream/clearStreamButtonView.js @@ -1,4 +1,4 @@ -define(function (require) { +define(function(require) { 'use strict'; var Tooltip = require('foreground/view/behavior/tooltip'); @@ -13,40 +13,40 @@ templateHelpers: { deleteIcon: _.template(DeleteIconTemplate)() }, - + events: { 'click': '_onClick', }, - + modelEvents: { 'change:enabled': '_onChangeEnabled' }, - + behaviors: { Tooltip: { behaviorClass: Tooltip } }, - + onRender: function() { this._setState(this.model.get('enabled'), this.model.getStateMessage()); }, - - _onClick: function () { + + _onClick: function() { if (this.model.get('enabled')) { this._showClearStreamDialog(); } }, - - _onChangeEnabled: function (model, enabled) { + + _onChangeEnabled: function(model, enabled) { this._setState(enabled, model.getStateMessage()); }, - - _setState: function (enabled, stateMessage) { + + _setState: function(enabled, stateMessage) { this.$el.toggleClass('is-disabled', !enabled).attr('title', stateMessage); }, - - _showClearStreamDialog: function () { + + _showClearStreamDialog: function() { var streamItems = this.model.get('streamItems'); // When deleting only a single StreamItem it is not necessary to show a dialog because it's not a very dangerous action. diff --git a/src/js/foreground/view/stream/radioButtonView.js b/src/js/foreground/view/stream/radioButtonView.js index fa131644..b557105d 100644 --- a/src/js/foreground/view/stream/radioButtonView.js +++ b/src/js/foreground/view/stream/radioButtonView.js @@ -1,4 +1,4 @@ -define(function (require) { +define(function(require) { 'use strict'; var Tooltip = require('foreground/view/behavior/tooltip'); @@ -27,19 +27,19 @@ } }, - onRender: function () { + onRender: function() { this._setState(this.model.get('enabled'), this.model.getStateMessage()); }, - _onClick: function () { + _onClick: function() { this.model.toggleEnabled(); }, - _onChangeEnabled: function (model, enabled) { + _onChangeEnabled: function(model, enabled) { this._setState(enabled, model.getStateMessage()); }, - _setState: function (enabled, stateMessage) { + _setState: function(enabled, stateMessage) { this.$el.toggleClass('is-enabled', enabled).attr('title', stateMessage); } }); diff --git a/src/js/foreground/view/stream/repeatButtonView.js b/src/js/foreground/view/stream/repeatButtonView.js index a0c13dca..1ba08693 100644 --- a/src/js/foreground/view/stream/repeatButtonView.js +++ b/src/js/foreground/view/stream/repeatButtonView.js @@ -1,4 +1,4 @@ -define(function (require) { +define(function(require) { 'use strict'; var RepeatButtonState = require('common/enum/repeatButtonState'); @@ -11,46 +11,46 @@ id: 'repeatButton', className: 'button button--icon button--icon--secondary button--medium js-tooltipable', template: _.template(RepeatButtonTemplate), - + templateHelpers: { repeatIcon: _.template(RepeatIconTemplate)(), repeatOneIcon: _.template(RepeatOneIconTemplate)() }, - - ui: function () { + + ui: function() { return { repeatIcon: '#' + this.id + '-repeatIcon', repeatOneIcon: '#' + this.id + '-repeatOneIcon' }; }, - + events: { 'click': '_onClick' }, - + modelEvents: { 'change:state': '_onChangeState' }, - + behaviors: { Tooltip: { behaviorClass: Tooltip } }, - onRender: function () { + onRender: function() { this._setState(this.model.get('state'), this.model.getStateMessage()); }, - - _onClick: function () { + + _onClick: function() { this.model.toggleRepeatState(); }, - + _onChangeState: function(model, state) { this._setState(state, model.getStateMessage()); }, - - _setState: function (state, stateMessage) { + + _setState: function(state, stateMessage) { // The button is considered enabled if it is anything but off. var enabled = state !== RepeatButtonState.Off; diff --git a/src/js/foreground/view/stream/saveStreamButtonView.js b/src/js/foreground/view/stream/saveStreamButtonView.js index a10bca62..2f906a78 100644 --- a/src/js/foreground/view/stream/saveStreamButtonView.js +++ b/src/js/foreground/view/stream/saveStreamButtonView.js @@ -1,4 +1,4 @@ -define(function (require) { +define(function(require) { 'use strict'; var Tooltip = require('foreground/view/behavior/tooltip'); @@ -20,32 +20,32 @@ modelEvents: { 'change:enabled': '_onChangeEnabled' }, - + behaviors: { Tooltip: { behaviorClass: Tooltip } }, - onRender: function () { + onRender: function() { this._setState(this.model.get('enabled'), this.model.getStateMessage()); }, - _onClick: function () { + _onClick: function() { if (this.model.get('enabled')) { this._showSaveSongsSimpleMenu(this.model.get('streamItems').pluck('song')); } }, - _onChangeEnabled: function (model, enabled) { + _onChangeEnabled: function(model, enabled) { this._setState(enabled, model.getStateMessage()); }, - _setState: function (enabled, stateMessage) { + _setState: function(enabled, stateMessage) { this.$el.toggleClass('is-disabled', !enabled).attr('title', stateMessage); }, - _showSaveSongsSimpleMenu: function (songs) { + _showSaveSongsSimpleMenu: function(songs) { var offset = this.$el.offset(); Streamus.channels.saveSongs.commands.trigger('show:simpleMenu', { diff --git a/src/js/foreground/view/stream/shuffleButtonView.js b/src/js/foreground/view/stream/shuffleButtonView.js index 2a247036..f1999e14 100644 --- a/src/js/foreground/view/stream/shuffleButtonView.js +++ b/src/js/foreground/view/stream/shuffleButtonView.js @@ -1,4 +1,4 @@ -define(function (require) { +define(function(require) { 'use strict'; var Tooltip = require('foreground/view/behavior/tooltip'); @@ -12,34 +12,34 @@ templateHelpers: { shuffleIcon: _.template(ShuffleIconTemplate)() }, - + events: { 'click': '_onClick' }, - + modelEvents: { 'change:enabled': '_onChangeEnabled' }, - + behaviors: { Tooltip: { behaviorClass: Tooltip } }, - + onRender: function() { this._setState(this.model.get('enabled'), this.model.getStateMessage()); }, - - _onClick: function () { + + _onClick: function() { this.model.toggleEnabled(); }, - - _onChangeEnabled: function (model, enabled) { + + _onChangeEnabled: function(model, enabled) { this._setState(enabled, model.getStateMessage()); }, - - _setState: function (enabled, stateMessage) { + + _setState: function(enabled, stateMessage) { this.$el.toggleClass('is-enabled', enabled).attr('title', stateMessage); } }); diff --git a/src/js/foreground/view/stream/streamItemView.js b/src/js/foreground/view/stream/streamItemView.js index aa0a8cf6..25757560 100644 --- a/src/js/foreground/view/stream/streamItemView.js +++ b/src/js/foreground/view/stream/streamItemView.js @@ -1,4 +1,4 @@ -define(function (require) { +define(function(require) { 'use strict'; var ListItemView = require('foreground/view/listItemView'); @@ -15,33 +15,33 @@ events: _.extend({}, ListItemView.prototype.events, { 'dblclick': '_onDblClick' }), - + modelEvents: { 'change:id': '_onChangeId', 'change:active': '_onChangeActive' }, - + behaviors: _.extend({}, ListItemView.prototype.behaviors, { ListItemMultiSelect: { behaviorClass: ListItemMultiSelect } }), - + buttonViews: [PlaySongButtonView, SaveSongButtonView, DeleteSongButtonView], - + player: null, playPauseButton: null, - - initialize: function () { + + initialize: function() { this.player = Streamus.backgroundPage.player; this.playPauseButton = Streamus.backgroundPage.playPauseButton; }, - onRender: function () { + onRender: function() { this._setActiveClass(this.model.get('active')); }, - - showContextMenu: function () { + + showContextMenu: function() { Streamus.channels.contextMenu.commands.trigger('reset:items', [{ text: chrome.i18n.getMessage('copyUrl'), onClick: this._copyUrl.bind(this) @@ -53,8 +53,8 @@ onClick: this._watchOnYouTube.bind(this) }]); }, - - _onDblClick: function () { + + _onDblClick: function() { if (this.model.get('active')) { this.playPauseButton.tryTogglePlayerState(); } else { @@ -62,33 +62,33 @@ this.model.save({ active: true }); } }, - - _onChangeId: function (model, id) { + + _onChangeId: function(model, id) { // I'm not 100% positive I need to set both here, but .data() is cached in jQuery and .attr() is on the view, so seems good to keep both up to date. this.$el.data('id', id).attr('id', id); }, - - _onChangeActive: function (model, active) { + + _onChangeActive: function(model, active) { this._setActiveClass(active); }, // Force the view to reflect the model's active class. It's important to do this here, and not through render always, because // render will cause the lazy-loaded image to be reset. - _setActiveClass: function (active) { + _setActiveClass: function(active) { this.$el.toggleClass('is-active', active); }, - _copyUrl: function () { + _copyUrl: function() { this.model.get('song').copyUrl(); }, - _copyTitleAndUrl: function () { + _copyTitleAndUrl: function() { this.model.get('song').copyTitleAndUrl(); }, - _watchOnYouTube: function () { + _watchOnYouTube: function() { this.player.watchInTab(this.model.get('song')); } }); return StreamItemView; -}); +}); \ No newline at end of file diff --git a/src/js/foreground/view/stream/streamItemsView.js b/src/js/foreground/view/stream/streamItemsView.js index ce7e2790..7592d18c 100644 --- a/src/js/foreground/view/stream/streamItemsView.js +++ b/src/js/foreground/view/stream/streamItemsView.js @@ -1,4 +1,4 @@ -define(function (require) { +define(function(require) { 'use strict'; var ListItemType = require('common/enum/listItemType'); @@ -16,7 +16,7 @@ childViewContainer: '@ui.childContainer', childView: StreamItemView, childViewType: ListItemType.StreamItem, - childViewOptions: function () { + childViewOptions: function() { return { type: this.childViewType, parentId: this.ui.childContainer[0].id @@ -24,13 +24,13 @@ }, // Overwrite resortView to only render children as expected - resortView: function () { + resortView: function() { this._renderChildren(); }, template: _.template(StreamItemsTemplate), - ui: function () { + ui: function() { return { // TODO: This has to be named generic for Sortable/SlidingRender behaviors. See issue here: https://github.com/marionettejs/backbone.marionette/issues/1909 childContainer: '#' + this.id + '-listItems' @@ -54,20 +54,20 @@ behaviorClass: Tooltip } }, - + activeStreamItemAreaEvents: { 'beforeVisible': '_onActiveStreamItemAreaBeforeVisible', 'visible': '_onActiveStreamItemAreaVisible', 'hidden': '_onActiveStreamItemAreaHidden' }, - initialize: function () { + initialize: function() { this.bindEntityEvents(Streamus.channels.activeStreamItemArea.vent, this.activeStreamItemAreaEvents); }, - - _onActiveStreamItemAreaBeforeVisible: function () { + + _onActiveStreamItemAreaBeforeVisible: function() { // ChildContainer's height isn't updated until ItemViews inside it are rendered which is just after the ActiveStreamItemArea is about to be visible. - setTimeout(function () { + setTimeout(function() { // If the content isn't going to have a scrollbar later then add a class to ensure that // a scrollbar doesn't shown for a second as the content transitions in. if (this.ui.childContainer.height() <= this.$el.height()) { @@ -76,12 +76,12 @@ }.bind(this)); }, - _onActiveStreamItemAreaVisible: function () { + _onActiveStreamItemAreaVisible: function() { this.ui.childContainer.removeClass('is-heightRestricted'); this.triggerMethod('ListHeightUpdated'); }, - _onActiveStreamItemAreaHidden: function () { + _onActiveStreamItemAreaHidden: function() { this.triggerMethod('ListHeightUpdated'); } }); diff --git a/src/js/foreground/view/stream/streamRegion.js b/src/js/foreground/view/stream/streamRegion.js index 85746988..84b5eb5d 100644 --- a/src/js/foreground/view/stream/streamRegion.js +++ b/src/js/foreground/view/stream/streamRegion.js @@ -1,14 +1,14 @@ -define(function (require) { +define(function(require) { 'use strict'; var StreamView = require('foreground/view/stream/streamView'); - + var StreamRegion = Marionette.Region.extend({ initialize: function() { this.listenTo(Streamus.channels.foregroundArea.vent, 'rendered', this._onForegroundAreaRendered); }, - - _onForegroundAreaRendered: function () { + + _onForegroundAreaRendered: function() { this.show(new StreamView({ model: Streamus.backgroundPage.stream })); diff --git a/src/js/foreground/view/stream/streamView.js b/src/js/foreground/view/stream/streamView.js index 04320733..0a20ca46 100644 --- a/src/js/foreground/view/stream/streamView.js +++ b/src/js/foreground/view/stream/streamView.js @@ -1,4 +1,4 @@ -define(function (require) { +define(function(require) { 'use strict'; var ClearStreamButton = require('foreground/model/clearStreamButton'); @@ -11,13 +11,13 @@ var ShuffleButtonView = require('foreground/view/stream/shuffleButtonView'); var StreamItemsView = require('foreground/view/stream/streamItemsView'); var StreamTemplate = require('text!template/stream/stream.html'); - + var StreamView = Marionette.LayoutView.extend({ id: 'stream', className: 'flexColumn', template: _.template(StreamTemplate), - - templateHelpers: function () { + + templateHelpers: function() { return { viewId: this.id, emptyMessage: chrome.i18n.getMessage('streamEmpty'), @@ -26,7 +26,7 @@ }; }, - regions: function () { + regions: function() { return { clearStreamButtonRegion: '#' + this.id + '-clearStreamButtonRegion', radioButtonRegion: '#' + this.id + '-radioButtonRegion', @@ -37,35 +37,35 @@ streamItemsRegion: '#' + this.id + '-streamItemsRegion' }; }, - - ui: function () { + + ui: function() { return { emptyMessage: '#' + this.id + '-emptyMessage', showSearchLink: '#' + this.id + '-showSearchLink' }; }, - + events: { 'click @ui.showSearchLink': '_onClickShowSearchLink' }, - + modelEvents: { 'change:activeItem': '_onChangeActiveItem' }, - + streamItemsEvents: { 'add': '_onStreamItemsAdd', 'remove': '_onStreamItemsRemove', 'reset': '_onStreamItemsReset' }, - - initialize: function () { + + initialize: function() { this.bindEntityEvents(this.model.get('items'), this.streamItemsEvents); }, - - onRender: function () { + + onRender: function() { this._setState(this.model.get('items').isEmpty()); - + this.streamItemsRegion.show(new StreamItemsView({ collection: this.model.get('items') })); @@ -87,7 +87,7 @@ streamItems: this.model.get('items') }) })); - + this.saveStreamButtonRegion.show(new SaveStreamButtonView({ model: new SaveStreamButton({ streamItems: this.model.get('items'), @@ -100,12 +100,12 @@ this._showActiveStreamItem(activeItem, true); } }, - - _onClickShowSearchLink: function () { + + _onClickShowSearchLink: function() { this._showSearch(); }, - _onChangeActiveItem: function (model, activeItem) { + _onChangeActiveItem: function(model, activeItem) { if (activeItem === null) { this.activeStreamItemRegion.currentView.hide(); } else { @@ -114,29 +114,29 @@ this._showActiveStreamItem(activeItem, instant); } }, - - _onStreamItemsAdd: function () { + + _onStreamItemsAdd: function() { this._setState(false); }, - _onStreamItemsRemove: function (model, collection) { + _onStreamItemsRemove: function(model, collection) { this._setState(collection.isEmpty()); }, - _onStreamItemsReset: function (collection) { + _onStreamItemsReset: function(collection) { this._setState(collection.isEmpty()); }, // Hide the empty message if there is anything in the collection - _setState: function (collectionEmpty) { + _setState: function(collectionEmpty) { this.ui.emptyMessage.toggleClass('is-hidden', !collectionEmpty); }, - _showSearch: function () { + _showSearch: function() { Streamus.channels.searchArea.commands.trigger('show:search'); }, - - _showActiveStreamItem: function (activeStreamItem, instant) { + + _showActiveStreamItem: function(activeStreamItem, instant) { this.activeStreamItemRegion.show(new ActiveStreamItemView({ model: activeStreamItem, instant: instant diff --git a/src/js/foreground/view/stream/timeAreaView.js b/src/js/foreground/view/stream/timeAreaView.js index d333c40b..2c65aa6e 100644 --- a/src/js/foreground/view/stream/timeAreaView.js +++ b/src/js/foreground/view/stream/timeAreaView.js @@ -1,4 +1,4 @@ -define(function (require) { +define(function(require) { 'use strict'; var PlayerState = require('common/enum/playerState'); @@ -10,7 +10,7 @@ id: 'timeArea', className: 'timeArea', template: _.template(TimeAreaTemplate), - templateHelpers: function () { + templateHelpers: function() { return { totalTimeMessage: chrome.i18n.getMessage('totalTime'), // TODO: I don't want totalTime written to localStorage which means I exclude it from the JSON serialization, but I need it in my template.. @@ -18,8 +18,8 @@ prettyTotalTime: Utility.prettyPrintTime(this.model.get('totalTime')) }; }, - - ui: function () { + + ui: function() { return { timeProgress: '#' + this.id + '-timeProgress', timeRange: '#' + this.id + '-timeRange', @@ -40,33 +40,33 @@ behaviorClass: Tooltip } }, - + streamItems: null, player: null, - + playerEvents: { 'change:currentTime': '_onPlayerChangeCurrentTime', 'change:state': '_onPlayerChangeState' }, - - initialize: function () { + + initialize: function() { this.streamItems = Streamus.backgroundPage.stream.get('items'); this.player = Streamus.backgroundPage.player; - + this.bindEntityEvents(this.player, this.playerEvents); }, - onRender: function () { + onRender: function() { this._setCurrentTime(this.player.get('currentTime')); this._setElapsedTimeLabelTitle(this.model.get('showRemainingTime')); }, - + _onInputTimeRange: function() { this._updateTimeProgress(); }, // Allow the user to manual time change by click or scroll. - _onWheelTimeRange: function (event) { + _onWheelTimeRange: function(event) { var delta = event.originalEvent.wheelDeltaY / 120; var currentTime = parseInt(this.ui.timeRange.val()); @@ -74,39 +74,39 @@ this.player.seekTo(currentTime + delta); }, - - _onMouseDownTimeRange: function (event) { + + _onMouseDownTimeRange: function(event) { // 1 is primary mouse button, usually left if (event.which === 1) { this._startSeeking(); } }, - - _onMouseUpTimeRange: function (event) { + + _onMouseUpTimeRange: function(event) { // 1 is primary mouse button, usually left // It's important to check seeking here because onMouseUp can run even if onMouseDown did not fire. if (event.which === 1 && this.model.get('seeking')) { this._seekToCurrentTime(); } }, - - _onClickElapsedTimeLabel: function () { + + _onClickElapsedTimeLabel: function() { this._toggleShowRemainingTime(); }, - - _onPlayerChangeState: function () { + + _onPlayerChangeState: function() { this._stopSeeking(); }, - - _onPlayerChangeCurrentTime: function (model, currentTime) { + + _onPlayerChangeCurrentTime: function(model, currentTime) { this._updateCurrentTime(currentTime); }, - _startSeeking: function () { + _startSeeking: function() { this.model.set('seeking', true); }, - - _stopSeeking: function () { + + _stopSeeking: function() { // Seek is known to have finished when the player announces a state change that isn't buffering / unstarted. var state = this.player.get('state'); @@ -115,13 +115,13 @@ } }, - _seekToCurrentTime: function () { + _seekToCurrentTime: function() { // Bind to progressBar mouse-up to support dragging as well as clicking. // I don't want to send a message until drag ends, so mouseup works nicely. var currentTime = parseInt(this.ui.timeRange.val()); this.player.seekTo(currentTime); }, - + _toggleShowRemainingTime: function() { var showRemainingTime = this.model.get('showRemainingTime'); // Toggle showRemainingTime and then read the new state and apply it. @@ -131,18 +131,18 @@ this._updateTimeProgress(); }, - - _setElapsedTimeLabelTitle: function (showRemainingTime) { + + _setElapsedTimeLabelTitle: function(showRemainingTime) { var title = chrome.i18n.getMessage(showRemainingTime ? 'remainingTime' : 'elapsedTime'); this.ui.elapsedTimeLabel.attr('title', title); }, - _setCurrentTime: function (currentTime) { + _setCurrentTime: function(currentTime) { this.ui.timeRange.val(currentTime); this._updateTimeProgress(); }, - _updateCurrentTime: function (currentTime) { + _updateCurrentTime: function(currentTime) { var seeking = this.model.get('seeking'); // If the time changes while user is seeking then do not update the view because user's input should override it. if (!seeking) { @@ -154,14 +154,14 @@ // Keep separate from render because render is based on the player's values and updateProgress is based on the progress bar's values. // This is an important distinction because when the user is dragging the progress bar -- the player won't be updating -- but progress bar // values need to be re-rendered. - _updateTimeProgress: function () { + _updateTimeProgress: function() { var currentTime = parseInt(this.ui.timeRange.val()); var totalTime = parseInt(this.ui.timeRange.prop('max')); // Don't divide by 0. var progressPercent = totalTime === 0 ? 0 : currentTime * 100 / totalTime; this.ui.timeProgress.width(progressPercent + '%'); - + if (this.model.get('showRemainingTime')) { // Calculate the remaining time from the current time and show that instead. var remainingTime = totalTime - currentTime; diff --git a/src/js/inject/beatportInject.js b/src/js/inject/beatportInject.js index bd9032a2..45c1444b 100644 --- a/src/js/inject/beatportInject.js +++ b/src/js/inject/beatportInject.js @@ -1,14 +1,14 @@ // This code runs on pro.beatport.com domains. -$(function () { +$(function() { 'use strict'; - + var isCodeInjected = false; var injectData = { canEnhance: false }; // Find out whether Streamus settings are configured to allow modifying Beatport's HTML. - chrome.runtime.sendMessage({ method: 'getBeatportInjectData' }, function (beatportInjectData) { + chrome.runtime.sendMessage({ method: 'getBeatportInjectData' }, function(beatportInjectData) { injectData = beatportInjectData; if (injectData.canEnhance) { @@ -16,9 +16,9 @@ $(function () { isCodeInjected = true; } }); - + // Listen for Streamus settings changing and toggle injected code. - chrome.runtime.onMessage.addListener(function (request) { + chrome.runtime.onMessage.addListener(function(request) { if (isCodeInjected) { if (request.event === 'enhance-off') { toggleInjectedCode(false); @@ -30,120 +30,122 @@ $(function () { isCodeInjected = true; } } - }); - - function toggleInjectedCode(enable) { - toggleStreamusIcons(enable); - toggleMonitorPageChange(enable); - } -}); - -// Pass enable: true in to enable Streamus icons. Pass enable: false in to disable Streamus icons and revert back to original Beatport functionality. -function toggleStreamusIcons(enable) { - // Work within a container because bucket-items are scattered throughout Beatport pages. - // Use class*= selector to keep query generic enough to be used across all Beatport pages. - var container = $('.bucket[class*=tracks]'); - - var tracks = container.find('.bucket-items .bucket-item'); - tracks.each(function () { - var button = $(this).find('[class*=track-play]'); - + }); + + // Pass enable: true in to enable Streamus icons. Pass enable: false in to disable Streamus icons and revert back to original Beatport functionality. + + function toggleStreamusIcons(enable) { + // Work within a container because bucket-items are scattered throughout Beatport pages. + // Use class*= selector to keep query generic enough to be used across all Beatport pages. + var container = $('.bucket[class*=tracks]'); + + var tracks = container.find('.bucket-items .bucket-item'); + tracks.each(function() { + var button = $(this).find('[class*=track-play]'); + + if (enable) { + // Figure out the information needed to find a song on YouTube. + // Query will look like "primaryTitle remix artist1 artist2" + var primaryTitle = $(this).find('[class*=track-primary-title]').text(); + var remix = $(this).find('[class*=track-remixed]').text(); + var artists = $(this).find('[class*=track-artists] a').map(function() { + return $(this).text(); + }).get().join(' '); + + var query = primaryTitle + ' ' + remix + ' ' + artists; + + // Decorate button to indicate it is Streamus-ified, cache query on the button so playAll can read it. + button.addClass('streamus-button'); + button.data('streamus-query', query); + // Namespace the click event so that it can be removed easily in the future. + button.on('click.streamus', function() { + chrome.runtime.sendMessage({ + method: 'searchAndStreamByQuery', + query: $(this).data('streamus-query') + }); + + // Return false to prevent Beatport from playing the track. + return false; + }); + } else { + button.removeClass('streamus-button'); + button.removeData('streamus-query'); + button.off('click.streamus'); + } + }); + + // Note that the play all button isn't necessarily within our tracks container depending on the page. + // For instance, Top 100 has it outside the container where as Top 10 has it within the container. + var playAllButton = $('.playable-play-all'); + if (enable) { - // Figure out the information needed to find a song on YouTube. - // Query will look like "primaryTitle remix artist1 artist2" - var primaryTitle = $(this).find('[class*=track-primary-title]').text(); - var remix = $(this).find('[class*=track-remixed]').text(); - var artists = $(this).find('[class*=track-artists] a').map(function () { - return $(this).text(); - }).get().join(' '); - - var query = primaryTitle + ' ' + remix + ' ' + artists; - - // Decorate button to indicate it is Streamus-ified, cache query on the button so playAll can read it. - button.addClass('streamus-button'); - button.data('streamus-query', query); + playAllButton.addClass('streamus-button'); // Namespace the click event so that it can be removed easily in the future. - button.on('click.streamus', function () { + playAllButton.on('click.streamus', function() { + // Grab all the stored queries from playTrack buttons. + var queries = tracks.find('.streamus-button').map(function() { + return $(this).data('streamus-query'); + }); + chrome.runtime.sendMessage({ - method: 'searchAndStreamByQuery', - query: $(this).data('streamus-query') + method: 'searchAndStreamByQueries', + queries: queries.toArray() }); - // Return false to prevent Beatport from playing the track. + // Return false to prevent Beatport from playing all tracks. return false; }); } else { - button.removeClass('streamus-button'); - button.removeData('streamus-query'); - button.off('click.streamus'); + playAllButton.removeClass('streamus-button'); + playAllButton.off('click.streamus'); } - }); - - // Note that the play all button isn't necessarily within our tracks container depending on the page. - // For instance, Top 100 has it outside the container where as Top 10 has it within the container. - var playAllButton = $('.playable-play-all'); - - if (enable) { - playAllButton.addClass('streamus-button'); - // Namespace the click event so that it can be removed easily in the future. - playAllButton.on('click.streamus', function () { - // Grab all the stored queries from playTrack buttons. - var queries = tracks.find('.streamus-button').map(function () { - return $(this).data('streamus-query'); - }); + } - chrome.runtime.sendMessage({ - method: 'searchAndStreamByQueries', - queries: queries.toArray() - }); + // When the user clicks a link on Beatport - the page doesn't reload since Beatport is a single-page application. + // So, monitor the URL for changes and, when detected, re-inject code. - // Return false to prevent Beatport from playing all tracks. - return false; - }); - } else { - playAllButton.removeClass('streamus-button'); - playAllButton.off('click.streamus'); - } -} - -// When the user clicks a link on Beatport - the page doesn't reload since Beatport is a single-page application. -// So, monitor the URL for changes and, when detected, re-inject code. -function toggleMonitorPageChange(enable) { - var checkUrlInterval = null; - - if (enable) { - var timeout = 10000; // Give it 10s to change before they'll need to refresh - var pollingFrequency = 1000; // Check every 1s - - // Filter out streamus links because they obviously can't take the user anywhere. - $(document).on('click.streamus', 'a[href]', function () { - var clickedLinkHref = $(this).attr('href'); - - // Stop any previous checks - clearInterval(checkUrlInterval); + function toggleMonitorPageChange(enable) { + var checkUrlInterval = null; - // Wait for browser to load and check occassionally until the URL matches or we give up. - checkUrlInterval = setInterval(function () { - var currentLocationHref = window.location.href; + if (enable) { + var timeout = 10000; // Give it 10s to change before they'll need to refresh + var pollingFrequency = 1000; // Check every 1s - // If they clicked something like /top-100, url will be beatport.com/top-100 so need to match both ways - var clickedRoutingLink = clickedLinkHref.charAt(0) === '/'; - var hrefContainsRoutingLink = clickedRoutingLink && currentLocationHref.indexOf(clickedLinkHref) !== -1; + // Filter out streamus links because they obviously can't take the user anywhere. + $(document).on('click.streamus', 'a[href]', function() { + var clickedLinkHref = $(this).attr('href'); - if (currentLocationHref == clickedLinkHref || hrefContainsRoutingLink) { - toggleStreamusIcons(true); - clearInterval(checkUrlInterval); - } + // Stop any previous checks + clearInterval(checkUrlInterval); - timeout -= pollingFrequency; + // Wait for browser to load and check occassionally until the URL matches or we give up. + checkUrlInterval = setInterval(function() { + var currentLocationHref = window.location.href; - if (timeout <= 0) { - clearInterval(checkUrlInterval); - } - }, pollingFrequency); - }); - } else { - $(document).off('click.streamus'); - clearInterval(checkUrlInterval); + // If they clicked something like /top-100, url will be beatport.com/top-100 so need to match both ways + var clickedRoutingLink = clickedLinkHref.charAt(0) === '/'; + var hrefContainsRoutingLink = clickedRoutingLink && currentLocationHref.indexOf(clickedLinkHref) !== -1; + + if (currentLocationHref == clickedLinkHref || hrefContainsRoutingLink) { + toggleStreamusIcons(true); + clearInterval(checkUrlInterval); + } + + timeout -= pollingFrequency; + + if (timeout <= 0) { + clearInterval(checkUrlInterval); + } + }, pollingFrequency); + }); + } else { + $(document).off('click.streamus'); + clearInterval(checkUrlInterval); + } + } + + function toggleInjectedCode(enable) { + toggleStreamusIcons(enable); + toggleMonitorPageChange(enable); } -} \ No newline at end of file +}); \ No newline at end of file diff --git a/src/js/inject/streamusInject.js b/src/js/inject/streamusInject.js deleted file mode 100644 index fc96260b..00000000 --- a/src/js/inject/streamusInject.js +++ /dev/null @@ -1,13 +0,0 @@ -// This code runs on all streamus.com domains. -$(function () { - 'use strict'; - - // Disable the install Streamus button when visiting the website after it has already been installed. - var installButton = $('#installButton'); - - if (installButton.length > 0) { - installButton - .attr('disabled', true) - .text(chrome.i18n.getMessage('installed')); - } -}); \ No newline at end of file diff --git a/src/js/inject/streamusShareInject.js b/src/js/inject/streamusShareInject.js deleted file mode 100644 index 85809630..00000000 --- a/src/js/inject/streamusShareInject.js +++ /dev/null @@ -1,34 +0,0 @@ -// This code only runs on the share.streamus.com sub-domain. -$(function () { - 'use strict'; - - var streamusNotInstalledWarning = $('.streamusNotInstalledWarning'); - - if (streamusNotInstalledWarning.length > 0) { - streamusNotInstalledWarning.remove(); - } - - var currentUrl = document.URL; - var urlSections = currentUrl.split('/'); - - // The last section of the URL should be the sharecode and the second to last section should be the indicator of playlist or stream. - var indicator = urlSections[urlSections.length - 3]; - var shareCodeShortId = urlSections[urlSections.length - 2]; - var urlFriendlyEntityTitle = urlSections[urlSections.length - 1]; - - if (indicator === 'playlist' && shareCodeShortId.length === 12 && urlFriendlyEntityTitle.length > 0) { - chrome.runtime.sendMessage({ - method: "addPlaylistByShareData", - shareCodeShortId: shareCodeShortId, - urlFriendlyEntityTitle: urlFriendlyEntityTitle - }, function (response) { - var isSuccess = response.result === 'success'; - var resultText = chrome.i18n.getMessage(isSuccess ? 'playlistCreated' : 'errorEncountered'); - - $('hr').last().before($('

', { - text: resultText, - 'class': 'jumbotron' - })); - }); - } -}); \ No newline at end of file diff --git a/src/js/inject/youTubeIFrameInject.js b/src/js/inject/youTubeIFrameInject.js index 2b1e6259..9910409f 100644 --- a/src/js/inject/youTubeIFrameInject.js +++ b/src/js/inject/youTubeIFrameInject.js @@ -1,16 +1,16 @@ var errorsEncountered = ''; -window.onerror = function (error) { +window.onerror = function(error) { errorsEncountered += error + ' '; }; -$(function () { +$(function() { // Only run against our intended iFrame -- not embedded YouTube iframes on other pages. if (window.name === 'youtube-player') { var youTubeIFrameConnectRequestPort = chrome.runtime.connect({ name: 'youTubeIFrameConnectRequest' }); - var monitorVideoStream = function () { + var monitorVideoStream = function() { youTubeIFrameConnectRequestPort.postMessage({ flashLoaded: false }); @@ -23,7 +23,7 @@ $(function () { // TimeUpdate has awesome resolution, but we only display to the nearest second. // So, round currentTime and only send a message when the rounded value has changed, not the actual value. - videoStream.on('timeupdate', function () { + videoStream.on('timeupdate', function() { var currentTime = Math.ceil(this.currentTime); if (currentTime !== lastPostedTime) { @@ -35,13 +35,13 @@ $(function () { } }); - videoStream.on('seeking', function () { + videoStream.on('seeking', function() { youTubeIFrameConnectRequestPort.postMessage({ seeking: true }); }); - videoStream.on('seeked', function () { + videoStream.on('seeked', function() { youTubeIFrameConnectRequestPort.postMessage({ seeking: false }); @@ -61,7 +61,7 @@ $(function () { flashLoaded: true }); } else { - var findVideoStreamInterval = setInterval(function () { + var findVideoStreamInterval = setInterval(function() { if (triesRemaining <= 0) { clearInterval(findVideoStreamInterval); @@ -75,8 +75,7 @@ $(function () { if (videoStream.length > 0) { clearInterval(findVideoStreamInterval); monitorVideoStream(); - } - else if (flashStream.length > 0) { + } else if (flashStream.length > 0) { youTubeIFrameConnectRequestPort.postMessage({ flashLoaded: true }); diff --git a/src/js/inject/youTubeInject.js b/src/js/inject/youTubeInject.js index e9691e15..5740732e 100644 --- a/src/js/inject/youTubeInject.js +++ b/src/js/inject/youTubeInject.js @@ -1,210 +1,209 @@ // This code runs on YouTube pages. -$(function () { +$(function() { 'use strict'; - - var enhanceYouTube = false; - var injectData = { - canEnhance: false, - SyncActionType: null - }; - - chrome.runtime.sendMessage({ method: 'getYouTubeInjectData' }, function (youTubeInjectData) { - injectData = youTubeInjectData; - - if (injectData.canEnhance) { - // This code handles the fact that when you navigate from a YouTube search results list to a video - // the page does not reload because they use AJAX to load the video page. - var isFirstLoad = true; - var waitingForLoad = false; - - var observer = new window.WebKitMutationObserver(function (mutations) { - if (isFirstLoad) { - isFirstLoad = false; - injectHtml(); - } - else { - var isPageLoaded = mutations[0].target.classList.contains('page-loaded'); - - if (!waitingForLoad && !isPageLoaded) { - waitingForLoad = true; - } - else if (waitingForLoad && isPageLoaded) { - injectHtml(); - waitingForLoad = false; - } - } - }); - - observer.observe(document.querySelector("body"), { - attributes: true, - attributeFilter: ["class"] - }); - - enhanceYouTube = true; - } - }); - - chrome.runtime.onMessage.addListener(function (request) { - if (enhanceYouTube) { - switch (request.event) { - case injectData.SyncActionType.Added: - var playlistOption = $('