Merge branch 'development'

This commit is contained in:
PrestonN 2018-08-05 22:56:33 -04:00
commit 142b30699a
49 changed files with 14811 additions and 2543 deletions

View File

@ -3,7 +3,7 @@
"development": {
"application/javascript": {
"presets": [
["env", { "targets": { "electron": "1.4" } }],
["env", { "targets": { "electron-renderer": "1.4" } }],
"react"
],
"plugins": ["transform-async-to-generator"],
@ -13,7 +13,7 @@
"production": {
"application/javascript": {
"presets": [
["env", { "targets": { "electron": "1.4" } }],
["env", { "targets": { "electron-renderer": "1.4" } }],
"react"
],
"plugins": ["transform-async-to-generator"],

1
.gitignore vendored
View File

@ -5,6 +5,7 @@ subscriptions\.db
.vscode/
.eslintrc*
*.db
*.code-workspace
electron-packager/win32-x64/FreeTube-win32-x64/

49
.jsbeautifyrc Normal file
View File

@ -0,0 +1,49 @@
{
"html": {
"allowed_file_extensions": ["htm", "html", "xhtml", "shtml", "xml", "svg", "dust"],
"brace_style": "collapse",
"end_with_newline": true,
"indent_char": " ",
"indent_handlebars": true,
"indent_inner_html": false,
"indent_scripts": "keep",
"indent_size": 4,
"max_preserve_newlines": 10,
"preserve_newlines": true,
"unformatted": ["a", "span", "img", "code", "pre", "sub", "sup", "em", "strong", "b", "i", "u", "strike", "big", "small", "pre", "h1", "h2", "h3", "h4", "h5", "h6"], // List of tags that should not be reformatted
"wrap_line_length": 0
},
"css": {
"allowed_file_extensions": ["css", "scss", "sass", "less"],
"end_with_newline": true,
"indent_char": " ",
"indent_size": 4,
"newline_between_rules": true,
"selector_separator": " ",
"selector_separator_newline": true,
"preserve_newlines": true,
"max_preserve_newlines": 10
},
"js": {
"allowed_file_extensions": ["js", "json", "jshintrc", "jsbeautifyrc"],
"brace_style": "collapse",
"break_chained_methods": false,
"e4x": false,
"end_with_newline": false,
"indent_char": " ",
"indent_level": 0,
"indent_size": 4,
"indent_with_tabs": false,
"jslint_happy": true,
"keep_array_indentation": false,
"keep_function_indentation": false,
"max_preserve_newlines": 0,
"preserve_newlines": true,
"space_after_anon_function": true,
"space_before_conditional": true,
"space_in_empty_paren": false,
"space_in_paren": false,
"unescape_strings": false,
"wrap_line_length": 0
}
}

95
locales/es.json Normal file
View File

@ -0,0 +1,95 @@
{
"File": "Archivo",
"Quit": "Salir",
"Edit": "Editar",
"Undo": "Deshacer",
"Redo": "Rehacer",
"Cut": "Cortar",
"Copy": "Copiar",
"Paste": "Pegar",
"Delete": "Eliminar",
"Select all": "Seleccionar todo",
"View": "Ver",
"Reload": "Recargar",
"Force Reload": "Forzar Recarga",
"Toggle Developer Tools": "Herramientas para desarrolladores",
"Actual size": "Tamaño real",
"Zoom in": "Aumentar zoom",
"Zoom out": "Reducir zoom",
"Toggle fullscreen": "Cambiar a pantalla completa",
"Window": "Ventana",
"Minimize": "Minimizar",
"Close": "Cerrar",
"FreeTube": "FreeTube",
"Subscriptions": "Suscripciones",
"Featured": "Destacados",
"Most Popular": "Más Popular",
"Saved": "Guardados",
"Playlists": "Listas de Reproducción",
"History": "Historial",
"Settings": "Ajustes",
"About": "Acerca de",
"Search / Go to URL": "Buscar / Ir a la URL",
"Search Results": "Resultados de búsqueda",
"Subscriber": "Suscriptor",
"Subscriber": "Suscriptores",
"Video": "Vídeo",
"Videos": "Vídeos",
"View Full Playlist": "Ver Lista de Reproducción completa",
"Live Now": "En vivo ahora",
"Fetch more results": "Obtener más resultados",
"Fetching results. Please wait": "Obteniendo más resultados. Por favor espere",
"Latest Subscriptions": "Últimas Suscripciones",
"Save Video": "Guardar Vídeo",
"Remove Saved Video": "Eliminar Vídeo Guardado",
"Open in YouTube": "Abrir en YouTube",
"Copy YouTube Link": "Copiar Enlace a YouTube",
"Open in HookTube": "Abrir en HookTube",
"Copy HookTube Link": "Copiar Enlace a HookTube",
"URL has been copied to the clipboard": "La URL ha sido copiada al portapapeles",
"Found valid URL for 480p, but returned a 404. Video type might be available in the future.": "URL para 480p encontrada, pero devolvió un 404. El tipo de vídeo podría estar disponible en el futuro.",
"Save": "Guardar",
"Mini Player": "Mini Reproductor",
"View": "Reproducción",
"Views": "Reproducciones",
"Subscribe": "Suscribirse",
"Unsubscribe": "Desuscribirse",
"Published on": "Publicado el",
"Jan": "ene",
"Feb": "feb",
"Mar": "mar",
"Apr": "abr",
"May": "may",
"Jun": "jun",
"Jul": "jul",
"Aug": "ago",
"Sep": "sep",
"Oct": "oct",
"Nov": "nov",
"Dec": "dic",
"Show Comments": "Ver Comentarios",
"Max of 100": "Máximo de 100",
"Recommendations": "Recomendaciones",
"Latest Subscriptions": "Últimas Suscripciones",
"Getting Subscriptions. Please wait...": "Obteniendo Suscripciones. Por favor espere...",
"Your Subscription list is currently empty. Start adding subscriptions to see them here.": "Tu lista de Suscripción está actualmente vacía. Empieza añadiendo suscripciones para verlas ahí.",
"Saved Videos": "Videos Guardados",
"Watch History": "Ver Historial",
"API Key": "API Key",
"Set API Key: Leave blank to use default": "Establecer API Key: Dejar en blanco para usar la de por defecto",
"Use Dark Theme": "Usar Tema Oscuro",
"Import Subscriptions": "Importar Suscripción",
"Export Subscriptions": "Exportar Suscripción",
"Clear History": "Limpiar Historial",
"Are you sure you want to delete your history?": "¿Estás seguro de que quieres eliminar tu historial?",
"Clear Saved Videos": "Limpiar Vídeos Guardados",
"Are you sure you want to remove all saved videos?": "¿Estás seguro de que quieres eliminar todos los vídeos guardados?",
"Clear Subscriptions": "Limpiar Suscripciones",
"Are you sure you want to remove all subscriptions?": "¿Estás seguro de que quieres eliminar todas las suscripciones?",
"Save Settings": "Guardar Ajustes",
"Yes": "Sí",
"No": "No",
"Beta": "Beta",
"This software is FOSS and released under the GNU Public License v3+.": "Este software es FOSS y liberado bajo la licencia GNU Public License v3+.",
"Found a bug? Want to suggest a feature? Want to help out? Check out our GitHub page. Pull requests are welcome.": "¿Encontraste un bug? ¿Quieres sugerir una nueva característica? ¿Quieres ayudar? Visita nuestra página de Github. Las contribuciones son bienvenidas"
}

398
package-lock.json generated
View File

@ -1,6 +1,6 @@
{
"name": "FreeTube",
"version": "0.3.0",
"version": "0.3.2",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@ -78,9 +78,7 @@
"abab": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/abab/-/abab-1.0.4.tgz",
"integrity": "sha1-X6rZwsB/YN12dw9xzwJbYqY8/U4=",
"dev": true,
"optional": true
"integrity": "sha1-X6rZwsB/YN12dw9xzwJbYqY8/U4="
},
"abbrev": {
"version": "1.1.1",
@ -163,6 +161,11 @@
"integrity": "sha512-UgAb8H9D41AQnu/PbWlCofQVcnV4Gs2bBJi9eZPxfU/hgglFh3SMDMENRIqdr7H6XFnXdoknctFByVsCOotTVw==",
"dev": true
},
"ansi-font": {
"version": "0.0.2",
"resolved": "https://registry.npmjs.org/ansi-font/-/ansi-font-0.0.2.tgz",
"integrity": "sha1-iQMBvVhBRi/TnAt3Ca/R9SUUMzE="
},
"ansi-regex": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
@ -355,6 +358,11 @@
"sprintf-js": "~1.0.2"
}
},
"array-equal": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/array-equal/-/array-equal-1.0.0.tgz",
"integrity": "sha1-jCpe8kcv2ep0KwTHenUJO6J1fJM="
},
"array-find-index": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz",
@ -418,6 +426,11 @@
"integrity": "sha512-NW2cX8m1Q7KPA7a5M2ULQeZ2wR5qI5PAbw5L0UOMxdioVk9PMZ0h1TmyZEkPYrCvYjDlFICusOu1dlEKAAeXBw==",
"dev": true
},
"async-limiter": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz",
"integrity": "sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg=="
},
"asynckit": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
@ -1347,6 +1360,11 @@
"concat-map": "0.0.1"
}
},
"browser-process-hrtime": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-0.1.2.tgz",
"integrity": "sha1-Ql1opY00R/AqBKqJQYf86K+Le44="
},
"browserslist": {
"version": "1.7.7",
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-1.7.7.tgz",
@ -1712,6 +1730,63 @@
"htmlparser2": "~3.8.1",
"jsdom": "^7.0.2",
"lodash": "^4.1.0"
},
"dependencies": {
"cssstyle": {
"version": "0.2.37",
"resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-0.2.37.tgz",
"integrity": "sha1-VBCXI0yyUTyDzu06zdwn/yeYfVQ=",
"dev": true,
"optional": true,
"requires": {
"cssom": "0.3.x"
}
},
"jsdom": {
"version": "7.2.2",
"resolved": "https://registry.npmjs.org/jsdom/-/jsdom-7.2.2.tgz",
"integrity": "sha1-QLQCdwwr2iNGkJa+6Rq2deOx/G4=",
"dev": true,
"optional": true,
"requires": {
"abab": "^1.0.0",
"acorn": "^2.4.0",
"acorn-globals": "^1.0.4",
"cssom": ">= 0.3.0 < 0.4.0",
"cssstyle": ">= 0.2.29 < 0.3.0",
"escodegen": "^1.6.1",
"nwmatcher": ">= 1.3.7 < 2.0.0",
"parse5": "^1.5.1",
"request": "^2.55.0",
"sax": "^1.1.4",
"symbol-tree": ">= 3.1.0 < 4.0.0",
"tough-cookie": "^2.2.0",
"webidl-conversions": "^2.0.0",
"whatwg-url-compat": "~0.6.5",
"xml-name-validator": ">= 2.0.1 < 3.0.0"
}
},
"parse5": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/parse5/-/parse5-1.5.1.tgz",
"integrity": "sha1-m387DeMr543CQBsXVzzK8Pb1nZQ=",
"dev": true,
"optional": true
},
"webidl-conversions": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-2.0.1.tgz",
"integrity": "sha1-O/glj30xjHRDw28uFpQCoaZwNQY=",
"dev": true,
"optional": true
},
"xml-name-validator": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-2.0.1.tgz",
"integrity": "sha1-TYuPHszTQZqjYgYb7O9RXh5VljU=",
"dev": true,
"optional": true
}
}
},
"chromium-pickle-js": {
@ -1901,6 +1976,15 @@
"integrity": "sha512-+YR16o3rK53SmWHU3rEM3tPAh2rwb1yPcQX5irVn7mb0gXbwuCCrnkbV5+PBfETdfg1vui07nM6PCG1zndcjQw==",
"dev": true
},
"commonjs": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/commonjs/-/commonjs-0.0.1.tgz",
"integrity": "sha1-ZcUx3P9lZcp8ld38lmIricwClNU=",
"requires": {
"system": ">=0.0.1",
"test": ">=0.0.5"
}
},
"compare-version": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/compare-version/-/compare-version-0.1.2.tgz",
@ -2193,17 +2277,14 @@
}
},
"cssom": {
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.2.tgz",
"integrity": "sha1-uANhcMefB6kP8vFuIihAJ6JDhIs=",
"dev": true
"version": "0.3.4",
"resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.4.tgz",
"integrity": "sha512-+7prCSORpXNeR4/fUP3rL+TzqtiFfhMvTd7uEqMdgPvLPt4+uzFUeufx5RHjGTACCargg/DiEt/moMQmvnfkog=="
},
"cssstyle": {
"version": "0.2.37",
"resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-0.2.37.tgz",
"integrity": "sha1-VBCXI0yyUTyDzu06zdwn/yeYfVQ=",
"dev": true,
"optional": true,
"version": "0.3.1",
"resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-0.3.1.tgz",
"integrity": "sha512-tNvaxM5blOnxanyxI6panOsnfiyLRj3HV4qjqqS45WPNS1usdYWRUQjqTEEELK73lpeP/1KoIGYUwrBn/VcECA==",
"requires": {
"cssom": "0.3.x"
}
@ -2230,6 +2311,16 @@
"assert-plus": "^1.0.0"
}
},
"data-urls": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/data-urls/-/data-urls-1.0.0.tgz",
"integrity": "sha512-ai40PPQR0Fn1lD2PPie79CibnlMN2AYiDhwFX/rZHVsxbs5kNJSjegqXIprhouGXlRdEnfybva7kqRGnB6mypA==",
"requires": {
"abab": "^1.0.4",
"whatwg-mimetype": "^2.0.0",
"whatwg-url": "^6.4.0"
}
},
"dateformat": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/dateformat/-/dateformat-3.0.3.tgz",
@ -2277,8 +2368,7 @@
"deep-is": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz",
"integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=",
"dev": true
"integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ="
},
"defaults": {
"version": "1.0.3",
@ -2417,6 +2507,14 @@
"integrity": "sha1-sXrtguirWeUt2cGbF1bg/BhyBMI=",
"dev": true
},
"domexception": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/domexception/-/domexception-1.0.1.tgz",
"integrity": "sha512-raigMkn7CJNNo6Ihro1fzG7wr3fHuYVytzquZKX5n0yizGsTcYgzdIUwj1X9pK0VvjeihV+XiclP+DjwbsSKug==",
"requires": {
"webidl-conversions": "^4.0.2"
}
},
"domhandler": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.3.0.tgz",
@ -4225,11 +4323,9 @@
"integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ="
},
"escodegen": {
"version": "1.9.1",
"resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.9.1.tgz",
"integrity": "sha512-6hTjO1NAWkHnDk3OqQ4YrCuwwmGHL9S3nPlzBOUG/R44rda3wLNrfvQ5fkSGjyhHFKM7ALPKcKGrwvCLe0lC7Q==",
"dev": true,
"optional": true,
"version": "1.11.0",
"resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.11.0.tgz",
"integrity": "sha512-IeMV45ReixHS53K/OmfKAIztN/igDHzTJUhZM3k1jMhIZWjk45SMwAtBsEXiJp3vSPmTcu6CXn7mDvFHRN66fw==",
"requires": {
"esprima": "^3.1.3",
"estraverse": "^4.2.0",
@ -4241,15 +4337,12 @@
"esprima": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/esprima/-/esprima-3.1.3.tgz",
"integrity": "sha1-/cpRzuYTOJXjyI1TXOSdv/YqRjM=",
"dev": true,
"optional": true
"integrity": "sha1-/cpRzuYTOJXjyI1TXOSdv/YqRjM="
},
"source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
"dev": true,
"optional": true
}
}
@ -4262,15 +4355,12 @@
"estraverse": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz",
"integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=",
"dev": true,
"optional": true
"integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM="
},
"esutils": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz",
"integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=",
"dev": true
"integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs="
},
"events": {
"version": "1.1.1",
@ -4382,8 +4472,7 @@
"fast-levenshtein": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
"integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=",
"dev": true
"integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc="
},
"fd-slicer": {
"version": "1.0.1",
@ -5054,6 +5143,14 @@
"integrity": "sha1-ZouTd26q5V696POtRkswekljYl4=",
"dev": true
},
"html-encoding-sniffer": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-1.0.2.tgz",
"integrity": "sha512-71lZziiDnsuabfdYiUeWdCVyKuqwWi23L8YeIgV9jSSZHCtb6wB1BKWooH7L3tn4/FuZJMVWyNaIDr4RGmaSYw==",
"requires": {
"whatwg-encoding": "^1.0.1"
}
},
"html-entities": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/html-entities/-/html-entities-1.2.1.tgz",
@ -5642,27 +5739,56 @@
"optional": true
},
"jsdom": {
"version": "7.2.2",
"resolved": "https://registry.npmjs.org/jsdom/-/jsdom-7.2.2.tgz",
"integrity": "sha1-QLQCdwwr2iNGkJa+6Rq2deOx/G4=",
"dev": true,
"optional": true,
"version": "11.11.0",
"resolved": "https://registry.npmjs.org/jsdom/-/jsdom-11.11.0.tgz",
"integrity": "sha512-ou1VyfjwsSuWkudGxb03FotDajxAto6USAlmMZjE2lc0jCznt7sBWkhfRBRaWwbnmDqdMSTKTLT5d9sBFkkM7A==",
"requires": {
"abab": "^1.0.0",
"acorn": "^2.4.0",
"acorn-globals": "^1.0.4",
"cssom": ">= 0.3.0 < 0.4.0",
"cssstyle": ">= 0.2.29 < 0.3.0",
"escodegen": "^1.6.1",
"nwmatcher": ">= 1.3.7 < 2.0.0",
"parse5": "^1.5.1",
"request": "^2.55.0",
"sax": "^1.1.4",
"symbol-tree": ">= 3.1.0 < 4.0.0",
"tough-cookie": "^2.2.0",
"webidl-conversions": "^2.0.0",
"whatwg-url-compat": "~0.6.5",
"xml-name-validator": ">= 2.0.1 < 3.0.0"
"abab": "^1.0.4",
"acorn": "^5.3.0",
"acorn-globals": "^4.1.0",
"array-equal": "^1.0.0",
"cssom": ">= 0.3.2 < 0.4.0",
"cssstyle": ">= 0.3.1 < 0.4.0",
"data-urls": "^1.0.0",
"domexception": "^1.0.0",
"escodegen": "^1.9.0",
"html-encoding-sniffer": "^1.0.2",
"left-pad": "^1.2.0",
"nwsapi": "^2.0.0",
"parse5": "4.0.0",
"pn": "^1.1.0",
"request": "^2.83.0",
"request-promise-native": "^1.0.5",
"sax": "^1.2.4",
"symbol-tree": "^3.2.2",
"tough-cookie": "^2.3.3",
"w3c-hr-time": "^1.0.1",
"webidl-conversions": "^4.0.2",
"whatwg-encoding": "^1.0.3",
"whatwg-mimetype": "^2.1.0",
"whatwg-url": "^6.4.1",
"ws": "^4.0.0",
"xml-name-validator": "^3.0.0"
},
"dependencies": {
"acorn": {
"version": "5.7.1",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.1.tgz",
"integrity": "sha512-d+nbxBUGKg7Arpsvbnlq61mc12ek3EY8EQldM3GPAhWJ1UVxC6TDGbIvUMNU6obBX3i1+ptCIzV4vq0gFPEGVQ=="
},
"acorn-globals": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-4.1.0.tgz",
"integrity": "sha512-KjZwU26uG3u6eZcfGbTULzFcsoz6pegNKtHPksZPOUsiKo5bUmiBPa38FuHZ/Eun+XYh/JCCkS9AS3Lu4McQOQ==",
"requires": {
"acorn": "^5.0.0"
}
},
"sax": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz",
"integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw=="
}
}
},
"jsesc": {
@ -5817,6 +5943,11 @@
"invert-kv": "^1.0.0"
}
},
"left-pad": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/left-pad/-/left-pad-1.3.0.tgz",
"integrity": "sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA=="
},
"less": {
"version": "2.7.3",
"resolved": "https://registry.npmjs.org/less/-/less-2.7.3.tgz",
@ -6005,7 +6136,6 @@
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz",
"integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=",
"dev": true,
"requires": {
"prelude-ls": "~1.1.2",
"type-check": "~0.3.2"
@ -6060,8 +6190,7 @@
"lodash": {
"version": "4.17.10",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz",
"integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg==",
"dev": true
"integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg=="
},
"lodash._reinterpolate": {
"version": "3.0.0",
@ -6135,6 +6264,11 @@
"integrity": "sha1-YAYMxr1iW01FZ+wn3EXNG+nuwBI=",
"dev": true
},
"lodash.sortby": {
"version": "4.7.0",
"resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz",
"integrity": "sha1-7dFMgk4sycHgsKG0K7UhBRakJDg="
},
"lodash.template": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-4.4.0.tgz",
@ -6619,6 +6753,11 @@
"dev": true,
"optional": true
},
"nwsapi": {
"version": "2.0.7",
"resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.0.7.tgz",
"integrity": "sha512-VZXniaaaORAXGCNsvUNefsKRQYk8zCzQZ57jalgrpHcU70OrAzKAiN/3plYtH/VPRmZeYyUzQiYfKzcMXC1g5Q=="
},
"oauth-sign": {
"version": "0.8.2",
"resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz",
@ -6706,7 +6845,6 @@
"version": "0.8.2",
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz",
"integrity": "sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q=",
"dev": true,
"requires": {
"deep-is": "~0.1.3",
"fast-levenshtein": "~2.0.4",
@ -6885,11 +7023,9 @@
"dev": true
},
"parse5": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/parse5/-/parse5-1.5.1.tgz",
"integrity": "sha1-m387DeMr543CQBsXVzzK8Pb1nZQ=",
"dev": true,
"optional": true
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/parse5/-/parse5-4.0.0.tgz",
"integrity": "sha512-VrZ7eOd3T1Fk4XWNXMgiGBK/z0MG48BWG2uQNU4I72fkQuKUTZpl+u9k+CxEG0twMVzSmXEEz12z5Fnw1jIQFA=="
},
"parser-toolkit": {
"version": "0.0.5",
@ -6986,6 +7122,11 @@
}
}
},
"pn": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/pn/-/pn-1.1.0.tgz",
"integrity": "sha512-2qHaIQr2VLRFoxe2nASzsV6ef4yOOH+Fi9FBOVH6cqeSgUnoyySPZkxzLuzd+RYOQTRpROA0ztTMqxROKSb/nA=="
},
"postcss": {
"version": "5.2.18",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-5.2.18.tgz",
@ -7301,8 +7442,7 @@
"prelude-ls": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz",
"integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=",
"dev": true
"integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ="
},
"prepend-http": {
"version": "1.0.4",
@ -7683,6 +7823,24 @@
"uuid": "^3.1.0"
}
},
"request-promise-core": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.1.tgz",
"integrity": "sha1-Pu4AssWqgyOc+wTFcA2jb4HNCLY=",
"requires": {
"lodash": "^4.13.1"
}
},
"request-promise-native": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/request-promise-native/-/request-promise-native-1.0.5.tgz",
"integrity": "sha1-UoF3D2jgyXGeUWP9P6tIIhX0/aU=",
"requires": {
"request-promise-core": "1.1.1",
"stealthy-require": "^1.1.0",
"tough-cookie": ">=2.3.3"
}
},
"require-directory": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
@ -8147,6 +8305,11 @@
"integrity": "sha1-5sgLYjEj19gM8TLOU480YokHJQI=",
"dev": true
},
"stealthy-require": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz",
"integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks="
},
"stream-json": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/stream-json/-/stream-json-0.6.1.tgz",
@ -8368,9 +8531,7 @@
"symbol-tree": {
"version": "3.2.2",
"resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.2.tgz",
"integrity": "sha1-rifbOPZgp64uHDt9G8KQgZuFGeY=",
"dev": true,
"optional": true
"integrity": "sha1-rifbOPZgp64uHDt9G8KQgZuFGeY="
},
"synchronous-promise": {
"version": "1.0.18",
@ -8378,6 +8539,11 @@
"integrity": "sha512-UqMAK6BBBXu8qaDI5omlyV9iDpM9nQfgthaBOK0nlfXnMgiuOBv+meWG73CGeCCFRhOOOa2e4rvqYzfynzy5zg==",
"dev": true
},
"system": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/system/-/system-2.0.1.tgz",
"integrity": "sha512-BwSUSa8LMHZouGadZ34ck3TsrH5s3oMmTKPK+xHdbBnTCZOZMJ38fHGKLAHkBl0PXru1Z4BsymQU4qqvTxWzdQ=="
},
"tabtab": {
"version": "2.2.2",
"resolved": "https://registry.npmjs.org/tabtab/-/tabtab-2.2.2.tgz",
@ -8596,6 +8762,14 @@
"lazy-val": "^1.0.3"
}
},
"test": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/test/-/test-0.6.0.tgz",
"integrity": "sha1-WYasRF7Bd1QyJRLRBLoyyKY+k44=",
"requires": {
"ansi-font": "0.0.2"
}
},
"thenify": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.0.tgz",
@ -8759,11 +8933,19 @@
}
},
"tr46": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
"integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=",
"dev": true,
"optional": true
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz",
"integrity": "sha1-qLE/1r/SSJUZZ0zN5VujaTtwbQk=",
"requires": {
"punycode": "^2.1.0"
},
"dependencies": {
"punycode": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
"integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A=="
}
}
},
"transformers": {
"version": "2.1.0",
@ -8865,7 +9047,6 @@
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz",
"integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=",
"dev": true,
"requires": {
"prelude-ls": "~1.1.2"
}
@ -9102,6 +9283,14 @@
"integrity": "sha512-x3LV3wdmmERhVCYy3quqA57NJW7F3i6faas++pJQWtknWT+n7k30F4TVdHvCLn48peTJFRvCpxs3UuFPqgeELg==",
"dev": true
},
"w3c-hr-time": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.1.tgz",
"integrity": "sha1-gqwr/2PZUOqeMYmlimViX+3xkEU=",
"requires": {
"browser-process-hrtime": "^0.1.2"
}
},
"wcwidth": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz",
@ -9112,11 +9301,39 @@
}
},
"webidl-conversions": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-2.0.1.tgz",
"integrity": "sha1-O/glj30xjHRDw28uFpQCoaZwNQY=",
"dev": true,
"optional": true
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz",
"integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg=="
},
"whatwg-encoding": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-1.0.3.tgz",
"integrity": "sha512-jLBwwKUhi8WtBfsMQlL4bUUcT8sMkAtQinscJAe/M4KHCkHuUJAF6vuB0tueNIw4c8ziO6AkRmgY+jL3a0iiPw==",
"requires": {
"iconv-lite": "0.4.19"
},
"dependencies": {
"iconv-lite": {
"version": "0.4.19",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.19.tgz",
"integrity": "sha512-oTZqweIP51xaGPI4uPa56/Pri/480R+mo7SeU+YETByQNhDG55ycFyNLIgta9vXhILrxXDmF7ZGhqZIcuN0gJQ=="
}
}
},
"whatwg-mimetype": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.1.0.tgz",
"integrity": "sha512-FKxhYLytBQiUKjkYteN71fAUA3g6KpNXoho1isLiLSB3N1G4F35Q5vUxWfKFhBwi5IWF27VE6WxhrnnC+m0Mew=="
},
"whatwg-url": {
"version": "6.5.0",
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-6.5.0.tgz",
"integrity": "sha512-rhRZRqx/TLJQWUpQ6bmrt2UV4f0HCQ463yQuONJqC6fO2VoEb1pTYddbe59SkYq87aoM5A3bdhMZiUiVws+fzQ==",
"requires": {
"lodash.sortby": "^4.7.0",
"tr46": "^1.0.1",
"webidl-conversions": "^4.0.2"
}
},
"whatwg-url-compat": {
"version": "0.6.5",
@ -9126,6 +9343,15 @@
"optional": true,
"requires": {
"tr46": "~0.0.1"
},
"dependencies": {
"tr46": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
"integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=",
"dev": true,
"optional": true
}
}
},
"whet.extend": {
@ -9190,8 +9416,7 @@
"wordwrap": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz",
"integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=",
"dev": true
"integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus="
},
"wrap-ansi": {
"version": "2.1.0",
@ -9214,12 +9439,19 @@
"dev": true,
"optional": true
},
"ws": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-4.1.0.tgz",
"integrity": "sha512-ZGh/8kF9rrRNffkLFV4AzhvooEclrOH0xaugmqGsIfFgOE/pIz4fMc4Ef+5HSQqTEug2S9JZIWDR47duDSLfaA==",
"requires": {
"async-limiter": "~1.0.0",
"safe-buffer": "~5.1.0"
}
},
"xml-name-validator": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-2.0.1.tgz",
"integrity": "sha1-TYuPHszTQZqjYgYb7O9RXh5VljU=",
"dev": true,
"optional": true
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz",
"integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw=="
},
"xml2js": {
"version": "0.4.17",

View File

@ -1,7 +1,7 @@
{
"name": "FreeTube",
"productName": "FreeTube",
"version": "0.3.1",
"version": "0.3.2",
"description": "An Open Source YouTube app for privacy.",
"main": "src/js/init.js",
"scripts": {
@ -9,16 +9,22 @@
"package": "electron-forge package",
"make": "electron-forge make",
"publish": "electron-forge publish",
"make:all": "npm run make:mac && npm run make:linux:x86 && npm run make:linux:arm && npm run make:win",
"make:mac": "electron-forge make --platform=darwin",
"make:linux": "electron-forge make --platform=linux",
"make:linux:zip": "electron-forge make --platform=linux --targets=zip",
"make:deb": "electron-forge make --platform=linux --targets=deb",
"make:rpm": "electron-forge make --platform=linux --targets=rpm",
"make:snap": "electron-forge package && electron-installer-snap --src=out/FreeTube-linux-x64",
"make:flatpak": "electron-installer-flatpak --src out/FreeTube-linux-x64/ --dest out/make --arch x64",
"make:appimage": "electron-forge make --platform=linux --targets=electron-forge-maker-appimage",
"make:win": "electron-forge make --platform=win32",
"make:win:zip": "electron-forge make --platform=win32 --targets=zip"
"make:win:zip": "electron-forge make --platform=win32 --targets=zip",
"make:linux:x86": "electron-forge make --platform=linux --arch x64",
"make:linux:x86:zip": "electron-forge make --platform=linux --targets=zip --arch x64",
"make:linux:x86:deb": "electron-forge make --platform=linux --targets=deb --arch x64",
"make:linux:x86:rpm": "electron-forge make --platform=linux --targets=rpm --arch x64",
"make:linux:x86:snap": "electron-forge package && electron-installer-snap --src=out/FreeTube-linux-x64 --arch x64",
"make:linux:x86:flatpak": "electron-installer-flatpak --src out/FreeTube-linux-x64/ --dest out/make --arch x64",
"make:linux:x86:appimage": "electron-forge make --platform=linux --targets=electron-forge-maker-appimage --arch x64",
"make:linux:arm": "electron-forge make --platform=linux --arch arm64",
"make:linux:arm:zip": "electron-forge make --platform=linux --targets=zip --arch arm64",
"make:linux:arm:deb": "electron-forge make --platform=linux --targets=deb --arch arm64",
"make:linux:arm:rpm": "electron-forge make --platform=linux --targets=rpm --arch arm64",
"make:linux:arm:appimage": "electron-forge make --platform=linux --targets=electron-forge-maker-appimage --arch arm64"
},
"keywords": [],
"author": {
@ -31,8 +37,7 @@
"forge": {
"make_targets": {
"win32": [
"squirrel",
"zip"
"squirrel"
],
"darwin": [
"zip"
@ -81,11 +86,13 @@
},
"dependencies": {
"autolinker": "^1.6.2",
"commonjs": "0.0.1",
"dateformat": "^3.0.3",
"electron-compile": "6.4.2",
"electron-squirrel-startup": "^1.0.0",
"github-version-checker": "^2.0.1",
"jquery": "^3.3.1",
"jsdom": "^11.11.0",
"mustache": "^2.3.0",
"nedb": "^1.8.0",
"opml-to-json": "0.0.3",

View File

@ -11,22 +11,11 @@
<link rel="stylesheet" href="style/loading.css">
<link rel="stylesheet" href="style/fa-solid.min.css">
<link rel="stylesheet" href="style/fontawesome-all.min.css">
<script src="js/youtubeApi.js"></script>
<script src="js/settings.js"></script>
<script src="js/updates.js"></script>
<script src="js/layout.js"></script>
<script src="js/videos.js"></script>
<script src="js/player.js"></script>
<script src="js/subscriptions.js"></script>
<script src="js/channels.js"></script>
<script src="js/savedVideos.js"></script>
<script src="js/history.js"></script>
<script src="js/events.js"></script>
<title>Freetube Player</title>
</head>
<body>
<div id='loading'>
<div v-if='seen' id='loading'>
<div class="spinner">
<div class="double-bounce1"></div>
<div class="double-bounce2"></div>
@ -47,23 +36,22 @@
<input id='search' class="search" type="text" placeholder="Search / Go to URL">
<i onclick='parseSearchText()' class="fas fa-search searchButton" style='margin-right: -10px; cursor: pointer'></i>
</div>
<img src='icons/iconBlack.png' id='menuIcon'/>
&nbsp;
<img src='icons/textBlack.png' id='menuText'/>
<img src='icons/iconBlack.png' id='menuIcon' /> &nbsp;
<img src='icons/textBlack.png' id='menuText' />
</div>
<div id='sideNavDisabled'></div>
<div id="sideNav">
<div class="sideNavContainer">
<ul>
<li onclick='loadSubscriptions()'><i class="fas fa-rss"></i>&nbsp;&nbsp;Subscriptions</li>
<li onclick='showMostPopular()'><i class="fas fa-users"></i>&nbsp;&nbsp;Most Popular</li>
<li onclick='showSavedVideos()'><i class="fas fa-star"></i>&nbsp;&nbsp;Saved</li>
<li onclick='showHistory()'><i class="fas fa-history"></i>&nbsp;&nbsp;History</li>
<li v-on:click='subscriptions'><i class="fas fa-rss"></i>&nbsp;&nbsp;Subscriptions</li>
<li v-on:click='popular'><i class="fas fa-users"></i>&nbsp;&nbsp;Most Popular</li>
<li v-on:click='saved'><i class="fas fa-star"></i>&nbsp;&nbsp;Favorites</li>
<li v-on:click='history'><i class="fas fa-history"></i>&nbsp;&nbsp;History</li>
</ul>
<hr />
<ul>
<li onclick='showSettings()'><i class="fas fa-sliders-h"></i>&nbsp;&nbsp;Settings</li>
<li onclick='showAbout()'><i class="fas fa-info-circle"></i>&nbsp;&nbsp;About</li>
<li v-on:click='settings'><i class="fas fa-sliders-h"></i>&nbsp;&nbsp;Settings</li>
<li v-on:click='about'><i class="fas fa-info-circle"></i>&nbsp;&nbsp;About</li>
</ul>
<hr />
<ul id='subscriptions'>
@ -71,7 +59,41 @@
</div>
</div>
<div id="main">
<div id='noSubscriptions' v-if='seen'>
<h2 class="message">
Your Subscription list is currently empty. Start adding subscriptions
to see them here.
<br /><br />
<i class="far fa-frown" style="font-size: 200px"></i>
</h2>
</div>
<div id='channelView'></div>
<div id='mainHeaderView'></div>
<div id='channelVideosView'></div>
<div id='searchView'></div>
<div id='subscriptionView'></div>
<div id='popularView'></div>
<div id='savedView'></div>
<div id='historyView'></div>
<div id='aboutView'></div>
<div id='settingsView'></div>
<div id='playerView'></div>
</div>
<div id='progressView'></div>
</body>
<script src="js/general.js"></script>
<script src="js/youtubeApi.js"></script>
<script src="js/updates.js"></script>
<script src="js/db.js"></script>
<script src="js/settings.js"></script>
<script src="js/layout.js"></script>
<script src="js/templates.js"></script>
<script src="js/videos.js"></script>
<script src="js/player.js"></script>
<script src="js/subscriptions.js"></script>
<script src="js/channels.js"></script>
<script src="js/savedVideos.js"></script>
<script src="js/history.js"></script>
<script src="js/events.js"></script>
</html>

View File

@ -1,75 +1,84 @@
/*
This file is part of FreeTube.
This file is part of FreeTube.
FreeTube is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
FreeTube is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
FreeTube is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
FreeTube is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with FreeTube. If not, see <http://www.gnu.org/licenses/>.
You should have received a copy of the GNU General Public License
along with FreeTube. If not, see <http://www.gnu.org/licenses/>.
*/
/*
* File for all functions related specifically for channels.
*/
/*function getChannelThumbnail(channelId, callback) {
let url = '';
youtubeAPI('channels', {
'id': channelId,
'part': 'snippet',
}, function (data){
callback(data.items[0].snippet.thumbnails.high.url);
});
}*/
* File for all functions related specifically for channels.
*/
/**
* Display a channel page, showing latest uploads.
*
* @param {string} channelId - The channel ID to display.
*
* @return {Void}
*/
* Display a channel page, showing latest uploads.
*
* @param {string} channelId - The channel ID to display.
*
* @return {Void}
*/
function goToChannel(channelId) {
event.stopPropagation();
clearMainContainer();
startLoadingAnimation();
let subButtonText;
headerView.title = 'Latest Uploads';
hideViews();
loadingView.seen = true;
// Setting subButtonText here as Mustache templates are logic-less.
isSubscribed(channelId).then((subscribed) => {
subButtonText = (subscribed ? "UNSUBSCRIBE" : "SUBSCRIBE");
channelView.subButtonText = (subscribed ? "UNSUBSCRIBE" : "SUBSCRIBE");
});
// Grab general channel information
youtubeAPI('channels', {
part: 'snippet,brandingSettings,statistics',
id: channelId,
}, function (data){
}, (data) => {
const channelData = data.items[0];
const channelViewTemplate = require('./templates/channelView.html');
mustache.parse(channelViewTemplate);
const rendered = mustache.render(channelViewTemplate, {
channelView.id = channelId;
channelView.name = channelData.brandingSettings.channel.title;
channelView.banner = channelData.brandingSettings.image.bannerImageUrl;
channelView.icon = channelData.snippet.thumbnails.high.url;
channelView.subCount = channelData.statistics.subscriberCount.toLocaleString(); //toLocaleString adds commas as thousands separators
channelView.description = autolinker.link(channelData.brandingSettings.channel.description); //autolinker makes URLs clickable
// Grab the channel's latest uploads. API forces a max of 50.
youtubeAPI('search', {
part: 'snippet',
channelId: channelId,
channelName: channelData.brandingSettings.channel.title,
channelBanner: channelData.brandingSettings.image.bannerImageUrl,
channelImage: channelData.snippet.thumbnails.high.url,
subCount: channelData.statistics.subscriberCount.toLocaleString(), //toLocaleString adds commas as thousands separators
channelDescription: autolinker.link(channelData.brandingSettings.channel.description), //autolinker makes URLs clickable
subButtonText: subButtonText,
type: 'video',
maxResults: 50,
order: 'date',
}, function (data) {
let grabDuration = getDuration(data.items);
grabDuration.then((videoList) => {
channelVideosView.videoList = [];
if (subscriptionView.seen === false && aboutView.seen === false && headerView.seen === false && searchView.seen === false && settingsView.seen === false && popularView.seen === false && savedView.seen === false && historyView.seen === false) {
channelVideosView.seen = true;
channelView.seen = true;
}
else{
return;
}
loadingView.seen = false;
videoList.items.forEach((video) => {
displayVideo(video, 'channel');
});
$('#main').html(rendered);
stopLoadingAnimation();
// Grab the channel's latest uploads. API forces a max of 50.
youtubeAPI('search', {
@ -89,4 +98,6 @@ function goToChannel(channelId) {
});
});
});
});
});
}

40
src/js/db.js Normal file
View File

@ -0,0 +1,40 @@
/*
This file is part of FreeTube.
FreeTube is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
FreeTube is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with FreeTube. If not, see <http://www.gnu.org/licenses/>.
*/
const electron = require('electron');
const Datastore = require('nedb'); // database logic
const localDataStorage = electron.remote.app.getPath('userData'); // Grabs the userdata directory based on the user's OS
const subDb = new Datastore({
filename: localDataStorage + '/subscriptions.db',
autoload: true
});
const historyDb = new Datastore({
filename: localDataStorage + '/videohistory.db',
autoload: true
});
const savedVidsDb = new Datastore({
filename: localDataStorage + '/savedvideos.db',
autoload: true
});
const settingsDb = new Datastore({
filename: localDataStorage + '/settings.db',
autoload: true
});

View File

@ -1,22 +1,20 @@
/*
This file is part of FreeTube.
/*
This file is part of FreeTube.
FreeTube is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
FreeTube is free software: you can redistribute it and/or modify
sit under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
FreeTube is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
FreeTube is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with FreeTube. If not, see <http://www.gnu.org/licenses/>.
You should have received a copy of the GNU General Public License
along with FreeTube. If not, see <http://www.gnu.org/licenses/>.
*/
/*
* File for events within application. Work needs to be done throughout the application
* to use this style more. Please use this style going forward if possible.
@ -26,7 +24,7 @@ along with FreeTube. If not, see <http://www.gnu.org/licenses/>.
* Event when user clicks comment box,
* and wants to show/display comments for the user.
*/
let showComments = function(event) {
let showComments = function (event) {
let comments = $('#comments');
if (comments.css('display') === 'none') {
@ -36,7 +34,7 @@ let showComments = function(event) {
'videoId': $('#comments').attr('data-video-id'),
'part': 'snippet,replies',
'maxResults': 100,
}, function (data){
}, function (data) {
let comments = [];
let items = data.items;
@ -63,19 +61,16 @@ let showComments = function(event) {
/**
* Play / Pause the video player upon click.
*/
let playPauseVideo = function(event) {
let playPauseVideo = function (event) {
let el = event.currentTarget;
el.paused ? el.play() : el.pause();
};
$('.videoPlayer').keypress((event) => {
console.log(event.which);
});
let videoShortcutHandler = function(event) {
console.log(event.which);
let videoShortcutHandler = function (event) {
let videoPlayer = $('.videoPlayer').get(0);
if (typeof(videoPlayer) !== 'undefined' && !$('#jumpToInput').is(':focus') && !$('#search').is(':focus')){
if (typeof (videoPlayer) !== 'undefined' && !$('#jumpToInput').is(':focus') && !$('#search').is(':focus')) {
switch (event.which) {
case 32:
// Space Bar
@ -106,21 +101,19 @@ let videoShortcutHandler = function(event) {
// M Key
event.preventDefault();
let volume = videoPlayer.volume;
console.log(volume);
if (volume > 0){
if (volume > 0) {
changeVolume(-1);
}
else{
} else {
changeVolume(1);
}
break;
case 67:
// C Key
let subtitleMode = $('.videoPlayer').get(0).textTracks[0].mode;
if (subtitleMode === 'hidden'){
if (subtitleMode === 'hidden') {
$('.videoPlayer').get(0).textTracks[0].mode = 'showing'
}
else{
} else {
$('.videoPlayer').get(0).textTracks[0].mode = 'hidden'
}
break;
@ -198,11 +191,10 @@ let videoShortcutHandler = function(event) {
}
};
let fullscreenVideo = function(event){
if (document.webkitFullscreenElement !== null){
let fullscreenVideo = function (event) {
if (document.webkitFullscreenElement !== null) {
document.webkitExitFullscreen();
}
else{
} else {
$('.videoPlayer').get(0).webkitRequestFullscreen();
}
}
@ -212,6 +204,7 @@ let fullscreenVideo = function(event){
* Bind click events
* --------------------------
*/
$(document).on('click', '#showComments', showComments);
$(document).on('click', '.videoPlayer', playPauseVideo);
@ -221,3 +214,17 @@ $(document).on('dblclick', '.videoPlayer', fullscreenVideo);
$(document).on('keydown', videoShortcutHandler);
$(document).on('click', '#confirmNo', hideConfirmFunction);
// Open links externally by default
$(document).on('click', 'a[href^="http"]', (event) => {
let el = event.currentTarget;
event.preventDefault();
shell.openExternal(el.href);
});
// Open links externally on middle click.
$(document).on('auxclick', 'a[href^="http"]', (event) => {
let el = event.currentTarget;
event.preventDefault();
shell.openExternal(el.href);
});

39
src/js/general.js Normal file
View File

@ -0,0 +1,39 @@
/*
This file is part of FreeTube.
FreeTube is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
FreeTube is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with FreeTube. If not, see <http://www.gnu.org/licenses/>.
*/
let ft = {};
/**
*
* Use this function instead of console.log.
* This function logs the date, time and presents the information in a readable format
*
* @param {*} data
*
* @returns {Void}
*/
ft.log = function (...data) {
let currentTime = new Date();
let time = currentTime.getDate() + "/" +
(currentTime.getMonth() + 1) + "/" +
currentTime.getFullYear() + "@" +
currentTime.getHours() + ":" +
currentTime.getMinutes() + ":" +
currentTime.getSeconds();
console.log('[' + time + '] ' + '[FREETUBE]', data);
}

View File

@ -1,16 +1,13 @@
/*
This file is part of FreeTube.
FreeTube is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
FreeTube is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with FreeTube. If not, see <http://www.gnu.org/licenses/>.
*/
@ -54,8 +51,8 @@ function removeFromHistory(videoId){
* @return {Void}
*/
function showHistory(){
clearMainContainer();
startLoadingAnimation();
//clearMainContainer();
//startLoadingAnimation();
console.log('checking history');
let videoList = '';
@ -80,15 +77,15 @@ function showHistory(){
id: videoList,
maxResults: 50,
}, function (data) {
createVideoListContainer('Watch History:');
let grabDuration = getDuration(data.items);
grabDuration.then((videoList) => {
historyView.videoList = [];
loadingView.seen = false;
videoList.items.forEach((video) => {
displayVideo(video, 'history');
});
});
stopLoadingAnimation()
});
});
}

View File

@ -1,28 +1,34 @@
/*
This file is part of FreeTube.
This file is part of FreeTube.
FreeTube is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
FreeTube is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
FreeTube is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
FreeTube is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with FreeTube. If not, see <http://www.gnu.org/licenses/>.
You should have received a copy of the GNU General Public License
along with FreeTube. If not, see <http://www.gnu.org/licenses/>.
*/
/*
* File used to initializing the application
*/
const {app, BrowserWindow, dialog, protocol} = require('electron');
* File used to initializing the application
*/
const {
app,
BrowserWindow,
dialog,
protocol
} = require('electron');
const path = require('path');
const url = require('url');
let win;
protocol.registerStandardSchemes(['freetube']);
@ -39,17 +45,21 @@ const isSecondInstance = app.makeSingleInstance((commandLine, workingDirectory)
}
});
if(require('electron-squirrel-startup') || isSecondInstance) app.quit();
if (require('electron-squirrel-startup') || isSecondInstance) app.quit();
/**
* Initialize the Electron application
* 1. create the browser window
* 2. load the index.html
*/
let init = function() {
let init = function () {
const Menu = require('electron').Menu;
win = new BrowserWindow({width: 1200, height: 800, autoHideMenuBar: true});
win = new BrowserWindow({
width: 1200,
height: 800,
autoHideMenuBar: true
});
win.loadURL(url.format({
pathname: path.join(__dirname, '../index.html'),
@ -65,43 +75,80 @@ let init = function() {
win = null;
});
const template = [
{
const template = [{
label: 'File',
submenu: [
submenu: [{
label: 'Open New Window',
click () { init() }
},
{role: 'quit'}
]
},
{
label: 'Edit',
submenu: [
{role: 'cut'},
{role: 'copy', accelerator: "CmdOrCtrl+C", selector: "copy:" },
{role: 'paste', accelerator: "CmdOrCtrl+V", selector: "paste:" },
{role: 'pasteandmatchstyle'},
{role: 'delete'},
{role: 'selectall'}
submenu: [{
role: 'cut'
},
{
role: 'copy',
accelerator: "CmdOrCtrl+C",
selector: "copy:"
},
{
role: 'paste',
accelerator: "CmdOrCtrl+V",
selector: "paste:"
},
{
role: 'pasteandmatchstyle'
},
{
role: 'delete'
},
{
role: 'selectall'
}
]
},
{
label: 'View',
submenu: [
{role: 'reload'},
{role: 'forcereload'},
{role: 'toggledevtools'},
{type: 'separator'},
{role: 'resetzoom'},
{role: 'zoomin'},
{role: 'zoomout'},
{type: 'separator'},
{role: 'togglefullscreen'}
submenu: [{
role: 'reload'
},
{
role: 'forcereload'
},
{
role: 'toggledevtools'
},
{
type: 'separator'
},
{
role: 'resetzoom'
},
{
role: 'zoomin'
},
{
role: 'zoomout'
},
{
type: 'separator'
},
{
role: 'togglefullscreen'
}
]
},
{
role: 'window',
submenu: [
{role: 'minimize'},
{role: 'close'}
submenu: [{
role: 'minimize'
},
{
role: 'close'
}
]
}
];
@ -113,7 +160,7 @@ let init = function() {
/**
* Quit the application
*/
let allWindowsClosed = function() {
let allWindowsClosed = function () {
win.webContents.session.clearStorageData([], (data) => {});
win.webContents.session.clearCache((data) => {});
app.quit();
@ -123,7 +170,7 @@ let allWindowsClosed = function() {
* On Mac, when dock icon is clicked,
* create a new window and launch the editor
*/
let active = function() {
let active = function () {
if (win === null) {
init();
}

View File

@ -1,18 +1,18 @@
/*
This file is part of FreeTube.
This file is part of FreeTube.
FreeTube is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
FreeTube is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
FreeTube is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
FreeTube is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with FreeTube. If not, see <http://www.gnu.org/licenses/>.
You should have received a copy of the GNU General Public License
along with FreeTube. If nsot, see <http://www.gnu.org/licenses/>.
*/
@ -23,87 +23,43 @@ along with FreeTube. If not, see <http://www.gnu.org/licenses/>.
*/
// Add general variables. Please put all require statements here.
const Datastore = require('nedb'); // database logic
window.$ = window.jQuery = require('jquery');
const mustache = require('mustache'); // templating
const dateFormat = require('dateformat'); // formatting dates
//const RxPlayer = require('rx-player'); // formatting dates
// Used for finding links within text and making them clickable. Used mostly for video descriptions.
const autolinker = require('autolinker');
const electron = require('electron');
const protocol = electron.remote.protocol;
// Used for getting the user's subscriptions. Can probably remove this when that function
// is rewritten.
//const asyncLoop = require('node-async-loop');
//const youtubedl = require('youtube-dl');
const ytdl = require('ytdl-core');
const shell = electron.shell; // Used to open external links into the user's native browser.
const localDataStorage = electron.remote.app.getPath('userData'); // Grabs the userdata directory based on the user's OS
const clipboard = electron.clipboard;
const getOpml = require('opml-to-json'); // Gets the file type for imported files.
const fs = require('fs'); // Used to read files. Specifically in the settings page.
const tor = require('tor-request');
let currentTheme = '';
let apiKey;
let useTor = false;
let dialog = electron.remote.dialog; // Used for opening file browser to export / import subscriptions.
let toastTimeout; // Timeout for toast notifications.
let mouseTimeout; // Timeout for hiding the mouse cursor on video playback
require.extensions['.html'] = function(module, filename) {
require.extensions['.html'] = function (module, filename) {
module.exports = fs.readFileSync(filename, 'utf8');
};
const subDb = new Datastore({
filename: localDataStorage + '/subscriptions.db',
autoload: true
});
const historyDb = new Datastore({
filename: localDataStorage + '/videohistory.db',
autoload: true
});
const savedVidsDb = new Datastore({
filename: localDataStorage + '/savedvideos.db',
autoload: true
});
const settingsDb = new Datastore({
filename: localDataStorage + '/settings.db',
autoload: true
});
// Grabs the default settings from the settings database file. Makes defaults if
// none are found.
checkDefaultSettings();
require('electron').ipcRenderer.on('ping', function(event, message) {
console.log(message);
electron.ipcRenderer.on('ping', function(event, message) {
ft.log(message);
let url = message[1].replace('freetube://', '');
parseSearchText(url);
console.log(message);
ft.log(message);
});
// Open links externally by default
$(document).on('click', 'a[href^="http"]', (event) => {
let el = event.currentTarget;
event.preventDefault();
shell.openExternal(el.href);
});
// Open links externally on middle click.
$(document).on('auxclick', 'a[href^="http"]', (event) => {
let el = event.currentTarget;
event.preventDefault();
shell.openExternal(el.href);
});
$(document).ready(() => {
const searchBar = document.getElementById('search');
const jumpToInput = document.getElementById('jumpToInput');
@ -120,6 +76,7 @@ $(document).ready(() => {
// Display subscriptions upon the app opening up. May allow user to specify.
// Home page in the future.
loadingView.seen = true;
loadSubscriptions();
});
@ -141,84 +98,6 @@ function toggleSideNavigation() {
}
}
/**
* Clears out the #main container to allow other information to be shown.
*
* @return {Void}
*/
function clearMainContainer() {
const container = document.getElementById('main');
container.innerHTML = '';
hideConfirmFunction();
}
function startLoadingAnimation() {
const loading = document.getElementById('loading');
const sideNavDisabled = document.getElementById('sideNavDisabled');
const searchBar = document.getElementById('search');
loading.style.display = 'inherit';
if(sideNavDisabled !== null){
sideNavDisabled.style.display = 'inherit';
}
searchBar.disabled = true;
}
function stopLoadingAnimation() {
const loading = document.getElementById('loading');
const sideNavDisabled = document.getElementById('sideNavDisabled');
const searchBar = document.getElementById('search');
loading.style.display = 'none';
if(sideNavDisabled !== null){
sideNavDisabled.style.display = 'none';
}
searchBar.disabled = false;
}
/**
* Creates a div container in #main meant to be a container for video lists.
*
* @param {string} headerLabel - The header of the container. Not used for showing video recommendations.
*
* @return {Void}
*/
function createVideoListContainer(headerLabel = '') {
const videoListContainer = document.createElement("div");
videoListContainer.id = 'videoListContainer';
let headerSpacer;
if (headerLabel != '') {
const headerElement = document.createElement("h2");
headerElement.innerHTML = headerLabel;
headerElement.style.marginLeft = '15px';
headerElement.appendChild(document.createElement("hr"));
videoListContainer.appendChild(headerElement);
}
document.getElementById("main").appendChild(videoListContainer);
}
/**
* Displays the about page to #main
*
* @return {Void}
*/
function showAbout() {
// Remove current information and display loading animation
clearMainContainer();
startLoadingAnimation();
const aboutTemplate = require('./templates/about.html')
mustache.parse(aboutTemplate);
$('#main').html(
mustache.render(aboutTemplate, {
versionNumber: require('electron').remote.app.getVersion(),
})
);
stopLoadingAnimation();
}
/**
* Display a toast message in the bottom right corner of the page.
* The toast automatically disappears after a timeout.
@ -271,10 +150,9 @@ function confirmFunction(message, performFunction, parameters = '') {
confirmContainer.style.visibility = 'visible';
$(document).on('click', '#confirmYes', (event) => {
if(parameters != ''){
if (parameters != '') {
performFunction(parameters);
}
else{
} else {
performFunction();
}
hideConfirmFunction();
@ -300,7 +178,7 @@ function hideConfirmFunction() {
function hideMouseTimeout() {
$('.videoPlayer')[0].style.cursor = 'default';
clearTimeout(mouseTimeout);
mouseTimeout = window.setTimeout(function() {
mouseTimeout = window.setTimeout(function () {
$('.videoPlayer')[0].style.cursor = 'none';
}, 3150);
}

View File

@ -1,18 +1,18 @@
/*
This file is part of FreeTube.
This file is part of FreeTube.
FreeTube is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
FreeTube is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
FreeTube is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
FreeTube is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with FreeTube. If not, see <http://www.gnu.org/licenses/>.
You should have received a copy of the GNU General Public License
along with FreeTube. If not, see <http://www.gnu.org/licenses/>.
*/
@ -21,9 +21,9 @@ along with FreeTube. If not, see <http://www.gnu.org/licenses/>.
* Video Comment Model
*/
export class comment {
authorDisplayName: string;
authorProfileImageUrl: string;
authorChannelId: string;
textDisplay: string;
publishedAt: string;
authorDisplayName;
authorProfileImageUrl;
authorChannelId;
textDisplay;
publishedAt;
}

View File

@ -1,18 +1,18 @@
/*
This file is part of FreeTube.
This file is part of FreeTube.
FreeTube is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
FreeTube is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
FreeTube is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
FreeTube is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with FreeTube. If not, see <http://www.gnu.org/licenses/>.
You should have received a copy of the GNU General Public License
along with FreeTube. If not, see <http://www.gnu.org/licenses/>.
*/

View File

@ -1,18 +1,18 @@
/*
This file is part of FreeTube.
This file is part of FreeTube.
FreeTube is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
FreeTube is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
FreeTube is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
FreeTube is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with FreeTube. If not, see <http://www.gnu.org/licenses/>.
You should have received a copy of the GNU General Public License
along with FreeTube. If not, see <http://www.gnu.org/licenses/>.
*/
@ -28,128 +28,101 @@ along with FreeTube. If not, see <http://www.gnu.org/licenses/>.
*
* @return {Void}
*/
function playVideo(videoId, videoThumbnail = '', useWindowPlayer = false) {
if (useWindowPlayer === false){
clearMainContainer();
startLoadingAnimation();
}
else{
showToast('Getting video information. Please wait...')
}
function playVideo(videoId) {
hideViews();
let subscribeText = '';
let savedText = '';
let savedIconClass = '';
let savedIconColor = '';
let video480p;
let video720p;
let videoSubtitles = '';
let subtitleHtml = '';
let subtitleLabel;
let subtitleLanguage;
let subtitleCode;
let subtitleUrl;
let defaultUrl;
let defaultQuality;
let channelId;
let videoHtml;
let videoType = 'video';
let embedPlayer = "<iframe width='560' height='315' src='https://www.youtube-nocookie.com/embed/" + videoId + "?rel=0' frameborder='0' allow='autoplay; encrypted-media' allowfullscreen></iframe>";
let useEmbedPlayer = false;
let validUrl;
let videoLikes;
let videoDislikes;
let totalLikes;
let likePercentage;
playerView.playerSeen = true;
playerView.videoId = videoId;
playerView.video480p = undefined;
playerView.video720p = undefined;
playerView.embededHtml = "<iframe width='560' height='315' src='https://www.youtube-nocookie.com/embed/" + videoId + "?rel=0' frameborder='0' allow='autoplay; encrypted-media' allowfullscreen></iframe>";
const checkSavedVideo = videoIsSaved(videoId);
// Change the save button icon and text depending on if the user has saved the video or not.
checkSavedVideo.then((results) => {
if (results === false) {
savedText = 'SAVE';
savedIconClass = 'far unsaved';
playerView.savedText = 'FAVORITE';
playerView.savedIconType = 'far unsaved';
} else {
savedText = 'SAVED';
savedIconClass = 'fas saved';
playerView.savedText = 'FAVORITED';
playerView.savedIconType = 'fas saved';
}
});
youtubeAPI('videos', {
part: 'statistics',
id: videoId,
}, function(data) {
console.log(data);
}, function (data) {
// Figure out the width for the like/dislike bar.
videoLikes = data['items'][0]['statistics']['likeCount'];
videoDislikes = data['items'][0]['statistics']['dislikeCount'];
totalLikes = parseInt(videoLikes) + parseInt(videoDislikes);
likePercentage = parseInt((videoLikes / totalLikes) * 100);
playerView.videoLikes = data['items'][0]['statistics']['likeCount'];
playerView.videoDislikes = data['items'][0]['statistics']['dislikeCount'];
let totalLikes = parseInt(playerView.videoLikes) + parseInt(playerView.videoDislikes);
playerView.likePercentage = parseInt((playerView.videoLikes / totalLikes) * 100);
});
/*
* FreeTube calls youtube-dl to grab the direct video URL.
*/
youtubedlGetInfo(videoId, (info) => {
console.log(info);
console.log(videoLikes);
channelId = info['author']['id'];
let channelThumbnail = info['author']['avatar'];
playerView.videoTitle = info['title'];
playerView.channelName = info['author']['name'];
playerView.channelId = info['author']['id'];
playerView.channelIcon = info['author']['avatar'];
let videoUrls = info['formats'];
// Add commas to the video view count.
const videoViews = info['view_count'].toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
playerView.videoViews = info['view_count'].toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
videoThumbnail = info['player_response']['videoDetails']['thumbnail']['thumbnails'][3]['url'];
playerView.videoThumbnail = info['player_response']['videoDetails']['thumbnail']['thumbnails'][3]['url'];
// Format the date to a more readable format.
let dateString = new Date(info['published']);
dateString.setDate(dateString.getDate() + 1);
const publishedDate = dateFormat(dateString, "mmm dS, yyyy");
playerView.publishedDate = dateFormat(dateString, "mmm dS, yyyy");
let description = info['description'];
// Adds clickable links to the description.
description = autolinker.link(description);
playerView.description = autolinker.link(description);
// Search through the returned object to get the 480p and 720p video URLs (If available)
Object.keys(videoUrls).forEach((key) => {
switch (videoUrls[key]['itag']) {
case '18':
video480p = decodeURIComponent(videoUrls[key]['url']);
console.log(video480p);
playerView.video480p = decodeURIComponent(videoUrls[key]['url']);
//console.log(video480p);
break;
case '22':
video720p = decodeURIComponent(videoUrls[key]['url']);
console.log(video720p);
playerView.video720p = decodeURIComponent(videoUrls[key]['url']);
//console.log(video720p);
break;
}
});
let useEmbedPlayer = false;
// Default to the embeded player if the URLs cannot be found.
if (typeof(video720p) === 'undefined' && typeof(video480p) === 'undefined') {
if (typeof(playerView.video720p) === 'undefined' && typeof(playerView.video480p) === 'undefined') {
//useEmbedPlayer = true;
playerView.currentQuality = 'EMBED';
playerView.playerSeen = false;
useEmbedPlayer = true;
defaultQuality = 'EMBED';
videoHtml = embedPlayer.replace(/\&quot\;/g, '"');
showToast('Unable to get video file. Reverting to embeded player.');
} else if (typeof(video720p) === 'undefined' && typeof(video480p) !== 'undefined') {
// Default to the 480p video if the 720p URL cannot be found.
defaultUrl = video480p;
defaultQuality = '480p';
playerView.videoUrl = playerView.video480p;
playerView.currentQuality = '480p';
} else {
// Default to the 720p video.
defaultUrl = video720p;
defaultQuality = '720p';
// Force the embeded player if needed.
//videoHtml = embedPlayer;
playerView.videoUrl = playerView.video720p;
playerView.currentQuality = '720p';
}
if (!useEmbedPlayer) {
//videoHtml = '<video class="videoPlayer" type="application/x-mpegURL" onmousemove="hideMouseTimeout()" onmouseleave="removeMouseTimeout()" controls="" src="' + defaultUrl + '" poster="' + videoThumbnail + '" autoplay>';
let videoHtml = '';
if (typeof(info.player_response.captions) === 'object') {
if (typeof(info.player_response.captions.playerCaptionsTracklistRenderer.captionTracks) === 'object') {
@ -168,84 +141,37 @@ function playVideo(videoId, videoThumbnail = '', useWindowPlayer = false) {
}
}
//videoHtml = videoHtml + '</video>';
}
playerView.subtitleHtml = videoHtml;
}
const checkSubscription = isSubscribed(channelId);
// Change the subscribe button text depending on if the user has subscribed to the channel or not.
const checkSubscription = isSubscribed(playerView.channelId);
checkSubscription.then((results) => {
const subscribeButton = document.getElementById('subscribeButton');
if (results === false) {
if (subscribeButton != null) {
subscribeButton.innerHTML = 'SUBSCRIBE';
playerView.subscribedText = 'SUBSCRIBE';
}
} else {
if (subscribeButton != null) {
subscribeButton.innerHTML = 'UNSUBSCRIBE';
playerView.subscribedText = 'UNSUBSCRIBE';
}
}
});
const playerTemplate = require('./templates/player.html')
mustache.parse(playerTemplate);
const rendered = mustache.render(playerTemplate, {
videoQuality: defaultQuality,
subtitleHtml: videoHtml,
defaultUrl: defaultUrl,
videoTitle: info['title'],
videoViews: videoViews,
videoThumbnail: videoThumbnail,
channelName: info['author']['name'],
videoLikes: videoLikes,
videoDislikes: videoDislikes,
likePercentage: likePercentage,
videoId: videoId,
channelId: channelId,
channelIcon: channelThumbnail,
publishedDate: publishedDate,
description: description,
isSubscribed: subscribeText,
savedText: savedText,
savedIconClass: savedIconClass,
savedIconColor: savedIconColor,
video480p: video480p,
video720p: video720p,
embedPlayer: embedPlayer,
});
// Add the video to the user's history
addToHistory(videoId);
if (useWindowPlayer){
// Create a new browser window.
const BrowserWindow = electron.remote.BrowserWindow;
let newWindow = new BrowserWindow({
width: 1200,
height: 700
});
let playerWindowHeader = require('./templates/playerWindow.html');
mustache.parse(playerWindowHeader);
const playerHeaderRender = mustache.render(playerWindowHeader, {
videoId: videoId,
channelId: channelId
});
newWindow.loadURL('data:text/html;charset=UTF-8,' + encodeURIComponent(playerHeaderRender + rendered), {
baseURLForDataURL: `file://${__dirname}/src`
});
}
else{
$('#main').html(rendered);
stopLoadingAnimation();
showVideoRecommendations(videoId);
loadingView.seen = false;
if (subscriptionView.seen === false && aboutView.seen === false && headerView.seen === false && searchView.seen === false && settingsView.seen === false && popularView.seen === false && savedView.seen === false && historyView.seen === false && channelView.seen === false && channelVideosView.seen === false) {
playerView.seen = true;
}
else{
return;
}
addToHistory(videoId);
// Hide subtitles by default
if (typeof(info['subtitles']) !== 'undefined' && Object.keys(info['subtitles']).length > 0) {
let textTracks = $('.videoPlayer').get(0).textTracks;
@ -253,15 +179,8 @@ function playVideo(videoId, videoThumbnail = '', useWindowPlayer = false) {
textTracks[track].mode = 'hidden';
});
}
}
// Sometimes a video URL is found, but the video will not play. I believe the issue is
// that the video has yet to render for that quality, as the video will be available at a later time.
// This will check the URLs and switch video sources if there is an error.
//checkVideoUrls(video480p, video720p);
window.setTimeout(checkVideoUrls, 5000, video480p, video720p);
window.setTimeout(checkVideoUrls, 5000, playerView.video480p, playerView.video720p);
});
}
@ -272,7 +191,7 @@ function playVideo(videoId, videoThumbnail = '', useWindowPlayer = false) {
*
* @return {Void}
*/
function openMiniPlayer(videoThumbnail) {
function openMiniPlayer() {
let lastTime;
let videoHtml;
@ -299,13 +218,13 @@ function openMiniPlayer(videoThumbnail) {
mustache.parse(template);
const rendered = mustache.render(template, {
videoHtml: videoHtml,
videoThumbnail: videoThumbnail,
videoThumbnail: playerView.thumbnail,
startTime: lastTime,
});
// Render the template to the new browser window.
miniPlayer.loadURL("data:text/html;charset=utf-8," + encodeURI(rendered));
});
}
}
/**
* Change the quality of the current video.
@ -316,7 +235,7 @@ function openMiniPlayer(videoThumbnail) {
*
* @return {Void}
*/
function changeQuality(videoHtml, qualityType, isEmbed = false) {
function changeQuality(url, qualityText, isEmbed = false) {
if (videoHtml == '') {
showToast('Video quality type is not available. Unable to change quality.')
return;
@ -324,8 +243,8 @@ function changeQuality(videoHtml, qualityType, isEmbed = false) {
videoHtml = videoHtml.replace(/\&quot\;/g, '"');
console.log(videoHtml);
console.log(isEmbed);
ft.log('HTML Video: ', videoHtml);
ft.log('(Is the video embeded?) isEmbed: ', isEmbed);
// The YouTube API creates 2 more iFrames. This is why a boolean value is sent
// with the function.
@ -333,8 +252,8 @@ function changeQuality(videoHtml, qualityType, isEmbed = false) {
const html5Player = document.getElementsByClassName('videoPlayer');
console.log(embedPlayer);
console.log(html5Player);
ft.log('Embeded Player Element: ', embedPlayer);
ft.log('HTML5 Player Element: ', html5Player);
if (isEmbed && html5Player.length == 0) {
// The embeded player is already playing. Return.

View File

@ -1,111 +1,104 @@
/*
This file is part of FreeTube.
This file is part of FreeTube.
FreeTube is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
FreeTube is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
FreeTube is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
FreeTube is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with FreeTube. If not, see <http://www.gnu.org/licenses/>.
You should have received a copy of the GNU General Public License
along with FreeTube. If not, see <http://www.gnu.org/licenses/>.
*/
/*
* File used for functions related to saving videos
*/
* File used for functions related to saving videos
*/
/**
* Adds a video to the user's saved video database.
*
* @param {string} videoId - The video ID of the video that will be saved.
*
* @return {Void}
*/
function addSavedVideo(videoId){
* Adds a video to the user's saved video database.
*
* @param {string} videoId - The video ID of the video that will be saved.
*
* @return {Void}
*/
function addSavedVideo(videoId) {
let checkIfSaved = videoIsSaved(videoId);
checkIfSaved.then((saved) => {
if (saved === false){
if (saved === false) {
let data = {
videoId: videoId,
timeSaved: new Date().getTime(),
};
savedVidsDb.insert(data, (err, newDoc) => {
showToast('Video has been saved!');
showToast('The video has been favorited!');
});
}
else{
showToast('Video already exists in saved file.')
} else {
showToast('The video has already been favorited!')
}
});
}
/**
* Removes a video from the user's saved video database.
*
* @param {string} videoId - The video ID of the video that will be removed.
*
* @return {Void}
*/
function removeSavedVideo(videoId, string){
* Removes a video from the user's saved video database.
*
* @param {string} videoId - The video ID of the video that will be removed.
*
* @return {Void}
*/
function removeSavedVideo(videoId, string) {
savedVidsDb.remove({
videoId: videoId
}, {}, (err, numRemoved) => {
showToast('Video has been removed from saved list.');
showToast('Video has been removed from the favorites list.');
});
}
/**
* Toggles the save video button styling and saved / remove a video based on the current status.
*
* @param {string} videoId - The video ID to toggle between.
*
* @return {Void}
*/
* Toggles the save video button styling and saved / remove a video based on the current status.
*
* @param {string} videoId - The video ID to toggle between.
*
* @return {Void}
*/
function toggleSavedVideo(videoId) {
event.stopPropagation();
const checkIfSaved = videoIsSaved(videoId);
const saveIcon = document.getElementById('saveIcon');
const savedText = document.getElementById('savedText');
checkIfSaved.then((results) => {
if (results === false) {
savedText.innerHTML = 'SAVED';
saveIcon.classList.remove('far');
saveIcon.classList.remove('unsaved');
saveIcon.classList.add('fas');
saveIcon.classList.add('saved');
playerView.savedText = 'FAVORITED';
playerView.savedIconType = 'fas saved';
addSavedVideo(videoId);
} else {
savedText.innerHTML = 'SAVE';
saveIcon.classList.remove('fas');
saveIcon.classList.remove('saved');
saveIcon.classList.add('far');
saveIcon.classList.add('unsaved');
playerView.savedText = 'FAVORITE';
playerView.savedIconType = 'far unsaved';
removeSavedVideo(videoId);
}
});
}
/**
* Checks if a video was saved in the user's saved video database
*
* @param {string} videoId - The video ID to check
*
* @return {promise} - A boolean value if the video was found or not.
*/
* Checks if a video was saved in the user's saved video database
*
* @param {string} videoId - The video ID to check
*
* @return {promise} - A boolean value if the video was found or not.
*/
function videoIsSaved(videoId) {
return new Promise((resolve, reject) => {
savedVidsDb.find({videoId: videoId}, (err, docs) => {
savedVidsDb.find({
videoId: videoId
}, (err, docs) => {
if (jQuery.isEmptyObject(docs)) {
resolve(false);
} else {
@ -121,8 +114,8 @@ function videoIsSaved(videoId) {
* @return {Void}
*/
function showSavedVideos(){
clearMainContainer();
startLoadingAnimation();
//clearMainContainer();
//startLoadingAnimation();
console.log('checking saved videos');
let videoList = '';
@ -133,12 +126,11 @@ function showSavedVideos(){
}).exec((err, docs) => {
// The YouTube API requires a max of 50 videos to be shown. Don't show more than 50.
// TODO: Allow the app to show more than 50 saved videos.
if(docs.length > 49){
if (docs.length > 49) {
for (let i = 0; i < 49; i++) {
videoList = videoList + ',' + docs[i].videoId;
}
}
else{
} else {
docs.forEach((video) => {
videoList = videoList + ',' + video.videoId;
});
@ -151,14 +143,14 @@ function showSavedVideos(){
maxResults: 50,
}, (data) => {
// Render the videos to the screen
createVideoListContainer('Saved Videos:');
let grabDuration = getDuration(data.items);
grabDuration.then((videoList) => {
savedView.videoList = [];
loadingView.seen = false;
videoList.items.forEach((video) => {
displayVideo(video, 'saved');
});
});
stopLoadingAnimation();
});
});
}

View File

@ -1,16 +1,13 @@
/*
This file is part of FreeTube.
FreeTube is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
FreeTube is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with FreeTube. If not, see <http://www.gnu.org/licenses/>.
*/
@ -22,20 +19,14 @@ along with FreeTube. If not, see <http://www.gnu.org/licenses/>.
*/
// To any third party devs that fork the project, please be ethical and change the API keys.
const apiKeyBank = ['AIzaSyC9E579nh_qqxg6BH4xIce3k_7a9mT4uQc', 'AIzaSyCKplYT6hZIlm2O9FbWTi1G7rkpsLNTq78', 'AIzaSyAE5xzh5GcA_tEDhXmMFd1pEzrL-W7z51E', 'AIzaSyDoFzqwuO9l386eF6BmNkVapjiTJ93CBy4', 'AIzaSyBljfZFPioB0TRJAj-0LS4tlIKl2iucyY4'];
const apiKeyBank = ['AIzaSyC9E579nh_qqxg6BH4xIce3k_7a9mT4uQc', 'AIzaSyCKplYT6hZIlm2O9FbWTi1G7rkpsLNTq78', 'AIzaSyAE5xzh5GcA_tEDhXmMFd1pEzrL-W7z51E', 'AIzaSyDoFzqwuO9l386eF6BmNkVapjiTJ93CBy4', 'AIzaSyBljfZFPioB0TRJAj-0LS4tlIKl2iucyY4', 'AIzaSyAiKgR75e3XAznCcb1cj4NUJ5rR_y3uB8E', 'AIzaSyBZL2Ie1masjwbIa74bR2GONF3p518npVU', 'AIzaSyA0CkT2lS1q9HHaFYGNGM4Ycjl1kmRy22s', 'AIzaSyDPy5jq2l1Bgv3-MbpGdZd3W3ik1BMZeDc'];
/**
* Display the settings screen to the user.
*
* @return {Void}
*/
function showSettings() {
clearMainContainer();
startLoadingAnimation();
let isChecked = '';
let key = '';
function updateSettingsView() {
/*
* Check the settings database for the user's current settings. This is so the
* settings page has the correct toggles related when it is rendered.
@ -45,7 +36,7 @@ function showSettings() {
switch (setting['_id']) {
case 'apiKey':
if (apiKeyBank.indexOf(setting['value']) == -1) {
key = setting['value'];
settingsView.apiKey = setting['value'];
}
break;
case 'theme':
@ -55,28 +46,17 @@ function showSettings() {
}
});
// Grab the settings.html template to prepare for rendering
const settingsTemplate = require('./templates/settings.html')
mustache.parse(settingsTemplate);
const rendered = mustache.render(settingsTemplate, {
isChecked: isChecked,
key: key,
});
// Render template to application
$('#main').html(rendered);
stopLoadingAnimation();
// Check / uncheck the switch depending on the user's settings.
if (currentTheme === 'light') {
document.getElementById('themeSwitch').checked = false;
settingsView.useTheme = false;
} else {
document.getElementById('themeSwitch').checked = true;
settingsView.useTheme = true;
}
if (useTor) {
document.getElementById('torSwitch').checked = true;
settingsView.useTor = true;
} else {
document.getElementById('torSwitch').checked = false;
settingsView.useTor = false;
}
});
}
@ -89,12 +69,12 @@ function showSettings() {
function checkDefaultSettings() {
// Grab a random API Key.
apiKey = apiKeyBank[Math.floor(Math.random() * apiKeyBank.length)];
settingsView.apiKey = apiKeyBank[Math.floor(Math.random() * apiKeyBank.length)];
let newSetting;
let settingDefaults = {
'theme': 'light',
'apiKey': apiKey,
'apiKey': settingsView.apiKey,
'useTor': false
};
@ -121,7 +101,10 @@ function checkDefaultSettings() {
break;
case 'apiKey':
if (apiKeyBank.indexOf(docs[0]['value']) == -1) {
apiKey = docs[0]['value'];
settingsView.apiKey = docs[0]['value'];
}
else{
settingsView.apiKey = settingDefaults.apiKey;
}
break;
case 'useTor':
@ -146,7 +129,12 @@ function updateSettings() {
let key = document.getElementById('api-key').value;
let theme = 'light';
apiKey = apiKeyBank[Math.floor(Math.random() * apiKeyBank.length)];
if (apiKeyBank.indexOf(key) == -1 && key !== '') {
settingsView.apiKey = key;
}
else{
settingsView.apiKey = apiKeyBank[Math.floor(Math.random() * apiKeyBank.length)];
}
console.log(themeSwitch);
@ -177,20 +165,12 @@ function updateSettings() {
useTor = torSwitch;
});
if (key != '') {
settingsDb.update({
_id: 'apiKey'
}, {
value: key
}, {});
} else {
// To any third party devs that fork the project, please be ethical and change the API key.
settingsDb.update({
_id: 'apiKey'
}, {
value: apiKey
value: settingsView.apiKey
}, {});
}
showToast('Settings have been saved.');
}
@ -300,11 +280,6 @@ function importSubscriptions(){
let fileType = (i < 0) ? '' : fileLocation[0].substr(i);
console.log(fileType);
/*if (fileType !== '.db'){
showToast('Incorrect filetype. Import was unsuccessful.');
return;
}*/
fs.readFile(fileLocation[0], function(readErr, data){
if(readErr){
showToast('Unable to read file. File may be corrupt or have invalid permissions.');
@ -424,3 +399,5 @@ function clearFile(type, showMessage = true){
}
})
}
checkDefaultSettings();

View File

@ -1,18 +1,18 @@
/*
This file is part of FreeTube.
This file is part of FreeTube.
FreeTube is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
FreeTube is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
FreeTube is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
FreeTube is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with FreeTube. If not, see <http://www.gnu.org/licenses/>.
You should have received a copy of the GNU General Public License
along with FreeTube. If not, see <http://www.gnu.org/licenses/>.
*/
@ -21,6 +21,9 @@ along with FreeTube. If not, see <http://www.gnu.org/licenses/>.
* File for all functions related to subscriptions.
*/
let subscriptionTimer;
let checkSubscriptions = true;
/**
* Add a channel to the user's subscription database.
*
@ -29,12 +32,12 @@ along with FreeTube. If not, see <http://www.gnu.org/licenses/>.
* @return {Void}
*/
function addSubscription(channelId, useToast = true) {
console.log(channelId);
ft.log('Channel ID: ', channelId);
// Request YouTube API
youtubeAPI('channels', {
part: 'snippet',
id: channelId,
}, function(data) {
}, (data) => {
const channelInfo = data['items'][0]['snippet'];
const channelName = channelInfo['title'];
const thumbnail = channelInfo['thumbnails']['high']['url'];
@ -77,18 +80,21 @@ function removeSubscription(channelId) {
*
* @return {Void}
*/
function loadSubscriptions() {
clearMainContainer();
showToast('Getting Subscriptions. Please wait...');
const loading = document.getElementById('loading');
startLoadingAnimation()
function loadSubscriptions() {
if (checkSubscriptions === false && subscriptionView.videoList.length > 0){
console.log('Will not load subscriptions. Timer still on.');
loadingView.seen = false;
return;
}
else{
showToast('Refreshing Subscription List. Please wait...');
checkSubscriptions = false;
}
let videoList = [];
const subscriptions = returnSubscriptions();
// Welcome to callback hell, we hope you enjoy your stay.
subscriptions.then((results) => {
let channelId = '';
let videoList = [];
@ -109,6 +115,7 @@ function loadSubscriptions() {
console.log(data);
videoList = videoList.concat(data.items);
counter++;
progressView.progressWidth = (counter / results.length) * 100;
if (counter === results.length) {
videoList.sort((a, b) => {
const date1 = Date.parse(a.snippet.publishedAt);
@ -117,19 +124,19 @@ function loadSubscriptions() {
return date2.valueOf() - date1.valueOf();
});
// Render the videos to the application.
createVideoListContainer('Latest Subscriptions:');
// The YouTube website limits the subscriptions to 100 before grabbing more so we only show 100
// to keep the app running at a good speed.
if (videoList.length < 50) {
let grabDuration = getDuration(videoList.slice(0, 49));
grabDuration.then((list) => {
subscriptionView.videoList = [];
list.items.forEach((video) => {
displayVideo(video);
displayVideo(video, 'subscriptions');
});
stopLoadingAnimation();
loadingView.seen = false;
progressView.seen = false;
progressView.progressWidth = 0;
});
} else {
console.log(videoList);
@ -142,10 +149,17 @@ function loadSubscriptions() {
secondBatchDuration.then((list2) => {
finishedList = finishedList.concat(list2.items);
console.log(finishedList);
subscriptionView.videoList = [];
finishedList.forEach((video) => {
displayVideo(video);
displayVideo(video, 'subscriptions');
});
stopLoadingAnimation();
loadingView.seen = false;
progressView.seen = false;
progressView.progressWidth = 0;
subscriptionTimer = window.setTimeout(() => {
checkSubscriptions = true;
}, 60000);
});
});
}
@ -157,14 +171,12 @@ function loadSubscriptions() {
} else {
// User has no subscriptions. Display message.
const container = document.getElementById('main');
stopLoadingAnimation();
container.innerHTML = `<h2 class="message">Your Subscription list is currently empty. Start adding subscriptions
to see them here.<br /><br /><i class="far fa-frown" style="font-size: 200px"></i></h2>`;
loadingView.seen = false;
headerView.seen = false;
noSubscriptions.seen = true;
}
});
}
}
/**
* Get the list of subscriptions from the user's subscription database.

432
src/js/templates.js Normal file
View File

@ -0,0 +1,432 @@
/*
This file is part of FreeTube.
FreeTube is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
FreeTube is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with FreeTube. If not, see <http://www.gnu.org/licenses/>.
*/
import Vue from './js/vue.js';
const mainHeaderTemplate = require('./templates/mainHeader.html');
const aboutTemplate = require('./templates/about.html');
const settingsTemplate = require('./templates/settings.html');
const videoListTemplate = require('./templates/videoTemplate.html');
const nextPageTemplate = require('./templates/searchNextPage.html');
const playerTemplate = require('./templates/player.html');
const channelTemplate = require('./templates/channelView.html');
const progressViewTemplate = require('./templates/progressView.html');
/*
* Progress view
*
* Shows progress bar on bottom of application.
*
* seen: Toggles visibility of view
* progressWidth: sets width of the progress bar
*/
let progressView = new Vue({
el: '#progressView',
data: {
seen: true,
progressWidth: 0
},
template: progressViewTemplate
});
let loadingView = new Vue({
el: '#loading',
data: {
seen: false
}
});
let noSubscriptions = new Vue({
el: '#noSubscriptions',
data: {
seen: false
}
});
let sideNavBar = new Vue({
el: '#sideNav',
methods: {
subscriptions: (event) => {
hideViews();
if(subscriptionView.videoList.length === 0){
loadingView.seen = true;
}
headerView.seen = true;
headerView.title = 'Latest Subscriptions';
subscriptionView.seen = true;
loadSubscriptions();
},
popular: (event) => {
hideViews();
if (loadingView.seen !== false){
loadingView.seen = false;
}
if(popularView.videoList.length === 0){
loadingView.seen = true;
}
headerView.seen = true;
headerView.title = 'Most Popular';
popularView.seen = true;
showMostPopular();
},
saved: (event) => {
hideViews();
if (loadingView.seen !== false){
loadingView.seen = false;
}
if(savedView.videoList.length === 0){
loadingView.seen = true;
}
headerView.seen = true;
headerView.title = 'Favorited Videos';
savedView.seen = true;
showSavedVideos();
},
history: (event) => {
hideViews();
if (loadingView.seen !== false){
loadingView.seen = false;
}
if(historyView.videoList.length === 0){
loadingView.seen = true;
}
headerView.seen = true;
headerView.title = 'Video History';
historyView.seen = true;
showHistory();
},
settings: (event) => {
hideViews();
if (loadingView.seen !== false){
loadingView.seen = false;
}
settingsView.seen = true;
updateSettingsView();
},
about: (event) => {
hideViews();
if (loadingView.seen !== false){
loadingView.seen = false;
}
aboutView.seen = true;
}
}
});
let headerView = new Vue({
el: '#mainHeaderView',
data: {
seen: true,
title: 'Latest Subscriptions'
},
template: mainHeaderTemplate
});
let subscriptionView = new Vue({
el: '#subscriptionView',
data: {
seen: true,
isSearch: false,
videoList: []
},
methods: {
play: (videoId) => {
loadingView.seen = true;
playVideo(videoId);
},
channel: (channelId) => {
goToChannel(channelId);
},
toggleSave: (videoId) => {
toggleSavedVideo(videoId);
},
copy: (site, videoId) => {
const url = 'https://' + site + '/watch?v=' + videoId;
clipboard.writeText(url);
showToast('URL has been copied to the clipboard');
}
},
template: videoListTemplate
});
let popularView = new Vue({
el: '#popularView',
data: {
seen: false,
isSearch: false,
videoList: []
},
methods: {
play: (videoId) => {
loadingView.seen = true;
playVideo(videoId);
},
channel: (channelId) => {
goToChannel(channelId);
},
toggleSave: (videoId) => {
addSavedVideo(videoId);
},
copy: (site, videoId) => {
const url = 'https://' + site + '/watch?v=' + videoId;
clipboard.writeText(url);
showToast('URL has been copied to the clipboard');
}
},
template: videoListTemplate
});
let savedView = new Vue({
el: '#savedView',
data: {
seen: false,
isSearch: false,
videoList: []
},
methods: {
play: (videoId) => {
loadingView.seen = true;
playVideo(videoId);
},
channel: (channelId) => {
goToChannel(channelId);
},
toggleSave: (videoId) => {
addSavedVideo(videoId);
},
copy: (site, videoId) => {
const url = 'https://' + site + '/watch?v=' + videoId;
clipboard.writeText(url);
showToast('URL has been copied to the clipboard');
}
},
template: videoListTemplate
});
let historyView = new Vue({
el: '#historyView',
data: {
seen: false,
isSearch: false,
videoList: []
},
methods: {
play: (videoId) => {
loadingView.seen = true;
playVideo(videoId);
},
channel: (channelId) => {
goToChannel(channelId);
},
toggleSave: (videoId) => {
addSavedVideo(videoId);
},
copy: (site, videoId) => {
const url = 'https://' + site + '/watch?v=' + videoId;
clipboard.writeText(url);
showToast('URL has been copied to the clipboard');
}
},
template: videoListTemplate
});
let aboutView = new Vue({
el: '#aboutView',
data: {
seen: false,
versionNumber: electron.remote.app.getVersion()
},
template: aboutTemplate
});
let settingsView = new Vue({
el: '#settingsView',
data: {
seen: false,
useTheme: false,
useTor: false,
apiKey: ''
},
template: settingsTemplate
});
let searchView = new Vue({
el: '#searchView',
data: {
seen: false,
isSearch: true,
nextPageToken: '',
videoList: []
},
methods: {
play: (videoId) => {
loadingView.seen = true;
playVideo(videoId);
},
channel: (channelId) => {
goToChannel(channelId);
},
toggleSave: (videoId) => {
addSavedVideo(videoId);
},
copy: (site, videoId) => {
const url = 'https://' + site + '/watch?v=' + videoId;
clipboard.writeText(url);
showToast('URL has been copied to the clipboard');
},
nextPage: (nextPageToken) => {
console.log(searchView.nextPageToken);
search(searchView.nextPageToken);
}
},
template: videoListTemplate
});
let channelView = new Vue({
el: '#channelView',
data: {
seen: false,
id: '',
name: '',
icon: '',
baner: '',
subCount: '',
subButtonText: '',
description: ''
},
methods: {
subscription: (channelId) => {
toggleSubscription(channelId);
},
},
template: channelTemplate
});
let channelVideosView = new Vue({
el: '#channelVideosView',
data: {
seen: false,
isSearch: false,
videoList: []
},
methods: {
play: (videoId) => {
loadingView.seen = true;
playVideo(videoId);
},
channel: (channelId) => {
goToChannel(channelId);
},
toggleSave: (videoId) => {
addSavedVideo(videoId);
},
copy: (site, videoId) => {
const url = 'https://' + site + '/watch?v=' + videoId;
clipboard.writeText(url);
showToast('URL has been copied to the clipboard');
},
},
template: videoListTemplate
});
let playerView = new Vue({
el: '#playerView',
data: {
seen: false,
publishedDate: '',
videoUrl: '',
videoId: '',
channelId: '',
channelIcon: '',
channelName: '',
subscribedText: '',
savedText: '',
savedIconType: 'far',
description: '',
videoThumbnail: '',
subtitleHtml: '',
currentQuality: '',
video480p: '',
video720p: '',
embededHtml: '',
currentSpeed: 1,
videoTitle: '',
videoViews: '',
likePercentage: 0,
videoLikes: 0,
videoDislikes: 0,
playerSeen: true,
recommendedVideoList: []
},
methods: {
channel: (channelId) => {
goToChannel(channelId);
},
subscription: (videoId) => {
toggleSubscription(videoId);
},
quality: (url, qualityText) => {
console.log(url);
console.log(qualityText);
if(playerView.playerSeen === true){
// Update time to new url
const currentPlayBackTime = $('.videoPlayer').get(0).currentTime;
console.log(currentPlayBackTime);
playerView.videoUrl = url;
playerView.currentQuality = qualityText;
setTimeout(() => {$('.videoPlayer').get(0).currentTime = currentPlayBackTime;}, 100);
}
else{
playerView.playerSeen = true;
playerView.videoUrl = url;
playerView.currentQuality = qualityText;
}
},
embededPlayer: () => {
playerView.playerSeen = false;
playerView.currentQuality = 'EMBED';
},
copy: (site, videoId) => {
const url = 'https://' + site + '/watch?v=' + videoId;
clipboard.writeText(url);
showToast('URL has been copied to the clipboard');
},
save: (videoId) => {
toggleSavedVideo(videoId);
},
play: (videoId) => {
loadingView.seen = true;
playVideo(videoId);
}
},
template: playerTemplate
});
function hideViews(){
subscriptionView.seen = false;
noSubscriptions.seen = false;
aboutView.seen = false;
headerView.seen = false;
searchView.seen = false;
settingsView.seen = false;
popularView.seen = false;
savedView.seen = false;
historyView.seen = false;
playerView.seen = false;
channelView.seen = false;
channelVideosView.seen = false;
}

View File

@ -1,45 +1,45 @@
/*
This file is part of FreeTube.
This file is part of FreeTube.
FreeTube is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
FreeTube is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
FreeTube is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
FreeTube is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with FreeTube. If not, see <http://www.gnu.org/licenses/>.
You should have received a copy of the GNU General Public License
along with FreeTube. If not, see <http://www.gnu.org/licenses/>.
*/
// import {freeTubeLog} from './events.js';
/*
* A file for checking / managing updates
*/
const updateChecker = require('github-version-checker');
const updateChecker = require('github-version-checker');
const options = {
const options = {
token: 'USERACCESSTOKEN', // personal access token. Github will not allow commiting the access token, which is why this is blank.
repo: 'freetube', // repository name
owner: 'freetubeapp', // repository owner
currentVersion: require('electron').remote.app.getVersion(), // your app's current version
fetchTags: false // whether to fetch releases or tags
};
};
const openReleasePage = function(){
const openReleasePage = function () {
shell.openExternal('https://github.com/FreeTubeApp/FreeTube/releases');
}
}
/*function checkForUpdates() {
updateChecker(options, function(error, update) { // callback function
if (error){
showToast('There was a problem with checking for updates');
console.log(error);
freeTubeLog(error);
}
if (update) { // print some update info if an update is available
confirmFunction(update.name + ' is now available! Would you like to download the update?', openReleasePage);
@ -50,7 +50,7 @@ along with FreeTube. If not, see <http://www.gnu.org/licenses/>.
});
}*/
updateChecker(options, function(error, update) { // callback function
updateChecker(options, function (error, update) { // callback function
if (error) throw error;
if (update) { // print some update info if an update is available
confirmFunction(update.name + ' is now available! Would you like to download the update?', openReleasePage);

View File

@ -1,18 +1,18 @@
/*
This file is part of FreeTube.
This file is part of FreeTube.
FreeTube is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
FreeTube is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
FreeTube is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
FreeTube is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with FreeTube. If not, see <http://www.gnu.org/licenses/>.
You should have received a copy of the GNU General Public License
along with FreeTube. If not, see <http://www.gnu.org/licenses/>.
*/
@ -32,8 +32,11 @@ function search(nextPageToken = '') {
}
if (nextPageToken === '') {
clearMainContainer();
startLoadingAnimation();
hideViews();
headerView.seen = true;
headerView.title = 'Search Results';
searchView.videoList = [];
searchView.seen = true;
} else {
console.log(nextPageToken);
showToast('Fetching results. Please wait...');
@ -44,8 +47,8 @@ function search(nextPageToken = '') {
part: 'id',
pageToken: nextPageToken,
maxResults: 25,
}, function(data) {
console.log(data);
}, function (data) {
ft.log('Search Data: ', data);
let channels = data.items.filter((item) => {
if (item.id.kind === 'youtube#channel') {
@ -65,15 +68,15 @@ function search(nextPageToken = '') {
}
});
console.log(channels);
console.log(typeof(channels));
console.log(playlists);
ft.log('Channels: ', channels);
ft.log('Typeof object above (channels) ^^', typeof (channels));
ft.log('Playlists', playlists);
if(playlists.length > 0){
if (playlists.length > 0) {
//displayPlaylists(playlists);
}
if(channels.length > 0){
if (channels.length > 0) {
displayChannels(channels);
}
@ -81,14 +84,13 @@ function search(nextPageToken = '') {
grabDuration.then((videoList) => {
console.log(videoList);
videoList.items.forEach(displayVideo);
videoList.items.forEach((video) => {
displayVideo(video, 'search');
});
});
if (nextPageToken === '') {
createVideoListContainer('Search results:');
stopLoadingAnimation();
}
addNextPage(data.nextPageToken);
searchView.nextPageToken = data.nextPageToken;
loadingView.seen = false;
})
}
@ -105,13 +107,13 @@ function getDuration(data) {
for (let i = 0; i < data.length; i++) {
if (videoIdList === '') {
if (typeof(data[i]['id']) === 'string') {
if (typeof (data[i]['id']) === 'string') {
videoIdList = data[i]['id'];
} else {
videoIdList = data[i]['id']['videoId'];
}
} else {
if (typeof(data[i]['id']) === 'string') {
if (typeof (data[i]['id']) === 'string') {
videoIdList = videoIdList + ', ' + data[i]['id'];
} else {
videoIdList = videoIdList + ', ' + data[i]['id']['videoId'];
@ -137,21 +139,21 @@ function getDuration(data) {
*
* @return {Void}
*/
function displayVideo(video, listType = '') {
const videoSnippet = video.snippet;
function displayVideo(videoData, listType = '') {
let video = {};
const videoDuration = parseVideoDuration(video.contentDetails.duration);
//const videoDuration = '00:00';
const videoSnippet = videoData.snippet;
video.duration = parseVideoDuration(videoData.contentDetails.duration);
// Grab the published date for the video and convert to a user readable state.
const dateString = new Date(videoSnippet.publishedAt);
const publishedDate = dateFormat(dateString, "mmm dS, yyyy");
video.publishedDate = dateFormat(dateString, "mmm dS, yyyy");
const searchMenu = $('#videoListContainer').html();
const videoId = video.id;
// Include a remove icon in the list if the application is displaying the history list or saved videos.
const deleteHtml = () => {
video.deleteHtml = () => {
switch (listType) {
case 'saved':
return `<li onclick="removeSavedVideo('${videoId}'); showSavedVideos();">Remove Saved Video</li>`;
@ -160,30 +162,43 @@ function displayVideo(video, listType = '') {
}
};
video.id = videoData.id;
video.youtubeUrl = 'https://youtube.com/watch?v=' + video.id;
video.invidiousUrl = 'https://invidio.us/watch?v=' + video.id;
// Includes text if the video is live.
const liveText = (videoSnippet.liveBroadcastContent === 'live') ? 'LIVE NOW' : '';
const videoListTemplate = require('./templates/videoList.html');
video.liveText = (videoSnippet.liveBroadcastContent === 'live') ? 'LIVE NOW' : '';
video.thumbnail = videoSnippet.thumbnails.medium.url;
video.title = videoSnippet.title;
video.channelName = videoSnippet.channelTitle;
video.channelId = videoSnippet.channelId;
video.description = videoSnippet.description;
video.isVideo = true;
mustache.parse(videoListTemplate);
const rendered = mustache.render(videoListTemplate, {
videoId: videoId,
videoThumbnail: videoSnippet.thumbnails.medium.url,
videoTitle: videoSnippet.title,
channelName: videoSnippet.channelTitle,
videoDescription: videoSnippet.description,
channelId: videoSnippet.channelId,
videoDuration: videoDuration,
publishedDate: publishedDate,
liveText: liveText,
deleteHtml: deleteHtml,
});
// Apply the render to the page
const nextButton = document.getElementById('getNextPage');
if (nextButton === null) {
$('#videoListContainer').append(rendered);
} else {
$(rendered).insertBefore('#getNextPage');
switch (listType) {
case 'subscriptions':
subscriptionView.videoList = subscriptionView.videoList.concat(video);
video.removeFromSave = true;
break;
case 'search':
searchView.videoList = searchView.videoList.concat(video);
video.removeFromSave = false;
break;
case 'popular':
popularView.videoList = popularView.videoList.concat(video);
video.removeFromSave = false;
break;
case 'saved':
savedView.videoList = savedView.videoList.concat(video);
video.removeFromSave = false;
break;
case 'history':
historyView.videoList = historyView.videoList.concat(video);
video.removeFromSave = false;
break;
case 'channel':
channelVideosView.videoList = channelVideosView.videoList.concat(video);
video.removeFromSave = false;
break;
}
}
@ -191,37 +206,39 @@ function displayChannels(channels) {
let channelIds;
channels.forEach((channel) => {
if (typeof(channelIds) === 'undefined') {
if (typeof (channelIds) === 'undefined') {
channelIds = channel.id.channelId;
} else {
channelIds = channelIds + ',' + channel.id.channelId;
}
});
console.log(channelIds);
ft.log('Channel IDs: ', channelIds);
youtubeAPI('channels', {
part: 'snippet,statistics',
id: channelIds,
}, function(data) {
console.log(data);
}, function (data) {
ft.log('Channel Data: ', data);
let items = data['items'].reverse();
const videoListTemplate = require('./templates/channelList.html');
console.log(items);
ft.log('Channel Items: ', items);
items.forEach((item) => {
mustache.parse(videoListTemplate);
let rendered = mustache.render(videoListTemplate, {
channelId: item.id,
channelThumbnail: item.snippet.thumbnails.medium.url,
channelName: item.snippet.title,
channelDescription: item.snippet.description,
subscriberCount: item.statistics.subscriberCount,
videoCount: item.statistics.videoCount,
});
let channelData = {};
$(rendered).insertBefore('#getNextPage');
channelData.channelId = item.id;
channelData.thumbnail = item.snippet.thumbnails.medium.url;
channelData.channelName = item.snippet.title;
channelData.description = item.snippet.description;
channelData.subscriberCount = item.statistics.subscriberCount;
channelData.videoCount = item.statistics.videoCount;
channelData.isVideo = false;
console.log(searchView.videoList);
console.log(channelData);
searchView.videoList = searchView.videoList.concat(channelData);
});
});
}
@ -230,24 +247,24 @@ function displayPlaylists(playlists) {
let playlistIds;
playlists.forEach((playlist) => {
if (typeof(playlistIds) === 'undefined') {
if (typeof (playlistIds) === 'undefined') {
playlistIds = playlist.id.playlistId;
} else {
playlistIds = playlistIds + ',' + playlist.id.playlistId;
}
});
console.log(playlistIds);
ft.log('Playlist IDs: ', playlistIds);
youtubeAPI('playlists', {
part: 'snippet,contentDetails',
id: playlistIds,
}, function(data) {
console.log(data);
}, function (data) {
ft.log('Playlist Data: ', data);
let items = data['items'].reverse();
const playlistListTemplate = require('./templates/playlistList.html');
console.log(items);
ft.log('Playlist Items: ', items);
items.forEach((item) => {
let dateString = new Date(item.snippet.publishedAt);
@ -303,6 +320,8 @@ function addNextPage(nextPageToken) {
* @param {string} videoId - The video ID of the video to get recommendations from.
*/
function showVideoRecommendations(videoId) {
playerView.recommendedVideoList = [];
youtubeAPI('search', {
part: 'id',
type: 'video',
@ -312,21 +331,17 @@ function showVideoRecommendations(videoId) {
let grabDuration = getDuration(data.items);
grabDuration.then((videoList) => {
videoList.items.forEach((video) => {
let data = {}
const snippet = video.snippet;
const videoDuration = parseVideoDuration(video.contentDetails.duration);
const recommTemplate = require('./templates/recommendations.html')
mustache.parse(recommTemplate);
const rendered = mustache.render(recommTemplate, {
videoId: video.id,
videoTitle: snippet.title,
channelName: snippet.channelTitle,
videoThumbnail: snippet.thumbnails.medium.url,
videoDuration: videoDuration,
publishedDate: dateFormat(snippet.publishedAt, "mmm dS, yyyy")
});
const recommendationHtml = $('#recommendations').html();
$('#recommendations').html(recommendationHtml + rendered);
data.duration = parseVideoDuration(video.contentDetails.duration);
data.id = video.id;
data.title = snippet.title;
data.channelName = snippet.channelTitle;
data.thumbnail = snippet.thumbnails.medium.url;
data.publishedDate = dateFormat(snippet.publishedAt, "mmm dS, yyyy");
playerView.recommendedVideoList = playerView.recommendedVideoList.concat(data);
});
});
});
@ -341,10 +356,9 @@ function showVideoRecommendations(videoId) {
function parseSearchText(url = '') {
let input;
if (url === ''){
if (url === '') {
input = document.getElementById('search').value;
}
else{
} else {
input = url;
}
@ -357,16 +371,35 @@ function parseSearchText(url = '') {
let match = input.match(rx);
console.log(match);
// Play video if a match is found.
try {
console.log('Match Found');
ft.log('Video ID: ', match);
let urlSplit = input.split('/');
if (match) {
ft.log('Video found');
loadingView.seen = true;
playVideo(match[2]);
} catch (err) {
console.log('Video not found');
} else if (urlSplit[3] == 'channel') {
ft.log('channel found');
loadingView.seen = true;
goToChannel(urlSplit[4]);
} else if (urlSplit[3] == 'user') {
ft.log('user found');
// call api to get the ID and then call goToChannel(id)
youtubeAPI('channels', {
part: 'id',
forUsername: urlSplit[4]
}, (data) => {
ft.log('Channel Data: ', data.items[0].id);
let channelID = data.items[0].id;
loadingView.seen = true;
goToChannel(channelID);
});
} else {
ft.log('Video not found');
loadingView.seen = true;
search();
}
}
/**
@ -377,10 +410,10 @@ function parseSearchText(url = '') {
* @return {string} - The formated string. Ex: 12:34:56
*/
function parseVideoDuration(durationString) {
let match = durationString.match(/PT(\d+H)?(\d+M)?(\d+S)?/);
let match = durationString.match(/P.*T(\d+H)?(\d+M)?(\d+S)?/);
let duration = '';
match = match.slice(1).map(function(x) {
match = match.slice(1).map(function (x) {
if (x != null) {
return x.replace(/\D/, '');
}
@ -421,8 +454,6 @@ function parseVideoDuration(durationString) {
* @return {Void}
*/
function showMostPopular() {
clearMainContainer();
startLoadingAnimation();
// Get the date of 2 days ago.
var d = new Date();
@ -439,20 +470,23 @@ function showMostPopular() {
publishedAfter: d.toISOString(),
maxResults: 50,
}, function(data) {
createVideoListContainer('Most Popular:');
//createVideoListContainer('Most Popular:');
console.log(data);
let grabDuration = getDuration(data.items);
grabDuration.then((videoList) => {
console.log(videoList);
videoList.items.forEach(displayVideo);
popularView.videoList = [];
loadingView.seen = false;
videoList.items.forEach((video) => {
displayVideo(video, 'popular');
});
});
stopLoadingAnimation();
});
}
/**
* Create a link of the video to HookTube or YouTube and copy it to the user's clipboard.
* Create a link of the video to Invidious or YouTube and copy it to the user's clipboard.
*
* @param {string} website - The website to watch the video on.
* @param {string} videoId - The video ID of the video to add to the URL
@ -461,9 +495,20 @@ function showMostPopular() {
*/
function copyLink(website, videoId) {
// Create the URL and copy to the clipboard.
if (website == "youtube") {
const url = 'https://' + website + '.com/watch?v=' + videoId;
clipboard.writeText(url);
showToast('URL has been copied to the clipboard');
}
if (website == "invidious") {
website = "invidio";
const url = "https://" + website + ".us/watch?v=" + videoId;
clipboard.writeText(url);
showToast('URL has been copied to the clipboard');
}
}
/**
@ -474,12 +519,12 @@ function copyLink(website, videoId) {
* @return {promise} - The HTML of the embeded player
*/
function getChannelAndPlayer(videoId) {
console.log(videoId);
ft.log('Video ID: ', videoId);
return new Promise((resolve, reject) => {
youtubeAPI('videos', {
part: 'snippet,player',
id: videoId,
}, function(data) {
}, function (data) {
let embedHtml = data.items[0].player.embedHtml;
embedHtml = embedHtml.replace('src="', 'src="https:');
embedHtml = embedHtml.replace('width="480px"', '');
@ -504,7 +549,7 @@ function checkVideoUrls(video480p, video720p) {
let valid480 = false;
if (typeof(video480p) !== 'undefined') {
if (typeof (video480p) !== 'undefined') {
let get480pUrl = fetch(video480p);
get480pUrl.then((status) => {
switch (status.status) {
@ -526,8 +571,8 @@ function checkVideoUrls(video480p, video720p) {
return;
break;
default:
console.log('480p is valid');
if (currentQuality === '720p' && typeof(video720p) === 'undefined') {
ft.log('480p is valid');
if (currentQuality === '720p' && typeof (video720p) === 'undefined') {
changeQuality(video480p);
}
break;
@ -535,7 +580,7 @@ function checkVideoUrls(video480p, video720p) {
});
}
if (typeof(video720p) !== 'undefined') {
if (typeof (video720p) !== 'undefined') {
let get720pUrl = fetch(video720p);
get720pUrl.then((status) => {
switch (status.status) {
@ -545,7 +590,7 @@ function checkVideoUrls(video480p, video720p) {
$(document).on('click', '#quality720p', (event) => {
changeQuality('');
});
if (typeof(valid480) !== 'undefined') {
if (typeof (valid480) !== 'undefined') {
changeQuality(video480p, '480p');
}
break;
@ -558,7 +603,7 @@ function checkVideoUrls(video480p, video720p) {
return;
break;
default:
console.log('720p is valid');
ft.log('720p is valid');
break;
}
});

10947
src/js/vue.js Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,3 +1,22 @@
/*
This file is part of FreeTube.
FreeTube is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
FreeTube is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with FreeTube. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* List a YouTube HTTP API resource.
*
@ -9,10 +28,12 @@
*/
function youtubeAPI(resource, params, success) {
params.key = apiKey;
params.key = settingsView.apiKey;
let requestUrl = 'https://www.googleapis.com/youtube/v3/' + resource + '?' + $.param(params);
if (useTor) {
tor.request('https://www.googleapis.com/youtube/v3/' + resource + '?' + $.param(params), function(err, res, body) {
tor.request(requestUrl, (err, res, body) => {
if (!err && res.statusCode == 200) {
success(JSON.parse(body));
} else {
@ -20,20 +41,19 @@ function youtubeAPI(resource, params, success) {
console.log(err);
console.log(res);
console.log(body);
stopLoadingAnimation();
loadingView.seen = false;
}
});
} else {
$.getJSON(
'https://www.googleapis.com/youtube/v3/' + resource,
params,
requestUrl,
success
).fail((xhr, textStatus, error) => {
showToast('There was an error calling the YouTube API.');
console.log(error);
console.log(xhr);
console.log(textStatus);
stopLoadingAnimation();
loadingView.seen = false;
});
}
@ -56,13 +76,13 @@ function youtubedlGetInfo(videoId, callback) {
ytdl.getInfo(url, options, function(err, info) {
if (err) {
showToast(err.message);
stopLoadingAnimation();
loadingView.seen = false;
console.log(err);
console.log(info);
return;
}
console.log('Success');
ft.log('Success');
callback(info);
});
}

View File

@ -1,22 +1,39 @@
.channelViewBanner{
/*
This file is part of FreeTube.
FreeTube is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
FreeTube is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with FreeTube. If not, see <http://www.gnu.org/licenses/>.
*/
.channelViewBanner {
width: 100%;
max-height: 200px;
margin-bottom: 30px;
}
.channelViewImage{
.channelViewImage {
float: left;
width: 100px;
border-radius: 200px 200px 200px 200px;
-webkit-border-radius: 200px 200px 200px 200px;
}
.channelViewTitle{
.channelViewTitle {
height: 100px;
margin-left: 100px;
}
.channelViewName{
.channelViewName {
font-weight: bold;
font-size: 25px;
margin-left: 20px;
@ -24,14 +41,14 @@
top: 20px;
}
.channelViewSubs{
.channelViewSubs {
margin-left: 20px;
margin-top: 20px;
position: relative;
top: 20px;
}
.channelSubButton{
.channelSubButton {
float: right;
width: 125px;
height: 50px;
@ -40,6 +57,6 @@
cursor: pointer;
}
.channelViewDescription{
.channelViewDescription {
white-space: pre-line;
}

View File

@ -1,3 +1,20 @@
/*
This file is part of FreeTube.
FreeTube is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
FreeTube is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with FreeTube. If not, see <http://www.gnu.org/licenses/>.
*/
::-webkit-scrollbar {
height: 12px;
width: 12px;
@ -14,54 +31,218 @@
background: #262626;
}
body{background-color: #212121;}
input[type=text] {color: #EEEEEE;}
.sk-cube-grid .sk-cube {background-color: #f44336;}
.searchBar ::-webkit-input-placeholder {color: #E0E0E0;}
.topNav{background-color: #262626; -webkit-box-shadow: 0px -4px 20px 0px rgba(0,0,0,0.75);}
.logo{color: #f44336;}
.searchBar input{border-bottom: 1px solid #ddd;}
.searchButton{color: #E0E0E0;}
.jumpToInput{border-bottom: 1px solid #E0E0E0;}
.message{color: #757575;}
.videoDelete{color: white;}
.channelViewImage{border: 0px solid white;}
.channelViewName{color: #EEEEEE;}
.channelViewSubs{color: #BDBDBD;}
.channelViewDescription{color: #E0E0E0;}
.channelSubButton{color: #E0E0E0; background-color: #f44336;}
.videoTitle{color: #EEEEEE;}
.channelName{color: #E0E0E0;}
.videoDescription{color: #E0E0E0;}
.statistics{background-color: #424242; color: #EEEEEE;}
.views{color: #E0E0E0;}
.details{background-color: #424242; color: #EEEEEE;}
.playerSubButton{color: #E0E0E0; background-color: #f44336;}
.smallButton{color: #E0E0E0; background-color: #757575;}
.recommendDate{color: #E0E0E0;}
.settingsButton {color: #BDBDBD; background-color: #424242;}
.qualityTypes{color: #E0E0E0; background-color: #757575;}
.speedTypes{color: #E0E0E0; background-color: #757575;}
.unsaved{color: #E0E0E0;}
.double-bounce1, .double-bounce2 {background-color: #f44336;}
.videoOptions ul {background-color: #262626; color: white;}
.videoOptions li:hover {background-color: #262626;}
.videoOptions a {color: white;}
body {
background-color: #212121;
}
#main{color: #EEEEEE;}
#main hr{border-bottom: 1px solid #212121;}
#subscriptions img{border: 0px solid white;}
#sideNav{background-color: #262626; color: #E0E0E0; -webkit-box-shadow: 4px -2px 51px -6px rgba(0,0,0,0.75);}
#sideNav hr{background-color: #f44336;}
#sideNav li:hover{background-color: #212121;}
#channelIcon{border: 0px solid white;}
#channelName{color: #E0E0E0;}
#publishDate{color: #E0E0E0;}
#description{color: #E0E0E0;}
#showComments{background-color: #757575; color: #E0E0E0;}
#recommendations{color: #EEEEEE;}
#videoListContainer{color: #EEEEEE;}
#toast{background-color: #616161; color: white;}
#confirmFunction{background-color: #616161; color: white;}
#getNextPage{background-color: #616161}
#comments{background-color: #424242;}
input[type=text] {
color: #EEEEEE;
}
.sk-cube-grid .sk-cube {
background-color: #f44336;
}
.searchBar ::-webkit-input-placeholder {
color: #E0E0E0;
}
.topNav {
background-color: #262626;
-webkit-box-shadow: 0px -4px 20px 0px rgba(0, 0, 0, 0.75);
}
.logo {
color: #f44336;
}
.searchBar input {
border-bottom: 1px solid #ddd;
}
.searchButton {
color: #E0E0E0;
}
.jumpToInput {
border-bottom: 1px solid #E0E0E0;
}
.message {
color: #757575;
}
.videoDelete {
color: white;
}
.channelViewImage {
border: 0px solid white;
}
.channelViewName {
color: #EEEEEE;
}
.channelViewSubs {
color: #BDBDBD;
}
.channelViewDescription {
color: #E0E0E0;
}
.channelSubButton {
color: #E0E0E0;
background-color: #f44336;
}
.videoTitle {
color: #EEEEEE;
}
.channelName {
color: #E0E0E0;
}
.videoDescription {
color: #E0E0E0;
}
.statistics {
background-color: #424242;
color: #EEEEEE;
}
.views {
color: #E0E0E0;
}
.details {
background-color: #424242;
color: #EEEEEE;
}
.playerSubButton {
color: #E0E0E0;
background-color: #f44336;
}
.smallButton {
color: #E0E0E0;
background-color: #757575;
}
.recommendDate {
color: #E0E0E0;
}
.settingsButton {
color: #BDBDBD;
background-color: #424242;
}
.qualityTypes {
color: #E0E0E0;
background-color: #757575;
}
.speedTypes {
color: #E0E0E0;
background-color: #757575;
}
.unsaved {
color: #E0E0E0;
}
.double-bounce1,
.double-bounce2 {
background-color: #f44336;
}
.videoOptions ul {
background-color: #262626;
color: white;
}
.videoOptions li:hover {
background-color: #262626;
}
.videoOptions a {
color: white;
}
#main {
color: #EEEEEE;
}
#main hr {
border-bottom: 1px solid #212121;
}
#subscriptions img {
border: 0px solid white;
}
#sideNav {
background-color: #262626;
color: #E0E0E0;
-webkit-box-shadow: 4px -2px 51px -6px rgba(0, 0, 0, 0.75);
}
#sideNav hr {
background-color: #f44336;
}
#sideNav li:hover {
background-color: #212121;
}
#channelIcon {
border: 0px solid white;
}
#channelName {
color: #E0E0E0;
}
#publishDate {
color: #E0E0E0;
}
#description {
color: #E0E0E0;
}
#showComments {
background-color: #757575;
color: #E0E0E0;
}
#recommendations {
color: #EEEEEE;
}
#videoListContainer {
color: #EEEEEE;
}
#toast {
background-color: #616161;
color: white;
}
#confirmFunction {
background-color: #616161;
color: white;
}
#getNextPage {
background-color: #616161
}
#comments {
background-color: #424242;
}

View File

@ -1,3 +1,20 @@
/*
This file is part of FreeTube.
FreeTube is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
FreeTube is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with FreeTube. If not, see <http://www.gnu.org/licenses/>.
*/
::-webkit-scrollbar {
height: 12px;
width: 12px;
@ -14,43 +31,167 @@
background: white;
}
body{background-color: #e0e0e0;}
.sk-cube-grid .sk-cube {background-color: #f44336;}
.searchBar ::-webkit-input-placeholder {color: #ddd;}
.topNav{background-color: #f44336; -webkit-box-shadow: 0px -4px 32px 0px rgba(0, 0, 0, 0.75);}
.searchBar input{border-bottom: 1px solid #ddd;}
.searchButton{color: black;}
.jumpToInput{border-bottom: 1px solid #757575;}
.message{color: #757575;}
.videoDelete{color: black;}
.channelViewImage{border: 0px solid #000000;}
.channelViewSubs{color: #757575;}
.channelSubButton{color: #616161; background-color: #eeeeee;}
.videoTitle{color: #000000;}
.channelName{color: #424242;}
.videoDescription{color: #424242;}
.statistics{background-color: white;}
.views{color: #424242;}
.details{background-color: white;}
.playerSubButton{color: #616161; background-color: #eeeeee;}
.smallButton{color: #616161; background-color: #eeeeee;}
.recommendDate{color: #616161;}
.settingsButton {color: #424242; background-color: #BDBDBD;}
.qualityTypes{background-color: #eeeeee;}
.speedTypes{background-color: #eeeeee;}
.unsaved{color: #616161;}
.double-bounce1, .double-bounce2 {background-color: #f44336;}
.videoOptions ul {background-color: #f5f5f5;}
.videoOptions li:hover {background-color: #e0e0e0;}
.videoOptions a {color: black;}
body {
background-color: #e0e0e0;
}
#subscriptions img{border: 0px solid #000000;}
#sideNav{background-color: white; -webkit-box-shadow: 4px -2px 51px -6px rgba(0,0,0,0.75);}
#sideNav hr{background-color: #f44336;}
#sideNav li:hover{background-color: #e0e0e0;}
#channelIcon{border: 0px solid #000000;}
#showComments{background-color: #eeeeee;}
#toast{background-color: #212121; color: #f5f5f5;;}
#confirmFunction{background-color: #f5f5f5;}
#getNextPage{background-color: #f5f5f5;}
#comments{background: #eee;}
.sk-cube-grid .sk-cube {
background-color: #f44336;
}
.searchBar ::-webkit-input-placeholder {
color: #ddd;
}
.topNav {
background-color: #f44336;
-webkit-box-shadow: 0px -4px 32px 0px rgba(0, 0, 0, 0.75);
}
.searchBar input {
border-bottom: 1px solid #ddd;
}
.searchButton {
color: black;
}
.jumpToInput {
border-bottom: 1px solid #757575;
}
.message {
color: #757575;
}
.videoDelete {
color: black;
}
.channelViewImage {
border: 0px solid #000000;
}
.channelViewSubs {
color: #757575;
}
.channelSubButton {
color: #616161;
background-color: #eeeeee;
}
.videoTitle {
color: #000000;
}
.channelName {
color: #424242;
}
.videoDescription {
color: #424242;
}
.statistics {
background-color: white;
}
.views {
color: #424242;
}
.details {
background-color: white;
}
.playerSubButton {
color: #616161;
background-color: #eeeeee;
}
.smallButton {
color: #616161;
background-color: #eeeeee;
}
.recommendDate {
color: #616161;
}
.settingsButton {
color: #424242;
background-color: #BDBDBD;
}
.qualityTypes {
background-color: #eeeeee;
}
.speedTypes {
background-color: #eeeeee;
}
.unsaved {
color: #616161;
}
.double-bounce1,
.double-bounce2 {
background-color: #f44336;
}
.videoOptions ul {
background-color: #f5f5f5;
}
.videoOptions li:hover {
background-color: #e0e0e0;
}
.videoOptions a {
color: black;
}
#subscriptions img {
border: 0px solid #000000;
}
#sideNav {
background-color: white;
-webkit-box-shadow: 4px -2px 51px -6px rgba(0, 0, 0, 0.75);
}
#sideNav hr {
background-color: #f44336;
}
#sideNav li:hover {
background-color: #e0e0e0;
}
#channelIcon {
border: 0px solid #000000;
}
#showComments {
background-color: #eeeeee;
}
#toast {
background-color: #212121;
color: #f5f5f5;
;
}
#confirmFunction {
background-color: #f5f5f5;
}
#getNextPage {
background-color: #f5f5f5;
}
#comments {
background: #eee;
}

View File

@ -1,8 +1,26 @@
/*
This file is part of FreeTube.
FreeTube is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
FreeTube is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with FreeTube. If not, see <http://www.gnu.org/licenses/>.
*/
/*
* Thanks to @tobiasahlin for the loading animation.
* Find it here: http://tobiasahlin.com/spinkit/
* Twitter: https://twitter.com/tobiasahlin
*/
.spinner {
width: 40px;
height: 40px;
@ -11,7 +29,8 @@
margin: 100px auto;
}
.double-bounce1, .double-bounce2 {
.double-bounce1,
.double-bounce2 {
width: 100%;
height: 100%;
border-radius: 50%;
@ -30,15 +49,22 @@
}
@-webkit-keyframes sk-bounce {
0%, 100% { -webkit-transform: scale(0.0) }
50% { -webkit-transform: scale(1.0) }
0%,
100% {
-webkit-transform: scale(0.0)
}
50% {
-webkit-transform: scale(1.0)
}
}
@keyframes sk-bounce {
0%, 100% {
0%,
100% {
transform: scale(0.0);
-webkit-transform: scale(0.0);
} 50% {
}
50% {
transform: scale(1.0);
-webkit-transform: scale(1.0);
}

View File

@ -1,3 +1,20 @@
/*
This file is part of FreeTube.
FreeTube is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
FreeTube is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with FreeTube. If not, see <http://www.gnu.org/licenses/>.
*/
@font-face {
font-family: Roboto;
src: url(Roboto-Regular.ttf);
@ -25,6 +42,16 @@ a {
color: #2196F3;
}
#progressBar{
height: 3px;
background-color: #f44336;
display: block;
position: absolute;
bottom: 0;
left: 0;
z-index: 1;
}
.center {
text-align: center;
margin: 0 auto;
@ -176,7 +203,6 @@ a {
left: 5;
bottom: 0;
right: 0;
display: none;
}
.settingsInput {
@ -221,7 +247,7 @@ a {
line-height: 10px;
}
#toast{
#toast {
min-width: 400px;
height: 50px;
position: fixed;
@ -240,7 +266,7 @@ a {
-o-transition: opacity 0.5s ease-in-out;
}
.closeToast{
.closeToast {
font-size: 15px;
position: absolute;
top: 5px;
@ -248,7 +274,7 @@ a {
cursor: pointer;
}
#confirmFunction{
#confirmFunction {
position: fixed;
top: 50%;
left: 30%;
@ -258,31 +284,31 @@ a {
line-height: 65px;
visibility: hidden;
z-index: 1;
-webkit-box-shadow: 5px 5px 15px -5px rgba(0,0,0,0.75);
-moz-box-shadow: 5px 5px 15px -5px rgba(0,0,0,0.75);
box-shadow: 5px 5px 15px -5px rgba(0,0,0,0.75);
-webkit-box-shadow: 5px 5px 15px -5px rgba(0, 0, 0, 0.75);
-moz-box-shadow: 5px 5px 15px -5px rgba(0, 0, 0, 0.75);
box-shadow: 5px 5px 15px -5px rgba(0, 0, 0, 0.75);
}
#confirmMessage{
#confirmMessage {
margin-left: 15px;
}
.confirmButton{
.confirmButton {
position: absolute;
top: 0px;
cursor: pointer;
}
#confirmYes{
#confirmYes {
right: 90px;
color: #2196f3;
}
#confirmNo{
#confirmNo {
right: 20px;
}
#getNextPage{
#getNextPage {
width: 100%;
height: 45px;
line-height: 45px;
@ -290,13 +316,15 @@ a {
cursor: pointer;
}
#comments{
display:none;
padding:1em;
#comments {
display: none;
padding: 1em;
margin-bottom: 1em;
}
.saved{color: #FFEB3B;}
.saved {
color: #FFEB3B;
}
/* Thanks to Guus Lieben for the Material Design Switch */
@ -315,7 +343,8 @@ a {
padding: 16px 0 16px 44px;
}
.switch-label:before, .switch-label:after {
.switch-label:before,
.switch-label:after {
content: "";
position: absolute;
margin: 0;

View File

@ -1,3 +1,20 @@
/*
This file is part of FreeTube.
FreeTube is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
FreeTube is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with FreeTube. If not, see <http://www.gnu.org/licenses/>.
*/
iframe {
width: 100%;
height: 41.25vw;
@ -11,7 +28,7 @@ iframe {
.video {
width: 95%;
max-width: 1000px;
height: 145px;
height: auto;
padding: 15px;
overflow: hidden;
}
@ -23,10 +40,20 @@ iframe {
cursor: pointer;
}
.videoThumbnail img{
.videoThumbnail img {
width: 100%;
}
.videoThumbnail i {
color: white;
background-color: black;
padding: 6px;
opacity: 0.7;
position: relative;
bottom: 159px;
left: 247px;
}
.channelThumbnail {
width: 140px;
height: 140px;
@ -36,7 +63,7 @@ iframe {
left: 60px;
}
.channelThumbnail img{
.channelThumbnail img {
width: 100%;
border-radius: 200px 200px 200px 200px;
-webkit-border-radius: 200px 200px 200px 200px;
@ -48,12 +75,11 @@ iframe {
}
.videoOptions i {
text-align: right;
width: 50px;
cursor: pointer;
}
.videoOptions ul {
width: 90px;
font-size: 12px;
position: relative;
@ -61,8 +87,9 @@ iframe {
bottom: 10px;
list-style-type: none;
display: none;
-webkit-box-shadow: 4px 4px 33px -7px rgba(0,0,0,0.75);
-webkit-box-shadow: 4px 4px 33px -7px rgba(0, 0, 0, 0.75);
cursor: pointer;
z-index: 10;
}
.videoOptions li {
@ -108,7 +135,6 @@ iframe {
}
.videoDuration {
display: inline-block;
float: right;
position: relative;
bottom: 36px;
@ -122,6 +148,7 @@ iframe {
.videoPlayer {
width: 100%;
max-height: 1100px;
}
.statistics {
@ -316,7 +343,7 @@ iframe {
float: left;
}
.recommendThumbnail img{
.recommendThumbnail img {
width: 100%;
}

View File

@ -1,6 +1,8 @@
.videoListContainer{width: 100%;}
.videoListContainer {
width: 100%;
}
.videoListContainer hr{
.videoListContainer hr {
width: 95%;
height: 1px;
border: 0;

View File

@ -1,6 +1,22 @@
<div class='center'>
<!--
This file is part of FreeTube.
FreeTube is free software: you can redistribute it and/or modify
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
FreeTube is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with FreeTube. If not, see <http://www.gnu.org/licenses/>.
-->
<div class='center' v-if="seen">
<img src='icons/logoColor.png' width='500px;'/>
<h1>{{versionNumber}} Beta</h1>
<h1>{{ versionNumber }} Beta</h1>
<h3><i class='fas fa-envelope'></i>&nbsp;&nbsp;FreeTubeApp@protonmail.com</h3>
<h2>This software is FOSS and released under the <a href='https://www.gnu.org/licenses/quick-guide-gplv3.html'>GNU Public License v3+</a>.</h2>
<p>Found a bug? Want to suggest a feature? Want to help out? Check out our <a href='https://github.com/FreeTubeApp/FreeTube'>GitHub</a> page. Pull requests are welcome.</p>

View File

@ -1,9 +0,0 @@
<div class='video'>
<div class='channelThumbnail'>
<img onclick='goToChannel("{{channelId}}")' src={{channelThumbnail}} />
</div>
<p onclick='goToChannel("{{channelId}}")' class='videoTitle'>{{channelName}}</p>
<p onclick='goToChannel("{{channelId}}")' class='channelName'>{{subscriberCount}} subscribers - {{videoCount}} videos</p>
<p onclick='goToChannel("{{channelId}}")' class='videoDescription'>{{channelDescription}}</p>
</div>
<hr />

View File

@ -1,21 +1,37 @@
<img class='channelViewBanner' src='{{channelBanner}}' />
<br />
<div class='channelViewTitle'>
<img class='channelViewImage' src='{{channelImage}}' />
<span class='channelViewName'>{{channelName}}</span>
<!--
This file is part of FreeTube.
FreeTube is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
(at your option) any later version.
the Free Software Foundation, either version 3 of the License, or
FreeTube is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
GNU General Public License for more details.
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
You should have received a copy of the GNU General Public License
along with FreeTube. If not, see <http://www.gnu.org/licenses/>.
-->
<div v-if='seen'>
<img class='channelViewBanner' :src='banner' />
<br />
<div class='channelViewTitle'>
<img class='channelViewImage' :src='icon' />
<span class='channelViewName'>{{name}}</span>
<br />
<span class='channelViewSubs'>{{subCount}} Subscribers</span>
<div id='subscribeButton' class='channelSubButton' onclick='toggleSubscription("{{channelId}}");'>
<div id='subscribeButton' class='channelSubButton' v-on:click='subscription(id);'>
{{subButtonText}}
</div>
</div>
<br />
<hr />
<div class='channelViewDescription'>
{{{channelDescription}}}
</div>
<br />
<hr />
<div id='videoListContainer'>
<h2>Latest Uploads</h2>
</div>
<br />
<hr />
<div class='channelViewDescription'>
<span v-html='description'></span>
</div>
<br />
<hr />
</div>

View File

@ -1,3 +1,20 @@
<!--
This file is part of FreeTube.
FreeTube is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
FreeTube is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with FreeTube. If not, see <http://www.gnu.org/licenses/>.
-->
{{#comments}}
<div class="user-comment">
<div onclick='goToChannel("{{authorChannelId.value}}")' style="float:left;margin-right: 1em; width:48px">

View File

@ -0,0 +1,4 @@
<div v-if="seen">
<h2 style='margin-left: 15px;'>{{ title }}</h2>
<hr />
</div>

View File

@ -1,15 +1,35 @@
<!--
This file is part of FreeTube.
FreeTube is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
FreeTube is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with FreeTube. If not, see <http://www.gnu.org/licenses/>.
-->
<title>Freetube Mini-Player</title>
<style>
body{
body {
background-color: #424242;
}
.videoPlayer{
.videoPlayer {
width: 100%;
}
iframe{
iframe {
width: 100%;
height: 54.9vw;
}
</style>
<script src="../js/events.js"></script>
<script>
@ -22,25 +42,25 @@ $('.videoPlayer').ready(() => {
});
/**
* Hide the mouse cursor after ~3 seconds. Used to hide the video when the user
* hovers the mouse over the video player.
*
* @return {Void}
*/
function hideMouseTimeout(){
* Hide the mouse cursor after ~3 seconds. Used to hide the video when the user
* hovers the mouse over the video player.
*
* @return {Void}
*/
function hideMouseTimeout() {
$('.videoPlayer')[0].style.cursor = 'default';
clearTimeout(mouseTimeout);
mouseTimeout = window.setTimeout(function(){
mouseTimeout = window.setTimeout(function() {
$('.videoPlayer')[0].style.cursor = 'none';
}, 3150);
}
/**
* Remove the timeout for the mouse cursor as a fallback.
*
* @return {Void}
*/
function removeMouseTimeout(){
* Remove the timeout for the mouse cursor as a fallback.
*
* @return {Void}
*/
function removeMouseTimeout() {
$('.videoPlayer')[0].style.cursor = 'default';
clearTimeout(mouseTimeout);
}
@ -53,5 +73,6 @@ let playPauseVideo = function(event) {
$(document).on('click', '.videoPlayer', playPauseVideo);
$(document).on('mouseover', '.videoPlayer', hideMouseTimeout);
$(document).on('mouseleave', '.videoPlayer', removeMouseTimeout);
</script>
{{{videoHtml}}}

View File

@ -1,22 +1,44 @@
<video class="videoPlayer" type="application/x-mpegURL" onmousemove="hideMouseTimeout()" onmouseleave="removeMouseTimeout()" controls="" src="{{defaultUrl}}" poster="{{videoThumbnail}}" autoplay>
{{{subtitleHtml}}}
</video>
<div class='statistics'>
<div class='smallButton' onclick="openMiniPlayer('{{videoThumbnail}}')">
<!--
This file is part of FreeTube.
FreeTube is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
FreeTube is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
along with FreeTube. If not, see <http://www.gnu.org/licenses/>.
You should have received a copy of the GNU General Public License
-->
<div v-if='seen'>
<div v-if='playerSeen'>
<video class="videoPlayer" type="application/x-mpegURL" onmousemove="hideMouseTimeout()" onmouseleave="removeMouseTimeout()" controls="" :src='videoUrl' :poster="videoThumbnail" v-html="subtitleHtml" autoplay>
</video>
</div>
<div v-else>
<span v-html="embededHtml"></span>
</div>
<div class='statistics'>
<div onclick='openMiniPlayer()' class='smallButton' >
MINI PLAYER <i class="fas fa-external-link-alt"></i>
</div>
<div class='smallButton videoQuality'>
<span id='currentQuality'>{{videoQuality}}</span> <i class="fas fa-angle-down"></i>
<span id='currentQuality'>{{currentQuality}}</span> <i class="fas fa-angle-down"></i>
<div class='qualityTypes'>
<ul>
<li id='quality480p' onclick='changeQuality("{{video480p}}", "480p")'>480p</li>
<li id='quality720p' onclick='changeQuality("{{video720p}}", "720p")'>720p</li>
<li id='qualityEmbed' onclick='changeQuality("{{embedPlayer}}", "EMBED", true)'>EMBED</li>
<li id='quality480p' v-on:click='quality(video480p, "480p")'>480p</li>
<li id='quality720p' v-on:click='quality(video720p, "720p")'>720p</li>
<li id='qualityEmbed' v-on:click='embededPlayer()'>EMBED</li>
</ul>
</div>
</div>
<div class='smallButton videoSpeed'>
<span id='currentSpeed'>1</span>x <i class="fas fa-angle-down"></i>
<span id='currentSpeed'>{{currentSpeed}}</span>x <i class="fas fa-angle-down"></i>
<div class='speedTypes'>
<ul>
<li onclick='changeVideoSpeed(0.25)'>0.25x</li>
@ -30,23 +52,23 @@
</ul>
</div>
</div>
<div onclick='toggleSavedVideo("{{videoId}}")' class='smallButton'>
<i id='saveIcon' style='color: {{savedIconColor}};' class="{{savedIconClass}} fa-star"></i> <span id='savedText'>{{savedText}}</span>
<div class='smallButton' v-on:click='save(videoId)'>
<i id='saveIcon' :class='savedIconType' class="fa-star"></i> <span id='savedText'>{{savedText}}</span>
</div>
<div class='smallButton' onclick='copyLink("youtube", "{{videoId}}")'>
<div class='smallButton' v-on:click='copy("youtube.com", videoId)'>
COPY YOUTUBE LINK
</div>
<a href='https://youtube.com/watch?v={{videoId}}'>
<a :href='"https://youtube.com/watch?v=" + videoId'>
<div class='smallButton'>
OPEN IN YOUTUBE
</div>
</a>
<div class='smallButton' onclick='copyLink("hooktube", "{{videoId}}")'>
COPY HOOKTUBE LINK
<div class='smallButton' v-on:click='copy("invidio.us", videoId)'>
COPY INVIDIOUS LINK
</div>
<a href='https://hooktube.com/watch?v={{videoId}}'>
<a :href='"https://invidio.us/watch?v=" + videoId'>
<div class='smallButton'>
OPEN IN HOOKTUBE
OPEN IN INVIDIOUS
</div>
</a>
<br />
@ -54,29 +76,42 @@
<p class='views'>{{videoViews}} views</p>
<div class='likeContainer'>
<div class='dislikeBar'>
<div class='likeBar' style='width: {{likePercentage}}%;'>
<div class='likeBar' :style='{width: likePercentage + "%"}'>
</div>
</div>
<span class='likes'><i class="fas fa-thumbs-up"></i> {{videoLikes}}</span><span class='dislikes'><i class="fas fa-thumbs-down"></i> {{videoDislikes}}</span>
</div>
</div>
<div class="details">
<img id='channelIcon' onclick='goToChannel("{{channelId}}")' src="{{channelIcon}}" />
<p id='channelName' onclick='goToChannel("{{channelId}}")'>{{channelName}}</p>
</div>
<div class="details">
<img id='channelIcon' v-on:click='channel(channelId)' :src="channelIcon" />
<p id='channelName' v-on:click='channel(channelId)'>{{channelName}}</p>
<p id='publishDate'>Published on {{publishedDate}}</p>
<div id='subscribeButton' class='playerSubButton' onclick='toggleSubscription("{{channelId}}")'>{{isSubscribed}}</div>
<div id='subscribeButton' class='playerSubButton' v-on:click='subscription(channelId)'>{{subscribedText}}</div>
<br /><br />
<div id='description'>
{{{description}}}
<span v-html="description"></span>
</div>
</div>
<div id='showComments'>
Show Comments <i class="far fa-comments"></i> (Max of 100)
</div>
<div id='comments' :data-video-id="videoId">
</div>
<div id='recommendations'>
<strong>Recommendations</strong>
<div v-for='video in recommendedVideoList'>
<div class='recommendVideo' v-on:click='play(video.id)'>
<div class='recommendThumbnail'>
<img :src='video.thumbnail'></img>
<p v-on:click='play(video.id)' class='videoDuration'>{{video.duration}}</p>
</div>
<p class='recommendTitle'>{{video.title}}</p>
<p class='recommendChannel'>{{video.channelName}}</p>
<p class='recommendDate'>{{video.publishedDate}}</p>
</div>
<hr />
</div>
</div>
</div>
<div id='showComments'>
Show Comments <i class="far fa-comments"></i> (Max of 100)
</div>
<div id='comments' data-video-id="{{videoId}}">
</div>
<div id='recommendations'>
<strong>Recommendations</strong>
</div>

View File

@ -1,94 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="style/main.css">
<link rel="stylesheet" href="style/lightTheme.css">
<link rel="stylesheet" href="style/player.css">
<link rel="stylesheet" href="style/videoList.css">
<link rel="stylesheet" href="style/channel.css">
<link rel="stylesheet" href="style/loading.css">
<link rel="stylesheet" href="style/fa-solid.min.css">
<link rel="stylesheet" href="style/fontawesome-all.min.css">
<script src="js/youtubeApi.js"></script>
<script src="js/settings.js"></script>
<script src="js/layout.js"></script>
<script src="js/videos.js"></script>
<script src="js/player.js"></script>
<script src="js/subscriptions.js"></script>
<script src="js/channels.js"></script>
<script src="js/savedVideos.js"></script>
<script src="js/history.js"></script>
<script src="js/events.js"></script>
<script>
showVideoRecommendations("{{videoId}}");
const checkIfSubscribed = isSubscribed('{{channelId}}');
checkIfSubscribed.then((results) => {
const subscribeButton = document.getElementById('subscribeButton');
console.log(results);
console.log(subscribeButton);
if (results === false) {
if (subscribeButton != null) {
subscribeButton.innerHTML = 'UNSUBSCRIBE';
}
} else {
if (subscribeButton != null) {
subscribeButton.innerHTML = 'SUBSCRIBE';
}
}
});
</script>
<title>Freetube Player</title>
<style>
#main{
width: 100%;
margin-left: 0px;
}
.spinner {
width: 40px;
height: 40px;
position: relative;
margin: 0px auto;
}
#loading{
width: 100%;
height: 0%;
}
</style>
</head>
<body>
<div id='loading'>
<div class="spinner">
<div class="double-bounce1"></div>
<div class="double-bounce2"></div>
</div>
</div>
<div id='confirmFunction'>
<span id='confirmMessage'>Would you like to perform the function?</span>
<div class='confirmButton' id='confirmYes'>Yes</div>
<div class='confirmButton' id='confirmNo'>No</div>
</div>
<div id='toast'>
<span id='toastMessage'></span>
<i onclick='hideToast()' class="closeToast fas fa-times"></i>
</div>
<div class="topNav">
<i onclick='toggleSideNavigation()' class="fas fa-bars" id='menuButton' style='display: none;'></i>
<div class="searchBar">
<input id='search' class="search" type="text" placeholder="Search / Go to URL">
<i onclick='parseSearchText()' class="fas fa-search searchButton" style='margin-right: -10px; cursor: pointer'></i>
</div>
<img src='icons/iconBlack.png' id='menuIcon'/>
&nbsp;
<img src='icons/textBlack.png' id='menuText'/>
</div>
<div id='main'>

View File

@ -1,6 +1,23 @@
<!--
This file is part of FreeTube.
FreeTube is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
FreeTube is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with FreeTube. If not, see <http://www.gnu.org/licenses/>.
-->
<div class='video'>
<div class='videoThumbnail'>
<img onclick='console.log("Not Implemented")' src={{playlistThumbnail}} />
<img onclick='console.log("Not Implemented")' src= {{playlistThumbnail}} />
</div>
<p onclick='goToChannel("{{channelId}}")' class='videoTitle'>{{playlistTitle}}</p>
<p onclick='goToChannel("{{channelId}}")' class='channelName'>{{channelName}} - {{publishedDate}}</p>

View File

@ -0,0 +1,3 @@
<div v-if='seen'>
<span id='progressBar' :style='{ width: progressWidth + "%" }'></span>
</div>

View File

@ -1,10 +0,0 @@
<div class='recommendVideo' onclick='playVideo("{{videoId}}")'>
<div class='recommendThumbnail'>
<img src='{{videoThumbnail}}'></img>
<p onclick='playVideo("{{videoId}}")' class='videoDuration'>{{videoDuration}}</p>
</div>
<p class='recommendTitle'>{{videoTitle}}</p>
<p class='recommendChannel'>{{channelName}}</p>
<p class='recommendDate'>{{publishedDate}}</p>
</div>
<hr />

View File

@ -0,0 +1 @@

View File

@ -1,24 +1,42 @@
<h1 class="center">Settings</h1>
<div class='center'>
<input type='text' name='api-key' id='api-key' class='settingsInput' value='{{key}}' placeholder='API Key' />
<!--
This file is part of FreeTube.
FreeTube is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
FreeTube is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with FreeTube. If not, see <http://www.gnu.org/licenses/>.
-->
<div v-if="seen">
<h1 class="center">Settings</h1>
<div class='center'>
<input type='text' name='api-key' id='api-key' class='settingsInput' :value='apiKey' placeholder='API Key' />
<br />
<label for='api-key'>Set API Key: Leave blank to use default</label>
<br />
<input type="checkbox" id="themeSwitch" name="set-name" class="switch-input" onchange='toggleTheme(this)' {{isChecked}}>
<input type="checkbox" id="themeSwitch" name="set-name" class="switch-input" onchange='toggleTheme(this)' :checked='useTheme'>
<label for="themeSwitch" class="switch-label">Use Dark Theme</label>
<input type="checkbox" id="torSwitch" name="set-name" class="switch-input" {{isChecked}}>
<input type="checkbox" id="torSwitch" name="set-name" class="switch-input" :checked='useTor'>
<label for="torSwitch" class="switch-label">Use Tor for API calls</label>
</div>
<div class='center'>
</div>
<div class='center'>
<div onclick='importSubscriptions()' class='settingsButton'>
IMPORT SUBSCRIPTIONS
</div>
<div onclick='exportSubscriptions();' class='settingsButton'>
EXPORT SUBSCRIPTIONS
</div>
</div>
<br /><br />
<div class='center'>
</div>
<br /><br />
<div class='center'>
<div onclick='confirmFunction("Are you sure you want to delete your history?", clearFile, "history")' class='settingsButton'>
CLEAR HISTORY
</div>
@ -28,8 +46,9 @@
<div onclick='confirmFunction("Are you sure you want to remove all subscriptions?", clearFile, "subscriptions")' class='settingsButton'>
CLEAR SUBSCRIPTIONS
</div>
</div>
<br /><br />
<div onclick='updateSettings()' class='center settingsSubmit'>
</div>
<br /><br />
<div onclick='updateSettings()' class='center settingsSubmit'>
SAVE SETTINGS
</div>
</div>

View File

@ -1,5 +1,20 @@
<!--
This file is part of FreeTube.
FreeTube is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
FreeTube is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with FreeTube. If not, see <http://www.gnu.org/licenses/>.
-->
<li onclick='goToChannel("{{channelId}}")'>
<img src='{{channelIcon}}' />
{{channelName}}
<img src='{{channelIcon}}' /> {{channelName}}
<i class='fas fa-times' onclick='toggleSubscription("{{channelId}}")'></i>
</li>

View File

@ -1,4 +0,0 @@
<video class="videoPlayer" onmousemove="hideMouseTimeout()" onmouseleave="removeMouseTimeout()" controls="" poster="{{videoThumbnail}}" autoplay>
<source src="{{videoUrl}}" type="video/mp4">
{{subtitleElements}}
</video>

View File

@ -1,21 +0,0 @@
<div class='video'>
<div class='videoOptions'>
<i class="fas fa-ellipsis-v" onclick='showVideoOptions(this)'></i>
<ul>
<li onclick='playVideo("{{videoId}}", "", true); showVideoOptions(this.parentNode.previousSibling);'>Open (New Window)</li>
<li onclick='addSavedVideo("{{videoId}}"); showVideoOptions(this.parentNode.previousSibling);'>Save Video</li>
<a href='https://youtube.com/watch?v={{videoId}}' onclick='showVideoOptions(this.parentNode.previousSibling);'><li>Open in YouTube</li></a>
<a href='https://hooktube.com/watch?v={{videoId}}' onclick='showVideoOptions(this.parentNode.previousSibling);'><li>Open in HookTube</li></a>
{{{deleteHtml}}}
</ul>
</div>
<div class='videoThumbnail'>
<img onclick='playVideo("{{videoId}}", "{{videoThumbnail}}")' src={{videoThumbnail}} />
<p onclick='playVideo("{{videoId}}")' class='videoDuration'>{{videoDuration}}</p>
</div>
<p onclick='playVideo("{{videoId}}")' class='videoTitle'>{{videoTitle}}</p>
<p onclick='goToChannel("{{channelId}}")' class='channelName'>{{channelName}} - {{publishedDate}}</p>
<p onclick='playVideo("{{videoId}}")' class='videoDescription'>{{videoDescription}}</p>
<p class='live'>{{liveText}}</p>
</div>
<hr />

View File

@ -0,0 +1,50 @@
<div>
<div v-if='seen'>
<div v-for="video in videoList">
<div v-if='video.isVideo'>
<div class='video'>
<div class='videoOptions'>
<i class="fas fa-ellipsis-v" onclick='showVideoOptions(this)'></i>
<ul>
<a :href='video.youtubeUrl' onclick='showVideoOptions(this.parentNode.previousSibling);'>
<li>Open in YouTube</li>
</a>
<li v-on:click='copy("youtube.com", video.id)' onclick='showVideoOptions(this.parentNode.previousSibling);'>Copy YouTube Link</li>
<a :href='video.invidiousUrl' onclick='showVideoOptions(this.parentNode.previousSibling);'>
<li>Open in Invidious</li>
</a>
<li v-on:click='copy("invidio.us", video.id)' onclick='showVideoOptions(this.parentNode.previousSibling);'>Copy Invidious Link</li>
</ul>
</div>
<div class='videoThumbnail'>
<img v-on:click='play(video.id)' :src='video.thumbnail' />
<p v-on:click='play(video.id)' class='videoDuration'>{{video.duration}}</p>
<i class="fas fa-history" v-on:click='toggleSave(video.id)'></i>
</div>
<p v-on:click='play(video.id)' class='videoTitle'>{{video.title}}</p>
<p v-on:click='channel(video.channelId)' class='channelName'>{{video.channelName}} - {{video.publishedDate}}</p>
<p v-on:click='play(video.id)' class='videoDescription'>{{video.description}}</p>
<p v-on:click='play(video.id)' class='live'>{{video.liveText}}</p>
</div>
<hr />
</div>
<!-- Channel View -->
<div v-else>
<div class='video'>
<div class='channelThumbnail'>
<img v-on:click='channel(video.channelId)' :src='video.thumbnail' />
</div>
<p v-on:click='channel(video.channelId)' class='videoTitle'>{{video.channelName}}</p>
<p v-on:click='channel(video.channelId)' class='channelName'>{{video.subscriberCount}} subscribers - {{video.videoCount}} videos</p>
<p v-on:click='channel(video.channelId)' class='videoDescription'>{{video.channelDescription}}</p>
</div>
<hr />
</div>
</div>
<div v-if='isSearch'>
<div v-on:click='nextPage' id='getNextPage'>
<i class="fas fa-search"></i> Fetch more results...
</div>
</div>
</div>
</div>