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,95 +1,102 @@
{
"name": "FreeTube",
"productName": "FreeTube",
"version": "0.3.1",
"description": "An Open Source YouTube app for privacy.",
"main": "src/js/init.js",
"scripts": {
"start": "electron-forge start",
"package": "electron-forge package",
"make": "electron-forge make",
"publish": "electron-forge publish",
"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"
},
"keywords": [],
"author": {
"name": "PrestonN",
"email": "FreeTubeApp@protonmail.com",
"url": "https://github.com/FreeTubeApp/FreeTube"
},
"license": "GPL-3.0-or-later",
"config": {
"forge": {
"make_targets": {
"win32": [
"squirrel",
"zip"
],
"darwin": [
"zip"
],
"linux": [
"deb",
"rpm",
"electron-forge-maker-appimage",
"zip"
]
},
"protocols": [
{
"name": "freetube",
"role": "Viewer",
"schemes": [
"freetube"
]
}
],
"electronPackagerConfig": {
"packageManager": "yarn",
"icon": "./src/icons/iconColor.icns"
},
"electronWinstallerConfig": {
"name": "freetube",
"iconUrl": "https://raw.githubusercontent.com/FreeTubeApp/FreeTubeApp.github.io/master/images/iconColor.ico",
"setupIcon": "./src/icons/iconColor.ico"
},
"electronInstallerDebian": {
"icon": "src/icons/iconColor.png"
},
"repository": {
"type": "git",
"name": "FreeTube",
"productName": "FreeTube",
"version": "0.3.2",
"description": "An Open Source YouTube app for privacy.",
"main": "src/js/init.js",
"scripts": {
"start": "electron-forge start",
"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:win": "electron-forge make --platform=win32",
"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": {
"name": "PrestonN",
"email": "FreeTubeApp@protonmail.com",
"url": "https://github.com/FreeTubeApp/FreeTube"
}
},
"license": "GPL-3.0-or-later",
"config": {
"forge": {
"make_targets": {
"win32": [
"squirrel"
],
"darwin": [
"zip"
],
"linux": [
"deb",
"rpm",
"electron-forge-maker-appimage",
"zip"
]
},
"protocols": [
{
"name": "freetube",
"role": "Viewer",
"schemes": [
"freetube"
]
}
],
"electronPackagerConfig": {
"packageManager": "yarn",
"icon": "./src/icons/iconColor.icns"
},
"electronWinstallerConfig": {
"name": "freetube",
"iconUrl": "https://raw.githubusercontent.com/FreeTubeApp/FreeTubeApp.github.io/master/images/iconColor.ico",
"setupIcon": "./src/icons/iconColor.ico"
},
"electronInstallerDebian": {
"icon": "src/icons/iconColor.png"
},
"repository": {
"type": "git",
"url": "https://github.com/FreeTubeApp/FreeTube"
}
}
},
"devDependencies": {
"electron-forge": "^5.2.2",
"electron-forge-maker-appimage": "^20.14.4",
"electron-installer-flatpak": "^0.8.0",
"electron-installer-snap": "^2.0.1",
"electron-prebuilt-compile": "2.0.2",
"electron-winstaller": "^2.6.4"
},
"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",
"tor-request": "^2.1.2",
"ytdl-core": "^0.20.4"
}
},
"devDependencies": {
"electron-forge": "^5.2.2",
"electron-forge-maker-appimage": "^20.14.4",
"electron-installer-flatpak": "^0.8.0",
"electron-installer-snap": "^2.0.1",
"electron-prebuilt-compile": "2.0.2",
"electron-winstaller": "^2.6.4"
},
"dependencies": {
"autolinker": "^1.6.2",
"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",
"mustache": "^2.3.0",
"nedb": "^1.8.0",
"opml-to-json": "0.0.3",
"tor-request": "^2.1.2",
"ytdl-core": "^0.20.4"
}
}

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,58 @@
/*
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, {
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,
});
$('#main').html(rendered);
stopLoadingAnimation();
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', {
@ -79,12 +62,40 @@ function goToChannel(channelId) {
maxResults: 50,
order: 'date',
}, function (data) {
// Display recent uploads to #main
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);
displayVideo(video, 'channel');
});
// Grab the channel's latest uploads. API forces a max of 50.
youtubeAPI('search', {
part: 'snippet',
channelId: channelId,
type: 'video',
maxResults: 50,
order: 'date',
}, function (data) {
// Display recent uploads to #main
let grabDuration = getDuration(data.items);
grabDuration.then((videoList) => {
videoList.items.forEach((video) => {
displayVideo(video);
});
});
});
});
});

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,185 +24,179 @@ 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 comments = $('#comments');
let showComments = function (event) {
let comments = $('#comments');
if (comments.css('display') === 'none') {
comments.attr('loaded', 'true');
if (comments.css('display') === 'none') {
comments.attr('loaded', 'true');
youtubeAPI('commentThreads', {
'videoId': $('#comments').attr('data-video-id'),
'part': 'snippet,replies',
'maxResults': 100,
}, function (data){
let comments = [];
let items = data.items;
youtubeAPI('commentThreads', {
'videoId': $('#comments').attr('data-video-id'),
'part': 'snippet,replies',
'maxResults': 100,
}, function (data) {
let comments = [];
let items = data.items;
items.forEach((object) => {
let snippet = object.snippet.topLevelComment.snippet;
items.forEach((object) => {
let snippet = object.snippet.topLevelComment.snippet;
snippet.publishedAt = dateFormat(new Date(snippet.publishedAt), "mmm dS, yyyy");
snippet.publishedAt = dateFormat(new Date(snippet.publishedAt), "mmm dS, yyyy");
comments.push(snippet);
})
const commentsTemplate = require('./templates/comments.html');
const html = mustache.render(commentsTemplate, {
comments: comments,
});
$('#comments').html(html);
});
comments.push(snippet);
})
const commentsTemplate = require('./templates/comments.html');
const html = mustache.render(commentsTemplate, {
comments: comments,
});
$('#comments').html(html);
});
comments.show();
} else {
comments.hide();
}
comments.show();
} else {
comments.hide();
}
};
/**
* Play / Pause the video player upon click.
*/
let playPauseVideo = function(event) {
let el = event.currentTarget;
el.paused ? el.play() : el.pause();
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 videoPlayer = $('.videoPlayer').get(0);
if (typeof(videoPlayer) !== 'undefined' && !$('#jumpToInput').is(':focus') && !$('#search').is(':focus')){
switch (event.which) {
case 32:
// Space Bar
event.preventDefault();
videoPlayer.paused ? videoPlayer.play() : videoPlayer.pause();
break;
case 74:
// J Key
event.preventDefault();
changeDurationBySeconds(-10);
break;
case 75:
// K Key
event.preventDefault();
videoPlayer.paused ? videoPlayer.play() : videoPlayer.pause();
break;
case 76:
// L Key
event.preventDefault();
changeDurationBySeconds(10);
break;
case 70:
// F Key
event.preventDefault();
videoPlayer.webkitRequestFullscreen();
break;
case 77:
// M Key
event.preventDefault();
let volume = videoPlayer.volume;
console.log(volume);
if (volume > 0){
changeVolume(-1);
let videoShortcutHandler = function (event) {
let videoPlayer = $('.videoPlayer').get(0);
if (typeof (videoPlayer) !== 'undefined' && !$('#jumpToInput').is(':focus') && !$('#search').is(':focus')) {
switch (event.which) {
case 32:
// Space Bar
event.preventDefault();
videoPlayer.paused ? videoPlayer.play() : videoPlayer.pause();
break;
case 74:
// J Key
event.preventDefault();
changeDurationBySeconds(-10);
break;
case 75:
// K Key
event.preventDefault();
videoPlayer.paused ? videoPlayer.play() : videoPlayer.pause();
break;
case 76:
// L Key
event.preventDefault();
changeDurationBySeconds(10);
break;
case 70:
// F Key
event.preventDefault();
videoPlayer.webkitRequestFullscreen();
break;
case 77:
// M Key
event.preventDefault();
let volume = videoPlayer.volume;
if (volume > 0) {
changeVolume(-1);
} else {
changeVolume(1);
}
break;
case 67:
// C Key
let subtitleMode = $('.videoPlayer').get(0).textTracks[0].mode;
if (subtitleMode === 'hidden') {
$('.videoPlayer').get(0).textTracks[0].mode = 'showing'
} else {
$('.videoPlayer').get(0).textTracks[0].mode = 'hidden'
}
break;
case 38:
// Up Arrow Key
event.preventDefault();
changeVolume(0.05);
break;
case 40:
// Down Arrow Key
event.preventDefault();
changeVolume(-0.05);
break;
case 37:
// Left Arrow Key
event.preventDefault();
changeDurationBySeconds(-5);
break;
case 39:
// Right Arrow Key
event.preventDefault();
changeDurationBySeconds(5);
break;
case 49:
// 1 Key
event.preventDefault();
changeDurationByPercentage(0.1);
break;
case 50:
// 2 Key
event.preventDefault();
changeDurationByPercentage(0.2);
break;
case 51:
// 3 Key
event.preventDefault();
changeDurationByPercentage(0.3);
break;
case 52:
// 4 Key
event.preventDefault();
changeDurationByPercentage(0.4);
break;
case 53:
// 5 Key
event.preventDefault();
changeDurationByPercentage(0.5);
break;
case 54:
// 6 Key
event.preventDefault();
changeDurationByPercentage(0.6);
break;
case 55:
// 7 Key
event.preventDefault();
changeDurationByPercentage(0.7);
break;
case 56:
// 8 Key
event.preventDefault();
changeDurationByPercentage(0.8);
break;
case 57:
// 9 Key
event.preventDefault();
changeDurationByPercentage(0.9);
break;
case 48:
// 0 Key
event.preventDefault();
changeDurationByPercentage(0);
break;
}
else{
changeVolume(1);
}
break;
case 67:
// C Key
let subtitleMode = $('.videoPlayer').get(0).textTracks[0].mode;
if (subtitleMode === 'hidden'){
$('.videoPlayer').get(0).textTracks[0].mode = 'showing'
}
else{
$('.videoPlayer').get(0).textTracks[0].mode = 'hidden'
}
break;
case 38:
// Up Arrow Key
event.preventDefault();
changeVolume(0.05);
break;
case 40:
// Down Arrow Key
event.preventDefault();
changeVolume(-0.05);
break;
case 37:
// Left Arrow Key
event.preventDefault();
changeDurationBySeconds(-5);
break;
case 39:
// Right Arrow Key
event.preventDefault();
changeDurationBySeconds(5);
break;
case 49:
// 1 Key
event.preventDefault();
changeDurationByPercentage(0.1);
break;
case 50:
// 2 Key
event.preventDefault();
changeDurationByPercentage(0.2);
break;
case 51:
// 3 Key
event.preventDefault();
changeDurationByPercentage(0.3);
break;
case 52:
// 4 Key
event.preventDefault();
changeDurationByPercentage(0.4);
break;
case 53:
// 5 Key
event.preventDefault();
changeDurationByPercentage(0.5);
break;
case 54:
// 6 Key
event.preventDefault();
changeDurationByPercentage(0.6);
break;
case 55:
// 7 Key
event.preventDefault();
changeDurationByPercentage(0.7);
break;
case 56:
// 8 Key
event.preventDefault();
changeDurationByPercentage(0.8);
break;
case 57:
// 9 Key
event.preventDefault();
changeDurationByPercentage(0.9);
break;
case 48:
// 0 Key
event.preventDefault();
changeDurationByPercentage(0);
break;
}
}
};
let fullscreenVideo = function(event){
if (document.webkitFullscreenElement !== null){
document.webkitExitFullscreen();
}
else{
$('.videoPlayer').get(0).webkitRequestFullscreen();
}
let fullscreenVideo = function (event) {
if (document.webkitFullscreenElement !== null) {
document.webkitExitFullscreen();
} 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']);
@ -30,103 +36,144 @@ protocol.registerStandardSchemes(['freetube']);
app.setAsDefaultProtocolClient('freetube');
const isSecondInstance = app.makeSingleInstance((commandLine, workingDirectory) => {
// Someone tried to run a second instance, we should focus our window.
if (win) {
if (win.isMinimized()) win.restore()
win.focus()
// Someone tried to run a second instance, we should focus our window.
if (win) {
if (win.isMinimized()) win.restore()
win.focus()
win.webContents.send('ping', commandLine)
}
win.webContents.send('ping', commandLine)
}
});
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() {
const Menu = require('electron').Menu;
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'),
protocol: 'file:',
slashes: true,
}));
win.loadURL(url.format({
pathname: path.join(__dirname, '../index.html'),
protocol: 'file:',
slashes: true,
}));
if (process.env = 'development') {
//win.webContents.openDevTools();ff
}
win.on('closed', () => {
win = null;
});
const template = [
{
label: 'File',
submenu: [
{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'}
]
},
{
label: 'View',
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'}
]
if (process.env = 'development') {
//win.webContents.openDevTools();ff
}
];
const menu = Menu.buildFromTemplate(template);
Menu.setApplicationMenu(menu);
win.on('closed', () => {
win = null;
});
const template = [{
label: 'File',
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'
}
]
},
{
label: 'View',
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'
}
]
}
];
const menu = Menu.buildFromTemplate(template);
Menu.setApplicationMenu(menu);
};
/**
* Quit the application
*/
let allWindowsClosed = function() {
win.webContents.session.clearStorageData([], (data) => {});
win.webContents.session.clearCache((data) => {});
app.quit();
let allWindowsClosed = function () {
win.webContents.session.clearStorageData([], (data) => {});
win.webContents.session.clearCache((data) => {});
app.quit();
};
/**
* On Mac, when dock icon is clicked,
* create a new window and launch the editor
*/
let active = function() {
if (win === null) {
init();
}
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,103 +23,60 @@ 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) {
module.exports = fs.readFileSync(filename, 'utf8');
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');
const searchBar = document.getElementById('search');
const jumpToInput = document.getElementById('jumpToInput');
// Displays the list of subscriptions in the side bar.
displaySubs();
// Displays the list of subscriptions in the side bar.
displaySubs();
// Allow user to use the 'enter' key to search for a video.
searchBar.onkeypress = (e) => {
if (e.keyCode === 13) {
parseSearchText();
}
};
// Allow user to use the 'enter' key to search for a video.
searchBar.onkeypress = (e) => {
if (e.keyCode === 13) {
parseSearchText();
}
};
// Display subscriptions upon the app opening up. May allow user to specify.
// Home page in the future.
loadingView.seen = true;
loadSubscriptions();
});
@ -129,94 +86,16 @@ $(document).ready(() => {
* @return {Void}
*/
function toggleSideNavigation() {
const sideNav = document.getElementById('sideNav');
const mainContainer = document.getElementById('main');
const sideNav = document.getElementById('sideNav');
const mainContainer = document.getElementById('main');
if (sideNav.style.display === 'none') {
sideNav.style.display = 'inline';
mainContainer.style.marginLeft = '250px';
} else {
sideNav.style.display = 'none';
mainContainer.style.marginLeft = '0px';
}
}
/**
* 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();
if (sideNav.style.display === 'none') {
sideNav.style.display = 'inline';
mainContainer.style.marginLeft = '250px';
} else {
sideNav.style.display = 'none';
mainContainer.style.marginLeft = '0px';
}
}
/**
@ -228,18 +107,18 @@ function showAbout() {
* @return {Void}
*/
function showToast(message) {
let toast = document.getElementById('toast');
let toastMessage = document.getElementById('toastMessage');
let toast = document.getElementById('toast');
let toastMessage = document.getElementById('toastMessage');
// If a toast message is already being displayed, this will remove the previous timer that was set.
clearTimeout(toastTimeout);
// If a toast message is already being displayed, this will remove the previous timer that was set.
clearTimeout(toastTimeout);
toastMessage.innerHTML = message;
toast.style.visibility = 'visible';
toast.style.opacity = 0.9;
toastMessage.innerHTML = message;
toast.style.visibility = 'visible';
toast.style.opacity = 0.9;
// Set the timer for the toast to be removed.
toastTimeout = window.setTimeout(hideToast, 5000);
// Set the timer for the toast to be removed.
toastTimeout = window.setTimeout(hideToast, 5000);
}
/**
@ -248,9 +127,9 @@ function showToast(message) {
* @return {Void}
*/
function hideToast() {
let toast = document.getElementById('toast');
toast.style.opacity = 0;
toast.style.visibility = 'hidden';
let toast = document.getElementById('toast');
toast.style.opacity = 0;
toast.style.visibility = 'hidden';
}
/**
@ -264,21 +143,20 @@ function hideToast() {
* @return {Void}
*/
function confirmFunction(message, performFunction, parameters = '') {
let confirmContainer = document.getElementById('confirmFunction');
let confirmMessage = document.getElementById('confirmMessage');
let confirmContainer = document.getElementById('confirmFunction');
let confirmMessage = document.getElementById('confirmMessage');
confirmMessage.innerHTML = message;
confirmContainer.style.visibility = 'visible';
confirmMessage.innerHTML = message;
confirmContainer.style.visibility = 'visible';
$(document).on('click', '#confirmYes', (event) => {
if(parameters != ''){
performFunction(parameters);
}
else{
performFunction();
}
hideConfirmFunction();
});
$(document).on('click', '#confirmYes', (event) => {
if (parameters != '') {
performFunction(parameters);
} else {
performFunction();
}
hideConfirmFunction();
});
}
/**
@ -287,8 +165,8 @@ function confirmFunction(message, performFunction, parameters = '') {
* @return {Void}
*/
function hideConfirmFunction() {
let confirmContainer = document.getElementById('confirmFunction');
confirmContainer.style.visibility = 'hidden';
let confirmContainer = document.getElementById('confirmFunction');
confirmContainer.style.visibility = 'hidden';
}
/**
@ -298,11 +176,11 @@ function hideConfirmFunction() {
* @return {Void}
*/
function hideMouseTimeout() {
$('.videoPlayer')[0].style.cursor = 'default';
clearTimeout(mouseTimeout);
mouseTimeout = window.setTimeout(function() {
$('.videoPlayer')[0].style.cursor = 'none';
}, 3150);
$('.videoPlayer')[0].style.cursor = 'default';
clearTimeout(mouseTimeout);
mouseTimeout = window.setTimeout(function () {
$('.videoPlayer')[0].style.cursor = 'none';
}, 3150);
}
/**
@ -311,14 +189,14 @@ function hideMouseTimeout() {
* @return {Void}
*/
function removeMouseTimeout() {
$('.videoPlayer')[0].style.cursor = 'default';
clearTimeout(mouseTimeout);
$('.videoPlayer')[0].style.cursor = 'default';
clearTimeout(mouseTimeout);
}
function showVideoOptions(element) {
if (element.nextElementSibling.style.display == 'none' || element.nextElementSibling.style.display == '') {
element.nextElementSibling.style.display = 'inline-block'
} else {
element.nextElementSibling.style.display = 'none'
}
}
if (element.nextElementSibling.style.display == 'none' || element.nextElementSibling.style.display == '') {
element.nextElementSibling.style.display = 'inline-block'
} else {
element.nextElementSibling.style.display = 'none'
}
}

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);
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);
youtubeAPI('videos', {
part: 'statistics',
id: videoId,
}, 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);
/*
* FreeTube calls youtube-dl to grab the direct video URL.
*/
youtubedlGetInfo(videoId, (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,100 +141,46 @@ 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,
});
showVideoRecommendations(videoId);
// Add the video to the user's history
addToHistory(videoId);
loadingView.seen = false;
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`
});
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{
$('#main').html(rendered);
stopLoadingAnimation();
showVideoRecommendations(videoId);
// Hide subtitles by default
if (typeof(info['subtitles']) !== 'undefined' && Object.keys(info['subtitles']).length > 0) {
let textTracks = $('.videoPlayer').get(0).textTracks;
Object.keys(textTracks).forEach((track) => {
textTracks[track].mode = 'hidden';
});
}
return;
}
// 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);
addToHistory(videoId);
window.setTimeout(checkVideoUrls, 5000, video480p, video720p);
// Hide subtitles by default
if (typeof(info['subtitles']) !== 'undefined' && Object.keys(info['subtitles']).length > 0) {
let textTracks = $('.videoPlayer').get(0).textTracks;
Object.keys(textTracks).forEach((track) => {
textTracks[track].mode = 'hidden';
});
}
window.setTimeout(checkVideoUrls, 5000, playerView.video480p, playerView.video720p);
});
}
@ -272,40 +191,40 @@ function playVideo(videoId, videoThumbnail = '', useWindowPlayer = false) {
*
* @return {Void}
*/
function openMiniPlayer(videoThumbnail) {
let lastTime;
let videoHtml;
function openMiniPlayer() {
let lastTime;
let videoHtml;
// Grabs whatever the HTML is for the current video player. Done this way to grab
// the HTML5 player (with varying qualities) as well as the YouTube embeded player.
if ($('.videoPlayer').length > 0) {
$('.videoPlayer').get(0).pause();
lastTime = $('.videoPlayer').get(0).currentTime;
videoHtml = $('.videoPlayer').get(0).outerHTML;
} else {
videoHtml = $('iframe').get(0).outerHTML;
}
// Grabs whatever the HTML is for the current video player. Done this way to grab
// the HTML5 player (with varying qualities) as well as the YouTube embeded player.
if ($('.videoPlayer').length > 0) {
$('.videoPlayer').get(0).pause();
lastTime = $('.videoPlayer').get(0).currentTime;
videoHtml = $('.videoPlayer').get(0).outerHTML;
} else {
videoHtml = $('iframe').get(0).outerHTML;
}
// Create a new browser window.
const BrowserWindow = electron.remote.BrowserWindow;
// Create a new browser window.
const BrowserWindow = electron.remote.BrowserWindow;
let miniPlayer = new BrowserWindow({
width: 1200,
height: 710
});
let miniPlayer = new BrowserWindow({
width: 1200,
height: 710
});
// Use the miniPlayer.html template.
$.get('templates/miniPlayer.html', (template) => {
mustache.parse(template);
const rendered = mustache.render(template, {
videoHtml: videoHtml,
videoThumbnail: videoThumbnail,
startTime: lastTime,
});
// Render the template to the new browser window.
miniPlayer.loadURL("data:text/html;charset=utf-8," + encodeURI(rendered));
});
}
// Use the miniPlayer.html template.
$.get('templates/miniPlayer.html', (template) => {
mustache.parse(template);
const rendered = mustache.render(template, {
videoHtml: videoHtml,
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,54 +235,54 @@ 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;
}
videoHtml = videoHtml.replace(/\&quot\;/g, '"');
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.
const embedPlayer = document.getElementsByTagName('IFRAME')[0];
// The YouTube API creates 2 more iFrames. This is why a boolean value is sent
// with the function.
const embedPlayer = document.getElementsByTagName('IFRAME')[0];
const html5Player = document.getElementsByClassName('videoPlayer');
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.
showToast('You are already using the embeded player.')
return;
} else if (isEmbed) {
// Switch from HTML 5 player to embeded Player
html5Player[0].remove();
const mainHtml = $('#main').html();
$('#main').html(videoHtml + mainHtml);
$('#currentQuality').html(qualityType);
} else if (html5Player.length == 0) {
// Switch from embeded player to HTML 5 player
embedPlayer.remove();
let videoPlayer = document.createElement('video');
videoPlayer.className = 'videoPlayer';
videoPlayer.src = videoHtml;
videoPlayer.controls = true;
videoPlayer.autoplay = true;
$('#main').prepend(videoPlayer);
$('#currentQuality').html(qualityType);
} else {
// Switch src on HTML 5 player
const currentPlayBackTime = $('.videoPlayer').get(0).currentTime;
html5Player[0].src = videoHtml;
html5Player[0].load();
$('.videoPlayer').get(0).currentTime = currentPlayBackTime;
$('#currentQuality').html(qualityType);
}
if (isEmbed && html5Player.length == 0) {
// The embeded player is already playing. Return.
showToast('You are already using the embeded player.')
return;
} else if (isEmbed) {
// Switch from HTML 5 player to embeded Player
html5Player[0].remove();
const mainHtml = $('#main').html();
$('#main').html(videoHtml + mainHtml);
$('#currentQuality').html(qualityType);
} else if (html5Player.length == 0) {
// Switch from embeded player to HTML 5 player
embedPlayer.remove();
let videoPlayer = document.createElement('video');
videoPlayer.className = 'videoPlayer';
videoPlayer.src = videoHtml;
videoPlayer.controls = true;
videoPlayer.autoplay = true;
$('#main').prepend(videoPlayer);
$('#currentQuality').html(qualityType);
} else {
// Switch src on HTML 5 player
const currentPlayBackTime = $('.videoPlayer').get(0).currentTime;
html5Player[0].src = videoHtml;
html5Player[0].load();
$('.videoPlayer').get(0).currentTime = currentPlayBackTime;
$('#currentQuality').html(qualityType);
}
}
/**
@ -374,8 +293,8 @@ function changeQuality(videoHtml, qualityType, isEmbed = false) {
* @return {Void}
*/
function changeVideoSpeed(speed) {
$('#currentSpeed').html(speed);
$('.videoPlayer').get(0).playbackRate = speed;
$('#currentSpeed').html(speed);
$('.videoPlayer').get(0).playbackRate = speed;
}
/**
@ -386,16 +305,16 @@ function changeVideoSpeed(speed) {
* @return {Void}
*/
function changeVolume(amount) {
const videoPlayer = $('.videoPlayer').get(0);
let volume = videoPlayer.volume;
volume = volume + amount;
if (volume > 1) {
videoPlayer.volume = 1;
} else if (volume < 0) {
videoPlayer.volume = 0;
} else {
videoPlayer.volume = volume;
}
const videoPlayer = $('.videoPlayer').get(0);
let volume = videoPlayer.volume;
volume = volume + amount;
if (volume > 1) {
videoPlayer.volume = 1;
} else if (volume < 0) {
videoPlayer.volume = 0;
} else {
videoPlayer.volume = volume;
}
}
/**
@ -406,8 +325,8 @@ function changeVolume(amount) {
* @return {Void}
*/
function changeDurationBySeconds(seconds) {
const videoPlayer = $('.videoPlayer').get(0);
videoPlayer.currentTime = videoPlayer.currentTime + seconds;
const videoPlayer = $('.videoPlayer').get(0);
videoPlayer.currentTime = videoPlayer.currentTime + seconds;
}
/**
@ -418,6 +337,6 @@ function changeDurationBySeconds(seconds) {
* @return {Void}
*/
function changeDurationByPercentage(percentage) {
const videoPlayer = $('.videoPlayer').get(0);
videoPlayer.currentTime = videoPlayer.duration * percentage;
const videoPlayer = $('.videoPlayer').get(0);
videoPlayer.currentTime = videoPlayer.duration * percentage;
}

View File

@ -1,118 +1,111 @@
/*
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){
let checkIfSaved = videoIsSaved(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){
let data = {
videoId: videoId,
timeSaved: new Date().getTime(),
};
checkIfSaved.then((saved) => {
if (saved === false) {
let data = {
videoId: videoId,
timeSaved: new Date().getTime(),
};
savedVidsDb.insert(data, (err, newDoc) => {
showToast('Video has been saved!');
});
}
else{
showToast('Video already exists in saved file.')
}
});
savedVidsDb.insert(data, (err, newDoc) => {
showToast('The video has been favorited!');
});
} 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){
savedVidsDb.remove({
videoId: videoId
}, {}, (err, numRemoved) => {
showToast('Video has been removed from saved list.');
});
* 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 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();
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) => {
if (jQuery.isEmptyObject(docs)) {
resolve(false);
} else {
resolve(true);
}
return new Promise((resolve, reject) => {
savedVidsDb.find({
videoId: videoId
}, (err, docs) => {
if (jQuery.isEmptyObject(docs)) {
resolve(false);
} else {
resolve(true);
}
});
});
});
}
/**
@ -121,28 +114,27 @@ function videoIsSaved(videoId) {
* @return {Void}
*/
function showSavedVideos(){
clearMainContainer();
startLoadingAnimation();
//clearMainContainer();
//startLoadingAnimation();
console.log('checking saved videos');
let videoList = '';
let videoList = '';
// Check the database for the list of videos.
savedVidsDb.find({}).sort({
timeSaved: -1
}).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){
for (let i = 0; i < 49; i++) {
videoList = videoList + ',' + docs[i].videoId;
}
}
else{
docs.forEach((video) => {
videoList = videoList + ',' + video.videoId;
});
}
// Check the database for the list of videos.
savedVidsDb.find({}).sort({
timeSaved: -1
}).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) {
for (let i = 0; i < 49; i++) {
videoList = videoList + ',' + docs[i].videoId;
}
} else {
docs.forEach((video) => {
videoList = videoList + ',' + video.videoId;
});
}
// Call the YouTube API
youtubeAPI('videos', {
@ -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
}, {});
}
// To any third party devs that fork the project, please be ethical and change the API key.
settingsDb.update({
_id: '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,30 +32,30 @@ along with FreeTube. If not, see <http://www.gnu.org/licenses/>.
* @return {Void}
*/
function addSubscription(channelId, useToast = true) {
console.log(channelId);
// Request YouTube API
youtubeAPI('channels', {
part: 'snippet',
id: channelId,
}, function(data) {
const channelInfo = data['items'][0]['snippet'];
const channelName = channelInfo['title'];
const thumbnail = channelInfo['thumbnails']['high']['url'];
ft.log('Channel ID: ', channelId);
// Request YouTube API
youtubeAPI('channels', {
part: 'snippet',
id: channelId,
}, (data) => {
const channelInfo = data['items'][0]['snippet'];
const channelName = channelInfo['title'];
const thumbnail = channelInfo['thumbnails']['high']['url'];
const channel = {
channelId: channelId,
channelName: channelName,
channelThumbnail: thumbnail,
};
const channel = {
channelId: channelId,
channelName: channelName,
channelThumbnail: thumbnail,
};
// Refresh the list of subscriptions on the side navigation bar.
subDb.insert(channel, (err, newDoc) => {
if (useToast) {
showToast('Added ' + channelName + ' to subscriptions.');
displaySubs();
}
// Refresh the list of subscriptions on the side navigation bar.
subDb.insert(channel, (err, newDoc) => {
if (useToast) {
showToast('Added ' + channelName + ' to subscriptions.');
displaySubs();
}
});
});
});
}
/**
@ -63,13 +66,13 @@ function addSubscription(channelId, useToast = true) {
* @return {Void}
*/
function removeSubscription(channelId) {
subDb.remove({
channelId: channelId
}, {}, (err, numRemoved) => {
// Refresh the list of subscriptions on the side navigation bar.
displaySubs();
showToast('Removed channel from subscriptions.');
});
subDb.remove({
channelId: channelId
}, {}, (err, numRemoved) => {
// Refresh the list of subscriptions on the side navigation bar.
displaySubs();
showToast('Removed channel from subscriptions.');
});
}
/**
@ -77,94 +80,103 @@ function removeSubscription(channelId) {
*
* @return {Void}
*/
function loadSubscriptions() {
clearMainContainer();
showToast('Getting Subscriptions. Please wait...');
const loading = document.getElementById('loading');
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;
}
startLoadingAnimation()
let videoList = [];
let videoList = [];
const subscriptions = returnSubscriptions();
const subscriptions = returnSubscriptions();
subscriptions.then((results) => {
let channelId = '';
let videoList = [];
// Welcome to callback hell, we hope you enjoy your stay.
subscriptions.then((results) => {
let channelId = '';
let videoList = [];
if (results.length > 0) {
let counter = 0;
if (results.length > 0) {
let counter = 0;
for (let i = 0; i < results.length; i++) {
channelId = results[i]['channelId'];
for (let i = 0; i < results.length; i++) {
channelId = results[i]['channelId'];
youtubeAPI('search', {
part: 'snippet',
channelId: channelId,
type: 'video',
maxResults: 15,
order: 'date',
}, (data) => {
console.log(data);
videoList = videoList.concat(data.items);
counter++;
progressView.progressWidth = (counter / results.length) * 100;
if (counter === results.length) {
videoList.sort((a, b) => {
const date1 = Date.parse(a.snippet.publishedAt);
const date2 = Date.parse(b.snippet.publishedAt);
youtubeAPI('search', {
part: 'snippet',
channelId: channelId,
type: 'video',
maxResults: 15,
order: 'date',
}, (data) => {
console.log(data);
videoList = videoList.concat(data.items);
counter++;
if (counter === results.length) {
videoList.sort((a, b) => {
const date1 = Date.parse(a.snippet.publishedAt);
const date2 = Date.parse(b.snippet.publishedAt);
return date2.valueOf() - date1.valueOf();
});
return date2.valueOf() - date1.valueOf();
});
// The YouTube website limits the subscriptions to 100 before grabbing more so we only show 100
// to keep the app running at a good speed.
if (videoList.length < 50) {
let grabDuration = getDuration(videoList.slice(0, 49));
// Render the videos to the application.
createVideoListContainer('Latest Subscriptions:');
grabDuration.then((list) => {
subscriptionView.videoList = [];
list.items.forEach((video) => {
displayVideo(video, 'subscriptions');
});
loadingView.seen = false;
progressView.seen = false;
progressView.progressWidth = 0;
});
} else {
console.log(videoList);
let finishedList = [];
let firstBatchDuration = getDuration(videoList.slice(0, 49));
// 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));
firstBatchDuration.then((list1) => {
finishedList = finishedList.concat(list1.items);
let secondBatchDuration = getDuration(videoList.slice(50, 99));
grabDuration.then((list) => {
list.items.forEach((video) => {
displayVideo(video);
});
stopLoadingAnimation();
});
} else {
console.log(videoList);
let finishedList = [];
let firstBatchDuration = getDuration(videoList.slice(0, 49));
firstBatchDuration.then((list1) => {
finishedList = finishedList.concat(list1.items);
let secondBatchDuration = getDuration(videoList.slice(50, 99));
secondBatchDuration.then((list2) => {
finishedList = finishedList.concat(list2.items);
finishedList.forEach((video) => {
displayVideo(video);
});
stopLoadingAnimation();
});
});
}
}
}
);
}
secondBatchDuration.then((list2) => {
finishedList = finishedList.concat(list2.items);
console.log(finishedList);
subscriptionView.videoList = [];
finishedList.forEach((video) => {
displayVideo(video, 'subscriptions');
});
loadingView.seen = false;
progressView.seen = false;
progressView.progressWidth = 0;
subscriptionTimer = window.setTimeout(() => {
checkSubscriptions = true;
}, 60000);
});
});
}
}
}
);
}
} 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>`;
}
});
}
} else {
// User has no subscriptions. Display message.
loadingView.seen = false;
headerView.seen = false;
noSubscriptions.seen = true;
}
});
}
/**
* Get the list of subscriptions from the user's subscription database.
@ -172,11 +184,11 @@ function loadSubscriptions() {
* @return {promise} The list of subscriptions.
*/
function returnSubscriptions() {
return new Promise((resolve, reject) => {
subDb.find({}, (err, subs) => {
resolve(subs);
return new Promise((resolve, reject) => {
subDb.find({}, (err, subs) => {
resolve(subs);
});
});
});
}
/**
@ -185,31 +197,31 @@ function returnSubscriptions() {
* @return {Void}
*/
function displaySubs() {
const subList = document.getElementById('subscriptions');
const subList = document.getElementById('subscriptions');
subList.innerHTML = '';
subList.innerHTML = '';
// Sort alphabetically
subDb.find({}).sort({
channelName: 1
}).exec((err, subs) => {
subs.forEach((channel) => {
// Grab subscriptions.html to be used as a template.
const subsTemplate = require('./templates/subscriptions.html')
mustache.parse(subsTemplate);
const rendered = mustache.render(subsTemplate, {
channelIcon: channel['channelThumbnail'],
channelName: channel['channelName'],
channelId: channel['channelId'],
});
// Render template to page.
const subscriptionsHtml = $('#subscriptions').html();
$('#subscriptions').html(subscriptionsHtml + rendered);
// Sort alphabetically
subDb.find({}).sort({
channelName: 1
}).exec((err, subs) => {
subs.forEach((channel) => {
// Grab subscriptions.html to be used as a template.
const subsTemplate = require('./templates/subscriptions.html')
mustache.parse(subsTemplate);
const rendered = mustache.render(subsTemplate, {
channelIcon: channel['channelThumbnail'],
channelName: channel['channelName'],
channelId: channel['channelId'],
});
// Render template to page.
const subscriptionsHtml = $('#subscriptions').html();
$('#subscriptions').html(subscriptionsHtml + rendered);
});
});
});
// Add onclick function
$('#subscriptions .fa-times').onClick = removeSubscription;
// Add onclick function
$('#subscriptions .fa-times').onClick = removeSubscription;
}
/**
@ -219,25 +231,25 @@ function displaySubs() {
* @return {Void}
*/
function toggleSubscription(channelId) {
event.stopPropagation();
event.stopPropagation();
const checkIfSubscribed = isSubscribed(channelId);
const subscribeButton = document.getElementById('subscribeButton');
const checkIfSubscribed = isSubscribed(channelId);
const subscribeButton = document.getElementById('subscribeButton');
checkIfSubscribed.then((results) => {
checkIfSubscribed.then((results) => {
if (results === false) {
if (subscribeButton != null) {
subscribeButton.innerHTML = 'UNSUBSCRIBE';
}
addSubscription(channelId);
} else {
if (subscribeButton != null) {
subscribeButton.innerHTML = 'SUBSCRIBE';
}
removeSubscription(channelId);
}
});
if (results === false) {
if (subscribeButton != null) {
subscribeButton.innerHTML = 'UNSUBSCRIBE';
}
addSubscription(channelId);
} else {
if (subscribeButton != null) {
subscribeButton.innerHTML = 'SUBSCRIBE';
}
removeSubscription(channelId);
}
});
}
/**
@ -248,15 +260,15 @@ function toggleSubscription(channelId) {
* @return {promise} - A boolean value if the channel is currently subscribed or not.
*/
function isSubscribed(channelId) {
return new Promise((resolve, reject) => {
subDb.find({
channelId: channelId
}, (err, docs) => {
if (jQuery.isEmptyObject(docs)) {
resolve(false);
} else {
resolve(true);
}
return new Promise((resolve, reject) => {
subDb.find({
channelId: channelId
}, (err, docs) => {
if (jQuery.isEmptyObject(docs)) {
resolve(false);
} else {
resolve(true);
}
});
});
});
}

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 = {
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 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(){
shell.openExternal('https://github.com/FreeTubeApp/FreeTube/releases');
}
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,9 +50,9 @@ along with FreeTube. If not, see <http://www.gnu.org/licenses/>.
});
}*/
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);
}
});
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/>.
*/
@ -25,70 +25,72 @@ along with FreeTube. If not, see <http://www.gnu.org/licenses/>.
* @return {Void}
*/
function search(nextPageToken = '') {
const query = document.getElementById('search').value;
const query = document.getElementById('search').value;
if (query === '') {
return;
}
if (query === '') {
return;
}
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...');
}
youtubeAPI('search', {
q: query,
part: 'id',
pageToken: nextPageToken,
maxResults: 25,
}, function(data) {
console.log(data);
youtubeAPI('search', {
q: query,
part: 'id',
pageToken: nextPageToken,
maxResults: 25,
}, function (data) {
ft.log('Search Data: ', data);
let channels = data.items.filter((item) => {
if (item.id.kind === 'youtube#channel') {
return true;
}
});
let channels = data.items.filter((item) => {
if (item.id.kind === 'youtube#channel') {
return true;
}
});
let playlists = data.items.filter((item) => {
if (item.id.kind === 'youtube#playlist') {
return true;
}
});
let playlists = data.items.filter((item) => {
if (item.id.kind === 'youtube#playlist') {
return true;
}
});
let videos = data.items.filter((item) => {
if (item.id.kind === 'youtube#video') {
return true;
}
});
let videos = data.items.filter((item) => {
if (item.id.kind === 'youtube#video') {
return true;
}
});
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){
//displayPlaylists(playlists);
}
if (playlists.length > 0) {
//displayPlaylists(playlists);
}
if(channels.length > 0){
displayChannels(channels);
}
if (channels.length > 0) {
displayChannels(channels);
}
let grabDuration = getDuration(videos);
let grabDuration = getDuration(videos);
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;
})
}
@ -100,32 +102,32 @@ function search(nextPageToken = '') {
* @return {promise} - The list of videos with the duration included.
*/
function getDuration(data) {
return new Promise((resolve, reject) => {
let videoIdList = '';
return new Promise((resolve, reject) => {
let videoIdList = '';
for (let i = 0; i < data.length; i++) {
if (videoIdList === '') {
if (typeof(data[i]['id']) === 'string') {
videoIdList = data[i]['id'];
} else {
videoIdList = data[i]['id']['videoId'];
for (let i = 0; i < data.length; i++) {
if (videoIdList === '') {
if (typeof (data[i]['id']) === 'string') {
videoIdList = data[i]['id'];
} else {
videoIdList = data[i]['id']['videoId'];
}
} else {
if (typeof (data[i]['id']) === 'string') {
videoIdList = videoIdList + ', ' + data[i]['id'];
} else {
videoIdList = videoIdList + ', ' + data[i]['id']['videoId'];
}
}
}
} else {
if (typeof(data[i]['id']) === 'string') {
videoIdList = videoIdList + ', ' + data[i]['id'];
} else {
videoIdList = videoIdList + ', ' + data[i]['id']['videoId'];
}
}
}
youtubeAPI('videos', {
part: 'snippet, contentDetails',
id: videoIdList
}, (data) => {
resolve(data);
youtubeAPI('videos', {
part: 'snippet, contentDetails',
id: videoIdList
}, (data) => {
resolve(data);
});
});
});
}
/**
@ -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,113 +162,128 @@ 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;
}
}
function displayChannels(channels) {
let channelIds;
let channelIds;
channels.forEach((channel) => {
if (typeof(channelIds) === 'undefined') {
channelIds = channel.id.channelId;
} else {
channelIds = channelIds + ',' + channel.id.channelId;
}
});
channels.forEach((channel) => {
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);
let items = data['items'].reverse();
const videoListTemplate = require('./templates/channelList.html');
youtubeAPI('channels', {
part: 'snippet,statistics',
id: channelIds,
}, function (data) {
ft.log('Channel Data: ', data);
let items = data['items'].reverse();
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);
});
});
}
function displayPlaylists(playlists) {
let playlistIds;
let playlistIds;
playlists.forEach((playlist) => {
if (typeof(playlistIds) === 'undefined') {
playlistIds = playlist.id.playlistId;
} else {
playlistIds = playlistIds + ',' + playlist.id.playlistId;
}
});
console.log(playlistIds);
youtubeAPI('playlists', {
part: 'snippet,contentDetails',
id: playlistIds,
}, function(data) {
console.log(data);
let items = data['items'].reverse();
const playlistListTemplate = require('./templates/playlistList.html');
console.log(items);
items.forEach((item) => {
let dateString = new Date(item.snippet.publishedAt);
let publishedDate = dateFormat(dateString, "mmm dS, yyyy");
mustache.parse(playlistListTemplate);
let rendered = mustache.render(playlistListTemplate, {
channelId: item.snippet.channelId,
channelName: item.snippet.channelTitle,
playlistThumbnail: item.snippet.thumbnails.medium.url,
playlistTitle: item.snippet.title,
playlistDescription: item.snippet.description,
videoCount: item.contentDetails.itemCount,
publishedDate: publishedDate,
});
$(rendered).insertBefore('#getNextPage');
playlists.forEach((playlist) => {
if (typeof (playlistIds) === 'undefined') {
playlistIds = playlist.id.playlistId;
} else {
playlistIds = playlistIds + ',' + playlist.id.playlistId;
}
});
ft.log('Playlist IDs: ', playlistIds);
youtubeAPI('playlists', {
part: 'snippet,contentDetails',
id: playlistIds,
}, function (data) {
ft.log('Playlist Data: ', data);
let items = data['items'].reverse();
const playlistListTemplate = require('./templates/playlistList.html');
ft.log('Playlist Items: ', items);
items.forEach((item) => {
let dateString = new Date(item.snippet.publishedAt);
let publishedDate = dateFormat(dateString, "mmm dS, yyyy");
mustache.parse(playlistListTemplate);
let rendered = mustache.render(playlistListTemplate, {
channelId: item.snippet.channelId,
channelName: item.snippet.channelTitle,
playlistThumbnail: item.snippet.thumbnails.medium.url,
playlistTitle: item.snippet.title,
playlistDescription: item.snippet.description,
videoCount: item.contentDetails.itemCount,
publishedDate: publishedDate,
});
$(rendered).insertBefore('#getNextPage');
});
});
});
}
/**
@ -277,22 +294,22 @@ function displayPlaylists(playlists) {
* @return {Void}
*/
function addNextPage(nextPageToken) {
let oldFetchButton = document.getElementById('getNextPage');
let oldFetchButton = document.getElementById('getNextPage');
// Creates the element if it doesn't exist.
if (oldFetchButton === null) {
let fetchButton = document.createElement('div');
fetchButton.id = 'getNextPage';
fetchButton.innerHTML = '<i class="fas fa-search"></i> Fetch more results...';
// Creates the element if it doesn't exist.
if (oldFetchButton === null) {
let fetchButton = document.createElement('div');
fetchButton.id = 'getNextPage';
fetchButton.innerHTML = '<i class="fas fa-search"></i> Fetch more results...';
$('#videoListContainer').append(fetchButton);
}
$('#videoListContainer').append(fetchButton);
}
// Update the on click method of the button.
$(document).off('click', '#getNextPage');
$(document).on('click', '#getNextPage', (event) => {
search(nextPageToken);
});
// Update the on click method of the button.
$(document).off('click', '#getNextPage');
$(document).on('click', '#getNextPage', (event) => {
search(nextPageToken);
});
}
/**
@ -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);
});
});
});
@ -339,34 +354,52 @@ function showVideoRecommendations(videoId) {
* @return {Void}
*/
function parseSearchText(url = '') {
let input;
let input;
if (url === ''){
input = document.getElementById('search').value;
}
else{
input = url;
}
if (url === '') {
input = document.getElementById('search').value;
} else {
input = url;
}
if (input === '') {
return;
}
if (input === '') {
return;
}
// The regex to get the video id from a YouTube link. Thanks StackOverflow.
let rx = /^.*(?:(?:(you|hook)tu\.?be\/|v\/|vi\/|u\/\w\/|embed\/)|(?:(?:watch)?\?v(?:i)?=|\&v(?:i)?=))([^#\&\?]*).*/;
// The regex to get the video id from a YouTube link. Thanks StackOverflow.
let rx = /^.*(?:(?:(you|hook)tu\.?be\/|v\/|vi\/|u\/\w\/|embed\/)|(?:(?:watch)?\?v(?:i)?=|\&v(?:i)?=))([^#\&\?]*).*/;
let match = input.match(rx);
let match = input.match(rx);
ft.log('Video ID: ', match);
let urlSplit = input.split('/');
if (match) {
ft.log('Video found');
loadingView.seen = true;
playVideo(match[2]);
} 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();
}
console.log(match);
// Play video if a match is found.
try {
console.log('Match Found');
playVideo(match[2]);
} catch (err) {
console.log('Video not found');
search();
}
}
/**
@ -377,42 +410,42 @@ 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 duration = '';
let match = durationString.match(/P.*T(\d+H)?(\d+M)?(\d+S)?/);
let duration = '';
match = match.slice(1).map(function(x) {
if (x != null) {
return x.replace(/\D/, '');
match = match.slice(1).map(function (x) {
if (x != null) {
return x.replace(/\D/, '');
}
});
let hours = (parseInt(match[0]) || 0);
let minutes = (parseInt(match[1]) || 0);
let seconds = (parseInt(match[2]) || 0);
if (hours != 0) {
duration = hours + ':';
} else {
duration = minutes + ':';
}
});
let hours = (parseInt(match[0]) || 0);
let minutes = (parseInt(match[1]) || 0);
let seconds = (parseInt(match[2]) || 0);
if (hours != 0 && minutes < 10) {
duration = duration + '0' + minutes + ':';
} else if (hours != 0 && minutes > 10) {
duration = duration + minutes + ':';
} else if (hours != 0 && minutes == 0) {
duration = duration + '00:';
}
if (hours != 0) {
duration = hours + ':';
} else {
duration = minutes + ':';
}
if (seconds == 0) {
duration = duration + '00';
} else if (seconds < 10) {
duration = duration + '0' + seconds;
} else {
duration = duration + seconds;
}
if (hours != 0 && minutes < 10) {
duration = duration + '0' + minutes + ':';
} else if (hours != 0 && minutes > 10) {
duration = duration + minutes + ':';
} else if (hours != 0 && minutes == 0) {
duration = duration + '00:';
}
if (seconds == 0) {
duration = duration + '00';
} else if (seconds < 10) {
duration = duration + '0' + seconds;
} else {
duration = duration + seconds;
}
return duration;
return duration;
}
/**
@ -421,12 +454,10 @@ function parseVideoDuration(durationString) {
* @return {Void}
*/
function showMostPopular() {
clearMainContainer();
startLoadingAnimation();
// Get the date of 2 days ago.
var d = new Date();
d.setDate(d.getDate() - 2);
// Get the date of 2 days ago.
var d = new Date();
d.setDate(d.getDate() - 2);
// Grab all videos published 2 days ago and after and order them by view count.
// These are the videos that are considered as 'most popular' and is how similar
@ -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
@ -460,10 +494,21 @@ function showMostPopular() {
* @return {Void}
*/
function copyLink(website, videoId) {
// Create the URL and copy to the clipboard.
const url = 'https://' + website + '.com/watch?v=' + videoId;
clipboard.writeText(url);
showToast('URL has been copied to the clipboard');
// 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,20 +519,20 @@ function copyLink(website, videoId) {
* @return {promise} - The HTML of the embeded player
*/
function getChannelAndPlayer(videoId) {
console.log(videoId);
return new Promise((resolve, reject) => {
youtubeAPI('videos', {
part: 'snippet,player',
id: videoId,
}, function(data) {
let embedHtml = data.items[0].player.embedHtml;
embedHtml = embedHtml.replace('src="', 'src="https:');
embedHtml = embedHtml.replace('width="480px"', '');
embedHtml = embedHtml.replace('height="270px"', '');
embedHtml = embedHtml.replace(/\"/g, '&quot;');
resolve([embedHtml, data.items[0].snippet.channelId]);
ft.log('Video ID: ', videoId);
return new Promise((resolve, reject) => {
youtubeAPI('videos', {
part: 'snippet,player',
id: videoId,
}, function (data) {
let embedHtml = data.items[0].player.embedHtml;
embedHtml = embedHtml.replace('src="', 'src="https:');
embedHtml = embedHtml.replace('width="480px"', '');
embedHtml = embedHtml.replace('height="270px"', '');
embedHtml = embedHtml.replace(/\"/g, '&quot;');
resolve([embedHtml, data.items[0].snippet.channelId]);
});
});
});
}
/**
@ -499,68 +544,68 @@ function getChannelAndPlayer(videoId) {
* @param {string} video720p - The URL to the 720p video.
*/
function checkVideoUrls(video480p, video720p) {
const currentQuality = $('#currentQuality').html();
let buttonEmbed = document.getElementById('qualityEmbed');
const currentQuality = $('#currentQuality').html();
let buttonEmbed = document.getElementById('qualityEmbed');
let valid480 = false;
let valid480 = false;
if (typeof(video480p) !== 'undefined') {
let get480pUrl = fetch(video480p);
get480pUrl.then((status) => {
switch (status.status) {
case 404:
showToast('Found valid URL for 480p, but returned a 404. Video type might be available in the future.');
$(document).off('click', '#quality480p');
$(document).on('click', '#quality480p', (event) => {
changeQuality('');
});
buttonEmbed.click();
return;
break;
case 403:
showToast('This video is unavailable in your country.');
$(document).off('click', '#quality480p');
$(document).on('click', '#quality480p', (event) => {
changeQuality('');
});
return;
break;
default:
console.log('480p is valid');
if (currentQuality === '720p' && typeof(video720p) === 'undefined') {
changeQuality(video480p);
}
break;
}
});
}
if (typeof (video480p) !== 'undefined') {
let get480pUrl = fetch(video480p);
get480pUrl.then((status) => {
switch (status.status) {
case 404:
showToast('Found valid URL for 480p, but returned a 404. Video type might be available in the future.');
$(document).off('click', '#quality480p');
$(document).on('click', '#quality480p', (event) => {
changeQuality('');
});
buttonEmbed.click();
return;
break;
case 403:
showToast('This video is unavailable in your country.');
$(document).off('click', '#quality480p');
$(document).on('click', '#quality480p', (event) => {
changeQuality('');
});
return;
break;
default:
ft.log('480p is valid');
if (currentQuality === '720p' && typeof (video720p) === 'undefined') {
changeQuality(video480p);
}
break;
}
});
}
if (typeof(video720p) !== 'undefined') {
let get720pUrl = fetch(video720p);
get720pUrl.then((status) => {
switch (status.status) {
case 404:
showToast('Found valid URL for 720p, but returned a 404. Video type might be available in the future.');
$(document).off('click', '#quality720p');
$(document).on('click', '#quality720p', (event) => {
changeQuality('');
});
if (typeof(valid480) !== 'undefined') {
changeQuality(video480p, '480p');
}
break;
case 403:
showToast('This video is unavailable in your country.');
$(document).off('click', '#quality720p');
$(document).on('click', '#quality720p', (event) => {
changeQuality('');
});
return;
break;
default:
console.log('720p is valid');
break;
}
});
}
if (typeof (video720p) !== 'undefined') {
let get720pUrl = fetch(video720p);
get720pUrl.then((status) => {
switch (status.status) {
case 404:
showToast('Found valid URL for 720p, but returned a 404. Video type might be available in the future.');
$(document).off('click', '#quality720p');
$(document).on('click', '#quality720p', (event) => {
changeQuality('');
});
if (typeof (valid480) !== 'undefined') {
changeQuality(video480p, '480p');
}
break;
case 403:
showToast('This video is unavailable in your country.');
$(document).off('click', '#quality720p');
$(document).on('click', '#quality720p', (event) => {
changeQuality('');
});
return;
break;
default:
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;
});
}
@ -50,19 +70,19 @@ function youtubeAPI(resource, params, success) {
*/
function youtubedlGetInfo(videoId, callback) {
let url = 'https://youtube.com/watch?v=' + videoId;
let options = ['--all-subs', '--write-subs'];
let url = 'https://youtube.com/watch?v=' + videoId;
let options = ['--all-subs', '--write-subs'];
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');
callback(info);
});
}
ft.log('Success');
callback(info);
});
}

View File

@ -1,45 +1,62 @@
.channelViewBanner{
width: 100%;
max-height: 200px;
margin-bottom: 30px;
/*
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{
float: left;
width: 100px;
border-radius: 200px 200px 200px 200px;
-webkit-border-radius: 200px 200px 200px 200px;
.channelViewImage {
float: left;
width: 100px;
border-radius: 200px 200px 200px 200px;
-webkit-border-radius: 200px 200px 200px 200px;
}
.channelViewTitle{
height: 100px;
margin-left: 100px;
.channelViewTitle {
height: 100px;
margin-left: 100px;
}
.channelViewName{
font-weight: bold;
font-size: 25px;
margin-left: 20px;
position: relative;
top: 20px;
.channelViewName {
font-weight: bold;
font-size: 25px;
margin-left: 20px;
position: relative;
top: 20px;
}
.channelViewSubs{
margin-left: 20px;
margin-top: 20px;
position: relative;
top: 20px;
.channelViewSubs {
margin-left: 20px;
margin-top: 20px;
position: relative;
top: 20px;
}
.channelSubButton{
float: right;
width: 125px;
height: 50px;
line-height: 50px;
text-align: center;
cursor: pointer;
.channelSubButton {
float: right;
width: 125px;
height: 50px;
line-height: 50px;
text-align: center;
cursor: pointer;
}
.channelViewDescription{
white-space: pre-line;
.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,45 +1,71 @@
/*
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;
position: relative;
margin: 100px auto;
.spinner {
width: 40px;
height: 40px;
position: relative;
margin: 100px auto;
}
.double-bounce1, .double-bounce2 {
width: 100%;
height: 100%;
border-radius: 50%;
opacity: 0.6;
position: absolute;
top: 0;
left: 0;
.double-bounce1,
.double-bounce2 {
width: 100%;
height: 100%;
border-radius: 50%;
opacity: 0.6;
position: absolute;
top: 0;
left: 0;
-webkit-animation: sk-bounce 2.0s infinite ease-in-out;
animation: sk-bounce 2.0s infinite ease-in-out;
-webkit-animation: sk-bounce 2.0s infinite ease-in-out;
animation: sk-bounce 2.0s infinite ease-in-out;
}
.double-bounce2 {
-webkit-animation-delay: -1.0s;
animation-delay: -1.0s;
-webkit-animation-delay: -1.0s;
animation-delay: -1.0s;
}
@-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% {
transform: scale(0.0);
-webkit-transform: scale(0.0);
} 50% {
transform: scale(1.0);
-webkit-transform: scale(1.0);
}
0%,
100% {
transform: scale(0.0);
-webkit-transform: scale(0.0);
}
50% {
transform: scale(1.0);
-webkit-transform: scale(1.0);
}
}

View File

@ -1,170 +1,197 @@
/*
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);
}
body {
font-family: 'Roboto', sans-serif;
font-family: 'Roboto', sans-serif;
}
input {
box-sizing: border-box;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
outline: none;
width: 100%;
padding: 7px;
border: none;
background: transparent;
margin-bottom: 10px;
font: 16px;
height: 45px;
box-sizing: border-box;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
outline: none;
width: 100%;
padding: 7px;
border: none;
background: transparent;
margin-bottom: 10px;
font: 16px;
height: 45px;
}
a {
color: #2196F3;
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;
text-align: center;
margin: 0 auto;
}
.topNav {
width: 100%;
height: 60px;
line-height: 60px;
position: fixed;
top: 0;
left: 0;
z-index: 1;
width: 100%;
height: 60px;
line-height: 60px;
position: fixed;
top: 0;
left: 0;
z-index: 1;
}
.topNav span {
display: inline-block;
vertical-align: middle;
line-height: normal;
display: inline-block;
vertical-align: middle;
line-height: normal;
}
#menuButton {
cursor: pointer;
font-weight: bold;
font-size: 20px;
margin-left: 20px;
position: relative;
top: 3px;
cursor: pointer;
font-weight: bold;
font-size: 20px;
margin-left: 20px;
position: relative;
top: 3px;
}
#menuIcon {
width: 25px;
position: relative;
top: 15px;
right: 120px;
float: right;
width: 25px;
position: relative;
top: 15px;
right: 120px;
float: right;
}
#menuText {
width: 100px;
position: relative;
top: 5px;
left: 10px;
float: right;
width: 100px;
position: relative;
top: 5px;
left: 10px;
float: right;
}
.searchBar {
position: absolute;
right: 135px;
top: 0;
width: 50%;
position: absolute;
right: 135px;
top: 0;
width: 50%;
}
.searchBar input {
width: 90%;
width: 90%;
}
.searchButton {
text-decoration: none;
text-decoration: none;
}
#sideNav {
height: 100vh;
width: 250px;
overflow-y: auto;
position: fixed;
left: 0;
top: 0px;
-webkit-box-shadow: 4px -2px 51px -6px rgba(0, 0, 0, 0.75);
height: 100vh;
width: 250px;
overflow-y: auto;
position: fixed;
left: 0;
top: 0px;
-webkit-box-shadow: 4px -2px 51px -6px rgba(0, 0, 0, 0.75);
}
#sideNav hr {
width: 95%;
height: 1px;
border: 0;
width: 95%;
height: 1px;
border: 0;
}
#sideNav ul {
list-style-type: none;
list-style-type: none;
}
#sideNav li {
padding: 20px;
width: 205px;
position: relative;
right: 50px;
cursor: pointer;
-webkit-transition: background 0.2s ease-out;
-moz-transition: background 0.2s ease-out;
-o-transition: background 0.2s ease-out;
transition: background 0.2s ease-out;
padding: 20px;
width: 205px;
position: relative;
right: 50px;
cursor: pointer;
-webkit-transition: background 0.2s ease-out;
-moz-transition: background 0.2s ease-out;
-o-transition: background 0.2s ease-out;
transition: background 0.2s ease-out;
}
#sideNav li:hover {
-moz-transition: background 0.2s ease-in;
-o-transition: background 0.2s ease-in;
transition: background 0.2s ease-in;
-moz-transition: background 0.2s ease-in;
-o-transition: background 0.2s ease-in;
transition: background 0.2s ease-in;
}
#sideNavDisabled {
height: 100vh;
width: 250px;
position: fixed;
left: 0;
top: 0px;
z-index: 1;
margin-top: 85px;
display: none;
height: 100vh;
width: 250px;
position: fixed;
left: 0;
top: 0px;
z-index: 1;
margin-top: 85px;
display: none;
}
.sideNavContainer {
margin-top: 85px;
margin-top: 85px;
}
#subscriptions img {
float: left;
width: 40px;
border-radius: 200px 200px 200px 200px;
-webkit-border-radius: 200px 200px 200px 200px;
position: relative;
top: -12px;
margin-right: 5px;
float: left;
width: 40px;
border-radius: 200px 200px 200px 200px;
-webkit-border-radius: 200px 200px 200px 200px;
position: relative;
top: -12px;
margin-right: 5px;
}
#subscriptions .fa-times {
float: right;
margin-right: 5px;
float: right;
margin-right: 5px;
}
#subscriptions li {
font-size: 11px;
font-size: 11px;
}
.message {
text-align: center;
text-align: center;
}
.videoDelete {
float: right;
cursor: pointer;
float: right;
cursor: pointer;
}
#loading {
@ -176,198 +203,200 @@ a {
left: 5;
bottom: 0;
right: 0;
display: none;
}
.settingsInput {
width: 50%;
border-bottom: 1px solid #616161;
width: 50%;
border-bottom: 1px solid #616161;
}
.settingsButton {
padding: 10px;
display: inline-block;
cursor: pointer;
margin: 5px;
-webkit-box-shadow: 4px 4px 10px 0px rgba(0, 0, 0, 0.75);
-moz-box-shadow: 4px 4px 10px 0px rgba(0, 0, 0, 0.75);
box-shadow: 4px 4px 10px 0px rgba(0, 0, 0, 0.75);
padding: 10px;
display: inline-block;
cursor: pointer;
margin: 5px;
-webkit-box-shadow: 4px 4px 10px 0px rgba(0, 0, 0, 0.75);
-moz-box-shadow: 4px 4px 10px 0px rgba(0, 0, 0, 0.75);
box-shadow: 4px 4px 10px 0px rgba(0, 0, 0, 0.75);
}
.settingsSubmit {
padding: 15px;
color: #212121;
background-color: #f44336;
cursor: pointer;
width: 150px;
-webkit-box-shadow: 4px 4px 10px 0px rgba(0, 0, 0, 0.75);
-moz-box-shadow: 4px 4px 10px 0px rgba(0, 0, 0, 0.75);
box-shadow: 4px 4px 10px 0px rgba(0, 0, 0, 0.75);
padding: 15px;
color: #212121;
background-color: #f44336;
cursor: pointer;
width: 150px;
-webkit-box-shadow: 4px 4px 10px 0px rgba(0, 0, 0, 0.75);
-moz-box-shadow: 4px 4px 10px 0px rgba(0, 0, 0, 0.75);
box-shadow: 4px 4px 10px 0px rgba(0, 0, 0, 0.75);
}
.help {
margin: 30px;
margin: 30px;
}
.live {
font-size: 12px;
height: 20px;
text-align: center;
width: 57px;
position: relative;
left: 284px;
color: #f44336;
bottom: 15px;
line-height: 10px;
font-size: 12px;
height: 20px;
text-align: center;
width: 57px;
position: relative;
left: 284px;
color: #f44336;
bottom: 15px;
line-height: 10px;
}
#toast{
min-width: 400px;
height: 50px;
position: fixed;
bottom: 20px;
right: 20px;
padding: 10px;
text-align: center;
font-size: 17px;
line-height: 50px;
opacity: 0;
z-index: 1;
visibility: hidden;
-webkit-transition: opacity 0.5s ease-in-out;
-moz-transition: opacity 0.5s ease-in-out;
-ms-transition: opacity 0.5s ease-in-out;
-o-transition: opacity 0.5s ease-in-out;
#toast {
min-width: 400px;
height: 50px;
position: fixed;
bottom: 20px;
right: 20px;
padding: 10px;
text-align: center;
font-size: 17px;
line-height: 50px;
opacity: 0;
z-index: 1;
visibility: hidden;
-webkit-transition: opacity 0.5s ease-in-out;
-moz-transition: opacity 0.5s ease-in-out;
-ms-transition: opacity 0.5s ease-in-out;
-o-transition: opacity 0.5s ease-in-out;
}
.closeToast{
font-size: 15px;
position: absolute;
top: 5px;
right: 10px;
cursor: pointer;
.closeToast {
font-size: 15px;
position: absolute;
top: 5px;
right: 10px;
cursor: pointer;
}
#confirmFunction{
position: fixed;
top: 50%;
left: 30%;
width: 800px;
height: 65px;
font-weight: bold;
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);
#confirmFunction {
position: fixed;
top: 50%;
left: 30%;
width: 800px;
height: 65px;
font-weight: bold;
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);
}
#confirmMessage{
margin-left: 15px;
#confirmMessage {
margin-left: 15px;
}
.confirmButton{
position: absolute;
top: 0px;
cursor: pointer;
.confirmButton {
position: absolute;
top: 0px;
cursor: pointer;
}
#confirmYes{
right: 90px;
color: #2196f3;
#confirmYes {
right: 90px;
color: #2196f3;
}
#confirmNo{
right: 20px;
#confirmNo {
right: 20px;
}
#getNextPage{
width: 100%;
height: 45px;
line-height: 45px;
text-align: center;
cursor: pointer;
#getNextPage {
width: 100%;
height: 45px;
line-height: 45px;
text-align: center;
cursor: pointer;
}
#comments{
display:none;
padding:1em;
margin-bottom: 1em;
#comments {
display: none;
padding: 1em;
margin-bottom: 1em;
}
.saved{color: #FFEB3B;}
.saved {
color: #FFEB3B;
}
/* Thanks to Guus Lieben for the Material Design Switch */
.switch-input {
display: none;
display: none;
}
.switch-label {
position: relative;
display: inline-block;
min-width: 112px;
cursor: pointer;
font-weight: 500;
text-align: left;
margin: 16px;
padding: 16px 0 16px 44px;
position: relative;
display: inline-block;
min-width: 112px;
cursor: pointer;
font-weight: 500;
text-align: left;
margin: 16px;
padding: 16px 0 16px 44px;
}
.switch-label:before, .switch-label:after {
content: "";
position: absolute;
margin: 0;
outline: 0;
top: 50%;
-ms-transform: translate(0, -50%);
-webkit-transform: translate(0, -50%);
transform: translate(0, -50%);
-webkit-transition: all 0.3s ease;
transition: all 0.3s ease;
.switch-label:before,
.switch-label:after {
content: "";
position: absolute;
margin: 0;
outline: 0;
top: 50%;
-ms-transform: translate(0, -50%);
-webkit-transform: translate(0, -50%);
transform: translate(0, -50%);
-webkit-transition: all 0.3s ease;
transition: all 0.3s ease;
}
.switch-label:before {
left: 1px;
width: 34px;
height: 14px;
background-color: #9E9E9E;
border-radius: 8px;
left: 1px;
width: 34px;
height: 14px;
background-color: #9E9E9E;
border-radius: 8px;
}
.switch-label:after {
left: 0;
width: 20px;
height: 20px;
background-color: #FAFAFA;
border-radius: 50%;
box-shadow: 0 3px 1px -2px rgba(0, 0, 0, 0.14), 0 2px 2px 0 rgba(0, 0, 0, 0.098), 0 1px 5px 0 rgba(0, 0, 0, 0.084);
left: 0;
width: 20px;
height: 20px;
background-color: #FAFAFA;
border-radius: 50%;
box-shadow: 0 3px 1px -2px rgba(0, 0, 0, 0.14), 0 2px 2px 0 rgba(0, 0, 0, 0.098), 0 1px 5px 0 rgba(0, 0, 0, 0.084);
}
.switch-label .toggle--on {
display: none;
display: none;
}
.switch-label .toggle--off {
display: inline-block;
display: inline-block;
}
.switch-input:checked+.switch-label:before {
background-color: #90CAF9;
background-color: #90CAF9;
}
.switch-input:checked+.switch-label:after {
background-color: #2196F3;
-ms-transform: translate(80%, -50%);
-webkit-transform: translate(80%, -50%);
transform: translate(80%, -50%);
background-color: #2196F3;
-ms-transform: translate(80%, -50%);
-webkit-transform: translate(80%, -50%);
transform: translate(80%, -50%);
}
.switch-input:checked+.switch-label .toggle--on {
display: inline-block;
display: inline-block;
}
.switch-input:checked+.switch-label .toggle--off {
display: none;
display: none;
}

View File

@ -1,338 +1,365 @@
/*
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;
width: 100%;
height: 41.25vw;
}
#main {
margin-top: 80px;
margin-left: 250px;
margin-top: 80px;
margin-left: 250px;
}
.video {
width: 95%;
max-width: 1000px;
height: 145px;
padding: 15px;
overflow: hidden;
width: 95%;
max-width: 1000px;
height: auto;
padding: 15px;
overflow: hidden;
}
.videoThumbnail {
width: 275px;
height: 160px;
float: left;
cursor: pointer;
width: 275px;
height: 160px;
float: left;
cursor: pointer;
}
.videoThumbnail img{
width: 100%;
.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;
float: left;
cursor: pointer;
position: relative;
left: 60px;
width: 140px;
height: 140px;
float: left;
cursor: pointer;
position: relative;
left: 60px;
}
.channelThumbnail img{
width: 100%;
border-radius: 200px 200px 200px 200px;
-webkit-border-radius: 200px 200px 200px 200px;
.channelThumbnail img {
width: 100%;
border-radius: 200px 200px 200px 200px;
-webkit-border-radius: 200px 200px 200px 200px;
}
.videoOptions {
float: right;
width: 50px;
float: right;
width: 50px;
}
.videoOptions i {
text-align: right;
width: 50px;
cursor: pointer;
}
.videoOptions ul {
width: 90px;
font-size: 12px;
position: relative;
right: 90px;
bottom: 10px;
list-style-type: none;
display: none;
-webkit-box-shadow: 4px 4px 33px -7px rgba(0,0,0,0.75);
cursor: pointer;
width: 90px;
font-size: 12px;
position: relative;
right: 90px;
bottom: 10px;
list-style-type: none;
display: none;
-webkit-box-shadow: 4px 4px 33px -7px rgba(0, 0, 0, 0.75);
cursor: pointer;
z-index: 10;
}
.videoOptions li {
width: 110px;
position: relative;
right: 40px;
padding: 10px;
-webkit-transition: background 0.2s ease-out;
-moz-transition: background 0.2s ease-out;
-o-transition: background 0.2s ease-out;
transition: background 0.2s ease-out;
width: 110px;
position: relative;
right: 40px;
padding: 10px;
-webkit-transition: background 0.2s ease-out;
-moz-transition: background 0.2s ease-out;
-o-transition: background 0.2s ease-out;
transition: background 0.2s ease-out;
}
.videoOptions li:hover {
-moz-transition: background 0.2s ease-in;
-o-transition: background 0.2s ease-in;
transition: background 0.2s ease-in;
-moz-transition: background 0.2s ease-in;
-o-transition: background 0.2s ease-in;
transition: background 0.2s ease-in;
}
.videoOptions a {
text-decoration: none;
text-decoration: none;
}
.videoTitle {
font-weight: bold;
margin-left: 285px;
margin-top: 5px;
cursor: pointer;
font-weight: bold;
margin-left: 285px;
margin-top: 5px;
cursor: pointer;
}
.channelName {
margin-left: 285px;
font-size: 14px;
cursor: pointer;
margin-left: 285px;
font-size: 14px;
cursor: pointer;
}
.videoDescription {
margin-left: 285px;
font-size: 13px;
cursor: pointer;
height: 45px;
overflow: hidden;
margin-left: 285px;
font-size: 13px;
cursor: pointer;
height: 45px;
overflow: hidden;
}
.videoDuration {
display: inline-block;
float: right;
position: relative;
bottom: 36px;
color: white;
background-color: black;
opacity: 0.7;
padding: 2px;
font-size: 13px;
text-align: right;
float: right;
position: relative;
bottom: 36px;
color: white;
background-color: black;
opacity: 0.7;
padding: 2px;
font-size: 13px;
text-align: right;
}
.videoPlayer {
width: 100%;
width: 100%;
max-height: 1100px;
}
.statistics {
padding: 20px;
padding-bottom: 45px;
padding: 20px;
padding-bottom: 45px;
}
.title {
font-weight: bold;
font-size: 25px;
font-weight: bold;
font-size: 25px;
}
.views {
margin-top: -10px;
float: left;
margin-top: -10px;
float: left;
}
.details {
padding: 15px;
padding: 15px;
}
.likeContainer {
width: 300px;
float: right;
width: 300px;
float: right;
}
.likes {
float: left;
font-size: 12px;
float: left;
font-size: 12px;
}
.dislikes {
float: right;
font-size: 12px;
float: right;
font-size: 12px;
}
.dislikeBar {
background-color: #9E9E9E;
width: 300px;
height: 5px;
margin-bottom: 5px;
border-radius: 200px 200px 200px 200px;
-webkit-border-radius: 200px 200px 200px 200px;
background-color: #9E9E9E;
width: 300px;
height: 5px;
margin-bottom: 5px;
border-radius: 200px 200px 200px 200px;
-webkit-border-radius: 200px 200px 200px 200px;
}
.likeBar {
height: 5px;
background-color: #2196F3;
border-radius: 200px 200px 200px 200px;
-webkit-border-radius: 200px 200px 200px 200px;
height: 5px;
background-color: #2196F3;
border-radius: 200px 200px 200px 200px;
-webkit-border-radius: 200px 200px 200px 200px;
}
#channelIcon {
float: left;
width: 80px;
border-radius: 200px 200px 200px 200px;
-webkit-border-radius: 200px 200px 200px 200px;
cursor: pointer;
float: left;
width: 80px;
border-radius: 200px 200px 200px 200px;
-webkit-border-radius: 200px 200px 200px 200px;
cursor: pointer;
}
#comments .line {
clear: both;
height: 1px;
background: #d8d8d8;
clear: both;
height: 1px;
background: #d8d8d8;
}
.userIcon {
float: left;
width: 48px;
border-radius: 200px 200px 200px 200px;
-webkit-border-radius: 200px 200px 200px 200px;
cursor: pointer;
float: left;
width: 48px;
border-radius: 200px 200px 200px 200px;
-webkit-border-radius: 200px 200px 200px 200px;
cursor: pointer;
}
#channelName {
font-weight: bold;
margin-left: 95px;
font-size: 16px;
cursor: pointer;
font-weight: bold;
margin-left: 95px;
font-size: 16px;
cursor: pointer;
}
#publishDate {
margin-left: 95px;
font-size: 13px;
margin-top: -10px;
margin-left: 95px;
font-size: 13px;
margin-top: -10px;
}
#description {
white-space: pre-line;
max-height: 200px;
overflow-y: auto;
white-space: pre-line;
max-height: 200px;
overflow-y: auto;
}
.playerSubButton {
float: right;
width: 125px;
height: 50px;
line-height: 50px;
text-align: center;
margin-top: -65px;
cursor: pointer;
float: right;
width: 125px;
height: 50px;
line-height: 50px;
text-align: center;
margin-top: -65px;
cursor: pointer;
}
.smallButton {
float: right;
height: 30px;
font-size: 10px;
line-height: 30px;
text-align: center;
margin-right: 5px;
padding-left: 15px;
padding-right: 15px;
cursor: pointer;
float: right;
height: 30px;
font-size: 10px;
line-height: 30px;
text-align: center;
margin-right: 5px;
padding-left: 15px;
padding-right: 15px;
cursor: pointer;
}
.videoQuality {
width: 42px;
width: 42px;
}
.videoQuality:hover .qualityTypes {
visibility: visible;
visibility: visible;
}
.qualityTypes {
visibility: hidden;
width: 72px;
position: relative;
bottom: 10px;
right: 15px;
visibility: hidden;
width: 72px;
position: relative;
bottom: 10px;
right: 15px;
}
.qualityTypes ul {
list-style-type: none;
position: relative;
right: 24px;
list-style-type: none;
position: relative;
right: 24px;
}
.qualityTypes ul li {
width: 72px;
position: relative;
right: 15px;
width: 72px;
position: relative;
right: 15px;
}
.videoSpeed {
width: 42px;
width: 42px;
}
.videoSpeed:hover .speedTypes {
visibility: visible;
visibility: visible;
}
.speedTypes {
visibility: hidden;
width: 72px;
position: relative;
bottom: 10px;
right: 15px;
visibility: hidden;
width: 72px;
position: relative;
bottom: 10px;
right: 15px;
}
.speedTypes ul {
list-style-type: none;
position: relative;
right: 24px;
list-style-type: none;
position: relative;
right: 24px;
}
.speedTypes ul li {
width: 72px;
position: relative;
right: 15px;
width: 72px;
position: relative;
right: 15px;
}
#showComments {
text-align: center;
height: 40px;
line-height: 40px;
margin-top: 15px;
margin-bottom: 15px;
text-align: center;
height: 40px;
line-height: 40px;
margin-top: 15px;
margin-bottom: 15px;
}
#recommendations {
width: 100%;
width: 100%;
}
.recommendVideo {
cursor: pointer;
height: 150px;
cursor: pointer;
height: 150px;
}
.recommendThumbnail {
width: 250px;
height: 145px;
float: left;
width: 250px;
height: 145px;
float: left;
}
.recommendThumbnail img{
width: 100%;
.recommendThumbnail img {
width: 100%;
}
.recommendTitle {
font-size: 16px;
font-weight: bold;
margin-left: 260px;
font-size: 16px;
font-weight: bold;
margin-left: 260px;
}
.recommendChannel {
margin-left: 260px;
font-size: 14px;
margin-top: -10px;
margin-left: 260px;
font-size: 14px;
margin-top: -10px;
}
.recommendDate {
margin-left: 260px;
font-size: 11px;
margin-left: 260px;
font-size: 11px;
}

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 />
<span class='channelViewSubs'>{{subCount}} Subscribers</span>
<div id='subscribeButton' class='channelSubButton' onclick='toggleSubscription("{{channelId}}");'>
{{subButtonText}}
<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' v-on:click='subscription(id);'>
{{subButtonText}}
</div>
</div>
</div>
<br />
<hr />
<div class='channelViewDescription'>
{{{channelDescription}}}
</div>
<br />
<hr />
<div id='videoListContainer'>
<h2>Latest Uploads</h2>
<br />
<hr />
<div class='channelViewDescription'>
<span v-html='description'></span>
</div>
<br />
<hr />
</div>

View File

@ -1,14 +1,31 @@
<!--
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">
<img class="userIcon" src="{{authorProfileImageUrl}}" alt="" class="user-icon" />
<div class="user-comment">
<div onclick='goToChannel("{{authorChannelId.value}}")' style="float:left;margin-right: 1em; width:48px">
<img class="userIcon" src="{{authorProfileImageUrl}}" alt="" class="user-icon" />
</div>
<div class="comment-data">
<p onclick='goToChannel("{{authorChannelId.value}}")' style='cursor: pointer; margin-left: 65px; font-weight: bold;'>{{authorDisplayName}}</p>
<p style="margin-bottom: 0; margin-left: 65px; word-break: break-word;">{{textOriginal}}</p>
<p style="font-size:80%; padding: 0; margin-left: 65px;">{{publishedAt}}</p>
</div>
</div>
<div class="comment-data">
<p onclick='goToChannel("{{authorChannelId.value}}")' style='cursor: pointer; margin-left: 65px; font-weight: bold;'>{{authorDisplayName}}</p>
<p style="margin-bottom: 0; margin-left: 65px; word-break: break-word;">{{textOriginal}}</p>
<p style="font-size:80%; padding: 0; margin-left: 65px;">{{publishedAt}}</p>
</div>
</div>
<br/>
<div class="line"></div>
<br/>
<div class="line"></div>
{{/comments}}

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{
background-color: #424242;
body {
background-color: #424242;
}
.videoPlayer{
width: 100%;
.videoPlayer {
width: 100%;
}
iframe{
width: 100%;
height: 54.9vw;
iframe {
width: 100%;
height: 54.9vw;
}
</style>
<script src="../js/events.js"></script>
<script>
@ -18,40 +38,41 @@ let startTime = '{{startTime}}';
let mouseTimeout;
$('.videoPlayer').ready(() => {
$('.videoPlayer').get(0).currentTime = startTime;
$('.videoPlayer').get(0).currentTime = startTime;
});
/**
* 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(){
$('.videoPlayer')[0].style.cursor = 'none';
}, 3150);
* 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() {
$('.videoPlayer')[0].style.cursor = 'none';
}, 3150);
}
/**
* Remove the timeout for the mouse cursor as a fallback.
*
* @return {Void}
*/
function removeMouseTimeout(){
$('.videoPlayer')[0].style.cursor = 'default';
clearTimeout(mouseTimeout);
* Remove the timeout for the mouse cursor as a fallback.
*
* @return {Void}
*/
function removeMouseTimeout() {
$('.videoPlayer')[0].style.cursor = 'default';
clearTimeout(mouseTimeout);
}
let playPauseVideo = function(event) {
let el = event.currentTarget;
el.paused ? el.play() : el.pause();
let el = event.currentTarget;
el.paused ? el.play() : el.pause();
}
$(document).on('click', '.videoPlayer', playPauseVideo);
$(document).on('mouseover', '.videoPlayer', hideMouseTimeout);
$(document).on('mouseleave', '.videoPlayer', removeMouseTimeout);
</script>
{{{videoHtml}}}

View File

@ -1,82 +1,117 @@
<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}}')">
MINI PLAYER <i class="fas fa-external-link-alt"></i>
<!--
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 class='smallButton videoQuality'>
<span id='currentQuality'>{{videoQuality}}</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>
</ul>
<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>
<div class='smallButton videoSpeed'>
<span id='currentSpeed'>1</span>x <i class="fas fa-angle-down"></i>
<div class='speedTypes'>
<ul>
<li onclick='changeVideoSpeed(0.25)'>0.25x</li>
<li onclick='changeVideoSpeed(0.5)'>0.5x</li>
<li onclick='changeVideoSpeed(0.75)'>0.75x</li>
<li onclick='changeVideoSpeed(1)'>1x</li>
<li onclick='changeVideoSpeed(1.25)'>1.25x</li>
<li onclick='changeVideoSpeed(1.5)'>1.5x</li>
<li onclick='changeVideoSpeed(1.75)'>1.75x</li>
<li onclick='changeVideoSpeed(2)'>2x</li>
</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>
<div class='smallButton' onclick='copyLink("youtube", "{{videoId}}")'>
COPY YOUTUBE LINK
</div>
<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>
<a href='https://hooktube.com/watch?v={{videoId}}'>
<div class='smallButton'>
OPEN IN HOOKTUBE
</div>
</a>
<br />
<p class='title'>{{videoTitle}}</p>
<p class='views'>{{videoViews}} views</p>
<div class='likeContainer'>
<div class='dislikeBar'>
<div class='likeBar' style='width: {{likePercentage}}%;'>
<div class='smallButton videoQuality'>
<span id='currentQuality'>{{currentQuality}}</span> <i class="fas fa-angle-down"></i>
<div class='qualityTypes'>
<ul>
<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>
<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 class='smallButton videoSpeed'>
<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>
<li onclick='changeVideoSpeed(0.5)'>0.5x</li>
<li onclick='changeVideoSpeed(0.75)'>0.75x</li>
<li onclick='changeVideoSpeed(1)'>1x</li>
<li onclick='changeVideoSpeed(1.25)'>1.25x</li>
<li onclick='changeVideoSpeed(1.5)'>1.5x</li>
<li onclick='changeVideoSpeed(1.75)'>1.75x</li>
<li onclick='changeVideoSpeed(2)'>2x</li>
</ul>
</div>
</div>
<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' v-on:click='copy("youtube.com", videoId)'>
COPY YOUTUBE LINK
</div>
<a :href='"https://youtube.com/watch?v=" + videoId'>
<div class='smallButton'>
OPEN IN YOUTUBE
</div>
</a>
<div class='smallButton' v-on:click='copy("invidio.us", videoId)'>
COPY INVIDIOUS LINK
</div>
<a :href='"https://invidio.us/watch?v=" + videoId'>
<div class='smallButton'>
OPEN IN INVIDIOUS
</div>
</a>
<br />
<p class='title'>{{videoTitle}}</p>
<p class='views'>{{videoViews}} views</p>
<div class='likeContainer'>
<div class='dislikeBar'>
<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' 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' v-on:click='subscription(channelId)'>{{subscribedText}}</div>
<br /><br />
<div id='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 class="details">
<img id='channelIcon' onclick='goToChannel("{{channelId}}")' src="{{channelIcon}}" />
<p id='channelName' onclick='goToChannel("{{channelId}}")'>{{channelName}}</p>
<p id='publishDate'>Published on {{publishedDate}}</p>
<div id='subscribeButton' class='playerSubButton' onclick='toggleSubscription("{{channelId}}")'>{{isSubscribed}}</div>
<br /><br />
<div id='description'>
{{{description}}}
</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,10 +1,27 @@
<!--
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}} />
</div>
<p onclick='goToChannel("{{channelId}}")' class='videoTitle'>{{playlistTitle}}</p>
<p onclick='goToChannel("{{channelId}}")' class='channelName'>{{channelName}} - {{publishedDate}}</p>
<p onclick='goToChannel("{{channelId}}")' class='videoDescription'>{{playlistDescription}}</p>
<p>VIEW FULL PLAYLIST ({{videoCount}} videos)</p>
<div class='videoThumbnail'>
<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>
<p onclick='goToChannel("{{channelId}}")' class='videoDescription'>{{playlistDescription}}</p>
<p>VIEW FULL PLAYLIST ({{videoCount}} videos)</p>
</div>
<hr />

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,35 +1,54 @@
<h1 class="center">Settings</h1>
<div class='center'>
<input type='text' name='api-key' id='api-key' class='settingsInput' value='{{key}}' 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}}>
<label for="themeSwitch" class="switch-label">Use Dark Theme</label>
<input type="checkbox" id="torSwitch" name="set-name" class="switch-input" {{isChecked}}>
<label for="torSwitch" class="switch-label">Use Tor for API calls</label>
</div>
<div class='center'>
<div onclick='importSubscriptions()' class='settingsButton'>
IMPORT SUBSCRIPTIONS
<!--
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)' :checked='useTheme'>
<label for="themeSwitch" class="switch-label">Use Dark Theme</label>
<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 onclick='exportSubscriptions();' class='settingsButton'>
EXPORT SUBSCRIPTIONS
<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 onclick='confirmFunction("Are you sure you want to delete your history?", clearFile, "history")' class='settingsButton'>
CLEAR HISTORY
</div>
<div onclick='confirmFunction("Are you sure you want to remove all saved videos?", clearFile, "saved")' class='settingsButton'>
CLEAR SAVED VIDEOS
</div>
<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'>
SAVE SETTINGS
</div>
</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>
<div onclick='confirmFunction("Are you sure you want to remove all saved videos?", clearFile, "saved")' class='settingsButton'>
CLEAR SAVED VIDEOS
</div>
<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'>
SAVE SETTINGS
</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}}
<i class='fas fa-times' onclick='toggleSubscription("{{channelId}}")'></i>
<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>