Huge Code Clean up and Small Issue Fixes

* Added ability for div.video to change size.

This is needed because it fixes the issue of not being able to see the new menus later on when we might need to add more menus to the context menu. I also deleted display: inline block because it won't work since there's a float: right (always enforces display: block).

* Removed display: inline-block

* Converted from Typescript to JS... Even if this was supposed to be for Vue, there's no Vue module installed on this branch...

* Added .code-workspace in .gitignore for those who use a vscode workspace. Also added .jsbeautifyrc to clean up the code.  This is definitely a good idea when the project gets larger.

* Formatted the entire codebase to use a single coding standard.

* Added comment header to all files

* Some things actually weren't formatted correctly, so I fixed those

* Code fixup

* Forgot to change API key

* Fixed issue #136

* Fixed ft.logs
This commit is contained in:
K06RA 2018-07-24 01:11:56 +00:00 committed by PrestonN
parent 791b564509
commit 13567ad4af
41 changed files with 4053 additions and 3035 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
}
}

398
package-lock.json generated
View File

@ -1,6 +1,6 @@
{
"name": "FreeTube",
"version": "0.3.0",
"version": "0.3.1",
"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,100 +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: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",
"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: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

@ -2,76 +2,77 @@
<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/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>
<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/general.js"></script>
<script src="js/youtubeApi.js"></script>
<script src="js/settings.js"></script>
<script src="js/updates.js"></script>
<script src="js/videos.js"></script>
<script src="js/layout.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 class="spinner">
<div class="double-bounce1"></div>
<div class="double-bounce2"></div>
<div id='loading'>
<div class="spinner">
<div class="double-bounce1"></div>
<div class="double-bounce2"></div>
</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'></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 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>
<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>
</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>
</ul>
<hr />
<ul id='subscriptions'>
</ul>
<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'></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='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;Favorites</li>
<li onclick='showHistory()'><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>
</ul>
<hr />
<ul id='subscriptions'>
</ul>
</div>
</div>
<div id="main">
</div>
</div>
<div id="main">
</div>
</body>
</html>

View File

@ -1,25 +1,25 @@
/*
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.
*/
* File for all functions related specifically for channels.
*/
/*function getChannelThumbnail(channelId, callback) {
let url = '';
@ -33,60 +33,60 @@ along with FreeTube. If not, see <http://www.gnu.org/licenses/>.
}*/
/**
* 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();
event.stopPropagation();
clearMainContainer();
startLoadingAnimation();
let subButtonText;
// Setting subButtonText here as Mustache templates are logic-less.
isSubscribed(channelId).then((subscribed) => {
subButtonText = (subscribed ? "UNSUBSCRIBE" : "SUBSCRIBE");
});
// Grab general channel information
youtubeAPI('channels', {
part: 'snippet,brandingSettings,statistics',
id: channelId,
}, function (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,
let subButtonText;
// Setting subButtonText here as Mustache templates are logic-less.
isSubscribed(channelId).then((subscribed) => {
subButtonText = (subscribed ? "UNSUBSCRIBE" : "SUBSCRIBE");
});
$('#main').html(rendered);
stopLoadingAnimation();
// Grab the channel's latest uploads. API forces a max of 50.
youtubeAPI('search', {
part: 'snippet',
channelId: channelId,
type: 'video',
maxResults: 50,
order: 'date',
// Grab general channel information
youtubeAPI('channels', {
part: 'snippet,brandingSettings,statistics',
id: channelId,
}, function (data) {
// Display recent uploads to #main
let grabDuration = getDuration(data.items);
const channelData = data.items[0];
grabDuration.then((videoList) => {
videoList.items.forEach((video) => {
displayVideo(video);
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();
// 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);
});
});
});
});
});
});
}
}

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);
@ -220,4 +213,4 @@ $(document).on('dblclick', '.videoPlayer', fullscreenVideo);
$(document).on('keydown', videoShortcutHandler);
$(document).on('click', '#confirmNo', hideConfirmFunction);
$(document).on('click', '#confirmNo', hideConfirmFunction);

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,94 +1,94 @@
/*
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 video history.
*/
* File used for functions related to video history.
*/
/**
* Add a video to the history database file
*
* @param {string} videoId - The video ID of the video to be saved.
*
* @return {Void}
*/
function addToHistory(videoId){
const data = {
videoId: videoId,
timeWatched: new Date().getTime(),
};
historyDb.insert(data, (err, newDoc) => {});
* Add a video to the history database file
*
* @param {string} videoId - The video ID of the video to be saved.
*
* @return {Void}
*/
function addToHistory(videoId) {
const data = {
videoId: videoId,
timeWatched: new Date().getTime(),
};
historyDb.insert(data, (err, newDoc) => {});
}
/**
* Remove a video from the history database file
*
* @param {string} videoId - The video ID of the video to be removed.
*
* @return {Void}
*/
function removeFromHistory(videoId){
const data = {videoId: videoId};
historyDb.remove(data, {}, (err, numRemoved) => {});
* Remove a video from the history database file
*
* @param {string} videoId - The video ID of the video to be removed.
*
* @return {Void}
*/
function removeFromHistory(videoId) {
const data = {
videoId: videoId
};
historyDb.remove(data, {}, (err, numRemoved) => {});
}
/**
* Show the videos within the history database.
*
* @return {Void}
*/
function showHistory(){
clearMainContainer();
startLoadingAnimation();
console.log('checking history');
* Show the videos within the history database.
*
* @return {Void}
*/
function showHistory() {
clearMainContainer();
startLoadingAnimation();
let videoList = '';
let videoList = '';
historyDb.find({}).sort({
timeWatched: -1
}).exec((err, docs) => {
if(docs.length > 49){
// The YouTube API limits the search to 50 videos, so grab 50 most recent.
for (let i = 0; i < 49; i++) {
videoList = videoList + ',' + docs[i]['videoId'];
}
}
else{
docs.forEach((video) => {
videoList = videoList + ',' + video['videoId'];
});
}
historyDb.find({}).sort({
timeWatched: -1
}).exec((err, docs) => {
if (docs.length > 49) {
// The YouTube API limits the search to 50 videos, so grab 50 most recent.
for (let i = 0; i < 49; i++) {
videoList = videoList + ',' + docs[i]['videoId'];
}
} else {
docs.forEach((video) => {
videoList = videoList + ',' + video['videoId'];
});
}
youtubeAPI('videos', {
part: 'snippet',
id: videoList,
maxResults: 50,
}, function (data) {
createVideoListContainer('Watch History:');
let grabDuration = getDuration(data.items);
youtubeAPI('videos', {
part: 'snippet',
id: videoList,
maxResults: 50,
}, function (data) {
createVideoListContainer('Watch History:');
let grabDuration = getDuration(data.items);
grabDuration.then((videoList) => {
videoList.items.forEach((video) => {
displayVideo(video, 'history');
grabDuration.then((videoList) => {
videoList.items.forEach((video) => {
displayVideo(video, 'history');
});
});
stopLoadingAnimation()
});
});
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,141 @@ 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: [{
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();
}
};
/**
@ -136,4 +180,4 @@ let active = function() {
*/
app.on('ready', init);
app.on('window-all-closed', allWindowsClosed);
app.on('activate', active);
app.on('activate', active);

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/>.
*/
@ -54,73 +54,73 @@ let dialog = electron.remote.dialog; // Used for opening file browser to export
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
filename: localDataStorage + '/subscriptions.db',
autoload: true
});
const historyDb = new Datastore({
filename: localDataStorage + '/videohistory.db',
autoload: true
filename: localDataStorage + '/videohistory.db',
autoload: true
});
const savedVidsDb = new Datastore({
filename: localDataStorage + '/savedvideos.db',
autoload: true
filename: localDataStorage + '/savedvideos.db',
autoload: true
});
const settingsDb = new Datastore({
filename: localDataStorage + '/settings.db',
autoload: true
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);
require('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);
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);
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.
loadSubscriptions();
// Display subscriptions upon the app opening up. May allow user to specify.
// Home page in the future.
loadSubscriptions();
});
/**
@ -129,16 +129,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';
}
if (sideNav.style.display === 'none') {
sideNav.style.display = 'inline';
mainContainer.style.marginLeft = '250px';
} else {
sideNav.style.display = 'none';
mainContainer.style.marginLeft = '0px';
}
}
/**
@ -147,35 +147,35 @@ function toggleSideNavigation() {
* @return {Void}
*/
function clearMainContainer() {
const container = document.getElementById('main');
container.innerHTML = '';
hideConfirmFunction();
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');
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';
}
loading.style.display = 'inherit';
if (sideNavDisabled !== null) {
sideNavDisabled.style.display = 'inherit';
}
searchBar.disabled = true;
searchBar.disabled = true;
}
function stopLoadingAnimation() {
const loading = document.getElementById('loading');
const sideNavDisabled = document.getElementById('sideNavDisabled');
const searchBar = document.getElementById('search');
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';
}
loading.style.display = 'none';
if (sideNavDisabled !== null) {
sideNavDisabled.style.display = 'none';
}
searchBar.disabled = false;
searchBar.disabled = false;
}
/**
@ -186,17 +186,17 @@ function stopLoadingAnimation() {
* @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);
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);
}
/**
@ -205,18 +205,18 @@ function createVideoListContainer(headerLabel = '') {
* @return {Void}
*/
function showAbout() {
// Remove current information and display loading animation
clearMainContainer();
startLoadingAnimation();
// 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();
const aboutTemplate = require('./templates/about.html')
mustache.parse(aboutTemplate);
$('#main').html(
mustache.render(aboutTemplate, {
versionNumber: require('electron').remote.app.getVersion(),
})
);
stopLoadingAnimation();
}
/**
@ -228,18 +228,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 +248,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 +264,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 +286,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 +297,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 +310,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,33 +1,35 @@
/*
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 {comment} from "./comment.model";
import {
comment
} from "./comment.model";
/**
* Entire Comment Threads for a Video
*/
export class commentThread {
videoId: ?string;
nextPageToken: ?string;
pageInfo: {
totalResults: number,
resultsPerPage: number
videoId;
nextPageToken;
pageInfo = {
totalResults,
resultsPerPage
};
items: comment[];
}
items;
}

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/>.
*/
@ -29,240 +29,234 @@ 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...')
}
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;
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';
if (useWindowPlayer === false) {
clearMainContainer();
startLoadingAnimation();
} else {
savedText = 'SAVED';
savedIconClass = 'fas saved';
showToast('Getting video information. Please wait...')
}
});
youtubeAPI('videos', {
part: 'statistics',
id: videoId,
}, function(data) {
console.log(data);
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;
// 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);
});
const checkSavedVideo = videoIsSaved(videoId);
/*
* FreeTube calls youtube-dl to grab the direct video URL.
*/
youtubedlGetInfo(videoId, (info) => {
console.log(info);
console.log(videoLikes);
channelId = info['author']['id'];
let channelThumbnail = info['author']['avatar'];
let videoUrls = info['formats'];
// Add commas to the video view count.
const videoViews = info['view_count'].toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
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");
let description = info['description'];
// Adds clickable links to the description.
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);
break;
case '22':
video720p = decodeURIComponent(videoUrls[key]['url']);
console.log(video720p);
break;
}
// 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 = 'FAVORITE';
savedIconClass = 'far unsaved';
} else {
savedText = 'UNFAVORITE';
savedIconClass = 'fas saved';
}
});
// Default to the embeded player if the URLs cannot be found.
if (typeof(video720p) === 'undefined' && typeof(video480p) === 'undefined') {
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';
} else {
// Default to the 720p video.
defaultUrl = video720p;
defaultQuality = '720p';
// Force the embeded player if needed.
//videoHtml = embedPlayer;
}
youtubeAPI('videos', {
part: 'statistics',
id: videoId,
}, function (data) {
if (!useEmbedPlayer) {
//videoHtml = '<video class="videoPlayer" type="application/x-mpegURL" onmousemove="hideMouseTimeout()" onmouseleave="removeMouseTimeout()" controls="" src="' + defaultUrl + '" poster="' + videoThumbnail + '" autoplay>';
// 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);
});
/*
* FreeTube calls youtube-dl to grab the direct video URL.
*/
youtubedlGetInfo(videoId, (info) => {
channelId = info['author']['id'];
let channelThumbnail = 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, ",");
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");
let description = info['description'];
// Adds clickable links to the description.
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']);
ft.log('480p Video: ', video480p);
break;
case '22':
video720p = decodeURIComponent(videoUrls[key]['url']);
ft.log('720p Video: ', video720p);
break;
}
});
// Default to the embeded player if the URLs cannot be found.
if (typeof (video720p) === 'undefined' && typeof (video480p) === 'undefined') {
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';
} else {
// Default to the 720p video.
defaultUrl = video720p;
defaultQuality = '720p';
// Force the embeded player if needed.
//videoHtml = embedPlayer;
}
if (!useEmbedPlayer) {
//videoHtml = '<video class="videoPlayer" type="application/x-mpegURL" onmousemove="hideMouseTimeout()" onmouseleave="removeMouseTimeout()" controls="" src="' + defaultUrl + '" poster="' + videoThumbnail + '" autoplay>';
if (typeof(info.player_response.captions) === 'object') {
if (typeof(info.player_response.captions.playerCaptionsTracklistRenderer.captionTracks) === 'object') {
const videoSubtitles = info.player_response.captions.playerCaptionsTracklistRenderer.captionTracks;
if (typeof (info.player_response.captions) === 'object') {
if (typeof (info.player_response.captions.playerCaptionsTracklistRenderer.captionTracks) === 'object') {
const videoSubtitles = info.player_response.captions.playerCaptionsTracklistRenderer.captionTracks;
videoSubtitles.forEach((subtitle) => {
let subtitleUrl = 'https://www.youtube.com/api/timedtext?lang=' + subtitle.languageCode + '&fmt=vtt&name=&v=' + videoId;
videoSubtitles.forEach((subtitle) => {
let subtitleUrl = 'https://www.youtube.com/api/timedtext?lang=' + subtitle.languageCode + '&fmt=vtt&name=&v=' + videoId;
if (subtitle.kind == 'asr') {
//subtitleUrl = subtitle.baseUrl;
return;
if (subtitle.kind == 'asr') {
//subtitleUrl = subtitle.baseUrl;
return;
}
videoHtml = videoHtml + '<track kind="subtitles" src="' + subtitleUrl + '" srclang="' + subtitle.languageCode + '" label="' + subtitle.name.simpleText + '">';
});
}
}
videoHtml = videoHtml + '<track kind="subtitles" src="' + subtitleUrl + '" srclang="' + subtitle.languageCode + '" label="' + subtitle.name.simpleText + '">';
});
//videoHtml = videoHtml + '</video>';
}
}
//videoHtml = videoHtml + '</video>';
}
const checkSubscription = isSubscribed(channelId);
const checkSubscription = isSubscribed(channelId);
// Change the subscribe button text depending on if the user has subscribed to the channel or not.
// Change the subscribe button text depending on if the user has subscribed to the channel or not.
checkSubscription.then((results) => {
const subscribeButton = document.getElementById('subscribeButton');
checkSubscription.then((results) => {
const subscribeButton = document.getElementById('subscribeButton');
if (results === false) {
if (subscribeButton != null) {
subscribeButton.innerHTML = 'SUBSCRIBE';
}
} else {
if (subscribeButton != null) {
subscribeButton.innerHTML = 'UNSUBSCRIBE';
}
}
});
const playerTemplate = require('./templates/player.html')
mustache.parse(playerTemplate);
const rendered = mustache.render(playerTemplate, {
videoQuality: defaultQuality,
subtitleHtml: videoHtml,
defaultUrl: defaultUrl,
videoTitle: info['title'],
videoViews: videoViews,
videoThumbnail: videoThumbnail,
channelName: info['author']['name'],
videoLikes: videoLikes,
videoDislikes: videoDislikes,
likePercentage: likePercentage,
videoId: videoId,
channelId: channelId,
channelIcon: channelThumbnail,
publishedDate: publishedDate,
description: description,
isSubscribed: subscribeText,
savedText: savedText,
savedIconClass: savedIconClass,
savedIconColor: savedIconColor,
video480p: video480p,
video720p: video720p,
embedPlayer: embedPlayer,
});
// Add the video to the user's history
addToHistory(videoId);
if (useWindowPlayer){
// Create a new browser window.
const BrowserWindow = electron.remote.BrowserWindow;
let newWindow = new BrowserWindow({
width: 1200,
height: 700
});
let playerWindowHeader = require('./templates/playerWindow.html');
mustache.parse(playerWindowHeader);
const playerHeaderRender = mustache.render(playerWindowHeader, {
videoId: videoId,
channelId: channelId
});
newWindow.loadURL('data:text/html;charset=UTF-8,' + encodeURIComponent(playerHeaderRender + rendered), {
baseURLForDataURL: `file://${__dirname}/src`
});
}
else{
$('#main').html(rendered);
stopLoadingAnimation();
showVideoRecommendations(videoId);
// 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';
if (results === false) {
if (subscribeButton != null) {
subscribeButton.innerHTML = 'SUBSCRIBE';
}
} else {
if (subscribeButton != null) {
subscribeButton.innerHTML = 'UNSUBSCRIBE';
}
}
});
}
}
// 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);
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,
});
window.setTimeout(checkVideoUrls, 5000, video480p, video720p);
// Add the video to the user's history
addToHistory(videoId);
});
if (useWindowPlayer) {
// Create a new browser window.
const BrowserWindow = electron.remote.BrowserWindow;
let newWindow = new BrowserWindow({
width: 1200,
height: 700
});
let playerWindowHeader = require('./templates/playerWindow.html');
mustache.parse(playerWindowHeader);
const playerHeaderRender = mustache.render(playerWindowHeader, {
videoId: videoId,
channelId: channelId
});
newWindow.loadURL('data:text/html;charset=UTF-8,' + encodeURIComponent(playerHeaderRender + rendered), {
baseURLForDataURL: `file://${__dirname}/src`
});
} else {
$('#main').html(rendered);
stopLoadingAnimation();
showVideoRecommendations(videoId);
// 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';
});
}
}
// Sometimes a video URL is found, but the video will not play. I believe the issue is
// that the video has yet to render for that quality, as the video will be available at a later time.
// This will check the URLs and switch video sources if there is an error.
//checkVideoUrls(video480p, video720p);
window.setTimeout(checkVideoUrls, 5000, video480p, video720p);
});
}
/**
@ -273,38 +267,38 @@ function playVideo(videoId, videoThumbnail = '', useWindowPlayer = false) {
* @return {Void}
*/
function openMiniPlayer(videoThumbnail) {
let lastTime;
let videoHtml;
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
});
// Use the miniPlayer.html template.
$.get('templates/miniPlayer.html', (template) => {
mustache.parse(template);
const rendered = mustache.render(template, {
videoHtml: videoHtml,
videoThumbnail: videoThumbnail,
startTime: lastTime,
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));
});
// Render the template to the new browser window.
miniPlayer.loadURL("data:text/html;charset=utf-8," + encodeURI(rendered));
});
}
/**
@ -317,53 +311,53 @@ function openMiniPlayer(videoThumbnail) {
* @return {Void}
*/
function changeQuality(videoHtml, qualityType, isEmbed = false) {
if (videoHtml == '') {
showToast('Video quality type is not available. Unable to change quality.')
return;
}
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 +368,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 +380,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 +400,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 +412,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,164 +1,164 @@
/*
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');
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');
addSavedVideo(videoId);
} else {
savedText.innerHTML = 'SAVE';
saveIcon.classList.remove('fas');
saveIcon.classList.remove('saved');
saveIcon.classList.add('far');
saveIcon.classList.add('unsaved');
removeSavedVideo(videoId);
}
});
checkIfSaved.then((results) => {
if (results === false) {
savedText.innerHTML = 'UNFAVORITE';
saveIcon.classList.remove('far');
saveIcon.classList.remove('unsaved');
saveIcon.classList.add('fas');
saveIcon.classList.add('saved');
addSavedVideo(videoId);
} else {
savedText.innerHTML = 'FAVORITE';
saveIcon.classList.remove('fas');
saveIcon.classList.remove('saved');
saveIcon.classList.add('far');
saveIcon.classList.add('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);
}
});
});
});
}
/**
* Displays a list of the user's saved videos.
*
* @return {Void}
*/
function showSavedVideos(){
clearMainContainer();
startLoadingAnimation();
console.log('checking saved videos');
* Displays a list of the user's saved videos.
*
* @return {Void}
*/
function showSavedVideos() {
clearMainContainer();
startLoadingAnimation();
ft.log('Checking favorited 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', {
part: 'snippet',
id: videoList,
maxResults: 50,
}, (data) => {
// Render the videos to the screen
createVideoListContainer('Saved Videos:');
let grabDuration = getDuration(data.items);
grabDuration.then((videoList) => {
videoList.items.forEach((video) => {
displayVideo(video, 'saved');
// Call the YouTube API
youtubeAPI('videos', {
part: 'snippet',
id: videoList,
maxResults: 50,
}, (data) => {
// Render the videos to the screen
createVideoListContainer('Favorited Videos:');
let grabDuration = getDuration(data.items);
grabDuration.then((videoList) => {
videoList.items.forEach((video) => {
displayVideo(video, 'saved');
});
});
stopLoadingAnimation();
});
});
stopLoadingAnimation();
});
});
}
}

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/>.
*/
@ -30,55 +30,55 @@ const apiKeyBank = ['AIzaSyC9E579nh_qqxg6BH4xIce3k_7a9mT4uQc', 'AIzaSyCKplYT6hZI
* @return {Void}
*/
function showSettings() {
clearMainContainer();
startLoadingAnimation();
clearMainContainer();
startLoadingAnimation();
let isChecked = '';
let key = '';
let isChecked = '';
let key = '';
/*
* 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.
*/
settingsDb.find({}, (err, docs) => {
docs.forEach((setting) => {
switch (setting['_id']) {
case 'apiKey':
if (apiKeyBank.indexOf(setting['value']) == -1) {
key = setting['value'];
}
break;
case 'theme':
if (currentTheme == '') {
currentTheme = setting['value'];
}
}
/*
* 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.
*/
settingsDb.find({}, (err, docs) => {
docs.forEach((setting) => {
switch (setting['_id']) {
case 'apiKey':
if (apiKeyBank.indexOf(setting['value']) == -1) {
key = setting['value'];
}
break;
case 'theme':
if (currentTheme == '') {
currentTheme = setting['value'];
}
}
});
// 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;
} else {
document.getElementById('themeSwitch').checked = true;
}
if (useTor) {
document.getElementById('torSwitch').checked = true;
} else {
document.getElementById('torSwitch').checked = false;
}
});
// 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;
} else {
document.getElementById('themeSwitch').checked = true;
}
if (useTor) {
document.getElementById('torSwitch').checked = true;
} else {
document.getElementById('torSwitch').checked = false;
}
});
}
/**
@ -88,51 +88,52 @@ function showSettings() {
*/
function checkDefaultSettings() {
// Grab a random API Key.
apiKey = apiKeyBank[Math.floor(Math.random() * apiKeyBank.length)];
let newSetting;
// Grab a random API Key.
apiKey = apiKeyBank[Math.floor(Math.random() * apiKeyBank.length)];
let newSetting;
let settingDefaults = {
'theme': 'light',
'apiKey': apiKey,
'useTor': false
};
let settingDefaults = {
'theme': 'light',
'apiKey': apiKey,
'useTor': false
};
console.log(settingDefaults);
ft.log('Default Settings: ', settingDefaults);
for (let key in settingDefaults){
settingsDb.find({_id: key}, (err, docs) => {
if (jQuery.isEmptyObject(docs)) {
newSetting = {
_id: key,
value: settingDefaults[key]
};
for (let key in settingDefaults) {
settingsDb.find({
_id: key
}, (err, docs) => {
if (jQuery.isEmptyObject(docs)) {
newSetting = {
_id: key,
value: settingDefaults[key]
};
settingsDb.insert(newSetting);
settingsDb.insert(newSetting);
if (key == 'theme'){
setTheme('light');
}
}
else{
switch (docs[0]['_id']) {
case 'theme':
setTheme(docs[0]['value']);
break;
case 'apiKey':
if (apiKeyBank.indexOf(docs[0]['value']) == -1) {
apiKey = docs[0]['value'];
if (key == 'theme') {
setTheme('light');
}
} else {
switch (docs[0]['_id']) {
case 'theme':
setTheme(docs[0]['value']);
break;
case 'apiKey':
if (apiKeyBank.indexOf(docs[0]['value']) == -1) {
apiKey = docs[0]['value'];
}
break;
case 'useTor':
useTor = docs[0]['value'];
break;
default:
break;
}
}
break;
case 'useTor':
useTor = docs[0]['value'];
break;
default:
break;
}
}
});
}
});
}
}
/**
@ -141,58 +142,58 @@ function checkDefaultSettings() {
* @return {Void}
*/
function updateSettings() {
let themeSwitch = document.getElementById('themeSwitch').checked;
let torSwitch = document.getElementById('torSwitch').checked;
let key = document.getElementById('api-key').value;
let theme = 'light';
let themeSwitch = document.getElementById('themeSwitch').checked;
let torSwitch = document.getElementById('torSwitch').checked;
let key = document.getElementById('api-key').value;
let theme = 'light';
apiKey = apiKeyBank[Math.floor(Math.random() * apiKeyBank.length)];
apiKey = apiKeyBank[Math.floor(Math.random() * apiKeyBank.length)];
console.log(themeSwitch);
ft.log('(Is the theme switch checked) themeSwitch: ', themeSwitch);
if (themeSwitch === true) {
theme = 'dark';
}
if (themeSwitch === true) {
theme = 'dark';
}
console.log(theme);
ft.log('Theme: ', theme);
// Update default theme
settingsDb.update({
_id: 'theme'
}, {
value: theme
}, {}, function(err, numReplaced) {
console.log(err);
console.log(numReplaced);
});
// Update tor usage.
settingsDb.update({
_id: 'useTor'
}, {
value: torSwitch
}, {}, function(err, numReplaced) {
console.log(err);
console.log(numReplaced);
useTor = torSwitch;
});
if (key != '') {
// Update default theme
settingsDb.update({
_id: 'apiKey'
_id: 'theme'
}, {
value: key
}, {});
} else {
// To any third party devs that fork the project, please be ethical and change the API key.
settingsDb.update({
_id: 'apiKey'
}, {
value: apiKey
}, {});
}
value: theme
}, {}, function (err, numReplaced) {
ft.log('Error while updating theme: ', err);
ft.log('Number replaced: ', numReplaced);
});
showToast('Settings have been saved.');
// Update tor usage.
settingsDb.update({
_id: 'useTor'
}, {
value: torSwitch
}, {}, function (err, numReplaced) {
ft.log('Error while connecting to tor: ', err);
ft.log('Number replaced: ', numReplaced);
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
}, {});
}
showToast('Settings have been saved.');
}
/**
@ -203,13 +204,13 @@ function updateSettings() {
* @return {Void}
*/
function toggleTheme(themeValue) {
if (themeValue.checked === true) {
setTheme('dark');
currentTheme = 'dark';
} else {
setTheme('light');
currentTheme = 'light';
}
if (themeValue.checked === true) {
setTheme('dark');
currentTheme = 'dark';
} else {
setTheme('light');
currentTheme = 'light';
}
}
/**
@ -220,122 +221,122 @@ function toggleTheme(themeValue) {
* @return {Void}
*/
function setTheme(option) {
let cssFile;
const currentTheme = document.getElementsByTagName("link").item(1);
let cssFile;
const currentTheme = document.getElementsByTagName("link").item(1);
// Create a link element
const newTheme = document.createElement("link");
newTheme.setAttribute("rel", "stylesheet");
newTheme.setAttribute("type", "text/css");
// Create a link element
const newTheme = document.createElement("link");
newTheme.setAttribute("rel", "stylesheet");
newTheme.setAttribute("type", "text/css");
// Grab the css file to be used.
switch (option) {
// Grab the css file to be used.
switch (option) {
case 'light':
cssFile = './style/lightTheme.css';
document.getElementById('menuText').src = 'icons/textBlack.png';
document.getElementById('menuIcon').src = 'icons/iconBlack.png';
document.getElementById('menuButton').style.color = 'black';
break;
cssFile = './style/lightTheme.css';
document.getElementById('menuText').src = 'icons/textBlack.png';
document.getElementById('menuIcon').src = 'icons/iconBlack.png';
document.getElementById('menuButton').style.color = 'black';
break;
case 'dark':
cssFile = './style/darkTheme.css';
document.getElementById('menuText').src = 'icons/textColor.png';
document.getElementById('menuIcon').src = 'icons/iconColor.png';
document.getElementById('menuButton').style.color = 'white';
break;
cssFile = './style/darkTheme.css';
document.getElementById('menuText').src = 'icons/textColor.png';
document.getElementById('menuIcon').src = 'icons/iconColor.png';
document.getElementById('menuButton').style.color = 'white';
break;
default:
// Default to the light theme
cssFile = './style/lightTheme.css';
break;
}
newTheme.setAttribute("href", cssFile);
// Replace the current theme with the new theme
document.getElementsByTagName("head").item(0).replaceChild(newTheme, currentTheme);
}
/**
* Import Subscriptions from an OPML file.
*
* @param {string} subFile - The file location of the OPML file.
*
* @return {Void}
*/
function importOpmlSubs(json){
if(!json[0]['folder'].includes('YouTube')){
showToast('Invalid OPML File. Import is unsuccessful.');
return;
}
json.forEach((channel) => {
let channelId = channel['xmlurl'].replace('https://www.youtube.com/feeds/videos.xml?channel_id=', '');
addSubscription(channelId, false);
});
window.setTimeout(displaySubs, 1000);
showToast('Subscriptions have been imported!');
return;
}
/**
* Import a subscriptions file that the user provides.
*
* @return {Void}
*/
function importSubscriptions(){
const appDatabaseFile = localDataStorage + '/subscriptions.db';
// Open user's file browser. Only show .db files.
dialog.showOpenDialog({
properties: ['openFile'],
filters: [
{name: 'Database File', extensions: ['*']},
]
}, function(fileLocation){
if(typeof(fileLocation) === 'undefined'){
console.log('Import Aborted');
return;
// Default to the light theme
cssFile = './style/lightTheme.css';
break;
}
console.log(fileLocation);
let i = fileLocation[0].lastIndexOf('.');
let fileType = (i < 0) ? '' : fileLocation[0].substr(i);
console.log(fileType);
newTheme.setAttribute("href", cssFile);
/*if (fileType !== '.db'){
showToast('Incorrect filetype. Import was unsuccessful.');
return;
}*/
// Replace the current theme with the new theme
document.getElementsByTagName("head").item(0).replaceChild(newTheme, currentTheme);
}
fs.readFile(fileLocation[0], function(readErr, data){
if(readErr){
showToast('Unable to read file. File may be corrupt or have invalid permissions.');
throw readErr;
}
if (data.includes("<opml")){
getOpml(data, function (error, json){
if (!error){
clearFile('subscriptions', false);
importOpmlSubs(json['children'][0]['children']);
}
});
/**
* Import Subscriptions from an OPML file.
*
* @param {string} subFile - The file location of the OPML file.
*
* @return {Void}
*/
function importOpmlSubs(json) {
if (!json[0]['folder'].includes('YouTube')) {
showToast('Invalid OPML File. Import is unsuccessful.');
return;
}
else if (fileType !== '.db'){
showToast('Incorrect file type. Import unsuccessful.');
return;
}
}
clearFile('subscriptions', false);
json.forEach((channel) => {
let channelId = channel['xmlurl'].replace('https://www.youtube.com/feeds/videos.xml?channel_id=', '');
fs.writeFile(appDatabaseFile, data, function(writeErr){
if(writeErr){
showToast('Unable to create file. Please check your permissions and try again.');
throw writeErr;
addSubscription(channelId, false);
});
window.setTimeout(displaySubs, 1000);
showToast('Subscriptions have been imported!');
return;
}
/**
* Import a subscriptions file that the user provides.
*
* @return {Void}
*/
function importSubscriptions() {
const appDatabaseFile = localDataStorage + '/subscriptions.db';
// Open user's file browser. Only show .db files.
dialog.showOpenDialog({
properties: ['openFile'],
filters: [{
name: 'Database File',
extensions: ['*']
}, ]
}, function (fileLocation) {
if (typeof (fileLocation) === 'undefined') {
ft.log('Import Aborted');
return;
}
showToast('Susbcriptions have been successfully imported. Please restart FreeTube for the changes to take effect.');
});
})
});
ft.log('File Location: ', fileLocation);
let i = fileLocation[0].lastIndexOf('.');
let fileType = (i < 0) ? '' : fileLocation[0].substr(i);
ft.log('File Type: ', 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.');
throw readErr;
}
if (data.includes("<opml")) {
getOpml(data, function (error, json) {
if (!error) {
clearFile('subscriptions', false);
importOpmlSubs(json['children'][0]['children']);
}
});
return;
} else if (fileType !== '.db') {
showToast('Incorrect file type. Import unsuccessful.');
return;
}
clearFile('subscriptions', false);
fs.writeFile(appDatabaseFile, data, function (writeErr) {
if (writeErr) {
showToast('Unable to create file. Please check your permissions and try again.');
throw writeErr;
}
showToast('Susbcriptions have been successfully imported. Please restart FreeTube for the changes to take effect.');
});
})
});
}
/**
@ -344,83 +345,83 @@ function importSubscriptions(){
* @return {Void}
*/
function exportSubscriptions() {
const appDatabaseFile = localDataStorage + '/subscriptions.db';
const appDatabaseFile = localDataStorage + '/subscriptions.db';
const date = new Date();
let dateMonth = date.getMonth() + 1;
const date = new Date();
let dateMonth = date.getMonth() + 1;
if (dateMonth < 10){
dateMonth = '0' + dateMonth;
}
let dateDay = date.getDate();
if (dateDay < 10){
dateDay = '0' + dateDay;
}
const dateYear = date.getFullYear();
const dateString = 'freetube-subscriptions-' + dateYear + '-' + dateMonth + '-' + dateDay;
// Open user file browser. User gives location of file to be created.
dialog.showSaveDialog({
defaultPath: dateString,
filters: [{
name: 'Database File',
extensions: ['db']
}, ]
}, function(fileLocation) {
console.log(fileLocation);
if (typeof(fileLocation) === 'undefined') {
console.log('Export Aborted');
return;
if (dateMonth < 10) {
dateMonth = '0' + dateMonth;
}
fs.readFile(appDatabaseFile, function(readErr, data) {
if (readErr) {
throw readErr;
}
fs.writeFile(fileLocation, data, function(writeErr) {
if (writeErr) {
throw writeErr;
let dateDay = date.getDate();
if (dateDay < 10) {
dateDay = '0' + dateDay;
}
const dateYear = date.getFullYear();
const dateString = 'freetube-subscriptions-' + dateYear + '-' + dateMonth + '-' + dateDay;
// Open user file browser. User gives location of file to be created.
dialog.showSaveDialog({
defaultPath: dateString,
filters: [{
name: 'Database File',
extensions: ['db']
}, ]
}, function (fileLocation) {
ft.log('File Location: ', fileLocation);
if (typeof (fileLocation) === 'undefined') {
ft.log('Export Aborted');
return;
}
showToast('Susbcriptions have been successfully exported');
});
})
});
fs.readFile(appDatabaseFile, function (readErr, data) {
if (readErr) {
throw readErr;
}
fs.writeFile(fileLocation, data, function (writeErr) {
if (writeErr) {
throw writeErr;
}
showToast('Susbcriptions have been successfully exported');
});
})
});
}
/**
* Clear out the data in a file.
*
* @param {string} type - The type of file to be cleared.
*/
function clearFile(type, showMessage = true){
console.log(type);
let dataBaseFile;
* Clear out the data in a file.
*
* @param {string} type - The type of file to be cleared.
*/
function clearFile(type, showMessage = true) {
ft.log('File Type: ', type);
let dataBaseFile;
switch (type) {
switch (type) {
case 'subscriptions':
dataBaseFile = localDataStorage + '/subscriptions.db';
break;
dataBaseFile = localDataStorage + '/subscriptions.db';
break;
case 'history':
dataBaseFile = localDataStorage + '/videohistory.db';
break;
dataBaseFile = localDataStorage + '/videohistory.db';
break;
case 'saved':
dataBaseFile = localDataStorage + '/savedvideos.db';
break;
dataBaseFile = localDataStorage + '/savedvideos.db';
break;
default:
showToast('Unknown file: ' + type)
return
}
// Replace data with an empty string.
fs.writeFile(dataBaseFile, '', function(err) {
if (err) {
throw err;
showToast('Unknown file: ' + type)
return
}
if (showMessage){
showToast('File has been cleared. Restart FreeTube to see the changes');
}
})
}
// Replace data with an empty string.
fs.writeFile(dataBaseFile, '', function (err) {
if (err) {
throw err;
}
if (showMessage) {
showToast('File has been cleared. Restart FreeTube to see the changes');
}
})
}

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/>.
*/
@ -29,30 +29,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 +63,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.');
});
}
/**
@ -78,92 +78,92 @@ function removeSubscription(channelId) {
* @return {Void}
*/
function loadSubscriptions() {
clearMainContainer();
showToast('Getting Subscriptions. Please wait...');
const loading = document.getElementById('loading');
clearMainContainer();
showToast('Getting Subscriptions. Please wait...');
const loading = document.getElementById('loading');
startLoadingAnimation()
startLoadingAnimation();
let videoList = [];
let videoList = [];
const subscriptions = returnSubscriptions();
const subscriptions = returnSubscriptions();
// Welcome to callback hell, we hope you enjoy your stay.
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) => {
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);
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();
});
// Render the videos to the application.
createVideoListContainer('Latest Subscriptions:');
// The YouTube website limits the subscriptions to 100 before grabbing more so we only show 100
// to keep the app running at a good speed.
if (videoList.length < 50) {
let grabDuration = getDuration(videoList.slice(0, 49));
grabDuration.then((list) => {
list.items.forEach((video) => {
displayVideo(video);
});
stopLoadingAnimation();
});
} else {
let finishedList = [];
let firstBatchDuration = getDuration(videoList.slice(0, 49));
firstBatchDuration.then((list1) => {
finishedList = finishedList.concat(list1.items);
let secondBatchDuration = getDuration(videoList.slice(50, 99));
secondBatchDuration.then((list2) => {
finishedList = finishedList.concat(list2.items);
finishedList.forEach((video) => {
displayVideo(video);
});
stopLoadingAnimation();
});
});
}
}
});
// Render the videos to the application.
createVideoListContainer('Latest Subscriptions:');
// The YouTube website limits the subscriptions to 100 before grabbing more so we only show 100
// to keep the app running at a good speed.
if (videoList.length < 50) {
let grabDuration = getDuration(videoList.slice(0, 49));
grabDuration.then((list) => {
list.items.forEach((video) => {
displayVideo(video);
});
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();
});
});
}
}
}
);
}
} else {
// User has no subscriptions. Display message.
const container = document.getElementById('main');
stopLoadingAnimation();
} else {
// User has no subscriptions. Display message.
const container = document.getElementById('main');
stopLoadingAnimation();
container.innerHTML = `<h2 class="message">Your Subscription list is currently empty. Start adding subscriptions
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>`;
}
});
}
});
}
/**
@ -172,11 +172,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 +185,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 +219,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 +248,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);
}
});
});
});
}
}

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,71 +25,71 @@ 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 (nextPageToken === '') {
clearMainContainer();
startLoadingAnimation();
} else {
console.log(nextPageToken);
showToast('Fetching results. Please wait...');
}
youtubeAPI('search', {
q: query,
part: 'id',
pageToken: nextPageToken,
maxResults: 25,
}, function(data) {
console.log(data);
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 videos = data.items.filter((item) => {
if (item.id.kind === 'youtube#video') {
return true;
}
});
console.log(channels);
console.log(typeof(channels));
console.log(playlists);
if(playlists.length > 0){
//displayPlaylists(playlists);
if (query === '') {
return;
}
if(channels.length > 0){
displayChannels(channels);
}
let grabDuration = getDuration(videos);
grabDuration.then((videoList) => {
console.log(videoList);
videoList.items.forEach(displayVideo);
});
if (nextPageToken === '') {
createVideoListContainer('Search results:');
stopLoadingAnimation();
clearMainContainer();
startLoadingAnimation();
} else {
ft.log('Next page token: ', nextPageToken);
showToast('Fetching results. Please wait...');
}
addNextPage(data.nextPageToken);
})
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 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;
}
});
ft.log('Channels: ', channels);
ft.log('Typeof object above (channels) ^^', typeof (channels));
ft.log('Playlists', playlists);
if (playlists.length > 0) {
//displayPlaylists(playlists);
}
if (channels.length > 0) {
displayChannels(channels);
}
let grabDuration = getDuration(videos);
grabDuration.then((videoList) => {
ft.log('Video Lists: ', videoList);
videoList.items.forEach(displayVideo);
});
if (nextPageToken === '') {
createVideoListContainer('Search results:');
stopLoadingAnimation();
}
addNextPage(data.nextPageToken);
})
}
/**
@ -100,32 +100,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);
});
});
});
}
/**
@ -138,135 +138,135 @@ function getDuration(data) {
* @return {Void}
*/
function displayVideo(video, listType = '') {
const videoSnippet = video.snippet;
const videoSnippet = video.snippet;
const videoDuration = parseVideoDuration(video.contentDetails.duration);
//const videoDuration = '00:00';
const videoDuration = parseVideoDuration(video.contentDetails.duration);
//const videoDuration = '00:00';
// Grab the published date for the video and convert to a user readable state.
const dateString = new Date(videoSnippet.publishedAt);
const publishedDate = dateFormat(dateString, "mmm dS, yyyy");
// Grab the published date for the video and convert to a user readable state.
const dateString = new Date(videoSnippet.publishedAt);
const publishedDate = dateFormat(dateString, "mmm dS, yyyy");
const searchMenu = $('#videoListContainer').html();
const videoId = video.id;
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 = () => {
switch (listType) {
case 'saved':
return `<li onclick="removeSavedVideo('${videoId}'); showSavedVideos();">Remove Saved Video</li>`;
case 'history':
return `<li onclick="removeFromHistory('${videoId}'); showHistory();">Remove From History</li>`;
// Include a remove icon in the list if the application is displaying the history list or saved videos.
const deleteHtml = () => {
switch (listType) {
case 'saved':
return `<li onclick="removeSavedVideo('${videoId}'); showSavedVideos();">Remove From Favorites</li>`;
case 'history':
return `<li onclick="removeFromHistory('${videoId}'); showHistory();">Remove From History</li>`;
}
};
// Includes text if the video is live.
const liveText = (videoSnippet.liveBroadcastContent === 'live') ? 'LIVE NOW' : '';
const videoListTemplate = require('./templates/videoList.html');
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');
}
};
// Includes text if the video is live.
const liveText = (videoSnippet.liveBroadcastContent === 'live') ? 'LIVE NOW' : '';
const videoListTemplate = require('./templates/videoList.html');
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');
}
}
function displayChannels(channels) {
let channelIds;
let channelIds;
channels.forEach((channel) => {
if (typeof(channelIds) === 'undefined') {
channelIds = channel.id.channelId;
} else {
channelIds = channelIds + ',' + channel.id.channelId;
}
});
console.log(channelIds);
youtubeAPI('channels', {
part: 'snippet,statistics',
id: channelIds,
}, function(data) {
console.log(data);
let items = data['items'].reverse();
const videoListTemplate = require('./templates/channelList.html');
console.log(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,
});
$(rendered).insertBefore('#getNextPage');
channels.forEach((channel) => {
if (typeof (channelIds) === 'undefined') {
channelIds = channel.id.channelId;
} else {
channelIds = channelIds + ',' + channel.id.channelId;
}
});
ft.log('Channel IDs: ', channelIds);
youtubeAPI('channels', {
part: 'snippet,statistics',
id: channelIds,
}, function (data) {
ft.log('Channel Data: ', data);
let items = data['items'].reverse();
const videoListTemplate = require('./templates/channelList.html');
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,
});
$(rendered).insertBefore('#getNextPage');
});
});
});
}
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 +277,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,33 +303,33 @@ function addNextPage(nextPageToken) {
* @param {string} videoId - The video ID of the video to get recommendations from.
*/
function showVideoRecommendations(videoId) {
youtubeAPI('search', {
part: 'id',
type: 'video',
relatedToVideoId: videoId,
maxResults: 15,
}, function(data) {
let grabDuration = getDuration(data.items);
grabDuration.then((videoList) => {
videoList.items.forEach((video) => {
const snippet = video.snippet;
const videoDuration = parseVideoDuration(video.contentDetails.duration);
youtubeAPI('search', {
part: 'id',
type: 'video',
relatedToVideoId: videoId,
maxResults: 15,
}, function (data) {
let grabDuration = getDuration(data.items);
grabDuration.then((videoList) => {
videoList.items.forEach((video) => {
const snippet = video.snippet;
const videoDuration = parseVideoDuration(video.contentDetails.duration);
const recommTemplate = require('./templates/recommendations.html')
mustache.parse(recommTemplate);
const rendered = mustache.render(recommTemplate, {
videoId: video.id,
videoTitle: snippet.title,
channelName: snippet.channelTitle,
videoThumbnail: snippet.thumbnails.medium.url,
videoDuration: videoDuration,
publishedDate: dateFormat(snippet.publishedAt, "mmm dS, yyyy")
const recommTemplate = require('./templates/recommendations.html')
mustache.parse(recommTemplate);
const rendered = mustache.render(recommTemplate, {
videoId: video.id,
videoTitle: snippet.title,
channelName: snippet.channelTitle,
videoThumbnail: snippet.thumbnails.medium.url,
videoDuration: videoDuration,
publishedDate: dateFormat(snippet.publishedAt, "mmm dS, yyyy")
});
const recommendationHtml = $('#recommendations').html();
$('#recommendations').html(recommendationHtml + rendered);
});
});
const recommendationHtml = $('#recommendations').html();
$('#recommendations').html(recommendationHtml + rendered);
});
});
});
}
/**
@ -339,50 +339,46 @@ 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);
console.log(match);
let urlSplit = input.split('/');
if(match){
console.log('Video found');
playVideo(match[2]);
}
else if (urlSplit[3] == 'channel'){
console.log('channel found');
goToChannel(urlSplit[4]);
}
else if (urlSplit[3] == 'user'){
console.log('user found');
// call api to get the ID and then call goToChannel(id)
youtubeAPI('channels', {
part: 'id',
forUsername: urlSplit[4]
}, (data) => {
console.log(data.items[0].id);
let channelID = data.items[0].id;
goToChannel(channelID);
});
}
else {
console.log('Video not found');
search();
}
ft.log('Video ID: ', match);
let urlSplit = input.split('/');
if (match) {
ft.log('Video found');
playVideo(match[2]);
} else if (urlSplit[3] == 'channel') {
ft.log('channel found');
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;
goToChannel(channelID);
});
} else {
ft.log('Video not found');
search();
}
}
@ -395,42 +391,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(/PT(\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;
}
/**
@ -439,38 +435,38 @@ function parseVideoDuration(durationString) {
* @return {Void}
*/
function showMostPopular() {
clearMainContainer();
startLoadingAnimation();
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
// Applications grab these. Videos in the 'Trending' tab on YouTube will be different.
// And there is no way to grab those videos.
youtubeAPI('search', {
part: 'id',
order: 'viewCount',
type: 'video',
publishedAfter: d.toISOString(),
maxResults: 50,
}, function(data) {
createVideoListContainer('Most Popular:');
console.log(data);
let grabDuration = getDuration(data.items);
// 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
// Applications grab these. Videos in the 'Trending' tab on YouTube will be different.
// And there is no way to grab those videos.
youtubeAPI('search', {
part: 'id',
order: 'viewCount',
type: 'video',
publishedAfter: d.toISOString(),
maxResults: 50,
}, function (data) {
createVideoListContainer('Most Popular:');
ft.log('Most Popular: ', data);
let grabDuration = getDuration(data.items);
grabDuration.then((videoList) => {
console.log(videoList);
videoList.items.forEach(displayVideo);
grabDuration.then((videoList) => {
ft.log('Video List: ', videoList);
videoList.items.forEach(displayVideo);
});
stopLoadingAnimation();
});
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
@ -478,10 +474,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');
}
}
/**
@ -492,20 +499,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]);
});
});
});
}
/**
@ -517,68 +524,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;
}
});
}
}

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,33 +28,33 @@
*/
function youtubeAPI(resource, params, success) {
params.key = apiKey;
params.key = apiKey;
if (useTor) {
tor.request('https://www.googleapis.com/youtube/v3/' + resource + '?' + $.param(params), function(err, res, body) {
if (!err && res.statusCode == 200) {
success(JSON.parse(body));
} else {
showToast('Unable to connect to the Tor network. Check the help page if you\'re having trouble setting up your node.');
console.log(err);
console.log(res);
console.log(body);
stopLoadingAnimation();
}
});
} else {
$.getJSON(
'https://www.googleapis.com/youtube/v3/' + resource,
params,
success
).fail((xhr, textStatus, error) => {
showToast('There was an error calling the YouTube API.');
console.log(error);
console.log(xhr);
console.log(textStatus);
stopLoadingAnimation();
});
}
if (useTor) {
tor.request('https://www.googleapis.com/youtube/v3/' + resource + '?' + $.param(params), function (err, res, body) {
if (!err && res.statusCode == 200) {
success(JSON.parse(body));
} else {
showToast('Unable to connect to the Tor network. Check the help page if you\'resss having trouble setting up your node.');
ft.log('Tor Error: ', err);
ft.log('Tor Error (Result): ', res);
ft.log('Tor Error (body): ', body);
stopLoadingAnimation();
}
});
} else {
$.getJSON(
'https://www.googleapis.com/youtube/v3/' + resource,
params,
success
).fail((xhr, textStatus, error) => {
showToast('There was an error calling the YouTube API.');
ft.log('YT API Error: ', error);
ft.log('YT API Error - XHR: ', xhr);
ft.log('YT API Error - Text Status: ', textStatus);
stopLoadingAnimation();
});
}
}
@ -50,19 +69,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();
console.log(err);
console.log(info);
return;
}
ytdl.getInfo(url, options, function (err, info) {
if (err) {
showToast(err.message);
stopLoadingAnimation();
ft.log('Error getting video download info: ', err.message);
ft.log('Error getting video download info: ', 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,373 +1,393 @@
/*
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;
}
.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 {
width: 75%;
height: 20%;
margin: auto;
position: absolute;
top: 0;
left: 5;
bottom: 0;
right: 0;
display: none;
width: 75%;
height: 20%;
margin: auto;
position: absolute;
top: 0;
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,356 @@
/*
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%;
}
.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;
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%;
}
.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,11 +1,28 @@
<!--
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='center'>
<img src='icons/logoColor.png' width='500px;'/>
<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>
<p>Thank you very much to the <a href='https://github.com/FreeTubeApp/FreeTube/wiki/Credits'>People and Projects</a> that make FreeTube possible!</p>
<p>Want to chat? Join our <a href='https://riot.im/app/#/room/#freetube:matrix.org'>Riot / Matrix Server</a>. Please check the <a href='https://github.com/FreeTubeApp/FreeTube/wiki/Matrix-Server-Info-&-Rules'>rules</a> before joining.</p>
<p>Looking for help? Check out our <a href='https://github.com/FreeTubeApp/FreeTube/wiki'>Wiki</a> page.</p>
<p>Check out our <a href='https://addons.mozilla.org/en-US/firefox/addon/freetube-redirect/'>Firefox</a> extension!</p>
<img src='icons/logoColor.png' width='500px;' />
<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>
<p>Thank you very much to the <a href='https://github.com/FreeTubeApp/FreeTube/wiki/Credits'>People and Projects</a> that make FreeTube possible!</p>
<p>Want to chat? Join our <a href='https://riot.im/app/#/room/#freetube:matrix.org'>Riot / Matrix Server</a>. Please check the <a href='https://github.com/FreeTubeApp/FreeTube/wiki/Matrix-Server-Info-&-Rules'>rules</a> before joining.</p>
<p>Looking for help? Check out our <a href='https://github.com/FreeTubeApp/FreeTube/wiki'>Wiki</a> page.</p>
<p>Check out our <a href='https://addons.mozilla.org/en-US/firefox/addon/freetube-redirect/'>Firefox</a> extension!</p>
</div>

View File

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

@ -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,100 @@
<!--
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/>.
-->
<video class="videoPlayer" type="application/x-mpegURL" onmousemove="hideMouseTimeout()" onmouseleave="removeMouseTimeout()" controls="" src="{{defaultUrl}}" poster="{{videoThumbnail}}" autoplay>
{{{subtitleHtml}}}
{{{subtitleHtml}}}
</video>
<div class='statistics'>
<div class='smallButton' onclick="openMiniPlayer('{{videoThumbnail}}')">
MINI PLAYER <i class="fas fa-external-link-alt"></i>
</div>
<div class='smallButton videoQuality'>
<span id='currentQuality'>{{videoQuality}}</span> <i class="fas fa-angle-down"></i>
<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 class='smallButton' onclick="openMiniPlayer('{{videoThumbnail}}')">
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 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>
</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 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>
</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 onclick='toggleSavedVideo("{{videoId}}")' class='smallButton'>
<i id='saveIcon' style='color: {{savedIconColor}};' class="{{savedIconClass}} fa-star"></i> <span id='savedText'>{{savedText}}</span>
</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 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("invidious", "{{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>
<span class='likes'><i class="fas fa-thumbs-up"></i> {{videoLikes}}</span><span class='dislikes'><i class="fas fa-thumbs-down"></i> {{videoDislikes}}</span>
</div>
</div>
<div class="details">
<img id='channelIcon' onclick='goToChannel("{{channelId}}")' src="{{channelIcon}}" />
<p id='channelName' onclick='goToChannel("{{channelId}}")'>{{channelName}}</p>
<p id='publishDate'>Published on {{publishedDate}}</p>
<div id='subscribeButton' class='playerSubButton' onclick='toggleSubscription("{{channelId}}")'>{{isSubscribed}}</div>
<br /><br />
<div id='description'>
{{{description}}}
</div>
<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)
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>
<strong>Recommendations</strong>
</div>

View File

@ -1,94 +1,112 @@
<!--
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/>.
-->
<!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>
<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);
const subscribeButton = document.getElementById('subscribeButton');
console.log(results);
console.log(subscribeButton);
if (results === false) {
if (subscribeButton != null) {
subscribeButton.innerHTML = 'UNSUBSCRIBE';
if (results === false) {
if (subscribeButton != null) {
subscribeButton.innerHTML = 'UNSUBSCRIBE';
}
} else {
if (subscribeButton != null) {
subscribeButton.innerHTML = 'SUBSCRIBE';
}
}
} else {
if (subscribeButton != null) {
subscribeButton.innerHTML = 'SUBSCRIBE';
}
}
});
</script>
<title>Freetube Player</title>
<style>
#main{
width: 100%;
margin-left: 0px;
}
.spinner {
width: 40px;
height: 40px;
</script>
<title>Freetube Player</title>
<style>
#main {
width: 100%;
margin-left: 0px;
}
position: relative;
margin: 0px auto;
}
.spinner {
width: 40px;
height: 40px;
#loading{
width: 100%;
height: 0%;
}
</style>
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 id='loading'>
<div class="spinner">
<div class="double-bounce1"></div>
<div class="double-bounce2"></div>
</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 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>
<img src='icons/iconBlack.png' id='menuIcon'/>
&nbsp;
<img src='icons/textBlack.png' id='menuText'/>
</div>
<div id='main'>
<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

@ -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='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 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

@ -1,35 +1,54 @@
<!--
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/>.
-->
<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>
<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
</div>
<div onclick='exportSubscriptions();' class='settingsButton'>
EXPORT SUBSCRIPTIONS
</div>
<div onclick='importSubscriptions()' class='settingsButton'>
IMPORT SUBSCRIPTIONS
</div>
<div onclick='exportSubscriptions();' class='settingsButton'>
EXPORT SUBSCRIPTIONS
</div>
</div>
<br /><br />
<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 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 />
<br />
<br />
<div onclick='updateSettings()' class='center settingsSubmit'>
SAVE SETTINGS
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 +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/>.
-->
<video class="videoPlayer" onmousemove="hideMouseTimeout()" onmouseleave="removeMouseTimeout()" controls="" poster="{{videoThumbnail}}" autoplay>
<source src="{{videoUrl}}" type="video/mp4">
{{subtitleElements}}
<source src="{{videoUrl}}" type="video/mp4"> {{subtitleElements}}
</video>

View File

@ -1,21 +1,42 @@
<!--
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='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 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);'>Favorite Video</li>
<a href='https://youtube.com/watch?v={{videoId}}' onclick='showVideoOptions(this.parentNode.previousSibling);'>
<li>Open in YouTube</li>
</a>
<a href='https://invidio.us/watch?v={{videoId}}' onclick='showVideoOptions(this.parentNode.previousSibling);'>
<li>Open in Invidious</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 />