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

1
.gitignore vendored
View File

@ -5,6 +5,7 @@ subscriptions\.db
.vscode/ .vscode/
.eslintrc* .eslintrc*
*.db *.db
*.code-workspace
electron-packager/win32-x64/FreeTube-win32-x64/ 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", "name": "FreeTube",
"version": "0.3.0", "version": "0.3.1",
"lockfileVersion": 1, "lockfileVersion": 1,
"requires": true, "requires": true,
"dependencies": { "dependencies": {
@ -78,9 +78,7 @@
"abab": { "abab": {
"version": "1.0.4", "version": "1.0.4",
"resolved": "https://registry.npmjs.org/abab/-/abab-1.0.4.tgz", "resolved": "https://registry.npmjs.org/abab/-/abab-1.0.4.tgz",
"integrity": "sha1-X6rZwsB/YN12dw9xzwJbYqY8/U4=", "integrity": "sha1-X6rZwsB/YN12dw9xzwJbYqY8/U4="
"dev": true,
"optional": true
}, },
"abbrev": { "abbrev": {
"version": "1.1.1", "version": "1.1.1",
@ -163,6 +161,11 @@
"integrity": "sha512-UgAb8H9D41AQnu/PbWlCofQVcnV4Gs2bBJi9eZPxfU/hgglFh3SMDMENRIqdr7H6XFnXdoknctFByVsCOotTVw==", "integrity": "sha512-UgAb8H9D41AQnu/PbWlCofQVcnV4Gs2bBJi9eZPxfU/hgglFh3SMDMENRIqdr7H6XFnXdoknctFByVsCOotTVw==",
"dev": true "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": { "ansi-regex": {
"version": "2.1.1", "version": "2.1.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
@ -355,6 +358,11 @@
"sprintf-js": "~1.0.2" "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": { "array-find-index": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz", "resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz",
@ -418,6 +426,11 @@
"integrity": "sha512-NW2cX8m1Q7KPA7a5M2ULQeZ2wR5qI5PAbw5L0UOMxdioVk9PMZ0h1TmyZEkPYrCvYjDlFICusOu1dlEKAAeXBw==", "integrity": "sha512-NW2cX8m1Q7KPA7a5M2ULQeZ2wR5qI5PAbw5L0UOMxdioVk9PMZ0h1TmyZEkPYrCvYjDlFICusOu1dlEKAAeXBw==",
"dev": true "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": { "asynckit": {
"version": "0.4.0", "version": "0.4.0",
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
@ -1347,6 +1360,11 @@
"concat-map": "0.0.1" "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": { "browserslist": {
"version": "1.7.7", "version": "1.7.7",
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-1.7.7.tgz", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-1.7.7.tgz",
@ -1712,6 +1730,63 @@
"htmlparser2": "~3.8.1", "htmlparser2": "~3.8.1",
"jsdom": "^7.0.2", "jsdom": "^7.0.2",
"lodash": "^4.1.0" "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": { "chromium-pickle-js": {
@ -1901,6 +1976,15 @@
"integrity": "sha512-+YR16o3rK53SmWHU3rEM3tPAh2rwb1yPcQX5irVn7mb0gXbwuCCrnkbV5+PBfETdfg1vui07nM6PCG1zndcjQw==", "integrity": "sha512-+YR16o3rK53SmWHU3rEM3tPAh2rwb1yPcQX5irVn7mb0gXbwuCCrnkbV5+PBfETdfg1vui07nM6PCG1zndcjQw==",
"dev": true "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": { "compare-version": {
"version": "0.1.2", "version": "0.1.2",
"resolved": "https://registry.npmjs.org/compare-version/-/compare-version-0.1.2.tgz", "resolved": "https://registry.npmjs.org/compare-version/-/compare-version-0.1.2.tgz",
@ -2193,17 +2277,14 @@
} }
}, },
"cssom": { "cssom": {
"version": "0.3.2", "version": "0.3.4",
"resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.2.tgz", "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.4.tgz",
"integrity": "sha1-uANhcMefB6kP8vFuIihAJ6JDhIs=", "integrity": "sha512-+7prCSORpXNeR4/fUP3rL+TzqtiFfhMvTd7uEqMdgPvLPt4+uzFUeufx5RHjGTACCargg/DiEt/moMQmvnfkog=="
"dev": true
}, },
"cssstyle": { "cssstyle": {
"version": "0.2.37", "version": "0.3.1",
"resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-0.2.37.tgz", "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-0.3.1.tgz",
"integrity": "sha1-VBCXI0yyUTyDzu06zdwn/yeYfVQ=", "integrity": "sha512-tNvaxM5blOnxanyxI6panOsnfiyLRj3HV4qjqqS45WPNS1usdYWRUQjqTEEELK73lpeP/1KoIGYUwrBn/VcECA==",
"dev": true,
"optional": true,
"requires": { "requires": {
"cssom": "0.3.x" "cssom": "0.3.x"
} }
@ -2230,6 +2311,16 @@
"assert-plus": "^1.0.0" "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": { "dateformat": {
"version": "3.0.3", "version": "3.0.3",
"resolved": "https://registry.npmjs.org/dateformat/-/dateformat-3.0.3.tgz", "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-3.0.3.tgz",
@ -2277,8 +2368,7 @@
"deep-is": { "deep-is": {
"version": "0.1.3", "version": "0.1.3",
"resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz",
"integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ="
"dev": true
}, },
"defaults": { "defaults": {
"version": "1.0.3", "version": "1.0.3",
@ -2417,6 +2507,14 @@
"integrity": "sha1-sXrtguirWeUt2cGbF1bg/BhyBMI=", "integrity": "sha1-sXrtguirWeUt2cGbF1bg/BhyBMI=",
"dev": true "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": { "domhandler": {
"version": "2.3.0", "version": "2.3.0",
"resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.3.0.tgz", "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.3.0.tgz",
@ -4225,11 +4323,9 @@
"integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ="
}, },
"escodegen": { "escodegen": {
"version": "1.9.1", "version": "1.11.0",
"resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.9.1.tgz", "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.11.0.tgz",
"integrity": "sha512-6hTjO1NAWkHnDk3OqQ4YrCuwwmGHL9S3nPlzBOUG/R44rda3wLNrfvQ5fkSGjyhHFKM7ALPKcKGrwvCLe0lC7Q==", "integrity": "sha512-IeMV45ReixHS53K/OmfKAIztN/igDHzTJUhZM3k1jMhIZWjk45SMwAtBsEXiJp3vSPmTcu6CXn7mDvFHRN66fw==",
"dev": true,
"optional": true,
"requires": { "requires": {
"esprima": "^3.1.3", "esprima": "^3.1.3",
"estraverse": "^4.2.0", "estraverse": "^4.2.0",
@ -4241,15 +4337,12 @@
"esprima": { "esprima": {
"version": "3.1.3", "version": "3.1.3",
"resolved": "https://registry.npmjs.org/esprima/-/esprima-3.1.3.tgz", "resolved": "https://registry.npmjs.org/esprima/-/esprima-3.1.3.tgz",
"integrity": "sha1-/cpRzuYTOJXjyI1TXOSdv/YqRjM=", "integrity": "sha1-/cpRzuYTOJXjyI1TXOSdv/YqRjM="
"dev": true,
"optional": true
}, },
"source-map": { "source-map": {
"version": "0.6.1", "version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
"dev": true,
"optional": true "optional": true
} }
} }
@ -4262,15 +4355,12 @@
"estraverse": { "estraverse": {
"version": "4.2.0", "version": "4.2.0",
"resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz",
"integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=", "integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM="
"dev": true,
"optional": true
}, },
"esutils": { "esutils": {
"version": "2.0.2", "version": "2.0.2",
"resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz",
"integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=", "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs="
"dev": true
}, },
"events": { "events": {
"version": "1.1.1", "version": "1.1.1",
@ -4382,8 +4472,7 @@
"fast-levenshtein": { "fast-levenshtein": {
"version": "2.0.6", "version": "2.0.6",
"resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
"integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc="
"dev": true
}, },
"fd-slicer": { "fd-slicer": {
"version": "1.0.1", "version": "1.0.1",
@ -5054,6 +5143,14 @@
"integrity": "sha1-ZouTd26q5V696POtRkswekljYl4=", "integrity": "sha1-ZouTd26q5V696POtRkswekljYl4=",
"dev": true "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": { "html-entities": {
"version": "1.2.1", "version": "1.2.1",
"resolved": "https://registry.npmjs.org/html-entities/-/html-entities-1.2.1.tgz", "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-1.2.1.tgz",
@ -5642,27 +5739,56 @@
"optional": true "optional": true
}, },
"jsdom": { "jsdom": {
"version": "7.2.2", "version": "11.11.0",
"resolved": "https://registry.npmjs.org/jsdom/-/jsdom-7.2.2.tgz", "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-11.11.0.tgz",
"integrity": "sha1-QLQCdwwr2iNGkJa+6Rq2deOx/G4=", "integrity": "sha512-ou1VyfjwsSuWkudGxb03FotDajxAto6USAlmMZjE2lc0jCznt7sBWkhfRBRaWwbnmDqdMSTKTLT5d9sBFkkM7A==",
"dev": true,
"optional": true,
"requires": { "requires": {
"abab": "^1.0.0", "abab": "^1.0.4",
"acorn": "^2.4.0", "acorn": "^5.3.0",
"acorn-globals": "^1.0.4", "acorn-globals": "^4.1.0",
"cssom": ">= 0.3.0 < 0.4.0", "array-equal": "^1.0.0",
"cssstyle": ">= 0.2.29 < 0.3.0", "cssom": ">= 0.3.2 < 0.4.0",
"escodegen": "^1.6.1", "cssstyle": ">= 0.3.1 < 0.4.0",
"nwmatcher": ">= 1.3.7 < 2.0.0", "data-urls": "^1.0.0",
"parse5": "^1.5.1", "domexception": "^1.0.0",
"request": "^2.55.0", "escodegen": "^1.9.0",
"sax": "^1.1.4", "html-encoding-sniffer": "^1.0.2",
"symbol-tree": ">= 3.1.0 < 4.0.0", "left-pad": "^1.2.0",
"tough-cookie": "^2.2.0", "nwsapi": "^2.0.0",
"webidl-conversions": "^2.0.0", "parse5": "4.0.0",
"whatwg-url-compat": "~0.6.5", "pn": "^1.1.0",
"xml-name-validator": ">= 2.0.1 < 3.0.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": { "jsesc": {
@ -5817,6 +5943,11 @@
"invert-kv": "^1.0.0" "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": { "less": {
"version": "2.7.3", "version": "2.7.3",
"resolved": "https://registry.npmjs.org/less/-/less-2.7.3.tgz", "resolved": "https://registry.npmjs.org/less/-/less-2.7.3.tgz",
@ -6005,7 +6136,6 @@
"version": "0.3.0", "version": "0.3.0",
"resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz",
"integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=",
"dev": true,
"requires": { "requires": {
"prelude-ls": "~1.1.2", "prelude-ls": "~1.1.2",
"type-check": "~0.3.2" "type-check": "~0.3.2"
@ -6060,8 +6190,7 @@
"lodash": { "lodash": {
"version": "4.17.10", "version": "4.17.10",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz",
"integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg==", "integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg=="
"dev": true
}, },
"lodash._reinterpolate": { "lodash._reinterpolate": {
"version": "3.0.0", "version": "3.0.0",
@ -6135,6 +6264,11 @@
"integrity": "sha1-YAYMxr1iW01FZ+wn3EXNG+nuwBI=", "integrity": "sha1-YAYMxr1iW01FZ+wn3EXNG+nuwBI=",
"dev": true "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": { "lodash.template": {
"version": "4.4.0", "version": "4.4.0",
"resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-4.4.0.tgz", "resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-4.4.0.tgz",
@ -6619,6 +6753,11 @@
"dev": true, "dev": true,
"optional": 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": { "oauth-sign": {
"version": "0.8.2", "version": "0.8.2",
"resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz", "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz",
@ -6706,7 +6845,6 @@
"version": "0.8.2", "version": "0.8.2",
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz",
"integrity": "sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q=", "integrity": "sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q=",
"dev": true,
"requires": { "requires": {
"deep-is": "~0.1.3", "deep-is": "~0.1.3",
"fast-levenshtein": "~2.0.4", "fast-levenshtein": "~2.0.4",
@ -6885,11 +7023,9 @@
"dev": true "dev": true
}, },
"parse5": { "parse5": {
"version": "1.5.1", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/parse5/-/parse5-1.5.1.tgz", "resolved": "https://registry.npmjs.org/parse5/-/parse5-4.0.0.tgz",
"integrity": "sha1-m387DeMr543CQBsXVzzK8Pb1nZQ=", "integrity": "sha512-VrZ7eOd3T1Fk4XWNXMgiGBK/z0MG48BWG2uQNU4I72fkQuKUTZpl+u9k+CxEG0twMVzSmXEEz12z5Fnw1jIQFA=="
"dev": true,
"optional": true
}, },
"parser-toolkit": { "parser-toolkit": {
"version": "0.0.5", "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": { "postcss": {
"version": "5.2.18", "version": "5.2.18",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-5.2.18.tgz", "resolved": "https://registry.npmjs.org/postcss/-/postcss-5.2.18.tgz",
@ -7301,8 +7442,7 @@
"prelude-ls": { "prelude-ls": {
"version": "1.1.2", "version": "1.1.2",
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz",
"integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ="
"dev": true
}, },
"prepend-http": { "prepend-http": {
"version": "1.0.4", "version": "1.0.4",
@ -7683,6 +7823,24 @@
"uuid": "^3.1.0" "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": { "require-directory": {
"version": "2.1.1", "version": "2.1.1",
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
@ -8147,6 +8305,11 @@
"integrity": "sha1-5sgLYjEj19gM8TLOU480YokHJQI=", "integrity": "sha1-5sgLYjEj19gM8TLOU480YokHJQI=",
"dev": true "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": { "stream-json": {
"version": "0.6.1", "version": "0.6.1",
"resolved": "https://registry.npmjs.org/stream-json/-/stream-json-0.6.1.tgz", "resolved": "https://registry.npmjs.org/stream-json/-/stream-json-0.6.1.tgz",
@ -8368,9 +8531,7 @@
"symbol-tree": { "symbol-tree": {
"version": "3.2.2", "version": "3.2.2",
"resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.2.tgz", "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.2.tgz",
"integrity": "sha1-rifbOPZgp64uHDt9G8KQgZuFGeY=", "integrity": "sha1-rifbOPZgp64uHDt9G8KQgZuFGeY="
"dev": true,
"optional": true
}, },
"synchronous-promise": { "synchronous-promise": {
"version": "1.0.18", "version": "1.0.18",
@ -8378,6 +8539,11 @@
"integrity": "sha512-UqMAK6BBBXu8qaDI5omlyV9iDpM9nQfgthaBOK0nlfXnMgiuOBv+meWG73CGeCCFRhOOOa2e4rvqYzfynzy5zg==", "integrity": "sha512-UqMAK6BBBXu8qaDI5omlyV9iDpM9nQfgthaBOK0nlfXnMgiuOBv+meWG73CGeCCFRhOOOa2e4rvqYzfynzy5zg==",
"dev": true "dev": true
}, },
"system": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/system/-/system-2.0.1.tgz",
"integrity": "sha512-BwSUSa8LMHZouGadZ34ck3TsrH5s3oMmTKPK+xHdbBnTCZOZMJ38fHGKLAHkBl0PXru1Z4BsymQU4qqvTxWzdQ=="
},
"tabtab": { "tabtab": {
"version": "2.2.2", "version": "2.2.2",
"resolved": "https://registry.npmjs.org/tabtab/-/tabtab-2.2.2.tgz", "resolved": "https://registry.npmjs.org/tabtab/-/tabtab-2.2.2.tgz",
@ -8596,6 +8762,14 @@
"lazy-val": "^1.0.3" "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": { "thenify": {
"version": "3.3.0", "version": "3.3.0",
"resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.0.tgz", "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.0.tgz",
@ -8759,11 +8933,19 @@
} }
}, },
"tr46": { "tr46": {
"version": "0.0.3", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz",
"integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=", "integrity": "sha1-qLE/1r/SSJUZZ0zN5VujaTtwbQk=",
"dev": true, "requires": {
"optional": true "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": { "transformers": {
"version": "2.1.0", "version": "2.1.0",
@ -8865,7 +9047,6 @@
"version": "0.3.2", "version": "0.3.2",
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz",
"integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=",
"dev": true,
"requires": { "requires": {
"prelude-ls": "~1.1.2" "prelude-ls": "~1.1.2"
} }
@ -9102,6 +9283,14 @@
"integrity": "sha512-x3LV3wdmmERhVCYy3quqA57NJW7F3i6faas++pJQWtknWT+n7k30F4TVdHvCLn48peTJFRvCpxs3UuFPqgeELg==", "integrity": "sha512-x3LV3wdmmERhVCYy3quqA57NJW7F3i6faas++pJQWtknWT+n7k30F4TVdHvCLn48peTJFRvCpxs3UuFPqgeELg==",
"dev": true "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": { "wcwidth": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz",
@ -9112,11 +9301,39 @@
} }
}, },
"webidl-conversions": { "webidl-conversions": {
"version": "2.0.1", "version": "4.0.2",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-2.0.1.tgz", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz",
"integrity": "sha1-O/glj30xjHRDw28uFpQCoaZwNQY=", "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg=="
"dev": true, },
"optional": true "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": { "whatwg-url-compat": {
"version": "0.6.5", "version": "0.6.5",
@ -9126,6 +9343,15 @@
"optional": true, "optional": true,
"requires": { "requires": {
"tr46": "~0.0.1" "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": { "whet.extend": {
@ -9190,8 +9416,7 @@
"wordwrap": { "wordwrap": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz",
"integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=", "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus="
"dev": true
}, },
"wrap-ansi": { "wrap-ansi": {
"version": "2.1.0", "version": "2.1.0",
@ -9214,12 +9439,19 @@
"dev": true, "dev": true,
"optional": 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": { "xml-name-validator": {
"version": "2.0.1", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-2.0.1.tgz", "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz",
"integrity": "sha1-TYuPHszTQZqjYgYb7O9RXh5VljU=", "integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw=="
"dev": true,
"optional": true
}, },
"xml2js": { "xml2js": {
"version": "0.4.17", "version": "0.4.17",

View File

@ -1,100 +1,102 @@
{ {
"name": "FreeTube", "name": "FreeTube",
"productName": "FreeTube", "productName": "FreeTube",
"version": "0.3.1", "version": "0.3.1",
"description": "An Open Source YouTube app for privacy.", "description": "An Open Source YouTube app for privacy.",
"main": "src/js/init.js", "main": "src/js/init.js",
"scripts": { "scripts": {
"start": "electron-forge start", "start": "electron-forge start",
"package": "electron-forge package", "package": "electron-forge package",
"make": "electron-forge make", "make": "electron-forge make",
"publish": "electron-forge publish", "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: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:mac": "electron-forge make --platform=darwin",
"make:win": "electron-forge make --platform=win32", "make:win": "electron-forge make --platform=win32",
"make:win:zip": "electron-forge make --platform=win32 --targets=zip", "make:win:zip": "electron-forge make --platform=win32 --targets=zip",
"make:linux:x86": "electron-forge make --platform=linux --arch x64", "make:linux:x86": "electron-forge make --platform=linux --arch x64",
"make:linux:x86:zip": "electron-forge make --platform=linux --targets=zip --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: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: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: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: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: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": "electron-forge make --platform=linux --arch arm64",
"make:linux:arm:zip": "electron-forge make --platform=linux --targets=zip --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: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: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" "make:linux:arm:appimage": "electron-forge make --platform=linux --targets=electron-forge-maker-appimage --arch arm64"
}, },
"keywords": [], "keywords": [],
"author": { "author": {
"name": "PrestonN", "name": "PrestonN",
"email": "FreeTubeApp@protonmail.com", "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" "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> <html>
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<link rel="stylesheet" href="style/main.css"> <link rel="stylesheet" href="style/main.css">
<link rel="stylesheet" href="style/lightTheme.css"> <link rel="stylesheet" href="style/lightTheme.css">
<link rel="stylesheet" href="style/player.css"> <link rel="stylesheet" href="style/player.css">
<link rel="stylesheet" href="style/videoList.css"> <link rel="stylesheet" href="style/videoList.css">
<link rel="stylesheet" href="style/channel.css"> <link rel="stylesheet" href="style/channel.css">
<link rel="stylesheet" href="style/loading.css"> <link rel="stylesheet" href="style/loading.css">
<link rel="stylesheet" href="style/fa-solid.min.css"> <link rel="stylesheet" href="style/fa-solid.min.css">
<link rel="stylesheet" href="style/fontawesome-all.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/general.js"></script>
<script src="js/updates.js"></script> <script src="js/youtubeApi.js"></script>
<script src="js/layout.js"></script> <script src="js/settings.js"></script>
<script src="js/videos.js"></script> <script src="js/updates.js"></script>
<script src="js/player.js"></script> <script src="js/videos.js"></script>
<script src="js/subscriptions.js"></script> <script src="js/layout.js"></script>
<script src="js/channels.js"></script> <script src="js/player.js"></script>
<script src="js/savedVideos.js"></script> <script src="js/subscriptions.js"></script>
<script src="js/history.js"></script> <script src="js/channels.js"></script>
<script src="js/events.js"></script> <script src="js/savedVideos.js"></script>
<title>Freetube Player</title> <script src="js/history.js"></script>
<script src="js/events.js"></script>
<title>Freetube Player</title>
</head> </head>
<body> <body>
<div id='loading'> <div id='loading'>
<div class="spinner"> <div class="spinner">
<div class="double-bounce1"></div> <div class="double-bounce1"></div>
<div class="double-bounce2"></div> <div class="double-bounce2"></div>
</div>
</div> </div>
</div> <div id='confirmFunction'>
<div id='confirmFunction'> <span id='confirmMessage'>Would you like to perform the function?</span>
<span id='confirmMessage'>Would you like to perform the function?</span> <div class='confirmButton' id='confirmYes'>Yes</div>
<div class='confirmButton' id='confirmYes'>Yes</div> <div class='confirmButton' id='confirmNo'>No</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> </div>
<img src='icons/iconBlack.png' id='menuIcon'/> <div id='toast'>
&nbsp; <span id='toastMessage'></span>
<img src='icons/textBlack.png' id='menuText'/> <i onclick='hideToast()' class="closeToast fas fa-times"></i>
</div> </div>
<div id='sideNavDisabled'></div> <div class="topNav">
<div id="sideNav"> <i onclick='toggleSideNavigation()' class="fas fa-bars" id='menuButton'></i>
<div class="sideNavContainer"> <div class="searchBar">
<ul> <input id='search' class="search" type="text" placeholder="Search / Go to URL">
<li onclick='loadSubscriptions()'><i class="fas fa-rss"></i>&nbsp;&nbsp;Subscriptions</li> <i onclick='parseSearchText()' class="fas fa-search searchButton" style='margin-right: -10px; cursor: pointer'></i>
<li onclick='showMostPopular()'><i class="fas fa-users"></i>&nbsp;&nbsp;Most Popular</li> </div>
<li onclick='showSavedVideos()'><i class="fas fa-star"></i>&nbsp;&nbsp;Saved</li> <img src='icons/iconBlack.png' id='menuIcon' /> &nbsp;
<li onclick='showHistory()'><i class="fas fa-history"></i>&nbsp;&nbsp;History</li> <img src='icons/textBlack.png' id='menuText' />
</ul> </div>
<hr /> <div id='sideNavDisabled'></div>
<ul> <div id="sideNav">
<li onclick='showSettings()'><i class="fas fa-sliders-h"></i>&nbsp;&nbsp;Settings</li> <div class="sideNavContainer">
<li onclick='showAbout()'><i class="fas fa-info-circle"></i>&nbsp;&nbsp;About</li> <ul>
</ul> <li onclick='loadSubscriptions()'><i class="fas fa-rss"></i>&nbsp;&nbsp;Subscriptions</li>
<hr /> <li onclick='showMostPopular()'><i class="fas fa-users"></i>&nbsp;&nbsp;Most Popular</li>
<ul id='subscriptions'> <li onclick='showSavedVideos()'><i class="fas fa-star"></i>&nbsp;&nbsp;Favorites</li>
</ul> <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>
<div id="main">
</div>
</body> </body>
</html> </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 FreeTube is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or the Free Software Foundation, either version 3 of the License, or
(at your option) any later version. (at your option) any later version.
FreeTube is distributed in the hope that it will be useful, FreeTube is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details. GNU General Public License for more details.
You should have received a copy of the GNU General Public License You should have received a copy of the GNU General Public License
along with FreeTube. If not, see <http://www.gnu.org/licenses/>. 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) { /*function getChannelThumbnail(channelId, callback) {
let url = ''; let url = '';
@ -33,60 +33,60 @@ along with FreeTube. If not, see <http://www.gnu.org/licenses/>.
}*/ }*/
/** /**
* Display a channel page, showing latest uploads. * Display a channel page, showing latest uploads.
* *
* @param {string} channelId - The channel ID to display. * @param {string} channelId - The channel ID to display.
* *
* @return {Void} * @return {Void}
*/ */
function goToChannel(channelId) { function goToChannel(channelId) {
event.stopPropagation(); event.stopPropagation();
clearMainContainer(); clearMainContainer();
startLoadingAnimation(); startLoadingAnimation();
let subButtonText; let subButtonText;
// Setting subButtonText here as Mustache templates are logic-less. // Setting subButtonText here as Mustache templates are logic-less.
isSubscribed(channelId).then((subscribed) => { isSubscribed(channelId).then((subscribed) => {
subButtonText = (subscribed ? "UNSUBSCRIBE" : "SUBSCRIBE"); 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,
}); });
$('#main').html(rendered);
stopLoadingAnimation();
// Grab the channel's latest uploads. API forces a max of 50. // Grab general channel information
youtubeAPI('search', { youtubeAPI('channels', {
part: 'snippet', part: 'snippet,brandingSettings,statistics',
channelId: channelId, id: channelId,
type: 'video',
maxResults: 50,
order: 'date',
}, function (data) { }, function (data) {
// Display recent uploads to #main const channelData = data.items[0];
let grabDuration = getDuration(data.items);
grabDuration.then((videoList) => { const channelViewTemplate = require('./templates/channelView.html');
videoList.items.forEach((video) => { mustache.parse(channelViewTemplate);
displayVideo(video); 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 FreeTube is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by sit under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or the Free Software Foundation, either version 3 of the License, or
(at your option) any later version. (at your option) any later version.
FreeTube is distributed in the hope that it will be useful, FreeTube is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details. GNU General Public License for more details.
You should have received a copy of the GNU General Public License You should have received a copy of the GNU General Public License
along with FreeTube. If not, see <http://www.gnu.org/licenses/>. along with FreeTube. If not, see <http://www.gnu.org/licenses/>.
*/ */
/* /*
* File for events within application. Work needs to be done throughout the application * 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. * 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, * Event when user clicks comment box,
* and wants to show/display comments for the user. * and wants to show/display comments for the user.
*/ */
let showComments = function(event) { let showComments = function (event) {
let comments = $('#comments'); let comments = $('#comments');
if (comments.css('display') === 'none') { if (comments.css('display') === 'none') {
comments.attr('loaded', 'true'); comments.attr('loaded', 'true');
youtubeAPI('commentThreads', { youtubeAPI('commentThreads', {
'videoId': $('#comments').attr('data-video-id'), 'videoId': $('#comments').attr('data-video-id'),
'part': 'snippet,replies', 'part': 'snippet,replies',
'maxResults': 100, 'maxResults': 100,
}, function (data){ }, function (data) {
let comments = []; let comments = [];
let items = data.items; let items = data.items;
items.forEach((object) => { items.forEach((object) => {
let snippet = object.snippet.topLevelComment.snippet; 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); comments.push(snippet);
}) })
const commentsTemplate = require('./templates/comments.html'); const commentsTemplate = require('./templates/comments.html');
const html = mustache.render(commentsTemplate, { const html = mustache.render(commentsTemplate, {
comments: comments, comments: comments,
}); });
$('#comments').html(html); $('#comments').html(html);
}); });
comments.show(); comments.show();
} else { } else {
comments.hide(); comments.hide();
} }
}; };
/** /**
* Play / Pause the video player upon click. * Play / Pause the video player upon click.
*/ */
let playPauseVideo = function(event) { let playPauseVideo = function (event) {
let el = event.currentTarget; let el = event.currentTarget;
el.paused ? el.play() : el.pause(); el.paused ? el.play() : el.pause();
}; };
$('.videoPlayer').keypress((event) => {
console.log(event.which);
});
let videoShortcutHandler = function(event) { let videoShortcutHandler = function (event) {
console.log(event.which);
let videoPlayer = $('.videoPlayer').get(0); let videoPlayer = $('.videoPlayer').get(0);
if (typeof(videoPlayer) !== 'undefined' && !$('#jumpToInput').is(':focus') && !$('#search').is(':focus')){ if (typeof (videoPlayer) !== 'undefined' && !$('#jumpToInput').is(':focus') && !$('#search').is(':focus')) {
switch (event.which) { switch (event.which) {
case 32: case 32:
// Space Bar // Space Bar
event.preventDefault(); event.preventDefault();
videoPlayer.paused ? videoPlayer.play() : videoPlayer.pause(); videoPlayer.paused ? videoPlayer.play() : videoPlayer.pause();
break; break;
case 74: case 74:
// J Key // J Key
event.preventDefault(); event.preventDefault();
changeDurationBySeconds(-10); changeDurationBySeconds(-10);
break; break;
case 75: case 75:
// K Key // K Key
event.preventDefault(); event.preventDefault();
videoPlayer.paused ? videoPlayer.play() : videoPlayer.pause(); videoPlayer.paused ? videoPlayer.play() : videoPlayer.pause();
break; break;
case 76: case 76:
// L Key // L Key
event.preventDefault(); event.preventDefault();
changeDurationBySeconds(10); changeDurationBySeconds(10);
break; break;
case 70: case 70:
// F Key // F Key
event.preventDefault(); event.preventDefault();
videoPlayer.webkitRequestFullscreen(); videoPlayer.webkitRequestFullscreen();
break; break;
case 77: case 77:
// M Key // M Key
event.preventDefault(); event.preventDefault();
let volume = videoPlayer.volume; let volume = videoPlayer.volume;
console.log(volume);
if (volume > 0){ if (volume > 0) {
changeVolume(-1); 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){ let fullscreenVideo = function (event) {
if (document.webkitFullscreenElement !== null){ if (document.webkitFullscreenElement !== null) {
document.webkitExitFullscreen(); document.webkitExitFullscreen();
} } else {
else{ $('.videoPlayer').get(0).webkitRequestFullscreen();
$('.videoPlayer').get(0).webkitRequestFullscreen(); }
}
} }
/** /**
@ -212,6 +204,7 @@ let fullscreenVideo = function(event){
* Bind click events * Bind click events
* -------------------------- * --------------------------
*/ */
$(document).on('click', '#showComments', showComments); $(document).on('click', '#showComments', showComments);
$(document).on('click', '.videoPlayer', playPauseVideo); $(document).on('click', '.videoPlayer', playPauseVideo);
@ -220,4 +213,4 @@ $(document).on('dblclick', '.videoPlayer', fullscreenVideo);
$(document).on('keydown', videoShortcutHandler); $(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 FreeTube is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or the Free Software Foundation, either version 3 of the License, or
(at your option) any later version. (at your option) any later version.
FreeTube is distributed in the hope that it will be useful, FreeTube is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details. GNU General Public License for more details.
You should have received a copy of the GNU General Public License You should have received a copy of the GNU General Public License
along with FreeTube. If not, see <http://www.gnu.org/licenses/>. 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 * Add a video to the history database file
* *
* @param {string} videoId - The video ID of the video to be saved. * @param {string} videoId - The video ID of the video to be saved.
* *
* @return {Void} * @return {Void}
*/ */
function addToHistory(videoId){ function addToHistory(videoId) {
const data = { const data = {
videoId: videoId, videoId: videoId,
timeWatched: new Date().getTime(), timeWatched: new Date().getTime(),
}; };
historyDb.insert(data, (err, newDoc) => {}); historyDb.insert(data, (err, newDoc) => {});
} }
/** /**
* Remove a video from the history database file * Remove a video from the history database file
* *
* @param {string} videoId - The video ID of the video to be removed. * @param {string} videoId - The video ID of the video to be removed.
* *
* @return {Void} * @return {Void}
*/ */
function removeFromHistory(videoId){ function removeFromHistory(videoId) {
const data = {videoId: videoId}; const data = {
historyDb.remove(data, {}, (err, numRemoved) => {}); videoId: videoId
};
historyDb.remove(data, {}, (err, numRemoved) => {});
} }
/** /**
* Show the videos within the history database. * Show the videos within the history database.
* *
* @return {Void} * @return {Void}
*/ */
function showHistory(){ function showHistory() {
clearMainContainer(); clearMainContainer();
startLoadingAnimation(); startLoadingAnimation();
console.log('checking history');
let videoList = ''; let videoList = '';
historyDb.find({}).sort({ historyDb.find({}).sort({
timeWatched: -1 timeWatched: -1
}).exec((err, docs) => { }).exec((err, docs) => {
if(docs.length > 49){ if (docs.length > 49) {
// The YouTube API limits the search to 50 videos, so grab 50 most recent. // The YouTube API limits the search to 50 videos, so grab 50 most recent.
for (let i = 0; i < 49; i++) { for (let i = 0; i < 49; i++) {
videoList = videoList + ',' + docs[i]['videoId']; videoList = videoList + ',' + docs[i]['videoId'];
} }
} } else {
else{ docs.forEach((video) => {
docs.forEach((video) => { videoList = videoList + ',' + video['videoId'];
videoList = videoList + ',' + video['videoId']; });
}); }
}
youtubeAPI('videos', { youtubeAPI('videos', {
part: 'snippet', part: 'snippet',
id: videoList, id: videoList,
maxResults: 50, maxResults: 50,
}, function (data) { }, function (data) {
createVideoListContainer('Watch History:'); createVideoListContainer('Watch History:');
let grabDuration = getDuration(data.items); let grabDuration = getDuration(data.items);
grabDuration.then((videoList) => { grabDuration.then((videoList) => {
videoList.items.forEach((video) => { videoList.items.forEach((video) => {
displayVideo(video, 'history'); 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 FreeTube is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or the Free Software Foundation, either version 3 of the License, or
(at your option) any later version. (at your option) any later version.
FreeTube is distributed in the hope that it will be useful, FreeTube is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details. GNU General Public License for more details.
You should have received a copy of the GNU General Public License You should have received a copy of the GNU General Public License
along with FreeTube. If not, see <http://www.gnu.org/licenses/>. along with FreeTube. If not, see <http://www.gnu.org/licenses/>.
*/ */
/* /*
* File used to initializing the application * File used to initializing the application
*/ */
const {app, BrowserWindow, dialog, protocol} = require('electron'); const {
app,
BrowserWindow,
dialog,
protocol
} = require('electron');
const path = require('path'); const path = require('path');
const url = require('url'); const url = require('url');
let win; let win;
protocol.registerStandardSchemes(['freetube']); protocol.registerStandardSchemes(['freetube']);
@ -30,103 +36,141 @@ protocol.registerStandardSchemes(['freetube']);
app.setAsDefaultProtocolClient('freetube'); app.setAsDefaultProtocolClient('freetube');
const isSecondInstance = app.makeSingleInstance((commandLine, workingDirectory) => { const isSecondInstance = app.makeSingleInstance((commandLine, workingDirectory) => {
// Someone tried to run a second instance, we should focus our window. // Someone tried to run a second instance, we should focus our window.
if (win) { if (win) {
if (win.isMinimized()) win.restore() if (win.isMinimized()) win.restore()
win.focus() 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 * Initialize the Electron application
* 1. create the browser window * 1. create the browser window
* 2. load the index.html * 2. load the index.html
*/ */
let init = function() { let init = function () {
const Menu = require('electron').Menu; 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({ win.loadURL(url.format({
pathname: path.join(__dirname, '../index.html'), pathname: path.join(__dirname, '../index.html'),
protocol: 'file:', protocol: 'file:',
slashes: true, slashes: true,
})); }));
if (process.env = 'development') { if (process.env = 'development') {
//win.webContents.openDevTools();ff //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'}
]
} }
];
const menu = Menu.buildFromTemplate(template); win.on('closed', () => {
Menu.setApplicationMenu(menu); 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 * Quit the application
*/ */
let allWindowsClosed = function() { let allWindowsClosed = function () {
win.webContents.session.clearStorageData([], (data) => {}); win.webContents.session.clearStorageData([], (data) => {});
win.webContents.session.clearCache((data) => {}); win.webContents.session.clearCache((data) => {});
app.quit(); app.quit();
}; };
/** /**
* On Mac, when dock icon is clicked, * On Mac, when dock icon is clicked,
* create a new window and launch the editor * create a new window and launch the editor
*/ */
let active = function() { let active = function () {
if (win === null) { if (win === null) {
init(); init();
} }
}; };
/** /**
@ -136,4 +180,4 @@ let active = function() {
*/ */
app.on('ready', init); app.on('ready', init);
app.on('window-all-closed', allWindowsClosed); 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 FreeTube is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or the Free Software Foundation, either version 3 of the License, or
(at your option) any later version. (at your option) any later version.
FreeTube is distributed in the hope that it will be useful, FreeTube is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details. GNU General Public License for more details.
You should have received a copy of the GNU General Public License You should have received a copy of the GNU General Public License
along with FreeTube. If not, see <http://www.gnu.org/licenses/>. 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 toastTimeout; // Timeout for toast notifications.
let mouseTimeout; // Timeout for hiding the mouse cursor on video playback let mouseTimeout; // Timeout for hiding the mouse cursor on video playback
require.extensions['.html'] = function(module, filename) { require.extensions['.html'] = function (module, filename) {
module.exports = fs.readFileSync(filename, 'utf8'); module.exports = fs.readFileSync(filename, 'utf8');
}; };
const subDb = new Datastore({ const subDb = new Datastore({
filename: localDataStorage + '/subscriptions.db', filename: localDataStorage + '/subscriptions.db',
autoload: true autoload: true
}); });
const historyDb = new Datastore({ const historyDb = new Datastore({
filename: localDataStorage + '/videohistory.db', filename: localDataStorage + '/videohistory.db',
autoload: true autoload: true
}); });
const savedVidsDb = new Datastore({ const savedVidsDb = new Datastore({
filename: localDataStorage + '/savedvideos.db', filename: localDataStorage + '/savedvideos.db',
autoload: true autoload: true
}); });
const settingsDb = new Datastore({ const settingsDb = new Datastore({
filename: localDataStorage + '/settings.db', filename: localDataStorage + '/settings.db',
autoload: true autoload: true
}); });
// Grabs the default settings from the settings database file. Makes defaults if // Grabs the default settings from the settings database file. Makes defaults if
// none are found. // none are found.
checkDefaultSettings(); checkDefaultSettings();
require('electron').ipcRenderer.on('ping', function(event, message) { require('electron').ipcRenderer.on('ping', function (event, message) {
console.log(message); ft.log(message);
let url = message[1].replace('freetube://', ''); let url = message[1].replace('freetube://', '');
parseSearchText(url); parseSearchText(url);
console.log(message); ft.log(message);
}); });
// Open links externally by default // Open links externally by default
$(document).on('click', 'a[href^="http"]', (event) => { $(document).on('click', 'a[href^="http"]', (event) => {
let el = event.currentTarget; let el = event.currentTarget;
event.preventDefault(); event.preventDefault();
shell.openExternal(el.href); shell.openExternal(el.href);
}); });
// Open links externally on middle click. // Open links externally on middle click.
$(document).on('auxclick', 'a[href^="http"]', (event) => { $(document).on('auxclick', 'a[href^="http"]', (event) => {
let el = event.currentTarget; let el = event.currentTarget;
event.preventDefault(); event.preventDefault();
shell.openExternal(el.href); shell.openExternal(el.href);
}); });
$(document).ready(() => { $(document).ready(() => {
const searchBar = document.getElementById('search'); const searchBar = document.getElementById('search');
const jumpToInput = document.getElementById('jumpToInput'); const jumpToInput = document.getElementById('jumpToInput');
// Displays the list of subscriptions in the side bar. // Displays the list of subscriptions in the side bar.
displaySubs(); displaySubs();
// Allow user to use the 'enter' key to search for a video. // Allow user to use the 'enter' key to search for a video.
searchBar.onkeypress = (e) => { searchBar.onkeypress = (e) => {
if (e.keyCode === 13) { if (e.keyCode === 13) {
parseSearchText(); parseSearchText();
} }
}; };
// Display subscriptions upon the app opening up. May allow user to specify. // Display subscriptions upon the app opening up. May allow user to specify.
// Home page in the future. // Home page in the future.
loadSubscriptions(); loadSubscriptions();
}); });
/** /**
@ -129,16 +129,16 @@ $(document).ready(() => {
* @return {Void} * @return {Void}
*/ */
function toggleSideNavigation() { function toggleSideNavigation() {
const sideNav = document.getElementById('sideNav'); const sideNav = document.getElementById('sideNav');
const mainContainer = document.getElementById('main'); const mainContainer = document.getElementById('main');
if (sideNav.style.display === 'none') { if (sideNav.style.display === 'none') {
sideNav.style.display = 'inline'; sideNav.style.display = 'inline';
mainContainer.style.marginLeft = '250px'; mainContainer.style.marginLeft = '250px';
} else { } else {
sideNav.style.display = 'none'; sideNav.style.display = 'none';
mainContainer.style.marginLeft = '0px'; mainContainer.style.marginLeft = '0px';
} }
} }
/** /**
@ -147,35 +147,35 @@ function toggleSideNavigation() {
* @return {Void} * @return {Void}
*/ */
function clearMainContainer() { function clearMainContainer() {
const container = document.getElementById('main'); const container = document.getElementById('main');
container.innerHTML = ''; container.innerHTML = '';
hideConfirmFunction(); hideConfirmFunction();
} }
function startLoadingAnimation() { function startLoadingAnimation() {
const loading = document.getElementById('loading'); const loading = document.getElementById('loading');
const sideNavDisabled = document.getElementById('sideNavDisabled'); const sideNavDisabled = document.getElementById('sideNavDisabled');
const searchBar = document.getElementById('search'); const searchBar = document.getElementById('search');
loading.style.display = 'inherit'; loading.style.display = 'inherit';
if(sideNavDisabled !== null){ if (sideNavDisabled !== null) {
sideNavDisabled.style.display = 'inherit'; sideNavDisabled.style.display = 'inherit';
} }
searchBar.disabled = true; searchBar.disabled = true;
} }
function stopLoadingAnimation() { function stopLoadingAnimation() {
const loading = document.getElementById('loading'); const loading = document.getElementById('loading');
const sideNavDisabled = document.getElementById('sideNavDisabled'); const sideNavDisabled = document.getElementById('sideNavDisabled');
const searchBar = document.getElementById('search'); const searchBar = document.getElementById('search');
loading.style.display = 'none'; loading.style.display = 'none';
if(sideNavDisabled !== null){ if (sideNavDisabled !== null) {
sideNavDisabled.style.display = 'none'; sideNavDisabled.style.display = 'none';
} }
searchBar.disabled = false; searchBar.disabled = false;
} }
/** /**
@ -186,17 +186,17 @@ function stopLoadingAnimation() {
* @return {Void} * @return {Void}
*/ */
function createVideoListContainer(headerLabel = '') { function createVideoListContainer(headerLabel = '') {
const videoListContainer = document.createElement("div"); const videoListContainer = document.createElement("div");
videoListContainer.id = 'videoListContainer'; videoListContainer.id = 'videoListContainer';
let headerSpacer; let headerSpacer;
if (headerLabel != '') { if (headerLabel != '') {
const headerElement = document.createElement("h2"); const headerElement = document.createElement("h2");
headerElement.innerHTML = headerLabel; headerElement.innerHTML = headerLabel;
headerElement.style.marginLeft = '15px'; headerElement.style.marginLeft = '15px';
headerElement.appendChild(document.createElement("hr")); headerElement.appendChild(document.createElement("hr"));
videoListContainer.appendChild(headerElement); videoListContainer.appendChild(headerElement);
} }
document.getElementById("main").appendChild(videoListContainer); document.getElementById("main").appendChild(videoListContainer);
} }
/** /**
@ -205,18 +205,18 @@ function createVideoListContainer(headerLabel = '') {
* @return {Void} * @return {Void}
*/ */
function showAbout() { function showAbout() {
// Remove current information and display loading animation // Remove current information and display loading animation
clearMainContainer(); clearMainContainer();
startLoadingAnimation(); startLoadingAnimation();
const aboutTemplate = require('./templates/about.html') const aboutTemplate = require('./templates/about.html')
mustache.parse(aboutTemplate); mustache.parse(aboutTemplate);
$('#main').html( $('#main').html(
mustache.render(aboutTemplate, { mustache.render(aboutTemplate, {
versionNumber: require('electron').remote.app.getVersion(), versionNumber: require('electron').remote.app.getVersion(),
}) })
); );
stopLoadingAnimation(); stopLoadingAnimation();
} }
/** /**
@ -228,18 +228,18 @@ function showAbout() {
* @return {Void} * @return {Void}
*/ */
function showToast(message) { function showToast(message) {
let toast = document.getElementById('toast'); let toast = document.getElementById('toast');
let toastMessage = document.getElementById('toastMessage'); let toastMessage = document.getElementById('toastMessage');
// If a toast message is already being displayed, this will remove the previous timer that was set. // If a toast message is already being displayed, this will remove the previous timer that was set.
clearTimeout(toastTimeout); clearTimeout(toastTimeout);
toastMessage.innerHTML = message; toastMessage.innerHTML = message;
toast.style.visibility = 'visible'; toast.style.visibility = 'visible';
toast.style.opacity = 0.9; toast.style.opacity = 0.9;
// Set the timer for the toast to be removed. // Set the timer for the toast to be removed.
toastTimeout = window.setTimeout(hideToast, 5000); toastTimeout = window.setTimeout(hideToast, 5000);
} }
/** /**
@ -248,9 +248,9 @@ function showToast(message) {
* @return {Void} * @return {Void}
*/ */
function hideToast() { function hideToast() {
let toast = document.getElementById('toast'); let toast = document.getElementById('toast');
toast.style.opacity = 0; toast.style.opacity = 0;
toast.style.visibility = 'hidden'; toast.style.visibility = 'hidden';
} }
/** /**
@ -264,21 +264,20 @@ function hideToast() {
* @return {Void} * @return {Void}
*/ */
function confirmFunction(message, performFunction, parameters = '') { function confirmFunction(message, performFunction, parameters = '') {
let confirmContainer = document.getElementById('confirmFunction'); let confirmContainer = document.getElementById('confirmFunction');
let confirmMessage = document.getElementById('confirmMessage'); let confirmMessage = document.getElementById('confirmMessage');
confirmMessage.innerHTML = message; confirmMessage.innerHTML = message;
confirmContainer.style.visibility = 'visible'; confirmContainer.style.visibility = 'visible';
$(document).on('click', '#confirmYes', (event) => { $(document).on('click', '#confirmYes', (event) => {
if(parameters != ''){ if (parameters != '') {
performFunction(parameters); performFunction(parameters);
} } else {
else{ performFunction();
performFunction(); }
} hideConfirmFunction();
hideConfirmFunction(); });
});
} }
/** /**
@ -287,8 +286,8 @@ function confirmFunction(message, performFunction, parameters = '') {
* @return {Void} * @return {Void}
*/ */
function hideConfirmFunction() { function hideConfirmFunction() {
let confirmContainer = document.getElementById('confirmFunction'); let confirmContainer = document.getElementById('confirmFunction');
confirmContainer.style.visibility = 'hidden'; confirmContainer.style.visibility = 'hidden';
} }
/** /**
@ -298,11 +297,11 @@ function hideConfirmFunction() {
* @return {Void} * @return {Void}
*/ */
function hideMouseTimeout() { function hideMouseTimeout() {
$('.videoPlayer')[0].style.cursor = 'default'; $('.videoPlayer')[0].style.cursor = 'default';
clearTimeout(mouseTimeout); clearTimeout(mouseTimeout);
mouseTimeout = window.setTimeout(function() { mouseTimeout = window.setTimeout(function () {
$('.videoPlayer')[0].style.cursor = 'none'; $('.videoPlayer')[0].style.cursor = 'none';
}, 3150); }, 3150);
} }
/** /**
@ -311,14 +310,14 @@ function hideMouseTimeout() {
* @return {Void} * @return {Void}
*/ */
function removeMouseTimeout() { function removeMouseTimeout() {
$('.videoPlayer')[0].style.cursor = 'default'; $('.videoPlayer')[0].style.cursor = 'default';
clearTimeout(mouseTimeout); clearTimeout(mouseTimeout);
} }
function showVideoOptions(element) { function showVideoOptions(element) {
if (element.nextElementSibling.style.display == 'none' || element.nextElementSibling.style.display == '') { if (element.nextElementSibling.style.display == 'none' || element.nextElementSibling.style.display == '') {
element.nextElementSibling.style.display = 'inline-block' element.nextElementSibling.style.display = 'inline-block'
} else { } else {
element.nextElementSibling.style.display = 'none' 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 FreeTube is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or the Free Software Foundation, either version 3 of the License, or
(at your option) any later version. (at your option) any later version.
FreeTube is distributed in the hope that it will be useful, FreeTube is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details. GNU General Public License for more details.
You should have received a copy of the GNU General Public License You should have received a copy of the GNU General Public License
along with FreeTube. If not, see <http://www.gnu.org/licenses/>. 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 * Video Comment Model
*/ */
export class comment { export class comment {
authorDisplayName: string; authorDisplayName;
authorProfileImageUrl: string; authorProfileImageUrl;
authorChannelId: string; authorChannelId;
textDisplay: string; textDisplay;
publishedAt: string; 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 FreeTube is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or the Free Software Foundation, either version 3 of the License, or
(at your option) any later version. (at your option) any later version.
FreeTube is distributed in the hope that it will be useful, FreeTube is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details. GNU General Public License for more details.
You should have received a copy of the GNU General Public License You should have received a copy of the GNU General Public License
along with FreeTube. If not, see <http://www.gnu.org/licenses/>. 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 * Entire Comment Threads for a Video
*/ */
export class commentThread { export class commentThread {
videoId: ?string; videoId;
nextPageToken: ?string; nextPageToken;
pageInfo: { pageInfo = {
totalResults: number, totalResults,
resultsPerPage: number 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 FreeTube is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or the Free Software Foundation, either version 3 of the License, or
(at your option) any later version. (at your option) any later version.
FreeTube is distributed in the hope that it will be useful, FreeTube is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details. GNU General Public License for more details.
You should have received a copy of the GNU General Public License You should have received a copy of the GNU General Public License
along with FreeTube. If not, see <http://www.gnu.org/licenses/>. 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} * @return {Void}
*/ */
function playVideo(videoId, videoThumbnail = '', useWindowPlayer = false) { function playVideo(videoId, videoThumbnail = '', useWindowPlayer = false) {
if (useWindowPlayer === false){ if (useWindowPlayer === false) {
clearMainContainer(); clearMainContainer();
startLoadingAnimation(); 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';
} else { } else {
savedText = 'SAVED'; showToast('Getting video information. Please wait...')
savedIconClass = 'fas saved';
} }
});
youtubeAPI('videos', { let subscribeText = '';
part: 'statistics', let savedText = '';
id: videoId, let savedIconClass = '';
}, function(data) { let savedIconColor = '';
console.log(data); 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. const checkSavedVideo = videoIsSaved(videoId);
videoLikes = data['items'][0]['statistics']['likeCount'];
videoDislikes = data['items'][0]['statistics']['dislikeCount'];
totalLikes = parseInt(videoLikes) + parseInt(videoDislikes);
likePercentage = parseInt((videoLikes / totalLikes) * 100);
});
/* // Change the save button icon and text depending on if the user has saved the video or not.
* FreeTube calls youtube-dl to grab the direct video URL. checkSavedVideo.then((results) => {
*/ if (results === false) {
youtubedlGetInfo(videoId, (info) => { savedText = 'FAVORITE';
console.log(info); savedIconClass = 'far unsaved';
} else {
console.log(videoLikes); savedText = 'UNFAVORITE';
savedIconClass = 'fas saved';
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;
}
}); });
// Default to the embeded player if the URLs cannot be found. youtubeAPI('videos', {
if (typeof(video720p) === 'undefined' && typeof(video480p) === 'undefined') { part: 'statistics',
useEmbedPlayer = true; id: videoId,
defaultQuality = 'EMBED'; }, function (data) {
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) { // Figure out the width for the like/dislike bar.
//videoHtml = '<video class="videoPlayer" type="application/x-mpegURL" onmousemove="hideMouseTimeout()" onmouseleave="removeMouseTimeout()" controls="" src="' + defaultUrl + '" poster="' + videoThumbnail + '" autoplay>'; 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) === 'object') {
if (typeof(info.player_response.captions.playerCaptionsTracklistRenderer.captionTracks) === 'object') { if (typeof (info.player_response.captions.playerCaptionsTracklistRenderer.captionTracks) === 'object') {
const videoSubtitles = info.player_response.captions.playerCaptionsTracklistRenderer.captionTracks; const videoSubtitles = info.player_response.captions.playerCaptionsTracklistRenderer.captionTracks;
videoSubtitles.forEach((subtitle) => { videoSubtitles.forEach((subtitle) => {
let subtitleUrl = 'https://www.youtube.com/api/timedtext?lang=' + subtitle.languageCode + '&fmt=vtt&name=&v=' + videoId; let subtitleUrl = 'https://www.youtube.com/api/timedtext?lang=' + subtitle.languageCode + '&fmt=vtt&name=&v=' + videoId;
if (subtitle.kind == 'asr') { if (subtitle.kind == 'asr') {
//subtitleUrl = subtitle.baseUrl; //subtitleUrl = subtitle.baseUrl;
return; 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) => { if (results === false) {
const subscribeButton = document.getElementById('subscribeButton'); if (subscribeButton != null) {
subscribeButton.innerHTML = 'SUBSCRIBE';
if (results === false) { }
if (subscribeButton != null) { } else {
subscribeButton.innerHTML = 'SUBSCRIBE'; if (subscribeButton != null) {
} subscribeButton.innerHTML = 'UNSUBSCRIBE';
} 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';
}); });
}
}
// Sometimes a video URL is found, but the video will not play. I believe the issue is const playerTemplate = require('./templates/player.html')
// that the video has yet to render for that quality, as the video will be available at a later time. mustache.parse(playerTemplate);
// This will check the URLs and switch video sources if there is an error. const rendered = mustache.render(playerTemplate, {
//checkVideoUrls(video480p, video720p); 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} * @return {Void}
*/ */
function openMiniPlayer(videoThumbnail) { function openMiniPlayer(videoThumbnail) {
let lastTime; let lastTime;
let videoHtml; let videoHtml;
// Grabs whatever the HTML is for the current video player. Done this way to grab // 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. // the HTML5 player (with varying qualities) as well as the YouTube embeded player.
if ($('.videoPlayer').length > 0) { if ($('.videoPlayer').length > 0) {
$('.videoPlayer').get(0).pause(); $('.videoPlayer').get(0).pause();
lastTime = $('.videoPlayer').get(0).currentTime; lastTime = $('.videoPlayer').get(0).currentTime;
videoHtml = $('.videoPlayer').get(0).outerHTML; videoHtml = $('.videoPlayer').get(0).outerHTML;
} else { } else {
videoHtml = $('iframe').get(0).outerHTML; videoHtml = $('iframe').get(0).outerHTML;
} }
// Create a new browser window. // Create a new browser window.
const BrowserWindow = electron.remote.BrowserWindow; const BrowserWindow = electron.remote.BrowserWindow;
let miniPlayer = new BrowserWindow({ let miniPlayer = new BrowserWindow({
width: 1200, width: 1200,
height: 710 height: 710
}); });
// Use the miniPlayer.html template. // Use the miniPlayer.html template.
$.get('templates/miniPlayer.html', (template) => { $.get('templates/miniPlayer.html', (template) => {
mustache.parse(template); mustache.parse(template);
const rendered = mustache.render(template, { const rendered = mustache.render(template, {
videoHtml: videoHtml, videoHtml: videoHtml,
videoThumbnail: videoThumbnail, videoThumbnail: videoThumbnail,
startTime: lastTime, 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} * @return {Void}
*/ */
function changeQuality(videoHtml, qualityType, isEmbed = false) { function changeQuality(videoHtml, qualityType, isEmbed = false) {
if (videoHtml == '') { if (videoHtml == '') {
showToast('Video quality type is not available. Unable to change quality.') showToast('Video quality type is not available. Unable to change quality.')
return; return;
} }
videoHtml = videoHtml.replace(/\&quot\;/g, '"'); videoHtml = videoHtml.replace(/\&quot\;/g, '"');
console.log(videoHtml); ft.log('HTML Video: ', videoHtml);
console.log(isEmbed); ft.log('(Is the video embeded?) isEmbed: ', isEmbed);
// The YouTube API creates 2 more iFrames. This is why a boolean value is sent // The YouTube API creates 2 more iFrames. This is why a boolean value is sent
// with the function. // with the function.
const embedPlayer = document.getElementsByTagName('IFRAME')[0]; const embedPlayer = document.getElementsByTagName('IFRAME')[0];
const html5Player = document.getElementsByClassName('videoPlayer'); const html5Player = document.getElementsByClassName('videoPlayer');
console.log(embedPlayer); ft.log('Embeded Player Element: ', embedPlayer);
console.log(html5Player); ft.log('HTML5 Player Element: ', html5Player);
if (isEmbed && html5Player.length == 0) { if (isEmbed && html5Player.length == 0) {
// The embeded player is already playing. Return. // The embeded player is already playing. Return.
showToast('You are already using the embeded player.') showToast('You are already using the embeded player.')
return; return;
} else if (isEmbed) { } else if (isEmbed) {
// Switch from HTML 5 player to embeded Player // Switch from HTML 5 player to embeded Player
html5Player[0].remove(); html5Player[0].remove();
const mainHtml = $('#main').html(); const mainHtml = $('#main').html();
$('#main').html(videoHtml + mainHtml); $('#main').html(videoHtml + mainHtml);
$('#currentQuality').html(qualityType); $('#currentQuality').html(qualityType);
} else if (html5Player.length == 0) { } else if (html5Player.length == 0) {
// Switch from embeded player to HTML 5 player // Switch from embeded player to HTML 5 player
embedPlayer.remove(); embedPlayer.remove();
let videoPlayer = document.createElement('video'); let videoPlayer = document.createElement('video');
videoPlayer.className = 'videoPlayer'; videoPlayer.className = 'videoPlayer';
videoPlayer.src = videoHtml; videoPlayer.src = videoHtml;
videoPlayer.controls = true; videoPlayer.controls = true;
videoPlayer.autoplay = true; videoPlayer.autoplay = true;
$('#main').prepend(videoPlayer); $('#main').prepend(videoPlayer);
$('#currentQuality').html(qualityType); $('#currentQuality').html(qualityType);
} else { } else {
// Switch src on HTML 5 player // Switch src on HTML 5 player
const currentPlayBackTime = $('.videoPlayer').get(0).currentTime; const currentPlayBackTime = $('.videoPlayer').get(0).currentTime;
html5Player[0].src = videoHtml; html5Player[0].src = videoHtml;
html5Player[0].load(); html5Player[0].load();
$('.videoPlayer').get(0).currentTime = currentPlayBackTime; $('.videoPlayer').get(0).currentTime = currentPlayBackTime;
$('#currentQuality').html(qualityType); $('#currentQuality').html(qualityType);
} }
} }
/** /**
@ -374,8 +368,8 @@ function changeQuality(videoHtml, qualityType, isEmbed = false) {
* @return {Void} * @return {Void}
*/ */
function changeVideoSpeed(speed) { function changeVideoSpeed(speed) {
$('#currentSpeed').html(speed); $('#currentSpeed').html(speed);
$('.videoPlayer').get(0).playbackRate = speed; $('.videoPlayer').get(0).playbackRate = speed;
} }
/** /**
@ -386,16 +380,16 @@ function changeVideoSpeed(speed) {
* @return {Void} * @return {Void}
*/ */
function changeVolume(amount) { function changeVolume(amount) {
const videoPlayer = $('.videoPlayer').get(0); const videoPlayer = $('.videoPlayer').get(0);
let volume = videoPlayer.volume; let volume = videoPlayer.volume;
volume = volume + amount; volume = volume + amount;
if (volume > 1) { if (volume > 1) {
videoPlayer.volume = 1; videoPlayer.volume = 1;
} else if (volume < 0) { } else if (volume < 0) {
videoPlayer.volume = 0; videoPlayer.volume = 0;
} else { } else {
videoPlayer.volume = volume; videoPlayer.volume = volume;
} }
} }
/** /**
@ -406,8 +400,8 @@ function changeVolume(amount) {
* @return {Void} * @return {Void}
*/ */
function changeDurationBySeconds(seconds) { function changeDurationBySeconds(seconds) {
const videoPlayer = $('.videoPlayer').get(0); const videoPlayer = $('.videoPlayer').get(0);
videoPlayer.currentTime = videoPlayer.currentTime + seconds; videoPlayer.currentTime = videoPlayer.currentTime + seconds;
} }
/** /**
@ -418,6 +412,6 @@ function changeDurationBySeconds(seconds) {
* @return {Void} * @return {Void}
*/ */
function changeDurationByPercentage(percentage) { function changeDurationByPercentage(percentage) {
const videoPlayer = $('.videoPlayer').get(0); const videoPlayer = $('.videoPlayer').get(0);
videoPlayer.currentTime = videoPlayer.duration * percentage; 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 FreeTube is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or the Free Software Foundation, either version 3 of the License, or
(at your option) any later version. (at your option) any later version.
FreeTube is distributed in the hope that it will be useful, FreeTube is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details. GNU General Public License for more details.
You should have received a copy of the GNU General Public License You should have received a copy of the GNU General Public License
along with FreeTube. If not, see <http://www.gnu.org/licenses/>. 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. * Adds a video to the user's saved video database.
* *
* @param {string} videoId - The video ID of the video that will be saved. * @param {string} videoId - The video ID of the video that will be saved.
* *
* @return {Void} * @return {Void}
*/ */
function addSavedVideo(videoId){ function addSavedVideo(videoId) {
let checkIfSaved = videoIsSaved(videoId); let checkIfSaved = videoIsSaved(videoId);
checkIfSaved.then((saved) => { checkIfSaved.then((saved) => {
if (saved === false){ if (saved === false) {
let data = { let data = {
videoId: videoId, videoId: videoId,
timeSaved: new Date().getTime(), timeSaved: new Date().getTime(),
}; };
savedVidsDb.insert(data, (err, newDoc) => { savedVidsDb.insert(data, (err, newDoc) => {
showToast('Video has been saved!'); showToast('The video has been favorited!');
}); });
} } else {
else{ showToast('The video has already been favorited!')
showToast('Video already exists in saved file.') }
} });
});
} }
/** /**
* Removes a video from the user's saved video database. * Removes a video from the user's saved video database.
* *
* @param {string} videoId - The video ID of the video that will be removed. * @param {string} videoId - The video ID of the video that will be removed.
* *
* @return {Void} * @return {Void}
*/ */
function removeSavedVideo(videoId, string){ function removeSavedVideo(videoId, string) {
savedVidsDb.remove({ savedVidsDb.remove({
videoId: videoId videoId: videoId
}, {}, (err, numRemoved) => { }, {}, (err, numRemoved) => {
showToast('Video has been removed from saved list.'); showToast('Video has been removed from the favorites list.');
}); });
} }
/** /**
* Toggles the save video button styling and saved / remove a video based on the current status. * 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. * @param {string} videoId - The video ID to toggle between.
* *
* @return {Void} * @return {Void}
*/ */
function toggleSavedVideo(videoId) { function toggleSavedVideo(videoId) {
event.stopPropagation(); event.stopPropagation();
const checkIfSaved = videoIsSaved(videoId); const checkIfSaved = videoIsSaved(videoId);
const saveIcon = document.getElementById('saveIcon'); const saveIcon = document.getElementById('saveIcon');
const savedText = document.getElementById('savedText'); const savedText = document.getElementById('savedText');
checkIfSaved.then((results) => { checkIfSaved.then((results) => {
if (results === false) { if (results === false) {
savedText.innerHTML = 'SAVED'; savedText.innerHTML = 'UNFAVORITE';
saveIcon.classList.remove('far'); saveIcon.classList.remove('far');
saveIcon.classList.remove('unsaved'); saveIcon.classList.remove('unsaved');
saveIcon.classList.add('fas'); saveIcon.classList.add('fas');
saveIcon.classList.add('saved'); saveIcon.classList.add('saved');
addSavedVideo(videoId); addSavedVideo(videoId);
} else { } else {
savedText.innerHTML = 'SAVE'; savedText.innerHTML = 'FAVORITE';
saveIcon.classList.remove('fas'); saveIcon.classList.remove('fas');
saveIcon.classList.remove('saved'); saveIcon.classList.remove('saved');
saveIcon.classList.add('far'); saveIcon.classList.add('far');
saveIcon.classList.add('unsaved'); saveIcon.classList.add('unsaved');
removeSavedVideo(videoId); removeSavedVideo(videoId);
} }
}); });
} }
/** /**
* Checks if a video was saved in the user's saved video database * Checks if a video was saved in the user's saved video database
* *
* @param {string} videoId - The video ID to check * @param {string} videoId - The video ID to check
* *
* @return {promise} - A boolean value if the video was found or not. * @return {promise} - A boolean value if the video was found or not.
*/ */
function videoIsSaved(videoId) { function videoIsSaved(videoId) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
savedVidsDb.find({videoId: videoId}, (err, docs) => { savedVidsDb.find({
if (jQuery.isEmptyObject(docs)) { videoId: videoId
resolve(false); }, (err, docs) => {
} else { if (jQuery.isEmptyObject(docs)) {
resolve(true); resolve(false);
} } else {
resolve(true);
}
});
}); });
});
} }
/** /**
* Displays a list of the user's saved videos. * Displays a list of the user's saved videos.
* *
* @return {Void} * @return {Void}
*/ */
function showSavedVideos(){ function showSavedVideos() {
clearMainContainer(); clearMainContainer();
startLoadingAnimation(); startLoadingAnimation();
console.log('checking saved videos'); ft.log('Checking favorited videos');
let videoList = ''; let videoList = '';
// Check the database for the list of videos. // Check the database for the list of videos.
savedVidsDb.find({}).sort({ savedVidsDb.find({}).sort({
timeSaved: -1 timeSaved: -1
}).exec((err, docs) => { }).exec((err, docs) => {
// The YouTube API requires a max of 50 videos to be shown. Don't show more than 50. // 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. // TODO: Allow the app to show more than 50 saved videos.
if(docs.length > 49){ if (docs.length > 49) {
for (let i = 0; i < 49; i++) { for (let i = 0; i < 49; i++) {
videoList = videoList + ',' + docs[i].videoId; videoList = videoList + ',' + docs[i].videoId;
} }
} } else {
else{ docs.forEach((video) => {
docs.forEach((video) => { videoList = videoList + ',' + video.videoId;
videoList = videoList + ',' + video.videoId; });
}); }
}
// Call the YouTube API // Call the YouTube API
youtubeAPI('videos', { youtubeAPI('videos', {
part: 'snippet', part: 'snippet',
id: videoList, id: videoList,
maxResults: 50, maxResults: 50,
}, (data) => { }, (data) => {
// Render the videos to the screen // Render the videos to the screen
createVideoListContainer('Saved Videos:'); createVideoListContainer('Favorited Videos:');
let grabDuration = getDuration(data.items); let grabDuration = getDuration(data.items);
grabDuration.then((videoList) => { grabDuration.then((videoList) => {
videoList.items.forEach((video) => { videoList.items.forEach((video) => {
displayVideo(video, 'saved'); 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 FreeTube is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or the Free Software Foundation, either version 3 of the License, or
(at your option) any later version. (at your option) any later version.
FreeTube is distributed in the hope that it will be useful, FreeTube is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details. GNU General Public License for more details.
You should have received a copy of the GNU General Public License You should have received a copy of the GNU General Public License
along with FreeTube. If not, see <http://www.gnu.org/licenses/>. along with FreeTube. If not, see <http://www.gnu.org/licenses/>.
*/ */
@ -30,55 +30,55 @@ const apiKeyBank = ['AIzaSyC9E579nh_qqxg6BH4xIce3k_7a9mT4uQc', 'AIzaSyCKplYT6hZI
* @return {Void} * @return {Void}
*/ */
function showSettings() { function showSettings() {
clearMainContainer(); clearMainContainer();
startLoadingAnimation(); startLoadingAnimation();
let isChecked = ''; let isChecked = '';
let key = ''; let key = '';
/* /*
* Check the settings database for the user's current settings. This is so the * 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. * settings page has the correct toggles related when it is rendered.
*/ */
settingsDb.find({}, (err, docs) => { settingsDb.find({}, (err, docs) => {
docs.forEach((setting) => { docs.forEach((setting) => {
switch (setting['_id']) { switch (setting['_id']) {
case 'apiKey': case 'apiKey':
if (apiKeyBank.indexOf(setting['value']) == -1) { if (apiKeyBank.indexOf(setting['value']) == -1) {
key = setting['value']; key = setting['value'];
} }
break; break;
case 'theme': case 'theme':
if (currentTheme == '') { if (currentTheme == '') {
currentTheme = setting['value']; 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() { function checkDefaultSettings() {
// Grab a random API Key. // Grab a random API Key.
apiKey = apiKeyBank[Math.floor(Math.random() * apiKeyBank.length)]; apiKey = apiKeyBank[Math.floor(Math.random() * apiKeyBank.length)];
let newSetting; let newSetting;
let settingDefaults = { let settingDefaults = {
'theme': 'light', 'theme': 'light',
'apiKey': apiKey, 'apiKey': apiKey,
'useTor': false 'useTor': false
}; };
console.log(settingDefaults); ft.log('Default Settings: ', settingDefaults);
for (let key in settingDefaults){ for (let key in settingDefaults) {
settingsDb.find({_id: key}, (err, docs) => { settingsDb.find({
if (jQuery.isEmptyObject(docs)) { _id: key
newSetting = { }, (err, docs) => {
_id: key, if (jQuery.isEmptyObject(docs)) {
value: settingDefaults[key] newSetting = {
}; _id: key,
value: settingDefaults[key]
};
settingsDb.insert(newSetting); settingsDb.insert(newSetting);
if (key == 'theme'){ if (key == 'theme') {
setTheme('light'); setTheme('light');
} }
} } else {
else{ switch (docs[0]['_id']) {
switch (docs[0]['_id']) { case 'theme':
case 'theme': setTheme(docs[0]['value']);
setTheme(docs[0]['value']); break;
break; case 'apiKey':
case 'apiKey': if (apiKeyBank.indexOf(docs[0]['value']) == -1) {
if (apiKeyBank.indexOf(docs[0]['value']) == -1) { apiKey = docs[0]['value'];
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} * @return {Void}
*/ */
function updateSettings() { function updateSettings() {
let themeSwitch = document.getElementById('themeSwitch').checked; let themeSwitch = document.getElementById('themeSwitch').checked;
let torSwitch = document.getElementById('torSwitch').checked; let torSwitch = document.getElementById('torSwitch').checked;
let key = document.getElementById('api-key').value; let key = document.getElementById('api-key').value;
let theme = 'light'; 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) { if (themeSwitch === true) {
theme = 'dark'; theme = 'dark';
} }
console.log(theme); ft.log('Theme: ', theme);
// Update default 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 != '') {
settingsDb.update({ settingsDb.update({
_id: 'apiKey' _id: 'theme'
}, { }, {
value: key value: theme
}, {}); }, {}, function (err, numReplaced) {
} else { ft.log('Error while updating theme: ', err);
// To any third party devs that fork the project, please be ethical and change the API key. ft.log('Number replaced: ', numReplaced);
settingsDb.update({ });
_id: 'apiKey'
}, {
value: apiKey
}, {});
}
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} * @return {Void}
*/ */
function toggleTheme(themeValue) { function toggleTheme(themeValue) {
if (themeValue.checked === true) { if (themeValue.checked === true) {
setTheme('dark'); setTheme('dark');
currentTheme = 'dark'; currentTheme = 'dark';
} else { } else {
setTheme('light'); setTheme('light');
currentTheme = 'light'; currentTheme = 'light';
} }
} }
/** /**
@ -220,122 +221,122 @@ function toggleTheme(themeValue) {
* @return {Void} * @return {Void}
*/ */
function setTheme(option) { function setTheme(option) {
let cssFile; let cssFile;
const currentTheme = document.getElementsByTagName("link").item(1); const currentTheme = document.getElementsByTagName("link").item(1);
// Create a link element // Create a link element
const newTheme = document.createElement("link"); const newTheme = document.createElement("link");
newTheme.setAttribute("rel", "stylesheet"); newTheme.setAttribute("rel", "stylesheet");
newTheme.setAttribute("type", "text/css"); newTheme.setAttribute("type", "text/css");
// Grab the css file to be used. // Grab the css file to be used.
switch (option) { switch (option) {
case 'light': case 'light':
cssFile = './style/lightTheme.css'; cssFile = './style/lightTheme.css';
document.getElementById('menuText').src = 'icons/textBlack.png'; document.getElementById('menuText').src = 'icons/textBlack.png';
document.getElementById('menuIcon').src = 'icons/iconBlack.png'; document.getElementById('menuIcon').src = 'icons/iconBlack.png';
document.getElementById('menuButton').style.color = 'black'; document.getElementById('menuButton').style.color = 'black';
break; break;
case 'dark': case 'dark':
cssFile = './style/darkTheme.css'; cssFile = './style/darkTheme.css';
document.getElementById('menuText').src = 'icons/textColor.png'; document.getElementById('menuText').src = 'icons/textColor.png';
document.getElementById('menuIcon').src = 'icons/iconColor.png'; document.getElementById('menuIcon').src = 'icons/iconColor.png';
document.getElementById('menuButton').style.color = 'white'; document.getElementById('menuButton').style.color = 'white';
break; break;
default: default:
// Default to the light theme // Default to the light theme
cssFile = './style/lightTheme.css'; cssFile = './style/lightTheme.css';
break; 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;
} }
console.log(fileLocation); newTheme.setAttribute("href", cssFile);
let i = fileLocation[0].lastIndexOf('.');
let fileType = (i < 0) ? '' : fileLocation[0].substr(i);
console.log(fileType);
/*if (fileType !== '.db'){ // Replace the current theme with the new theme
showToast('Incorrect filetype. Import was unsuccessful.'); document.getElementsByTagName("head").item(0).replaceChild(newTheme, currentTheme);
return; }
}*/
fs.readFile(fileLocation[0], function(readErr, data){ /**
if(readErr){ * Import Subscriptions from an OPML file.
showToast('Unable to read file. File may be corrupt or have invalid permissions.'); *
throw readErr; * @param {string} subFile - The file location of the OPML file.
} *
* @return {Void}
if (data.includes("<opml")){ */
getOpml(data, function (error, json){ function importOpmlSubs(json) {
if (!error){ if (!json[0]['folder'].includes('YouTube')) {
clearFile('subscriptions', false); showToast('Invalid OPML File. Import is unsuccessful.');
importOpmlSubs(json['children'][0]['children']);
}
});
return; 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){ addSubscription(channelId, false);
if(writeErr){ });
showToast('Unable to create file. Please check your permissions and try again.'); window.setTimeout(displaySubs, 1000);
throw writeErr; 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} * @return {Void}
*/ */
function exportSubscriptions() { function exportSubscriptions() {
const appDatabaseFile = localDataStorage + '/subscriptions.db'; const appDatabaseFile = localDataStorage + '/subscriptions.db';
const date = new Date(); const date = new Date();
let dateMonth = date.getMonth() + 1; let dateMonth = date.getMonth() + 1;
if (dateMonth < 10){ if (dateMonth < 10) {
dateMonth = '0' + dateMonth; 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;
} }
fs.readFile(appDatabaseFile, function(readErr, data) {
if (readErr) { let dateDay = date.getDate();
throw readErr;
} if (dateDay < 10) {
fs.writeFile(fileLocation, data, function(writeErr) { dateDay = '0' + dateDay;
if (writeErr) { }
throw writeErr;
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. * Clear out the data in a file.
* *
* @param {string} type - The type of file to be cleared. * @param {string} type - The type of file to be cleared.
*/ */
function clearFile(type, showMessage = true){ function clearFile(type, showMessage = true) {
console.log(type); ft.log('File Type: ', type);
let dataBaseFile; let dataBaseFile;
switch (type) { switch (type) {
case 'subscriptions': case 'subscriptions':
dataBaseFile = localDataStorage + '/subscriptions.db'; dataBaseFile = localDataStorage + '/subscriptions.db';
break; break;
case 'history': case 'history':
dataBaseFile = localDataStorage + '/videohistory.db'; dataBaseFile = localDataStorage + '/videohistory.db';
break; break;
case 'saved': case 'saved':
dataBaseFile = localDataStorage + '/savedvideos.db'; dataBaseFile = localDataStorage + '/savedvideos.db';
break; break;
default: default:
showToast('Unknown file: ' + type) showToast('Unknown file: ' + type)
return return
}
// Replace data with an empty string.
fs.writeFile(dataBaseFile, '', function(err) {
if (err) {
throw err;
} }
if (showMessage){ // Replace data with an empty string.
showToast('File has been cleared. Restart FreeTube to see the changes'); 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 FreeTube is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or the Free Software Foundation, either version 3 of the License, or
(at your option) any later version. (at your option) any later version.
FreeTube is distributed in the hope that it will be useful, FreeTube is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details. GNU General Public License for more details.
You should have received a copy of the GNU General Public License You should have received a copy of the GNU General Public License
along with FreeTube. If not, see <http://www.gnu.org/licenses/>. 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} * @return {Void}
*/ */
function addSubscription(channelId, useToast = true) { function addSubscription(channelId, useToast = true) {
console.log(channelId); ft.log('Channel ID: ', channelId);
// Request YouTube API // Request YouTube API
youtubeAPI('channels', { youtubeAPI('channels', {
part: 'snippet', part: 'snippet',
id: channelId, id: channelId,
}, function(data) { }, (data) => {
const channelInfo = data['items'][0]['snippet']; const channelInfo = data['items'][0]['snippet'];
const channelName = channelInfo['title']; const channelName = channelInfo['title'];
const thumbnail = channelInfo['thumbnails']['high']['url']; const thumbnail = channelInfo['thumbnails']['high']['url'];
const channel = { const channel = {
channelId: channelId, channelId: channelId,
channelName: channelName, channelName: channelName,
channelThumbnail: thumbnail, channelThumbnail: thumbnail,
}; };
// Refresh the list of subscriptions on the side navigation bar. // Refresh the list of subscriptions on the side navigation bar.
subDb.insert(channel, (err, newDoc) => { subDb.insert(channel, (err, newDoc) => {
if (useToast) { if (useToast) {
showToast('Added ' + channelName + ' to subscriptions.'); showToast('Added ' + channelName + ' to subscriptions.');
displaySubs(); displaySubs();
} }
});
}); });
});
} }
/** /**
@ -63,13 +63,13 @@ function addSubscription(channelId, useToast = true) {
* @return {Void} * @return {Void}
*/ */
function removeSubscription(channelId) { function removeSubscription(channelId) {
subDb.remove({ subDb.remove({
channelId: channelId channelId: channelId
}, {}, (err, numRemoved) => { }, {}, (err, numRemoved) => {
// Refresh the list of subscriptions on the side navigation bar. // Refresh the list of subscriptions on the side navigation bar.
displaySubs(); displaySubs();
showToast('Removed channel from subscriptions.'); showToast('Removed channel from subscriptions.');
}); });
} }
/** /**
@ -78,92 +78,92 @@ function removeSubscription(channelId) {
* @return {Void} * @return {Void}
*/ */
function loadSubscriptions() { function loadSubscriptions() {
clearMainContainer(); clearMainContainer();
showToast('Getting Subscriptions. Please wait...'); showToast('Getting Subscriptions. Please wait...');
const loading = document.getElementById('loading'); 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. // Welcome to callback hell, we hope you enjoy your stay.
subscriptions.then((results) => { subscriptions.then((results) => {
let channelId = ''; let channelId = '';
let videoList = []; let videoList = [];
if (results.length > 0) { if (results.length > 0) {
let counter = 0; let counter = 0;
for (let i = 0; i < results.length; i++) {
channelId = results[i]['channelId'];
for (let i = 0; i < results.length; i++) { youtubeAPI('search', {
channelId = results[i]['channelId']; part: 'snippet',
channelId: channelId,
type: 'video',
maxResults: 15,
order: 'date',
}, (data) => {
youtubeAPI('search', { videoList = videoList.concat(data.items);
part: 'snippet', counter++;
channelId: channelId, if (counter === results.length) {
type: 'video', videoList.sort((a, b) => {
maxResults: 15, const date1 = Date.parse(a.snippet.publishedAt);
order: 'date', const date2 = Date.parse(b.snippet.publishedAt);
}, (data) => {
console.log(data);
videoList = videoList.concat(data.items);
counter++;
if (counter === results.length) {
videoList.sort((a, b) => {
const date1 = Date.parse(a.snippet.publishedAt);
const date2 = Date.parse(b.snippet.publishedAt);
return date2.valueOf() - date1.valueOf(); return date2.valueOf() - date1.valueOf();
});
// 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 { } else {
// User has no subscriptions. Display message. // User has no subscriptions. Display message.
const container = document.getElementById('main'); const container = document.getElementById('main');
stopLoadingAnimation(); 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>`; 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. * @return {promise} The list of subscriptions.
*/ */
function returnSubscriptions() { function returnSubscriptions() {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
subDb.find({}, (err, subs) => { subDb.find({}, (err, subs) => {
resolve(subs); resolve(subs);
});
}); });
});
} }
/** /**
@ -185,31 +185,31 @@ function returnSubscriptions() {
* @return {Void} * @return {Void}
*/ */
function displaySubs() { function displaySubs() {
const subList = document.getElementById('subscriptions'); const subList = document.getElementById('subscriptions');
subList.innerHTML = ''; subList.innerHTML = '';
// Sort alphabetically // Sort alphabetically
subDb.find({}).sort({ subDb.find({}).sort({
channelName: 1 channelName: 1
}).exec((err, subs) => { }).exec((err, subs) => {
subs.forEach((channel) => { subs.forEach((channel) => {
// Grab subscriptions.html to be used as a template. // Grab subscriptions.html to be used as a template.
const subsTemplate = require('./templates/subscriptions.html') const subsTemplate = require('./templates/subscriptions.html')
mustache.parse(subsTemplate); mustache.parse(subsTemplate);
const rendered = mustache.render(subsTemplate, { const rendered = mustache.render(subsTemplate, {
channelIcon: channel['channelThumbnail'], channelIcon: channel['channelThumbnail'],
channelName: channel['channelName'], channelName: channel['channelName'],
channelId: channel['channelId'], channelId: channel['channelId'],
}); });
// Render template to page. // Render template to page.
const subscriptionsHtml = $('#subscriptions').html(); const subscriptionsHtml = $('#subscriptions').html();
$('#subscriptions').html(subscriptionsHtml + rendered); $('#subscriptions').html(subscriptionsHtml + rendered);
});
}); });
});
// Add onclick function // Add onclick function
$('#subscriptions .fa-times').onClick = removeSubscription; $('#subscriptions .fa-times').onClick = removeSubscription;
} }
/** /**
@ -219,25 +219,25 @@ function displaySubs() {
* @return {Void} * @return {Void}
*/ */
function toggleSubscription(channelId) { function toggleSubscription(channelId) {
event.stopPropagation(); event.stopPropagation();
const checkIfSubscribed = isSubscribed(channelId); const checkIfSubscribed = isSubscribed(channelId);
const subscribeButton = document.getElementById('subscribeButton'); const subscribeButton = document.getElementById('subscribeButton');
checkIfSubscribed.then((results) => { checkIfSubscribed.then((results) => {
if (results === false) { if (results === false) {
if (subscribeButton != null) { if (subscribeButton != null) {
subscribeButton.innerHTML = 'UNSUBSCRIBE'; subscribeButton.innerHTML = 'UNSUBSCRIBE';
} }
addSubscription(channelId); addSubscription(channelId);
} else { } else {
if (subscribeButton != null) { if (subscribeButton != null) {
subscribeButton.innerHTML = 'SUBSCRIBE'; subscribeButton.innerHTML = 'SUBSCRIBE';
} }
removeSubscription(channelId); removeSubscription(channelId);
} }
}); });
} }
/** /**
@ -248,15 +248,15 @@ function toggleSubscription(channelId) {
* @return {promise} - A boolean value if the channel is currently subscribed or not. * @return {promise} - A boolean value if the channel is currently subscribed or not.
*/ */
function isSubscribed(channelId) { function isSubscribed(channelId) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
subDb.find({ subDb.find({
channelId: channelId channelId: channelId
}, (err, docs) => { }, (err, docs) => {
if (jQuery.isEmptyObject(docs)) { if (jQuery.isEmptyObject(docs)) {
resolve(false); resolve(false);
} else { } else {
resolve(true); 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 FreeTube is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or the Free Software Foundation, either version 3 of the License, or
(at your option) any later version. (at your option) any later version.
FreeTube is distributed in the hope that it will be useful, FreeTube is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details. GNU General Public License for more details.
You should have received a copy of the GNU General Public License You should have received a copy of the GNU General Public License
along with FreeTube. If not, see <http://www.gnu.org/licenses/>. along with FreeTube. If not, see <http://www.gnu.org/licenses/>.
*/ */
// import {freeTubeLog} from './events.js';
/* /*
* A file for checking / managing updates * A file for checking / managing updates
*/ */
const updateChecker = require('github-version-checker'); const updateChecker = require('github-version-checker');
const options = { const options = {
token: 'USERACCESSTOKEN', // personal access token. Github will not allow commiting the access token, which is why this is blank. token: 'USERACCESSTOKEN', // personal access token. Github will not allow commiting the access token, which is why this is blank.
repo: 'freetube', // repository name repo: 'freetube', // repository name
owner: 'freetubeapp', // repository owner owner: 'freetubeapp', // repository owner
currentVersion: require('electron').remote.app.getVersion(), // your app's current version currentVersion: require('electron').remote.app.getVersion(), // your app's current version
fetchTags: false // whether to fetch releases or tags fetchTags: false // whether to fetch releases or tags
}; };
const openReleasePage = function(){ const openReleasePage = function () {
shell.openExternal('https://github.com/FreeTubeApp/FreeTube/releases'); shell.openExternal('https://github.com/FreeTubeApp/FreeTube/releases');
} }
/*function checkForUpdates() { /*function checkForUpdates() {
updateChecker(options, function(error, update) { // callback function updateChecker(options, function(error, update) { // callback function
if (error){ if (error){
showToast('There was a problem with checking for updates'); 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 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); 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 updateChecker(options, function (error, update) { // callback function
if (error) throw error; if (error) throw error;
if (update) { // print some update info if an update is available 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); 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 FreeTube is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or the Free Software Foundation, either version 3 of the License, or
(at your option) any later version. (at your option) any later version.
FreeTube is distributed in the hope that it will be useful, FreeTube is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details. GNU General Public License for more details.
You should have received a copy of the GNU General Public License You should have received a copy of the GNU General Public License
along with FreeTube. If not, see <http://www.gnu.org/licenses/>. 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} * @return {Void}
*/ */
function search(nextPageToken = '') { function search(nextPageToken = '') {
const query = document.getElementById('search').value; const query = document.getElementById('search').value;
if (query === '') { if (query === '') {
return; 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(channels.length > 0){
displayChannels(channels);
}
let grabDuration = getDuration(videos);
grabDuration.then((videoList) => {
console.log(videoList);
videoList.items.forEach(displayVideo);
});
if (nextPageToken === '') { if (nextPageToken === '') {
createVideoListContainer('Search results:'); clearMainContainer();
stopLoadingAnimation(); 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. * @return {promise} - The list of videos with the duration included.
*/ */
function getDuration(data) { function getDuration(data) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
let videoIdList = ''; let videoIdList = '';
for (let i = 0; i < data.length; i++) { for (let i = 0; i < data.length; i++) {
if (videoIdList === '') { if (videoIdList === '') {
if (typeof(data[i]['id']) === 'string') { if (typeof (data[i]['id']) === 'string') {
videoIdList = data[i]['id']; videoIdList = data[i]['id'];
} else { } else {
videoIdList = data[i]['id']['videoId']; 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', { youtubeAPI('videos', {
part: 'snippet, contentDetails', part: 'snippet, contentDetails',
id: videoIdList id: videoIdList
}, (data) => { }, (data) => {
resolve(data); resolve(data);
});
}); });
});
} }
/** /**
@ -138,135 +138,135 @@ function getDuration(data) {
* @return {Void} * @return {Void}
*/ */
function displayVideo(video, listType = '') { function displayVideo(video, listType = '') {
const videoSnippet = video.snippet; const videoSnippet = video.snippet;
const videoDuration = parseVideoDuration(video.contentDetails.duration); const videoDuration = parseVideoDuration(video.contentDetails.duration);
//const videoDuration = '00:00'; //const videoDuration = '00:00';
// Grab the published date for the video and convert to a user readable state. // Grab the published date for the video and convert to a user readable state.
const dateString = new Date(videoSnippet.publishedAt); const dateString = new Date(videoSnippet.publishedAt);
const publishedDate = dateFormat(dateString, "mmm dS, yyyy"); const publishedDate = dateFormat(dateString, "mmm dS, yyyy");
const searchMenu = $('#videoListContainer').html(); const searchMenu = $('#videoListContainer').html();
const videoId = video.id; const videoId = video.id;
// Include a remove icon in the list if the application is displaying the history list or saved videos. // Include a remove icon in the list if the application is displaying the history list or saved videos.
const deleteHtml = () => { const deleteHtml = () => {
switch (listType) { switch (listType) {
case 'saved': case 'saved':
return `<li onclick="removeSavedVideo('${videoId}'); showSavedVideos();">Remove Saved Video</li>`; return `<li onclick="removeSavedVideo('${videoId}'); showSavedVideos();">Remove From Favorites</li>`;
case 'history': case 'history':
return `<li onclick="removeFromHistory('${videoId}'); showHistory();">Remove From History</li>`; 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) { function displayChannels(channels) {
let channelIds; let channelIds;
channels.forEach((channel) => { channels.forEach((channel) => {
if (typeof(channelIds) === 'undefined') { if (typeof (channelIds) === 'undefined') {
channelIds = channel.id.channelId; channelIds = channel.id.channelId;
} else { } else {
channelIds = channelIds + ',' + channel.id.channelId; channelIds = channelIds + ',' + channel.id.channelId;
} }
}); });
console.log(channelIds); ft.log('Channel IDs: ', channelIds);
youtubeAPI('channels', { youtubeAPI('channels', {
part: 'snippet,statistics', part: 'snippet,statistics',
id: channelIds, id: channelIds,
}, function(data) { }, function (data) {
console.log(data); ft.log('Channel Data: ', data);
let items = data['items'].reverse(); let items = data['items'].reverse();
const videoListTemplate = require('./templates/channelList.html'); const videoListTemplate = require('./templates/channelList.html');
console.log(items); ft.log('Channel Items: ', items);
items.forEach((item) => { items.forEach((item) => {
mustache.parse(videoListTemplate); mustache.parse(videoListTemplate);
let rendered = mustache.render(videoListTemplate, { let rendered = mustache.render(videoListTemplate, {
channelId: item.id, channelId: item.id,
channelThumbnail: item.snippet.thumbnails.medium.url, channelThumbnail: item.snippet.thumbnails.medium.url,
channelName: item.snippet.title, channelName: item.snippet.title,
channelDescription: item.snippet.description, channelDescription: item.snippet.description,
subscriberCount: item.statistics.subscriberCount, subscriberCount: item.statistics.subscriberCount,
videoCount: item.statistics.videoCount, videoCount: item.statistics.videoCount,
}); });
$(rendered).insertBefore('#getNextPage'); $(rendered).insertBefore('#getNextPage');
});
}); });
});
} }
function displayPlaylists(playlists) { function displayPlaylists(playlists) {
let playlistIds; let playlistIds;
playlists.forEach((playlist) => { playlists.forEach((playlist) => {
if (typeof(playlistIds) === 'undefined') { if (typeof (playlistIds) === 'undefined') {
playlistIds = playlist.id.playlistId; playlistIds = playlist.id.playlistId;
} else { } else {
playlistIds = playlistIds + ',' + playlist.id.playlistId; playlistIds = playlistIds + ',' + playlist.id.playlistId;
} }
}); });
console.log(playlistIds); ft.log('Playlist IDs: ', playlistIds);
youtubeAPI('playlists', { youtubeAPI('playlists', {
part: 'snippet,contentDetails', part: 'snippet,contentDetails',
id: playlistIds, id: playlistIds,
}, function(data) { }, function (data) {
console.log(data); ft.log('Playlist Data: ', data);
let items = data['items'].reverse(); let items = data['items'].reverse();
const playlistListTemplate = require('./templates/playlistList.html'); const playlistListTemplate = require('./templates/playlistList.html');
console.log(items); ft.log('Playlist Items: ', items);
items.forEach((item) => { items.forEach((item) => {
let dateString = new Date(item.snippet.publishedAt); let dateString = new Date(item.snippet.publishedAt);
let publishedDate = dateFormat(dateString, "mmm dS, yyyy"); let publishedDate = dateFormat(dateString, "mmm dS, yyyy");
mustache.parse(playlistListTemplate); mustache.parse(playlistListTemplate);
let rendered = mustache.render(playlistListTemplate, { let rendered = mustache.render(playlistListTemplate, {
channelId: item.snippet.channelId, channelId: item.snippet.channelId,
channelName: item.snippet.channelTitle, channelName: item.snippet.channelTitle,
playlistThumbnail: item.snippet.thumbnails.medium.url, playlistThumbnail: item.snippet.thumbnails.medium.url,
playlistTitle: item.snippet.title, playlistTitle: item.snippet.title,
playlistDescription: item.snippet.description, playlistDescription: item.snippet.description,
videoCount: item.contentDetails.itemCount, videoCount: item.contentDetails.itemCount,
publishedDate: publishedDate, publishedDate: publishedDate,
}); });
$(rendered).insertBefore('#getNextPage'); $(rendered).insertBefore('#getNextPage');
});
}); });
});
} }
/** /**
@ -277,22 +277,22 @@ function displayPlaylists(playlists) {
* @return {Void} * @return {Void}
*/ */
function addNextPage(nextPageToken) { function addNextPage(nextPageToken) {
let oldFetchButton = document.getElementById('getNextPage'); let oldFetchButton = document.getElementById('getNextPage');
// Creates the element if it doesn't exist. // Creates the element if it doesn't exist.
if (oldFetchButton === null) { if (oldFetchButton === null) {
let fetchButton = document.createElement('div'); let fetchButton = document.createElement('div');
fetchButton.id = 'getNextPage'; fetchButton.id = 'getNextPage';
fetchButton.innerHTML = '<i class="fas fa-search"></i> Fetch more results...'; 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. // Update the on click method of the button.
$(document).off('click', '#getNextPage'); $(document).off('click', '#getNextPage');
$(document).on('click', '#getNextPage', (event) => { $(document).on('click', '#getNextPage', (event) => {
search(nextPageToken); search(nextPageToken);
}); });
} }
/** /**
@ -303,33 +303,33 @@ function addNextPage(nextPageToken) {
* @param {string} videoId - The video ID of the video to get recommendations from. * @param {string} videoId - The video ID of the video to get recommendations from.
*/ */
function showVideoRecommendations(videoId) { function showVideoRecommendations(videoId) {
youtubeAPI('search', { youtubeAPI('search', {
part: 'id', part: 'id',
type: 'video', type: 'video',
relatedToVideoId: videoId, relatedToVideoId: videoId,
maxResults: 15, maxResults: 15,
}, function(data) { }, function (data) {
let grabDuration = getDuration(data.items); let grabDuration = getDuration(data.items);
grabDuration.then((videoList) => { grabDuration.then((videoList) => {
videoList.items.forEach((video) => { videoList.items.forEach((video) => {
const snippet = video.snippet; const snippet = video.snippet;
const videoDuration = parseVideoDuration(video.contentDetails.duration); const videoDuration = parseVideoDuration(video.contentDetails.duration);
const recommTemplate = require('./templates/recommendations.html') const recommTemplate = require('./templates/recommendations.html')
mustache.parse(recommTemplate); mustache.parse(recommTemplate);
const rendered = mustache.render(recommTemplate, { const rendered = mustache.render(recommTemplate, {
videoId: video.id, videoId: video.id,
videoTitle: snippet.title, videoTitle: snippet.title,
channelName: snippet.channelTitle, channelName: snippet.channelTitle,
videoThumbnail: snippet.thumbnails.medium.url, videoThumbnail: snippet.thumbnails.medium.url,
videoDuration: videoDuration, videoDuration: videoDuration,
publishedDate: dateFormat(snippet.publishedAt, "mmm dS, yyyy") 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} * @return {Void}
*/ */
function parseSearchText(url = '') { function parseSearchText(url = '') {
let input; let input;
if (url === ''){ if (url === '') {
input = document.getElementById('search').value; input = document.getElementById('search').value;
} } else {
else{ input = url;
input = url; }
}
if (input === '') { if (input === '') {
return; return;
} }
// The regex to get the video id from a YouTube link. Thanks StackOverflow. // 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 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); ft.log('Video ID: ', match);
let urlSplit = input.split('/'); let urlSplit = input.split('/');
if(match){ if (match) {
console.log('Video found'); ft.log('Video found');
playVideo(match[2]); playVideo(match[2]);
} } else if (urlSplit[3] == 'channel') {
else if (urlSplit[3] == 'channel'){ ft.log('channel found');
console.log('channel found'); goToChannel(urlSplit[4]);
goToChannel(urlSplit[4]); } else if (urlSplit[3] == 'user') {
} ft.log('user found');
else if (urlSplit[3] == 'user'){ // call api to get the ID and then call goToChannel(id)
console.log('user found'); youtubeAPI('channels', {
// call api to get the ID and then call goToChannel(id) part: 'id',
youtubeAPI('channels', { forUsername: urlSplit[4]
part: 'id', }, (data) => {
forUsername: urlSplit[4] ft.log('Channel Data: ', data.items[0].id);
}, (data) => { let channelID = data.items[0].id;
console.log(data.items[0].id); goToChannel(channelID);
let channelID = data.items[0].id; });
goToChannel(channelID); } else {
}); ft.log('Video not found');
} search();
else { }
console.log('Video not found');
search();
}
} }
@ -395,42 +391,42 @@ function parseSearchText(url = '') {
* @return {string} - The formated string. Ex: 12:34:56 * @return {string} - The formated string. Ex: 12:34:56
*/ */
function parseVideoDuration(durationString) { function parseVideoDuration(durationString) {
let match = durationString.match(/PT(\d+H)?(\d+M)?(\d+S)?/); let match = durationString.match(/PT(\d+H)?(\d+M)?(\d+S)?/);
let duration = ''; let duration = '';
match = match.slice(1).map(function(x) { match = match.slice(1).map(function (x) {
if (x != null) { if (x != null) {
return x.replace(/\D/, ''); 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); if (hours != 0 && minutes < 10) {
let minutes = (parseInt(match[1]) || 0); duration = duration + '0' + minutes + ':';
let seconds = (parseInt(match[2]) || 0); } else if (hours != 0 && minutes > 10) {
duration = duration + minutes + ':';
} else if (hours != 0 && minutes == 0) {
duration = duration + '00:';
}
if (hours != 0) { if (seconds == 0) {
duration = hours + ':'; duration = duration + '00';
} else { } else if (seconds < 10) {
duration = minutes + ':'; duration = duration + '0' + seconds;
} } else {
duration = duration + seconds;
}
if (hours != 0 && minutes < 10) { return duration;
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;
} }
/** /**
@ -439,38 +435,38 @@ function parseVideoDuration(durationString) {
* @return {Void} * @return {Void}
*/ */
function showMostPopular() { function showMostPopular() {
clearMainContainer(); clearMainContainer();
startLoadingAnimation(); startLoadingAnimation();
// Get the date of 2 days ago. // Get the date of 2 days ago.
var d = new Date(); var d = new Date();
d.setDate(d.getDate() - 2); d.setDate(d.getDate() - 2);
// Grab all videos published 2 days ago and after and order them by view count. // 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 // 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. // Applications grab these. Videos in the 'Trending' tab on YouTube will be different.
// And there is no way to grab those videos. // And there is no way to grab those videos.
youtubeAPI('search', { youtubeAPI('search', {
part: 'id', part: 'id',
order: 'viewCount', order: 'viewCount',
type: 'video', type: 'video',
publishedAfter: d.toISOString(), publishedAfter: d.toISOString(),
maxResults: 50, maxResults: 50,
}, function(data) { }, function (data) {
createVideoListContainer('Most Popular:'); createVideoListContainer('Most Popular:');
console.log(data); ft.log('Most Popular: ', data);
let grabDuration = getDuration(data.items); let grabDuration = getDuration(data.items);
grabDuration.then((videoList) => { grabDuration.then((videoList) => {
console.log(videoList); ft.log('Video List: ', videoList);
videoList.items.forEach(displayVideo); 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} website - The website to watch the video on.
* @param {string} videoId - The video ID of the video to add to the URL * @param {string} videoId - The video ID of the video to add to the URL
@ -478,10 +474,21 @@ function showMostPopular() {
* @return {Void} * @return {Void}
*/ */
function copyLink(website, videoId) { function copyLink(website, videoId) {
// Create the URL and copy to the clipboard. // Create the URL and copy to the clipboard.
const url = 'https://' + website + '.com/watch?v=' + videoId; if (website == "youtube") {
clipboard.writeText(url); const url = 'https://' + website + '.com/watch?v=' + videoId;
showToast('URL has been copied to the clipboard'); 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 * @return {promise} - The HTML of the embeded player
*/ */
function getChannelAndPlayer(videoId) { function getChannelAndPlayer(videoId) {
console.log(videoId); ft.log('Video ID: ', videoId);
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
youtubeAPI('videos', { youtubeAPI('videos', {
part: 'snippet,player', part: 'snippet,player',
id: videoId, id: videoId,
}, function(data) { }, function (data) {
let embedHtml = data.items[0].player.embedHtml; let embedHtml = data.items[0].player.embedHtml;
embedHtml = embedHtml.replace('src="', 'src="https:'); embedHtml = embedHtml.replace('src="', 'src="https:');
embedHtml = embedHtml.replace('width="480px"', ''); embedHtml = embedHtml.replace('width="480px"', '');
embedHtml = embedHtml.replace('height="270px"', ''); embedHtml = embedHtml.replace('height="270px"', '');
embedHtml = embedHtml.replace(/\"/g, '&quot;'); embedHtml = embedHtml.replace(/\"/g, '&quot;');
resolve([embedHtml, data.items[0].snippet.channelId]); resolve([embedHtml, data.items[0].snippet.channelId]);
});
}); });
});
} }
/** /**
@ -517,68 +524,68 @@ function getChannelAndPlayer(videoId) {
* @param {string} video720p - The URL to the 720p video. * @param {string} video720p - The URL to the 720p video.
*/ */
function checkVideoUrls(video480p, video720p) { function checkVideoUrls(video480p, video720p) {
const currentQuality = $('#currentQuality').html(); const currentQuality = $('#currentQuality').html();
let buttonEmbed = document.getElementById('qualityEmbed'); let buttonEmbed = document.getElementById('qualityEmbed');
let valid480 = false; let valid480 = false;
if (typeof(video480p) !== 'undefined') { if (typeof (video480p) !== 'undefined') {
let get480pUrl = fetch(video480p); let get480pUrl = fetch(video480p);
get480pUrl.then((status) => { get480pUrl.then((status) => {
switch (status.status) { switch (status.status) {
case 404: case 404:
showToast('Found valid URL for 480p, but returned a 404. Video type might be available in the future.'); showToast('Found valid URL for 480p, but returned a 404. Video type might be available in the future.');
$(document).off('click', '#quality480p'); $(document).off('click', '#quality480p');
$(document).on('click', '#quality480p', (event) => { $(document).on('click', '#quality480p', (event) => {
changeQuality(''); changeQuality('');
}); });
buttonEmbed.click(); buttonEmbed.click();
return; return;
break; break;
case 403: case 403:
showToast('This video is unavailable in your country.'); showToast('This video is unavailable in your country.');
$(document).off('click', '#quality480p'); $(document).off('click', '#quality480p');
$(document).on('click', '#quality480p', (event) => { $(document).on('click', '#quality480p', (event) => {
changeQuality(''); changeQuality('');
}); });
return; return;
break; break;
default: default:
console.log('480p is valid'); ft.log('480p is valid');
if (currentQuality === '720p' && typeof(video720p) === 'undefined') { if (currentQuality === '720p' && typeof (video720p) === 'undefined') {
changeQuality(video480p); changeQuality(video480p);
} }
break; break;
} }
}); });
} }
if (typeof(video720p) !== 'undefined') { if (typeof (video720p) !== 'undefined') {
let get720pUrl = fetch(video720p); let get720pUrl = fetch(video720p);
get720pUrl.then((status) => { get720pUrl.then((status) => {
switch (status.status) { switch (status.status) {
case 404: case 404:
showToast('Found valid URL for 720p, but returned a 404. Video type might be available in the future.'); showToast('Found valid URL for 720p, but returned a 404. Video type might be available in the future.');
$(document).off('click', '#quality720p'); $(document).off('click', '#quality720p');
$(document).on('click', '#quality720p', (event) => { $(document).on('click', '#quality720p', (event) => {
changeQuality(''); changeQuality('');
}); });
if (typeof(valid480) !== 'undefined') { if (typeof (valid480) !== 'undefined') {
changeQuality(video480p, '480p'); changeQuality(video480p, '480p');
} }
break; break;
case 403: case 403:
showToast('This video is unavailable in your country.'); showToast('This video is unavailable in your country.');
$(document).off('click', '#quality720p'); $(document).off('click', '#quality720p');
$(document).on('click', '#quality720p', (event) => { $(document).on('click', '#quality720p', (event) => {
changeQuality(''); changeQuality('');
}); });
return; return;
break; break;
default: default:
console.log('720p is valid'); ft.log('720p is valid');
break; 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. * List a YouTube HTTP API resource.
* *
@ -9,33 +28,33 @@
*/ */
function youtubeAPI(resource, params, success) { function youtubeAPI(resource, params, success) {
params.key = apiKey; params.key = apiKey;
if (useTor) { if (useTor) {
tor.request('https://www.googleapis.com/youtube/v3/' + resource + '?' + $.param(params), function(err, res, body) { tor.request('https://www.googleapis.com/youtube/v3/' + resource + '?' + $.param(params), function (err, res, body) {
if (!err && res.statusCode == 200) { if (!err && res.statusCode == 200) {
success(JSON.parse(body)); success(JSON.parse(body));
} else { } else {
showToast('Unable to connect to the Tor network. Check the help page if you\'re having trouble setting up your node.'); showToast('Unable to connect to the Tor network. Check the help page if you\'resss having trouble setting up your node.');
console.log(err); ft.log('Tor Error: ', err);
console.log(res); ft.log('Tor Error (Result): ', res);
console.log(body); ft.log('Tor Error (body): ', body);
stopLoadingAnimation(); stopLoadingAnimation();
} }
}); });
} else { } else {
$.getJSON( $.getJSON(
'https://www.googleapis.com/youtube/v3/' + resource, 'https://www.googleapis.com/youtube/v3/' + resource,
params, params,
success success
).fail((xhr, textStatus, error) => { ).fail((xhr, textStatus, error) => {
showToast('There was an error calling the YouTube API.'); showToast('There was an error calling the YouTube API.');
console.log(error); ft.log('YT API Error: ', error);
console.log(xhr); ft.log('YT API Error - XHR: ', xhr);
console.log(textStatus); ft.log('YT API Error - Text Status: ', textStatus);
stopLoadingAnimation(); stopLoadingAnimation();
}); });
} }
} }
@ -50,19 +69,19 @@ function youtubeAPI(resource, params, success) {
*/ */
function youtubedlGetInfo(videoId, callback) { function youtubedlGetInfo(videoId, callback) {
let url = 'https://youtube.com/watch?v=' + videoId; let url = 'https://youtube.com/watch?v=' + videoId;
let options = ['--all-subs', '--write-subs']; let options = ['--all-subs', '--write-subs'];
ytdl.getInfo(url, options, function(err, info) { ytdl.getInfo(url, options, function (err, info) {
if (err) { if (err) {
showToast(err.message); showToast(err.message);
stopLoadingAnimation(); stopLoadingAnimation();
console.log(err); ft.log('Error getting video download info: ', err.message);
console.log(info); ft.log('Error getting video download info: ', info);
return; return;
} }
console.log('Success'); ft.log('Success');
callback(info); callback(info);
}); });
} }

View File

@ -1,45 +1,62 @@
.channelViewBanner{ /*
width: 100%; This file is part of FreeTube.
max-height: 200px;
margin-bottom: 30px; FreeTube is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
FreeTube is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with FreeTube. If not, see <http://www.gnu.org/licenses/>.
*/
.channelViewBanner {
width: 100%;
max-height: 200px;
margin-bottom: 30px;
} }
.channelViewImage{ .channelViewImage {
float: left; float: left;
width: 100px; width: 100px;
border-radius: 200px 200px 200px 200px; border-radius: 200px 200px 200px 200px;
-webkit-border-radius: 200px 200px 200px 200px; -webkit-border-radius: 200px 200px 200px 200px;
} }
.channelViewTitle{ .channelViewTitle {
height: 100px; height: 100px;
margin-left: 100px; margin-left: 100px;
} }
.channelViewName{ .channelViewName {
font-weight: bold; font-weight: bold;
font-size: 25px; font-size: 25px;
margin-left: 20px; margin-left: 20px;
position: relative; position: relative;
top: 20px; top: 20px;
} }
.channelViewSubs{ .channelViewSubs {
margin-left: 20px; margin-left: 20px;
margin-top: 20px; margin-top: 20px;
position: relative; position: relative;
top: 20px; top: 20px;
} }
.channelSubButton{ .channelSubButton {
float: right; float: right;
width: 125px; width: 125px;
height: 50px; height: 50px;
line-height: 50px; line-height: 50px;
text-align: center; text-align: center;
cursor: pointer; cursor: pointer;
} }
.channelViewDescription{ .channelViewDescription {
white-space: pre-line; 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 { ::-webkit-scrollbar {
height: 12px; height: 12px;
width: 12px; width: 12px;
@ -14,54 +31,218 @@
background: #262626; background: #262626;
} }
body{background-color: #212121;} body {
input[type=text] {color: #EEEEEE;} background-color: #212121;
.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;} input[type=text] {
#main hr{border-bottom: 1px solid #212121;} color: #EEEEEE;
#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;} .sk-cube-grid .sk-cube {
#sideNav li:hover{background-color: #212121;} background-color: #f44336;
#channelIcon{border: 0px solid white;} }
#channelName{color: #E0E0E0;}
#publishDate{color: #E0E0E0;} .searchBar ::-webkit-input-placeholder {
#description{color: #E0E0E0;} color: #E0E0E0;
#showComments{background-color: #757575; color: #E0E0E0;} }
#recommendations{color: #EEEEEE;}
#videoListContainer{color: #EEEEEE;} .topNav {
#toast{background-color: #616161; color: white;} background-color: #262626;
#confirmFunction{background-color: #616161; color: white;} -webkit-box-shadow: 0px -4px 20px 0px rgba(0, 0, 0, 0.75);
#getNextPage{background-color: #616161} }
#comments{background-color: #424242;}
.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 { ::-webkit-scrollbar {
height: 12px; height: 12px;
width: 12px; width: 12px;
@ -14,43 +31,167 @@
background: white; background: white;
} }
body{background-color: #e0e0e0;} body {
.sk-cube-grid .sk-cube {background-color: #f44336;} background-color: #e0e0e0;
.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;} .sk-cube-grid .sk-cube {
#sideNav{background-color: white; -webkit-box-shadow: 4px -2px 51px -6px rgba(0,0,0,0.75);} background-color: #f44336;
#sideNav hr{background-color: #f44336;} }
#sideNav li:hover{background-color: #e0e0e0;}
#channelIcon{border: 0px solid #000000;} .searchBar ::-webkit-input-placeholder {
#showComments{background-color: #eeeeee;} color: #ddd;
#toast{background-color: #212121; color: #f5f5f5;;} }
#confirmFunction{background-color: #f5f5f5;}
#getNextPage{background-color: #f5f5f5;} .topNav {
#comments{background: #eee;} 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. * Thanks to @tobiasahlin for the loading animation.
* Find it here: http://tobiasahlin.com/spinkit/ * Find it here: http://tobiasahlin.com/spinkit/
* Twitter: https://twitter.com/tobiasahlin * Twitter: https://twitter.com/tobiasahlin
*/ */
.spinner {
width: 40px;
height: 40px;
position: relative; .spinner {
margin: 100px auto; width: 40px;
height: 40px;
position: relative;
margin: 100px auto;
} }
.double-bounce1, .double-bounce2 { .double-bounce1,
width: 100%; .double-bounce2 {
height: 100%; width: 100%;
border-radius: 50%; height: 100%;
opacity: 0.6; border-radius: 50%;
position: absolute; opacity: 0.6;
top: 0; position: absolute;
left: 0; top: 0;
left: 0;
-webkit-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; animation: sk-bounce 2.0s infinite ease-in-out;
} }
.double-bounce2 { .double-bounce2 {
-webkit-animation-delay: -1.0s; -webkit-animation-delay: -1.0s;
animation-delay: -1.0s; animation-delay: -1.0s;
} }
@-webkit-keyframes sk-bounce { @-webkit-keyframes sk-bounce {
0%, 100% { -webkit-transform: scale(0.0) } 0%,
50% { -webkit-transform: scale(1.0) } 100% {
-webkit-transform: scale(0.0)
}
50% {
-webkit-transform: scale(1.0)
}
} }
@keyframes sk-bounce { @keyframes sk-bounce {
0%, 100% { 0%,
transform: scale(0.0); 100% {
-webkit-transform: scale(0.0); transform: scale(0.0);
} 50% { -webkit-transform: scale(0.0);
transform: scale(1.0); }
-webkit-transform: scale(1.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-face {
font-family: Roboto; font-family: Roboto;
src: url(Roboto-Regular.ttf); src: url(Roboto-Regular.ttf);
} }
body { body {
font-family: 'Roboto', sans-serif; font-family: 'Roboto', sans-serif;
} }
input { input {
box-sizing: border-box; box-sizing: border-box;
-webkit-box-sizing: border-box; -webkit-box-sizing: border-box;
-moz-box-sizing: border-box; -moz-box-sizing: border-box;
outline: none; outline: none;
width: 100%; width: 100%;
padding: 7px; padding: 7px;
border: none; border: none;
background: transparent; background: transparent;
margin-bottom: 10px; margin-bottom: 10px;
font: 16px; font: 16px;
height: 45px; height: 45px;
} }
a { a {
color: #2196F3; color: #2196F3;
} }
.center { .center {
text-align: center; text-align: center;
margin: 0 auto; margin: 0 auto;
} }
.topNav { .topNav {
width: 100%; width: 100%;
height: 60px; height: 60px;
line-height: 60px; line-height: 60px;
position: fixed; position: fixed;
top: 0; top: 0;
left: 0; left: 0;
z-index: 1; z-index: 1;
} }
.topNav span { .topNav span {
display: inline-block; display: inline-block;
vertical-align: middle; vertical-align: middle;
line-height: normal; line-height: normal;
} }
#menuButton { #menuButton {
cursor: pointer; cursor: pointer;
font-weight: bold; font-weight: bold;
font-size: 20px; font-size: 20px;
margin-left: 20px; margin-left: 20px;
position: relative; position: relative;
top: 3px; top: 3px;
} }
#menuIcon { #menuIcon {
width: 25px; width: 25px;
position: relative; position: relative;
top: 15px; top: 15px;
right: 120px; right: 120px;
float: right; float: right;
} }
#menuText { #menuText {
width: 100px; width: 100px;
position: relative; position: relative;
top: 5px; top: 5px;
left: 10px; left: 10px;
float: right; float: right;
} }
.searchBar { .searchBar {
position: absolute; position: absolute;
right: 135px; right: 135px;
top: 0; top: 0;
width: 50%; width: 50%;
} }
.searchBar input { .searchBar input {
width: 90%; width: 90%;
} }
.searchButton { .searchButton {
text-decoration: none; text-decoration: none;
} }
#sideNav { #sideNav {
height: 100vh; height: 100vh;
width: 250px; width: 250px;
overflow-y: auto; overflow-y: auto;
position: fixed; position: fixed;
left: 0; left: 0;
top: 0px; top: 0px;
-webkit-box-shadow: 4px -2px 51px -6px rgba(0, 0, 0, 0.75); -webkit-box-shadow: 4px -2px 51px -6px rgba(0, 0, 0, 0.75);
} }
#sideNav hr { #sideNav hr {
width: 95%; width: 95%;
height: 1px; height: 1px;
border: 0; border: 0;
} }
#sideNav ul { #sideNav ul {
list-style-type: none; list-style-type: none;
} }
#sideNav li { #sideNav li {
padding: 20px; padding: 20px;
width: 205px; width: 205px;
position: relative; position: relative;
right: 50px; right: 50px;
cursor: pointer; cursor: pointer;
-webkit-transition: background 0.2s ease-out; -webkit-transition: background 0.2s ease-out;
-moz-transition: background 0.2s ease-out; -moz-transition: background 0.2s ease-out;
-o-transition: background 0.2s ease-out; -o-transition: background 0.2s ease-out;
transition: background 0.2s ease-out; transition: background 0.2s ease-out;
} }
#sideNav li:hover { #sideNav li:hover {
-moz-transition: background 0.2s ease-in; -moz-transition: background 0.2s ease-in;
-o-transition: background 0.2s ease-in; -o-transition: background 0.2s ease-in;
transition: background 0.2s ease-in; transition: background 0.2s ease-in;
} }
#sideNavDisabled { #sideNavDisabled {
height: 100vh; height: 100vh;
width: 250px; width: 250px;
position: fixed; position: fixed;
left: 0; left: 0;
top: 0px; top: 0px;
z-index: 1; z-index: 1;
margin-top: 85px; margin-top: 85px;
display: none; display: none;
} }
.sideNavContainer { .sideNavContainer {
margin-top: 85px; margin-top: 85px;
} }
#subscriptions img { #subscriptions img {
float: left; float: left;
width: 40px; width: 40px;
border-radius: 200px 200px 200px 200px; border-radius: 200px 200px 200px 200px;
-webkit-border-radius: 200px 200px 200px 200px; -webkit-border-radius: 200px 200px 200px 200px;
position: relative; position: relative;
top: -12px; top: -12px;
margin-right: 5px; margin-right: 5px;
} }
#subscriptions .fa-times { #subscriptions .fa-times {
float: right; float: right;
margin-right: 5px; margin-right: 5px;
} }
#subscriptions li { #subscriptions li {
font-size: 11px; font-size: 11px;
} }
.message { .message {
text-align: center; text-align: center;
} }
.videoDelete { .videoDelete {
float: right; float: right;
cursor: pointer; cursor: pointer;
} }
#loading { #loading {
width: 75%; width: 75%;
height: 20%; height: 20%;
margin: auto; margin: auto;
position: absolute; position: absolute;
top: 0; top: 0;
left: 5; left: 5;
bottom: 0; bottom: 0;
right: 0; right: 0;
display: none; display: none;
} }
.settingsInput { .settingsInput {
width: 50%; width: 50%;
border-bottom: 1px solid #616161; border-bottom: 1px solid #616161;
} }
.settingsButton { .settingsButton {
padding: 10px; padding: 10px;
display: inline-block; display: inline-block;
cursor: pointer; cursor: pointer;
margin: 5px; margin: 5px;
-webkit-box-shadow: 4px 4px 10px 0px rgba(0, 0, 0, 0.75); -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); -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); box-shadow: 4px 4px 10px 0px rgba(0, 0, 0, 0.75);
} }
.settingsSubmit { .settingsSubmit {
padding: 15px; padding: 15px;
color: #212121; color: #212121;
background-color: #f44336; background-color: #f44336;
cursor: pointer; cursor: pointer;
width: 150px; width: 150px;
-webkit-box-shadow: 4px 4px 10px 0px rgba(0, 0, 0, 0.75); -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); -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); box-shadow: 4px 4px 10px 0px rgba(0, 0, 0, 0.75);
} }
.help { .help {
margin: 30px; margin: 30px;
} }
.live { .live {
font-size: 12px; font-size: 12px;
height: 20px; height: 20px;
text-align: center; text-align: center;
width: 57px; width: 57px;
position: relative; position: relative;
left: 284px; left: 284px;
color: #f44336; color: #f44336;
bottom: 15px; bottom: 15px;
line-height: 10px; line-height: 10px;
} }
#toast{ #toast {
min-width: 400px; min-width: 400px;
height: 50px; height: 50px;
position: fixed; position: fixed;
bottom: 20px; bottom: 20px;
right: 20px; right: 20px;
padding: 10px; padding: 10px;
text-align: center; text-align: center;
font-size: 17px; font-size: 17px;
line-height: 50px; line-height: 50px;
opacity: 0; opacity: 0;
z-index: 1; z-index: 1;
visibility: hidden; visibility: hidden;
-webkit-transition: opacity 0.5s ease-in-out; -webkit-transition: opacity 0.5s ease-in-out;
-moz-transition: opacity 0.5s ease-in-out; -moz-transition: opacity 0.5s ease-in-out;
-ms-transition: opacity 0.5s ease-in-out; -ms-transition: opacity 0.5s ease-in-out;
-o-transition: opacity 0.5s ease-in-out; -o-transition: opacity 0.5s ease-in-out;
} }
.closeToast{ .closeToast {
font-size: 15px; font-size: 15px;
position: absolute; position: absolute;
top: 5px; top: 5px;
right: 10px; right: 10px;
cursor: pointer; cursor: pointer;
} }
#confirmFunction{ #confirmFunction {
position: fixed; position: fixed;
top: 50%; top: 50%;
left: 30%; left: 30%;
width: 800px; width: 800px;
height: 65px; height: 65px;
font-weight: bold; font-weight: bold;
line-height: 65px; line-height: 65px;
visibility: hidden; visibility: hidden;
z-index: 1; z-index: 1;
-webkit-box-shadow: 5px 5px 15px -5px rgba(0,0,0,0.75); -webkit-box-shadow: 5px 5px 15px -5px rgba(0, 0, 0, 0.75);
-moz-box-shadow: 5px 5px 15px -5px rgba(0,0,0,0.75); -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); box-shadow: 5px 5px 15px -5px rgba(0, 0, 0, 0.75);
} }
#confirmMessage{ #confirmMessage {
margin-left: 15px; margin-left: 15px;
} }
.confirmButton{ .confirmButton {
position: absolute; position: absolute;
top: 0px; top: 0px;
cursor: pointer; cursor: pointer;
} }
#confirmYes{ #confirmYes {
right: 90px; right: 90px;
color: #2196f3; color: #2196f3;
} }
#confirmNo{ #confirmNo {
right: 20px; right: 20px;
} }
#getNextPage{ #getNextPage {
width: 100%; width: 100%;
height: 45px; height: 45px;
line-height: 45px; line-height: 45px;
text-align: center; text-align: center;
cursor: pointer; cursor: pointer;
} }
#comments{ #comments {
display:none; display: none;
padding:1em; padding: 1em;
margin-bottom: 1em; margin-bottom: 1em;
} }
.saved{color: #FFEB3B;} .saved {
color: #FFEB3B;
}
/* Thanks to Guus Lieben for the Material Design Switch */ /* Thanks to Guus Lieben for the Material Design Switch */
.switch-input { .switch-input {
display: none; display: none;
} }
.switch-label { .switch-label {
position: relative; position: relative;
display: inline-block; display: inline-block;
min-width: 112px; min-width: 112px;
cursor: pointer; cursor: pointer;
font-weight: 500; font-weight: 500;
text-align: left; text-align: left;
margin: 16px; margin: 16px;
padding: 16px 0 16px 44px; padding: 16px 0 16px 44px;
} }
.switch-label:before, .switch-label:after { .switch-label:before,
content: ""; .switch-label:after {
position: absolute; content: "";
margin: 0; position: absolute;
outline: 0; margin: 0;
top: 50%; outline: 0;
-ms-transform: translate(0, -50%); top: 50%;
-webkit-transform: translate(0, -50%); -ms-transform: translate(0, -50%);
transform: translate(0, -50%); -webkit-transform: translate(0, -50%);
-webkit-transition: all 0.3s ease; transform: translate(0, -50%);
transition: all 0.3s ease; -webkit-transition: all 0.3s ease;
transition: all 0.3s ease;
} }
.switch-label:before { .switch-label:before {
left: 1px; left: 1px;
width: 34px; width: 34px;
height: 14px; height: 14px;
background-color: #9E9E9E; background-color: #9E9E9E;
border-radius: 8px; border-radius: 8px;
} }
.switch-label:after { .switch-label:after {
left: 0; left: 0;
width: 20px; width: 20px;
height: 20px; height: 20px;
background-color: #FAFAFA; background-color: #FAFAFA;
border-radius: 50%; 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); 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 { .switch-label .toggle--on {
display: none; display: none;
} }
.switch-label .toggle--off { .switch-label .toggle--off {
display: inline-block; display: inline-block;
} }
.switch-input:checked+.switch-label:before { .switch-input:checked+.switch-label:before {
background-color: #90CAF9; background-color: #90CAF9;
} }
.switch-input:checked+.switch-label:after { .switch-input:checked+.switch-label:after {
background-color: #2196F3; background-color: #2196F3;
-ms-transform: translate(80%, -50%); -ms-transform: translate(80%, -50%);
-webkit-transform: translate(80%, -50%); -webkit-transform: translate(80%, -50%);
transform: translate(80%, -50%); transform: translate(80%, -50%);
} }
.switch-input:checked+.switch-label .toggle--on { .switch-input:checked+.switch-label .toggle--on {
display: inline-block; display: inline-block;
} }
.switch-input:checked+.switch-label .toggle--off { .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 { iframe {
width: 100%; width: 100%;
height: 41.25vw; height: 41.25vw;
} }
#main { #main {
margin-top: 80px; margin-top: 80px;
margin-left: 250px; margin-left: 250px;
} }
.video { .video {
width: 95%; width: 95%;
max-width: 1000px; max-width: 1000px;
height: 145px; height: auto;
padding: 15px; padding: 15px;
overflow: hidden; overflow: hidden;
} }
.videoThumbnail { .videoThumbnail {
width: 275px; width: 275px;
height: 160px; height: 160px;
float: left; float: left;
cursor: pointer; cursor: pointer;
} }
.videoThumbnail img{ .videoThumbnail img {
width: 100%; width: 100%;
} }
.channelThumbnail { .channelThumbnail {
width: 140px; width: 140px;
height: 140px; height: 140px;
float: left; float: left;
cursor: pointer; cursor: pointer;
position: relative; position: relative;
left: 60px; left: 60px;
} }
.channelThumbnail img{ .channelThumbnail img {
width: 100%; width: 100%;
border-radius: 200px 200px 200px 200px; border-radius: 200px 200px 200px 200px;
-webkit-border-radius: 200px 200px 200px 200px; -webkit-border-radius: 200px 200px 200px 200px;
} }
.videoOptions { .videoOptions {
float: right; float: right;
width: 50px; width: 50px;
} }
.videoOptions i { .videoOptions i {
text-align: right; text-align: right;
width: 50px; width: 50px;
cursor: pointer; cursor: pointer;
} }
.videoOptions ul { .videoOptions ul {
width: 90px;
font-size: 12px; width: 90px;
position: relative; font-size: 12px;
right: 90px; position: relative;
bottom: 10px; right: 90px;
list-style-type: none; bottom: 10px;
display: none; list-style-type: none;
-webkit-box-shadow: 4px 4px 33px -7px rgba(0,0,0,0.75); display: none;
cursor: pointer; -webkit-box-shadow: 4px 4px 33px -7px rgba(0, 0, 0, 0.75);
cursor: pointer;
z-index: 10;
} }
.videoOptions li { .videoOptions li {
width: 110px; width: 110px;
position: relative; position: relative;
right: 40px; right: 40px;
padding: 10px; padding: 10px;
-webkit-transition: background 0.2s ease-out; -webkit-transition: background 0.2s ease-out;
-moz-transition: background 0.2s ease-out; -moz-transition: background 0.2s ease-out;
-o-transition: background 0.2s ease-out; -o-transition: background 0.2s ease-out;
transition: background 0.2s ease-out; transition: background 0.2s ease-out;
} }
.videoOptions li:hover { .videoOptions li:hover {
-moz-transition: background 0.2s ease-in; -moz-transition: background 0.2s ease-in;
-o-transition: background 0.2s ease-in; -o-transition: background 0.2s ease-in;
transition: background 0.2s ease-in; transition: background 0.2s ease-in;
} }
.videoOptions a { .videoOptions a {
text-decoration: none; text-decoration: none;
} }
.videoTitle { .videoTitle {
font-weight: bold; font-weight: bold;
margin-left: 285px; margin-left: 285px;
margin-top: 5px; margin-top: 5px;
cursor: pointer; cursor: pointer;
} }
.channelName { .channelName {
margin-left: 285px; margin-left: 285px;
font-size: 14px; font-size: 14px;
cursor: pointer; cursor: pointer;
} }
.videoDescription { .videoDescription {
margin-left: 285px; margin-left: 285px;
font-size: 13px; font-size: 13px;
cursor: pointer; cursor: pointer;
height: 45px; height: 45px;
overflow: hidden; overflow: hidden;
} }
.videoDuration { .videoDuration {
display: inline-block; float: right;
float: right; position: relative;
position: relative; bottom: 36px;
bottom: 36px; color: white;
color: white; background-color: black;
background-color: black; opacity: 0.7;
opacity: 0.7; padding: 2px;
padding: 2px; font-size: 13px;
font-size: 13px; text-align: right;
text-align: right;
} }
.videoPlayer { .videoPlayer {
width: 100%; width: 100%;
} }
.statistics { .statistics {
padding: 20px; padding: 20px;
padding-bottom: 45px; padding-bottom: 45px;
} }
.title { .title {
font-weight: bold; font-weight: bold;
font-size: 25px; font-size: 25px;
} }
.views { .views {
margin-top: -10px; margin-top: -10px;
float: left; float: left;
} }
.details { .details {
padding: 15px; padding: 15px;
} }
.likeContainer { .likeContainer {
width: 300px; width: 300px;
float: right; float: right;
} }
.likes { .likes {
float: left; float: left;
font-size: 12px; font-size: 12px;
} }
.dislikes { .dislikes {
float: right; float: right;
font-size: 12px; font-size: 12px;
} }
.dislikeBar { .dislikeBar {
background-color: #9E9E9E; background-color: #9E9E9E;
width: 300px; width: 300px;
height: 5px; height: 5px;
margin-bottom: 5px; margin-bottom: 5px;
border-radius: 200px 200px 200px 200px; border-radius: 200px 200px 200px 200px;
-webkit-border-radius: 200px 200px 200px 200px; -webkit-border-radius: 200px 200px 200px 200px;
} }
.likeBar { .likeBar {
height: 5px; height: 5px;
background-color: #2196F3; background-color: #2196F3;
border-radius: 200px 200px 200px 200px; border-radius: 200px 200px 200px 200px;
-webkit-border-radius: 200px 200px 200px 200px; -webkit-border-radius: 200px 200px 200px 200px;
} }
#channelIcon { #channelIcon {
float: left; float: left;
width: 80px; width: 80px;
border-radius: 200px 200px 200px 200px; border-radius: 200px 200px 200px 200px;
-webkit-border-radius: 200px 200px 200px 200px; -webkit-border-radius: 200px 200px 200px 200px;
cursor: pointer; cursor: pointer;
} }
#comments .line { #comments .line {
clear: both; clear: both;
height: 1px; height: 1px;
background: #d8d8d8; background: #d8d8d8;
} }
.userIcon { .userIcon {
float: left; float: left;
width: 48px; width: 48px;
border-radius: 200px 200px 200px 200px; border-radius: 200px 200px 200px 200px;
-webkit-border-radius: 200px 200px 200px 200px; -webkit-border-radius: 200px 200px 200px 200px;
cursor: pointer; cursor: pointer;
} }
#channelName { #channelName {
font-weight: bold; font-weight: bold;
margin-left: 95px; margin-left: 95px;
font-size: 16px; font-size: 16px;
cursor: pointer; cursor: pointer;
} }
#publishDate { #publishDate {
margin-left: 95px; margin-left: 95px;
font-size: 13px; font-size: 13px;
margin-top: -10px; margin-top: -10px;
} }
#description { #description {
white-space: pre-line; white-space: pre-line;
max-height: 200px; max-height: 200px;
overflow-y: auto; overflow-y: auto;
} }
.playerSubButton { .playerSubButton {
float: right; float: right;
width: 125px; width: 125px;
height: 50px; height: 50px;
line-height: 50px; line-height: 50px;
text-align: center; text-align: center;
margin-top: -65px; margin-top: -65px;
cursor: pointer; cursor: pointer;
} }
.smallButton { .smallButton {
float: right; float: right;
height: 30px; height: 30px;
font-size: 10px; font-size: 10px;
line-height: 30px; line-height: 30px;
text-align: center; text-align: center;
margin-right: 5px; margin-right: 5px;
padding-left: 15px; padding-left: 15px;
padding-right: 15px; padding-right: 15px;
cursor: pointer; cursor: pointer;
} }
.videoQuality { .videoQuality {
width: 42px; width: 42px;
} }
.videoQuality:hover .qualityTypes { .videoQuality:hover .qualityTypes {
visibility: visible; visibility: visible;
} }
.qualityTypes { .qualityTypes {
visibility: hidden; visibility: hidden;
width: 72px; width: 72px;
position: relative; position: relative;
bottom: 10px; bottom: 10px;
right: 15px; right: 15px;
} }
.qualityTypes ul { .qualityTypes ul {
list-style-type: none; list-style-type: none;
position: relative; position: relative;
right: 24px; right: 24px;
} }
.qualityTypes ul li { .qualityTypes ul li {
width: 72px; width: 72px;
position: relative; position: relative;
right: 15px; right: 15px;
} }
.videoSpeed { .videoSpeed {
width: 42px; width: 42px;
} }
.videoSpeed:hover .speedTypes { .videoSpeed:hover .speedTypes {
visibility: visible; visibility: visible;
} }
.speedTypes { .speedTypes {
visibility: hidden; visibility: hidden;
width: 72px; width: 72px;
position: relative; position: relative;
bottom: 10px; bottom: 10px;
right: 15px; right: 15px;
} }
.speedTypes ul { .speedTypes ul {
list-style-type: none; list-style-type: none;
position: relative; position: relative;
right: 24px; right: 24px;
} }
.speedTypes ul li { .speedTypes ul li {
width: 72px; width: 72px;
position: relative; position: relative;
right: 15px; right: 15px;
} }
#showComments { #showComments {
text-align: center; text-align: center;
height: 40px; height: 40px;
line-height: 40px; line-height: 40px;
margin-top: 15px; margin-top: 15px;
margin-bottom: 15px; margin-bottom: 15px;
} }
#recommendations { #recommendations {
width: 100%; width: 100%;
} }
.recommendVideo { .recommendVideo {
cursor: pointer; cursor: pointer;
height: 150px; height: 150px;
} }
.recommendThumbnail { .recommendThumbnail {
width: 250px; width: 250px;
height: 145px; height: 145px;
float: left; float: left;
} }
.recommendThumbnail img{ .recommendThumbnail img {
width: 100%; width: 100%;
} }
.recommendTitle { .recommendTitle {
font-size: 16px; font-size: 16px;
font-weight: bold; font-weight: bold;
margin-left: 260px; margin-left: 260px;
} }
.recommendChannel { .recommendChannel {
margin-left: 260px; margin-left: 260px;
font-size: 14px; font-size: 14px;
margin-top: -10px; margin-top: -10px;
} }
.recommendDate { .recommendDate {
margin-left: 260px; margin-left: 260px;
font-size: 11px; font-size: 11px;
} }

View File

@ -1,6 +1,8 @@
.videoListContainer{width: 100%;} .videoListContainer {
width: 100%;
}
.videoListContainer hr{ .videoListContainer hr {
width: 95%; width: 95%;
height: 1px; height: 1px;
border: 0; 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'> <div class='center'>
<img src='icons/logoColor.png' width='500px;'/> <img src='icons/logoColor.png' width='500px;' />
<h1>{{versionNumber}} Beta</h1> <h1>{{versionNumber}} Beta</h1>
<h3><i class='fas fa-envelope'></i>&nbsp;&nbsp;FreeTubeApp@protonmail.com</h3> <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> <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>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>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>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>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> <p>Check out our <a href='https://addons.mozilla.org/en-US/firefox/addon/freetube-redirect/'>Firefox</a> extension!</p>
</div> </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='video'>
<div class='channelThumbnail'> <div class='channelThumbnail'>
<img onclick='goToChannel("{{channelId}}")' src={{channelThumbnail}} /> <img onclick='goToChannel("{{channelId}}")' src= {{channelThumbnail}} />
</div> </div>
<p onclick='goToChannel("{{channelId}}")' class='videoTitle'>{{channelName}}</p> <p onclick='goToChannel("{{channelId}}")' class='videoTitle'>{{channelName}}</p>
<p onclick='goToChannel("{{channelId}}")' class='channelName'>{{subscriberCount}} subscribers - {{videoCount}} videos</p> <p onclick='goToChannel("{{channelId}}")' class='channelName'>{{subscriberCount}} subscribers - {{videoCount}} videos</p>
<p onclick='goToChannel("{{channelId}}")' class='videoDescription'>{{channelDescription}}</p> <p onclick='goToChannel("{{channelId}}")' class='videoDescription'>{{channelDescription}}</p>
</div> </div>
<hr /> <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}}' /> <img class='channelViewBanner' src='{{channelBanner}}' />
<br /> <br />
<div class='channelViewTitle'> <div class='channelViewTitle'>
<img class='channelViewImage' src='{{channelImage}}' /> <img class='channelViewImage' src='{{channelImage}}' />
<span class='channelViewName'>{{channelName}}</span> <span class='channelViewName'>{{channelName}}</span>
<br /> <br />
<span class='channelViewSubs'>{{subCount}} Subscribers</span> <span class='channelViewSubs'>{{subCount}} Subscribers</span>
<div id='subscribeButton' class='channelSubButton' onclick='toggleSubscription("{{channelId}}");'> <div id='subscribeButton' class='channelSubButton' onclick='toggleSubscription("{{channelId}}");'>
{{subButtonText}} {{subButtonText}}
</div> </div>
</div> </div>
<br /> <br />
<hr /> <hr />
<div class='channelViewDescription'> <div class='channelViewDescription'>
{{{channelDescription}}} {{{channelDescription}}}
</div> </div>
<br /> <br />
<hr /> <hr />
<div id='videoListContainer'> <div id='videoListContainer'>
<h2>Latest Uploads</h2> <h2>Latest Uploads</h2>
</div> </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}} {{#comments}}
<div class="user-comment"> <div class="user-comment">
<div onclick='goToChannel("{{authorChannelId.value}}")' style="float:left;margin-right: 1em; width:48px"> <div onclick='goToChannel("{{authorChannelId.value}}")' style="float:left;margin-right: 1em; width:48px">
<img class="userIcon" src="{{authorProfileImageUrl}}" alt="" class="user-icon" /> <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>
<div class="comment-data"> <br/>
<p onclick='goToChannel("{{authorChannelId.value}}")' style='cursor: pointer; margin-left: 65px; font-weight: bold;'>{{authorDisplayName}}</p> <div class="line"></div>
<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>
{{/comments}} {{/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> <title>Freetube Mini-Player</title>
<style> <style>
body{ body {
background-color: #424242; background-color: #424242;
} }
.videoPlayer{
width: 100%; .videoPlayer {
width: 100%;
} }
iframe{
width: 100%; iframe {
height: 54.9vw; width: 100%;
height: 54.9vw;
} }
</style> </style>
<script src="../js/events.js"></script> <script src="../js/events.js"></script>
<script> <script>
@ -18,40 +38,41 @@ let startTime = '{{startTime}}';
let mouseTimeout; let mouseTimeout;
$('.videoPlayer').ready(() => { $('.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 * Hide the mouse cursor after ~3 seconds. Used to hide the video when the user
* hovers the mouse over the video player. * hovers the mouse over the video player.
* *
* @return {Void} * @return {Void}
*/ */
function hideMouseTimeout(){ function hideMouseTimeout() {
$('.videoPlayer')[0].style.cursor = 'default'; $('.videoPlayer')[0].style.cursor = 'default';
clearTimeout(mouseTimeout); clearTimeout(mouseTimeout);
mouseTimeout = window.setTimeout(function(){ mouseTimeout = window.setTimeout(function() {
$('.videoPlayer')[0].style.cursor = 'none'; $('.videoPlayer')[0].style.cursor = 'none';
}, 3150); }, 3150);
} }
/** /**
* Remove the timeout for the mouse cursor as a fallback. * Remove the timeout for the mouse cursor as a fallback.
* *
* @return {Void} * @return {Void}
*/ */
function removeMouseTimeout(){ function removeMouseTimeout() {
$('.videoPlayer')[0].style.cursor = 'default'; $('.videoPlayer')[0].style.cursor = 'default';
clearTimeout(mouseTimeout); clearTimeout(mouseTimeout);
} }
let playPauseVideo = function(event) { let playPauseVideo = function(event) {
let el = event.currentTarget; let el = event.currentTarget;
el.paused ? el.play() : el.pause(); el.paused ? el.play() : el.pause();
} }
$(document).on('click', '.videoPlayer', playPauseVideo); $(document).on('click', '.videoPlayer', playPauseVideo);
$(document).on('mouseover', '.videoPlayer', hideMouseTimeout); $(document).on('mouseover', '.videoPlayer', hideMouseTimeout);
$(document).on('mouseleave', '.videoPlayer', removeMouseTimeout); $(document).on('mouseleave', '.videoPlayer', removeMouseTimeout);
</script> </script>
{{{videoHtml}}} {{{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> <video class="videoPlayer" type="application/x-mpegURL" onmousemove="hideMouseTimeout()" onmouseleave="removeMouseTimeout()" controls="" src="{{defaultUrl}}" poster="{{videoThumbnail}}" autoplay>
{{{subtitleHtml}}} {{{subtitleHtml}}}
</video> </video>
<div class='statistics'> <div class='statistics'>
<div class='smallButton' onclick="openMiniPlayer('{{videoThumbnail}}')"> <div class='smallButton' onclick="openMiniPlayer('{{videoThumbnail}}')">
MINI PLAYER <i class="fas fa-external-link-alt"></i> 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> </div>
</div> <div class='smallButton videoQuality'>
<div class='smallButton videoSpeed'> <span id='currentQuality'>{{videoQuality}}</span> <i class="fas fa-angle-down"></i>
<span id='currentSpeed'>1</span>x <i class="fas fa-angle-down"></i> <div class='qualityTypes'>
<div class='speedTypes'> <ul>
<ul> <li id='quality480p' onclick='changeQuality("{{video480p}}", "480p")'>480p</li>
<li onclick='changeVideoSpeed(0.25)'>0.25x</li> <li id='quality720p' onclick='changeQuality("{{video720p}}", "720p")'>720p</li>
<li onclick='changeVideoSpeed(0.5)'>0.5x</li> <li id='qualityEmbed' onclick='changeQuality("{{embedPlayer}}", "EMBED", true)'>EMBED</li>
<li onclick='changeVideoSpeed(0.75)'>0.75x</li> </ul>
<li onclick='changeVideoSpeed(1)'>1x</li> </div>
<li onclick='changeVideoSpeed(1.25)'>1.25x</li>
<li onclick='changeVideoSpeed(1.5)'>1.5x</li>
<li onclick='changeVideoSpeed(1.75)'>1.75x</li>
<li onclick='changeVideoSpeed(2)'>2x</li>
</ul>
</div> </div>
</div> <div class='smallButton videoSpeed'>
<div onclick='toggleSavedVideo("{{videoId}}")' class='smallButton'> <span id='currentSpeed'>1</span>x <i class="fas fa-angle-down"></i>
<i id='saveIcon' style='color: {{savedIconColor}};' class="{{savedIconClass}} fa-star"></i> <span id='savedText'>{{savedText}}</span> <div class='speedTypes'>
</div> <ul>
<div class='smallButton' onclick='copyLink("youtube", "{{videoId}}")'> <li onclick='changeVideoSpeed(0.25)'>0.25x</li>
COPY YOUTUBE LINK <li onclick='changeVideoSpeed(0.5)'>0.5x</li>
</div> <li onclick='changeVideoSpeed(0.75)'>0.75x</li>
<a href='https://youtube.com/watch?v={{videoId}}'> <li onclick='changeVideoSpeed(1)'>1x</li>
<div class='smallButton'> <li onclick='changeVideoSpeed(1.25)'>1.25x</li>
OPEN IN YOUTUBE <li onclick='changeVideoSpeed(1.5)'>1.5x</li>
<li onclick='changeVideoSpeed(1.75)'>1.75x</li>
<li onclick='changeVideoSpeed(2)'>2x</li>
</ul>
</div>
</div> </div>
</a> <div onclick='toggleSavedVideo("{{videoId}}")' class='smallButton'>
<div class='smallButton' onclick='copyLink("hooktube", "{{videoId}}")'> <i id='saveIcon' style='color: {{savedIconColor}};' class="{{savedIconClass}} fa-star"></i> <span id='savedText'>{{savedText}}</span>
COPY HOOKTUBE LINK
</div>
<a href='https://hooktube.com/watch?v={{videoId}}'>
<div class='smallButton'>
OPEN IN HOOKTUBE
</div> </div>
</a> <div class='smallButton' onclick='copyLink("youtube", "{{videoId}}")'>
<br /> COPY YOUTUBE LINK
<p class='title'>{{videoTitle}}</p> </div>
<p class='views'>{{videoViews}} views</p> <a href='https://youtube.com/watch?v={{videoId}}'>
<div class='likeContainer'> <div class='smallButton'>
<div class='dislikeBar'> OPEN IN YOUTUBE
<div class='likeBar' style='width: {{likePercentage}}%;'> </div>
</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> </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>
<div class="details"> <div class="details">
<img id='channelIcon' onclick='goToChannel("{{channelId}}")' src="{{channelIcon}}" /> <img id='channelIcon' onclick='goToChannel("{{channelId}}")' src="{{channelIcon}}" />
<p id='channelName' onclick='goToChannel("{{channelId}}")'>{{channelName}}</p> <p id='channelName' onclick='goToChannel("{{channelId}}")'>{{channelName}}</p>
<p id='publishDate'>Published on {{publishedDate}}</p> <p id='publishDate'>Published on {{publishedDate}}</p>
<div id='subscribeButton' class='playerSubButton' onclick='toggleSubscription("{{channelId}}")'>{{isSubscribed}}</div> <div id='subscribeButton' class='playerSubButton' onclick='toggleSubscription("{{channelId}}")'>{{isSubscribed}}</div>
<br /><br /> <br />
<div id='description'> <br />
{{{description}}} <div id='description'>
</div> {{{description}}}
</div>
</div> </div>
<div id='showComments'> <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>
<div id='comments' data-video-id="{{videoId}}"> <div id='comments' data-video-id="{{videoId}}">
</div> </div>
<div id='recommendations'> <div id='recommendations'>
<strong>Recommendations</strong> <strong>Recommendations</strong>
</div> </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> <!DOCTYPE html>
<html> <html>
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<link rel="stylesheet" href="style/main.css"> <link rel="stylesheet" href="style/main.css">
<link rel="stylesheet" href="style/lightTheme.css"> <link rel="stylesheet" href="style/lightTheme.css">
<link rel="stylesheet" href="style/player.css"> <link rel="stylesheet" href="style/player.css">
<link rel="stylesheet" href="style/videoList.css"> <link rel="stylesheet" href="style/videoList.css">
<link rel="stylesheet" href="style/channel.css"> <link rel="stylesheet" href="style/channel.css">
<link rel="stylesheet" href="style/loading.css"> <link rel="stylesheet" href="style/loading.css">
<link rel="stylesheet" href="style/fa-solid.min.css"> <link rel="stylesheet" href="style/fa-solid.min.css">
<link rel="stylesheet" href="style/fontawesome-all.min.css"> <link rel="stylesheet" href="style/fontawesome-all.min.css">
<script src="js/youtubeApi.js"></script> <script src="js/youtubeApi.js"></script>
<script src="js/settings.js"></script> <script src="js/settings.js"></script>
<script src="js/layout.js"></script> <script src="js/layout.js"></script>
<script src="js/videos.js"></script> <script src="js/videos.js"></script>
<script src="js/player.js"></script> <script src="js/player.js"></script>
<script src="js/subscriptions.js"></script> <script src="js/subscriptions.js"></script>
<script src="js/channels.js"></script> <script src="js/channels.js"></script>
<script src="js/savedVideos.js"></script> <script src="js/savedVideos.js"></script>
<script src="js/history.js"></script> <script src="js/history.js"></script>
<script src="js/events.js"></script> <script src="js/events.js"></script>
<script> <script>
showVideoRecommendations("{{videoId}}"); showVideoRecommendations("{{videoId}}");
const checkIfSubscribed = isSubscribed('{{channelId}}'); const checkIfSubscribed = isSubscribed('{{channelId}}');
checkIfSubscribed.then((results) => { checkIfSubscribed.then((results) => {
const subscribeButton = document.getElementById('subscribeButton'); const subscribeButton = document.getElementById('subscribeButton');
console.log(results); console.log(results);
console.log(subscribeButton); console.log(subscribeButton);
if (results === false) { if (results === false) {
if (subscribeButton != null) { if (subscribeButton != null) {
subscribeButton.innerHTML = 'UNSUBSCRIBE'; 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 { </script>
width: 40px; <title>Freetube Player</title>
height: 40px; <style>
#main {
width: 100%;
margin-left: 0px;
}
position: relative; .spinner {
margin: 0px auto; width: 40px;
} height: 40px;
#loading{ position: relative;
width: 100%; margin: 0px auto;
height: 0%; }
}
</style> #loading {
width: 100%;
height: 0%;
}
</style>
</head> </head>
<body> <body>
<div id='loading'> <div id='loading'>
<div class="spinner"> <div class="spinner">
<div class="double-bounce1"></div> <div class="double-bounce1"></div>
<div class="double-bounce2"></div> <div class="double-bounce2"></div>
</div>
</div> </div>
</div> <div id='confirmFunction'>
<div id='confirmFunction'> <span id='confirmMessage'>Would you like to perform the function?</span>
<span id='confirmMessage'>Would you like to perform the function?</span> <div class='confirmButton' id='confirmYes'>Yes</div>
<div class='confirmButton' id='confirmYes'>Yes</div> <div class='confirmButton' id='confirmNo'>No</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> </div>
<img src='icons/iconBlack.png' id='menuIcon'/> <div id='toast'>
&nbsp; <span id='toastMessage'></span>
<img src='icons/textBlack.png' id='menuText'/> <i onclick='hideToast()' class="closeToast fas fa-times"></i>
</div> </div>
<div id='main'> <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='video'>
<div class='videoThumbnail'> <div class='videoThumbnail'>
<img onclick='console.log("Not Implemented")' src={{playlistThumbnail}} /> <img onclick='console.log("Not Implemented")' src= {{playlistThumbnail}} />
</div> </div>
<p onclick='goToChannel("{{channelId}}")' class='videoTitle'>{{playlistTitle}}</p> <p onclick='goToChannel("{{channelId}}")' class='videoTitle'>{{playlistTitle}}</p>
<p onclick='goToChannel("{{channelId}}")' class='channelName'>{{channelName}} - {{publishedDate}}</p> <p onclick='goToChannel("{{channelId}}")' class='channelName'>{{channelName}} - {{publishedDate}}</p>
<p onclick='goToChannel("{{channelId}}")' class='videoDescription'>{{playlistDescription}}</p> <p onclick='goToChannel("{{channelId}}")' class='videoDescription'>{{playlistDescription}}</p>
<p>VIEW FULL PLAYLIST ({{videoCount}} videos)</p> <p>VIEW FULL PLAYLIST ({{videoCount}} videos)</p>
</div> </div>
<hr /> <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='recommendVideo' onclick='playVideo("{{videoId}}")'>
<div class='recommendThumbnail'> <div class='recommendThumbnail'>
<img src='{{videoThumbnail}}'></img> <img src='{{videoThumbnail}}'></img>
<p onclick='playVideo("{{videoId}}")' class='videoDuration'>{{videoDuration}}</p> <p onclick='playVideo("{{videoId}}")' class='videoDuration'>{{videoDuration}}</p>
</div> </div>
<p class='recommendTitle'>{{videoTitle}}</p> <p class='recommendTitle'>{{videoTitle}}</p>
<p class='recommendChannel'>{{channelName}}</p> <p class='recommendChannel'>{{channelName}}</p>
<p class='recommendDate'>{{publishedDate}}</p> <p class='recommendDate'>{{publishedDate}}</p>
</div> </div>
<hr /> <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> <h1 class="center">Settings</h1>
<div class='center'> <div class='center'>
<input type='text' name='api-key' id='api-key' class='settingsInput' value='{{key}}' placeholder='API Key' /> <input type='text' name='api-key' id='api-key' class='settingsInput' value='{{key}}' placeholder='API Key' />
<br /> <br />
<label for='api-key'>Set API Key: Leave blank to use default</label> <label for='api-key'>Set API Key: Leave blank to use default</label>
<br /> <br />
<input type="checkbox" id="themeSwitch" name="set-name" class="switch-input" onchange='toggleTheme(this)' {{isChecked}}> <input type="checkbox" id="themeSwitch" name="set-name" class="switch-input" onchange='toggleTheme(this)' {{isChecked}}>
<label for="themeSwitch" class="switch-label">Use Dark Theme</label> <label for="themeSwitch" class="switch-label">Use Dark Theme</label>
<input type="checkbox" id="torSwitch" name="set-name" class="switch-input" {{isChecked}}> <input type="checkbox" id="torSwitch" name="set-name" class="switch-input" {{isChecked}}>
<label for="torSwitch" class="switch-label">Use Tor for API calls</label> <label for="torSwitch" class="switch-label">Use Tor for API calls</label>
</div> </div>
<div class='center'> <div class='center'>
<div onclick='importSubscriptions()' class='settingsButton'> <div onclick='importSubscriptions()' class='settingsButton'>
IMPORT SUBSCRIPTIONS IMPORT SUBSCRIPTIONS
</div> </div>
<div onclick='exportSubscriptions();' class='settingsButton'> <div onclick='exportSubscriptions();' class='settingsButton'>
EXPORT SUBSCRIPTIONS EXPORT SUBSCRIPTIONS
</div> </div>
</div> </div>
<br /><br /> <br />
<br />
<div class='center'> <div class='center'>
<div onclick='confirmFunction("Are you sure you want to delete your history?", clearFile, "history")' class='settingsButton'> <div onclick='confirmFunction("Are you sure you want to delete your history?", clearFile, "history")' class='settingsButton'>
CLEAR HISTORY CLEAR HISTORY
</div> </div>
<div onclick='confirmFunction("Are you sure you want to remove all saved videos?", clearFile, "saved")' class='settingsButton'> <div onclick='confirmFunction("Are you sure you want to remove all saved videos?", clearFile, "saved")' class='settingsButton'>
CLEAR SAVED VIDEOS CLEAR SAVED VIDEOS
</div> </div>
<div onclick='confirmFunction("Are you sure you want to remove all subscriptions?", clearFile, "subscriptions")' class='settingsButton'> <div onclick='confirmFunction("Are you sure you want to remove all subscriptions?", clearFile, "subscriptions")' class='settingsButton'>
CLEAR SUBSCRIPTIONS CLEAR SUBSCRIPTIONS
</div> </div>
</div> </div>
<br /><br /> <br />
<br />
<div onclick='updateSettings()' class='center settingsSubmit'> <div onclick='updateSettings()' class='center settingsSubmit'>
SAVE SETTINGS SAVE SETTINGS
</div> </div>

View File

@ -1,5 +1,20 @@
<!--
This file is part of FreeTube.
FreeTube is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
FreeTube is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with FreeTube. If not, see <http://www.gnu.org/licenses/>.
-->
<li onclick='goToChannel("{{channelId}}")'> <li onclick='goToChannel("{{channelId}}")'>
<img src='{{channelIcon}}' /> <img src='{{channelIcon}}' /> {{channelName}}
{{channelName}} <i class='fas fa-times' onclick='toggleSubscription("{{channelId}}")'></i>
<i class='fas fa-times' onclick='toggleSubscription("{{channelId}}")'></i>
</li> </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> <video class="videoPlayer" onmousemove="hideMouseTimeout()" onmouseleave="removeMouseTimeout()" controls="" poster="{{videoThumbnail}}" autoplay>
<source src="{{videoUrl}}" type="video/mp4"> <source src="{{videoUrl}}" type="video/mp4"> {{subtitleElements}}
{{subtitleElements}}
</video> </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='video'>
<div class='videoOptions'> <div class='videoOptions'>
<i class="fas fa-ellipsis-v" onclick='showVideoOptions(this)'></i> <i class="fas fa-ellipsis-v" onclick='showVideoOptions(this)'></i>
<ul> <ul>
<li onclick='playVideo("{{videoId}}", "", true); showVideoOptions(this.parentNode.previousSibling);'>Open (New Window)</li> <li onclick='playVideo("{{videoId}}", "", true); showVideoOptions(this.parentNode.previousSibling);'>Open (New Window)</li>
<li onclick='addSavedVideo("{{videoId}}"); showVideoOptions(this.parentNode.previousSibling);'>Save Video</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://youtube.com/watch?v={{videoId}}' onclick='showVideoOptions(this.parentNode.previousSibling);'>
<a href='https://hooktube.com/watch?v={{videoId}}' onclick='showVideoOptions(this.parentNode.previousSibling);'><li>Open in HookTube</li></a> <li>Open in YouTube</li>
{{{deleteHtml}}} </a>
</ul> <a href='https://invidio.us/watch?v={{videoId}}' onclick='showVideoOptions(this.parentNode.previousSibling);'>
</div> <li>Open in Invidious</li>
<div class='videoThumbnail'> </a>
<img onclick='playVideo("{{videoId}}", "{{videoThumbnail}}")' src={{videoThumbnail}} /> {{{deleteHtml}}}
<p onclick='playVideo("{{videoId}}")' class='videoDuration'>{{videoDuration}}</p> </ul>
</div> </div>
<p onclick='playVideo("{{videoId}}")' class='videoTitle'>{{videoTitle}}</p> <div class='videoThumbnail'>
<p onclick='goToChannel("{{channelId}}")' class='channelName'>{{channelName}} - {{publishedDate}}</p> <img onclick='playVideo("{{videoId}}", "{{videoThumbnail}}")' src= {{videoThumbnail}} />
<p onclick='playVideo("{{videoId}}")' class='videoDescription'>{{videoDescription}}</p> <p onclick='playVideo("{{videoId}}")' class='videoDuration'>{{videoDuration}}</p>
<p class='live'>{{liveText}}</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> </div>
<hr /> <hr />