From c7e7e7e783ca57195a29c4a1963a43ebc58ee657 Mon Sep 17 00:00:00 2001 From: Emma Date: Sat, 21 Jan 2023 21:34:07 -0500 Subject: [PATCH] Base Cordova build off of upstream web build (#84) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * ! Fix incorrect selector (meant to be multiple targets not nested) (#2962) * Translated using Weblate (German) Currently translated at 100.0% (651 of 651 strings) Translation: FreeTube/Translations Translate-URL: https://hosted.weblate.org/projects/free-tube/translations/de/ * Translated using Weblate (Finnish) Currently translated at 99.5% (648 of 651 strings) Translation: FreeTube/Translations Translate-URL: https://hosted.weblate.org/projects/free-tube/translations/fi/ * Translated using Weblate (French) Currently translated at 100.0% (651 of 651 strings) Translation: FreeTube/Translations Translate-URL: https://hosted.weblate.org/projects/free-tube/translations/fr/ * Translated using Weblate (Italian) Currently translated at 100.0% (651 of 651 strings) Translation: FreeTube/Translations Translate-URL: https://hosted.weblate.org/projects/free-tube/translations/it/ * Translated using Weblate (English (United Kingdom)) Currently translated at 100.0% (651 of 651 strings) Translation: FreeTube/Translations Translate-URL: https://hosted.weblate.org/projects/free-tube/translations/en_GB/ * Fix docs link in the data settings section (#2981) * Cleanup and simplify the settings password code (#2982) * Translated using Weblate (Portuguese) Currently translated at 100.0% (651 of 651 strings) Translation: FreeTube/Translations Translate-URL: https://hosted.weblate.org/projects/free-tube/translations/pt/ * Translated using Weblate (Hindi) Currently translated at 23.3% (152 of 651 strings) Translation: FreeTube/Translations Translate-URL: https://hosted.weblate.org/projects/free-tube/translations/hi/ * Update stale.yml (#2985) * Translated using Weblate (Croatian) Currently translated at 100.0% (651 of 651 strings) Translation: FreeTube/Translations Translate-URL: https://hosted.weblate.org/projects/free-tube/translations/hr/ * Set width so that overflow css rules are applied. (#2988) Added overflow-wrap break word so that more of the clipped title is displayed. Co-authored-by: Simon Epstein * Support extra youtube embed playlist links (#2972) * Fix strange embed playlist links missing / * support embed/videoseries links * remove duplicated replace line Co-authored-by: petaded * Option to skip through the video by scrolling (#2418) * initial commit * remove extra play button toggle * add missing comment Co-authored-by: Alin Co-authored-by: ChunkyPtogrammer <78101139+ChunkyProgrammer@users.noreply.github.com> * Translated using Weblate (Spanish) Currently translated at 100.0% (653 of 653 strings) Translation: FreeTube/Translations Translate-URL: https://hosted.weblate.org/projects/free-tube/translations/es/ * Translated using Weblate (Polish) Currently translated at 100.0% (653 of 653 strings) Translation: FreeTube/Translations Translate-URL: https://hosted.weblate.org/projects/free-tube/translations/pl/ * Translated using Weblate (Turkish) Currently translated at 100.0% (653 of 653 strings) Translation: FreeTube/Translations Translate-URL: https://hosted.weblate.org/projects/free-tube/translations/tr/ * Translated using Weblate (Galician) Currently translated at 100.0% (653 of 653 strings) Translation: FreeTube/Translations Translate-URL: https://hosted.weblate.org/projects/free-tube/translations/gl/ * Translated using Weblate (Ukrainian) Currently translated at 100.0% (653 of 653 strings) Translation: FreeTube/Translations Translate-URL: https://hosted.weblate.org/projects/free-tube/translations/uk/ * Translated using Weblate (Chinese (Simplified)) Currently translated at 100.0% (653 of 653 strings) Translation: FreeTube/Translations Translate-URL: https://hosted.weblate.org/projects/free-tube/translations/zh_Hans/ * Translated using Weblate (Chinese (Traditional)) Currently translated at 100.0% (653 of 653 strings) Translation: FreeTube/Translations Translate-URL: https://hosted.weblate.org/projects/free-tube/translations/zh_Hant/ * Translated using Weblate (Italian) Currently translated at 100.0% (653 of 653 strings) Translation: FreeTube/Translations Translate-URL: https://hosted.weblate.org/projects/free-tube/translations/it/ * Translated using Weblate (Czech) Currently translated at 100.0% (653 of 653 strings) Translation: FreeTube/Translations Translate-URL: https://hosted.weblate.org/projects/free-tube/translations/cs/ * Improve accessibility of Watch View (#2986) * Improve Watch page accessibility Co-Authored-By: Jason <84899178+jasonhenriquez@users.noreply.github.com> * fix title issue, remove unused gotochannel function Co-authored-by: Jason <84899178+jasonhenriquez@users.noreply.github.com> * Translated using Weblate (Portuguese (Portugal)) Currently translated at 100.0% (653 of 653 strings) Translation: FreeTube/Translations Translate-URL: https://hosted.weblate.org/projects/free-tube/translations/pt_PT/ * Translated using Weblate (Japanese) Currently translated at 100.0% (653 of 653 strings) Translation: FreeTube/Translations Translate-URL: https://hosted.weblate.org/projects/free-tube/translations/ja/ * Translated using Weblate (Portuguese) Currently translated at 100.0% (653 of 653 strings) Translation: FreeTube/Translations Translate-URL: https://hosted.weblate.org/projects/free-tube/translations/pt/ * Translated using Weblate (Polish) Currently translated at 100.0% (657 of 657 strings) Translation: FreeTube/Translations Translate-URL: https://hosted.weblate.org/projects/free-tube/translations/pl/ * Translated using Weblate (Arabic) Currently translated at 100.0% (657 of 657 strings) Translation: FreeTube/Translations Translate-URL: https://hosted.weblate.org/projects/free-tube/translations/ar/ * Translated using Weblate (Spanish) Currently translated at 100.0% (657 of 657 strings) Translation: FreeTube/Translations Translate-URL: https://hosted.weblate.org/projects/free-tube/translations/es/ * Translated using Weblate (Portuguese) Currently translated at 100.0% (657 of 657 strings) Translation: FreeTube/Translations Translate-URL: https://hosted.weblate.org/projects/free-tube/translations/pt/ * Translated using Weblate (Turkish) Currently translated at 100.0% (657 of 657 strings) Translation: FreeTube/Translations Translate-URL: https://hosted.weblate.org/projects/free-tube/translations/tr/ * Translated using Weblate (Czech) Currently translated at 100.0% (657 of 657 strings) Translation: FreeTube/Translations Translate-URL: https://hosted.weblate.org/projects/free-tube/translations/cs/ * Translated using Weblate (Ukrainian) Currently translated at 100.0% (657 of 657 strings) Translation: FreeTube/Translations Translate-URL: https://hosted.weblate.org/projects/free-tube/translations/uk/ * Translated using Weblate (Chinese (Simplified)) Currently translated at 100.0% (657 of 657 strings) Translation: FreeTube/Translations Translate-URL: https://hosted.weblate.org/projects/free-tube/translations/zh_Hans/ * Translated using Weblate (Italian) Currently translated at 100.0% (657 of 657 strings) Translation: FreeTube/Translations Translate-URL: https://hosted.weblate.org/projects/free-tube/translations/it/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (657 of 657 strings) Translation: FreeTube/Translations Translate-URL: https://hosted.weblate.org/projects/free-tube/translations/hu/ * Translated using Weblate (Hebrew) Currently translated at 100.0% (657 of 657 strings) Translation: FreeTube/Translations Translate-URL: https://hosted.weblate.org/projects/free-tube/translations/he/ * Translated using Weblate (Galician) Currently translated at 99.8% (656 of 657 strings) Translation: FreeTube/Translations Translate-URL: https://hosted.weblate.org/projects/free-tube/translations/gl/ * Translated using Weblate (Galician) Currently translated at 100.0% (657 of 657 strings) Translation: FreeTube/Translations Translate-URL: https://hosted.weblate.org/projects/free-tube/translations/gl/ * Translated using Weblate (German) Currently translated at 99.3% (653 of 657 strings) Translation: FreeTube/Translations Translate-URL: https://hosted.weblate.org/projects/free-tube/translations/de/ * Translated using Weblate (Norwegian Bokmål) Currently translated at 82.0% (539 of 657 strings) Translation: FreeTube/Translations Translate-URL: https://hosted.weblate.org/projects/free-tube/translations/nb_NO/ * Translated using Weblate (Chinese (Simplified)) Currently translated at 100.0% (657 of 657 strings) Translation: FreeTube/Translations Translate-URL: https://hosted.weblate.org/projects/free-tube/translations/zh_Hans/ * Fixing Stale bot (#2992) * Fix insecure coookie error (#2990) * ! Fix possible to do both actions in video player by scrolling (#2989) "Scroll playback rate over video player" and "Skip by Scrolling Over Video Player" * Translated using Weblate (Russian) Currently translated at 100.0% (657 of 657 strings) Translation: FreeTube/Translations Translate-URL: https://hosted.weblate.org/projects/free-tube/translations/ru/ * Translated using Weblate (Norwegian Bokmål) Currently translated at 89.8% (590 of 657 strings) Translation: FreeTube/Translations Translate-URL: https://hosted.weblate.org/projects/free-tube/translations/nb_NO/ * Translated using Weblate (Chinese (Traditional)) Currently translated at 100.0% (657 of 657 strings) Translation: FreeTube/Translations Translate-URL: https://hosted.weblate.org/projects/free-tube/translations/zh_Hant/ * Bump videojs-contrib-quality-levels from 2.2.1 to 3.0.0 (#2996) Bumps [videojs-contrib-quality-levels](https://github.com/videojs/videojs-contrib-quality-levels) from 2.2.1 to 3.0.0. - [Release notes](https://github.com/videojs/videojs-contrib-quality-levels/releases) - [Changelog](https://github.com/videojs/videojs-contrib-quality-levels/blob/main/CHANGELOG.md) - [Commits](https://github.com/videojs/videojs-contrib-quality-levels/compare/v2.2.1...v3.0.0) --- updated-dependencies: - dependency-name: videojs-contrib-quality-levels dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Bump marked from 4.2.4 to 4.2.5 (#2998) Bumps [marked](https://github.com/markedjs/marked) from 4.2.4 to 4.2.5. - [Release notes](https://github.com/markedjs/marked/releases) - [Changelog](https://github.com/markedjs/marked/blob/master/.releaserc.json) - [Commits](https://github.com/markedjs/marked/compare/v4.2.4...v4.2.5) --- updated-dependencies: - dependency-name: marked dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Bump sass from 1.57.0 to 1.57.1 (#2995) Bumps [sass](https://github.com/sass/dart-sass) from 1.57.0 to 1.57.1. - [Release notes](https://github.com/sass/dart-sass/releases) - [Changelog](https://github.com/sass/dart-sass/blob/main/CHANGELOG.md) - [Commits](https://github.com/sass/dart-sass/compare/1.57.0...1.57.1) --- updated-dependencies: - dependency-name: sass dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Lazy load playlist components to improve performance (#2993) * Update app menu to add items for side nav items (#2965) * * Update app menu to add items for side nav items * * Show app menu items accoridng to user settings * * Update app menu on setting update * ! Fix setting values reading when db entry(s) absent * $ Use `?.` instead of try/catch Suggested by absidue Co-authored-by: absidue <48293849+absidue@users.noreply.github.com> Co-authored-by: absidue <48293849+absidue@users.noreply.github.com> * Translated using Weblate (Russian) Currently translated at 100.0% (657 of 657 strings) Translation: FreeTube/Translations Translate-URL: https://hosted.weblate.org/projects/free-tube/translations/ru/ * Escape special characters in channel names in ompl export (#3004) * Import the product name instead of using a define for it (#3003) * Use path.sep instead of working out the directory separator ourselves (#3002) * Bump @babel/core from 7.20.5 to 7.20.7 (#2997) Bumps [@babel/core](https://github.com/babel/babel/tree/HEAD/packages/babel-core) from 7.20.5 to 7.20.7. - [Release notes](https://github.com/babel/babel/releases) - [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md) - [Commits](https://github.com/babel/babel/commits/v7.20.7/packages/babel-core) --- updated-dependencies: - dependency-name: "@babel/core" dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Only show prompt close button when it is needed (#3001) * Translated using Weblate (German) Currently translated at 99.6% (655 of 657 strings) Translation: FreeTube/Translations Translate-URL: https://hosted.weblate.org/projects/free-tube/translations/de/ * Translated using Weblate (Finnish) Currently translated at 99.0% (651 of 657 strings) Translation: FreeTube/Translations Translate-URL: https://hosted.weblate.org/projects/free-tube/translations/fi/ * Translated using Weblate (French) Currently translated at 100.0% (657 of 657 strings) Translation: FreeTube/Translations Translate-URL: https://hosted.weblate.org/projects/free-tube/translations/fr/ * Translated using Weblate (English (United Kingdom)) Currently translated at 100.0% (657 of 657 strings) Translation: FreeTube/Translations Translate-URL: https://hosted.weblate.org/projects/free-tube/translations/en_GB/ * Migrate the trending page to YouTube.js (#3005) * Migrate the trending page to YouTube.js * Move more of the logic to the local API file * This function doesn't need to be exported anymore * Translated using Weblate (Russian) Currently translated at 100.0% (657 of 657 strings) Translation: FreeTube/Translations Translate-URL: https://hosted.weblate.org/projects/free-tube/translations/ru/ * Translated using Weblate (Japanese) Currently translated at 100.0% (657 of 657 strings) Translation: FreeTube/Translations Translate-URL: https://hosted.weblate.org/projects/free-tube/translations/ja/ * Translated using Weblate (Russian) Currently translated at 100.0% (657 of 657 strings) Translation: FreeTube/Translations Translate-URL: https://hosted.weblate.org/projects/free-tube/translations/ru/ * Switch from sync fs APIs to fs/promises (#2991) * Switch from sync fs APIs to fs/promises * Fix error and await storyboard creation * Start a new file for filesystem helpers starting with pathExists * Translated using Weblate (Portuguese (Portugal)) Currently translated at 100.0% (657 of 657 strings) Translation: FreeTube/Translations Translate-URL: https://hosted.weblate.org/projects/free-tube/translations/pt_PT/ * Translated using Weblate (Croatian) Currently translated at 100.0% (657 of 657 strings) Translation: FreeTube/Translations Translate-URL: https://hosted.weblate.org/projects/free-tube/translations/hr/ * Translated using Weblate (Bulgarian) Currently translated at 100.0% (657 of 657 strings) Translation: FreeTube/Translations Translate-URL: https://hosted.weblate.org/projects/free-tube/translations/bg/ * Translated using Weblate (Bulgarian) Currently translated at 100.0% (657 of 657 strings) Translation: FreeTube/Translations Translate-URL: https://hosted.weblate.org/projects/free-tube/translations/bg/ * Translated using Weblate (Norwegian Nynorsk) Currently translated at 75.0% (493 of 657 strings) Translation: FreeTube/Translations Translate-URL: https://hosted.weblate.org/projects/free-tube/translations/nn/ * Update conflicts.yml (#3009) * Update conflicts.yml * Delete dummy-conflicts.yml * Improve responsiveness of `Hide Videos From Channels` (#2994) * Add wrapper in `ft-input` to correct absolute pos Previously, the action icon was absolutely positioned to the relative `ft-input-component`; however, when the label wrapped (on small displays), the height of the ft-input-component changed, and the icon would overflow the bottom of the component. Now, it should be absolutely positioned to the relative `inputWrapper` which begins with the top of the input element, and thus, it should not change position based on the height of the label making it easier to ensure it is in the right position. * Modify wrapper to not interfere with existing components * Make ft-input-tags `width: 100%` on small displays There isn't a lot of screen real estate on small displays, so this component should just expand to fill it instead of crunching in on itself. * Modify media queries to use more standard breakpoint # Conflicts: # src/renderer/components/ft-input/ft-input.css * Hide line-break in distraction-settings on small displays * Fix lint issue by removing self-close from `br` * Translated using Weblate (Russian) Currently translated at 100.0% (657 of 657 strings) Translation: FreeTube/Translations Translate-URL: https://hosted.weblate.org/projects/free-tube/translations/ru/ * Translated using Weblate (Russian) Currently translated at 100.0% (657 of 657 strings) Translation: FreeTube/Translations Translate-URL: https://hosted.weblate.org/projects/free-tube/translations/ru/ * Translated using Weblate (Italian) Currently translated at 100.0% (657 of 657 strings) Translation: FreeTube/Translations Translate-URL: https://hosted.weblate.org/projects/free-tube/translations/it/ * Translated using Weblate (Czech) Currently translated at 100.0% (657 of 657 strings) Translation: FreeTube/Translations Translate-URL: https://hosted.weblate.org/projects/free-tube/translations/cs/ * Fix styling of the comment author text (#3012) * Parse channel handles in video descriptions (#3011) * Use a function for menu navigations (#3010) * Update copyleft year to 2023 (#3017) * Remove unused meta.icon properties from the router config (#3000) * Fix clear button covered by new input wrapper (#3016) * $ Fix CSS warnings * ! Fix clear button covered by new input wrapper * Make getVideoParamsFromUrl and showExternalPlayerUnsupportedActionToast helpers (#3018) * Translated using Weblate (Czech) Currently translated at 100.0% (657 of 657 strings) Translation: FreeTube/Translations Translate-URL: https://hosted.weblate.org/projects/free-tube/translations/cs/ * Add option to display distraction free titles (#2987) * 2953 Added boilerplate to manage setting. * Hooked in distraction free title to video list view. * Tweaked the regexp to include apostrophe when looking for runs. Also only change case for runs of 3 or more, to avoid messing with common abbreviations. * Addressed review feedback. Co-authored-by: Simon Epstein * Translated using Weblate (Japanese) Currently translated at 100.0% (658 of 658 strings) Translation: FreeTube/Translations Translate-URL: https://hosted.weblate.org/projects/free-tube/translations/ja/ * Translated using Weblate (Bulgarian) Currently translated at 100.0% (658 of 658 strings) Translation: FreeTube/Translations Translate-URL: https://hosted.weblate.org/projects/free-tube/translations/bg/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (658 of 658 strings) Translation: FreeTube/Translations Translate-URL: https://hosted.weblate.org/projects/free-tube/translations/hu/ * Translated using Weblate (Czech) Currently translated at 100.0% (658 of 658 strings) Translation: FreeTube/Translations Translate-URL: https://hosted.weblate.org/projects/free-tube/translations/cs/ * Translated using Weblate (Ukrainian) Currently translated at 100.0% (658 of 658 strings) Translation: FreeTube/Translations Translate-URL: https://hosted.weblate.org/projects/free-tube/translations/uk/ * Fix no sources error on the watch page (#3019) * Translated using Weblate (Chinese (Simplified)) Currently translated at 100.0% (658 of 658 strings) Translation: FreeTube/Translations Translate-URL: https://hosted.weblate.org/projects/free-tube/translations/zh_Hans/ * Translated using Weblate (Chinese (Simplified)) Currently translated at 100.0% (658 of 658 strings) Translation: FreeTube/Translations Translate-URL: https://hosted.weblate.org/projects/free-tube/translations/zh_Hans/ * Translated using Weblate (Italian) Currently translated at 100.0% (658 of 658 strings) Translation: FreeTube/Translations Translate-URL: https://hosted.weblate.org/projects/free-tube/translations/it/ * Translated using Weblate (Turkish) Currently translated at 100.0% (658 of 658 strings) Translation: FreeTube/Translations Translate-URL: https://hosted.weblate.org/projects/free-tube/translations/tr/ * Translated using Weblate (Greek) Currently translated at 100.0% (658 of 658 strings) Translation: FreeTube/Translations Translate-URL: https://hosted.weblate.org/projects/free-tube/translations/el/ * Translated using Weblate (Hebrew) Currently translated at 100.0% (658 of 658 strings) Translation: FreeTube/Translations Translate-URL: https://hosted.weblate.org/projects/free-tube/translations/he/ * Translated using Weblate (Icelandic) Currently translated at 100.0% (658 of 658 strings) Translation: FreeTube/Translations Translate-URL: https://hosted.weblate.org/projects/free-tube/translations/is/ * Bump actions/stale from 6 to 7 (#2999) * Bump actions/stale from 6 to 7 Bumps [actions/stale](https://github.com/actions/stale) from 6 to 7. - [Release notes](https://github.com/actions/stale/releases) - [Changelog](https://github.com/actions/stale/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/stale/compare/v6...v7) --- updated-dependencies: - dependency-name: actions/stale dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] * Only give necessary permissions to workflow * readd spacing Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: ChunkyProgrammer <78101139+ChunkyProgrammer@users.noreply.github.com> * Translated using Weblate (Portuguese (Portugal)) Currently translated at 100.0% (658 of 658 strings) Translation: FreeTube/Translations Translate-URL: https://hosted.weblate.org/projects/free-tube/translations/pt_PT/ * Translated using Weblate (Spanish) Currently translated at 100.0% (658 of 658 strings) Translation: FreeTube/Translations Translate-URL: https://hosted.weblate.org/projects/free-tube/translations/es/ * Translated using Weblate (Portuguese) Currently translated at 100.0% (658 of 658 strings) Translation: FreeTube/Translations Translate-URL: https://hosted.weblate.org/projects/free-tube/translations/pt/ * Translated using Weblate (Galician) Currently translated at 100.0% (658 of 658 strings) Translation: FreeTube/Translations Translate-URL: https://hosted.weblate.org/projects/free-tube/translations/gl/ * Translated using Weblate (Russian) Currently translated at 100.0% (658 of 658 strings) Translation: FreeTube/Translations Translate-URL: https://hosted.weblate.org/projects/free-tube/translations/ru/ * Fix workflow warnings (#3024) * update actions * update node12 actions fix version * update node12 actions * Use the new retrieve_player option in YouTube.js (#3022) * Translated using Weblate (Chinese (Traditional)) Currently translated at 100.0% (658 of 658 strings) Translation: FreeTube/Translations Translate-URL: https://hosted.weblate.org/projects/free-tube/translations/zh_Hant/ * update linters & add stylelint (#3023) * update linters, add stylelint, switch from sass to scss * remove unused babel-eslint module * fix spacing in scss files * dont use npm in script calls * dont error for `:deep` selector in css * ! Fix Ctrl/Cmd + C unable to copy text when viewing video (#3027) * Add support for lefthook local config for setting up option `rc` (#2961) * ^ Update lefthook to 1.2.6 * + Add support for lefthook local config for setting up option `rc` https://github.com/evilmartians/lefthook/blob/master/docs/configuration.md#rc * Translated using Weblate (French) Currently translated at 100.0% (658 of 658 strings) Translation: FreeTube/Translations Translate-URL: https://hosted.weblate.org/projects/free-tube/translations/fr/ * Translated using Weblate (Portuguese (Brazil)) Currently translated at 100.0% (658 of 658 strings) Translation: FreeTube/Translations Translate-URL: https://hosted.weblate.org/projects/free-tube/translations/pt_BR/ * Bump json5 from 1.0.1 to 1.0.2 (#3029) Bumps [json5](https://github.com/json5/json5) from 1.0.1 to 1.0.2. - [Release notes](https://github.com/json5/json5/releases) - [Changelog](https://github.com/json5/json5/blob/main/CHANGELOG.md) - [Commits](https://github.com/json5/json5/compare/v1.0.1...v1.0.2) --- updated-dependencies: - dependency-name: json5 dependency-type: indirect ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Translated using Weblate (Croatian) Currently translated at 100.0% (658 of 658 strings) Translation: FreeTube/Translations Translate-URL: https://hosted.weblate.org/projects/free-tube/translations/hr/ * Fix CSS variables (#3030) * Translated using Weblate (Arabic) Currently translated at 100.0% (658 of 658 strings) Translation: FreeTube/Translations Translate-URL: https://hosted.weblate.org/projects/free-tube/translations/ar/ * Add more electron guards (#3031) * Migrate search to YouTube.js (#3028) * Migrate search to YouTube.js * Fix linting issue * Improve accessibility of Channel View (#2984) * Improve channel info bar * Reduce width of channel search bar * fix sizing * improve channel view accessibility Co-Authored-By: Jason <84899178+jasonhenriquez@users.noreply.github.com> * Update src/renderer/components/ft-channel-bubble/ft-channel-bubble.js Co-authored-by: PikachuEXE * Stop space from clicking channel-bubble (links) Co-authored-by: vallode <18506096+vallode@users.noreply.github.com> Co-authored-by: Jason <84899178+jasonhenriquez@users.noreply.github.com> Co-authored-by: PikachuEXE * Translated using Weblate (Russian) Currently translated at 100.0% (659 of 659 strings) Translation: FreeTube/Translations Translate-URL: https://hosted.weblate.org/projects/free-tube/translations/ru/ * Translated using Weblate (Italian) Currently translated at 100.0% (659 of 659 strings) Translation: FreeTube/Translations Translate-URL: https://hosted.weblate.org/projects/free-tube/translations/it/ * Translated using Weblate (Greek) Currently translated at 100.0% (659 of 659 strings) Translation: FreeTube/Translations Translate-URL: https://hosted.weblate.org/projects/free-tube/translations/el/ * Translated using Weblate (Turkish) Currently translated at 100.0% (659 of 659 strings) Translation: FreeTube/Translations Translate-URL: https://hosted.weblate.org/projects/free-tube/translations/tr/ * Translated using Weblate (Hebrew) Currently translated at 100.0% (659 of 659 strings) Translation: FreeTube/Translations Translate-URL: https://hosted.weblate.org/projects/free-tube/translations/he/ * Translated using Weblate (Chinese (Simplified)) Currently translated at 100.0% (659 of 659 strings) Translation: FreeTube/Translations Translate-URL: https://hosted.weblate.org/projects/free-tube/translations/zh_Hans/ * Translated using Weblate (Arabic) Currently translated at 100.0% (659 of 659 strings) Translation: FreeTube/Translations Translate-URL: https://hosted.weblate.org/projects/free-tube/translations/ar/ * Translated using Weblate (Icelandic) Currently translated at 100.0% (659 of 659 strings) Translation: FreeTube/Translations Translate-URL: https://hosted.weblate.org/projects/free-tube/translations/is/ * Translated using Weblate (French) Currently translated at 100.0% (659 of 659 strings) Translation: FreeTube/Translations Translate-URL: https://hosted.weblate.org/projects/free-tube/translations/fr/ * Translated using Weblate (Polish) Currently translated at 100.0% (659 of 659 strings) Translation: FreeTube/Translations Translate-URL: https://hosted.weblate.org/projects/free-tube/translations/pl/ * Translated using Weblate (Croatian) Currently translated at 100.0% (659 of 659 strings) Translation: FreeTube/Translations Translate-URL: https://hosted.weblate.org/projects/free-tube/translations/hr/ * Translated using Weblate (Spanish) Currently translated at 100.0% (659 of 659 strings) Translation: FreeTube/Translations Translate-URL: https://hosted.weblate.org/projects/free-tube/translations/es/ * Translated using Weblate (Czech) Currently translated at 100.0% (659 of 659 strings) Translation: FreeTube/Translations Translate-URL: https://hosted.weblate.org/projects/free-tube/translations/cs/ * Translated using Weblate (Galician) Currently translated at 100.0% (659 of 659 strings) Translation: FreeTube/Translations Translate-URL: https://hosted.weblate.org/projects/free-tube/translations/gl/ * Translated using Weblate (Bulgarian) Currently translated at 100.0% (659 of 659 strings) Translation: FreeTube/Translations Translate-URL: https://hosted.weblate.org/projects/free-tube/translations/bg/ * Translated using Weblate (Ukrainian) Currently translated at 100.0% (659 of 659 strings) Translation: FreeTube/Translations Translate-URL: https://hosted.weblate.org/projects/free-tube/translations/uk/ * Translated using Weblate (Portuguese (Portugal)) Currently translated at 100.0% (659 of 659 strings) Translation: FreeTube/Translations Translate-URL: https://hosted.weblate.org/projects/free-tube/translations/pt_PT/ * Translated using Weblate (Portuguese) Currently translated at 100.0% (659 of 659 strings) Translation: FreeTube/Translations Translate-URL: https://hosted.weblate.org/projects/free-tube/translations/pt/ * Migrate the watch page to YouTube.js (#3035) * Migrate the watch page to YouTube.js * Fix YouTube.js exclusion in the web build * Translated using Weblate (Chinese (Traditional)) Currently translated at 100.0% (659 of 659 strings) Translation: FreeTube/Translations Translate-URL: https://hosted.weblate.org/projects/free-tube/translations/zh_Hant/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (659 of 659 strings) Translation: FreeTube/Translations Translate-URL: https://hosted.weblate.org/projects/free-tube/translations/hu/ * Translated using Weblate (German) Currently translated at 99.8% (658 of 659 strings) Translation: FreeTube/Translations Translate-URL: https://hosted.weblate.org/projects/free-tube/translations/de/ * Use webpack externals to exclude dependencies (#3036) * Hide the proxy settings in the web build (#3037) * Update mastodon link to `@FreeTube@fosstodon.org` (#3038) AFAIK mastodon.technology is shut down * Bump prettier from 2.8.1 to 2.8.2 (#3044) Bumps [prettier](https://github.com/prettier/prettier) from 2.8.1 to 2.8.2. - [Release notes](https://github.com/prettier/prettier/releases) - [Changelog](https://github.com/prettier/prettier/blob/main/CHANGELOG.md) - [Commits](https://github.com/prettier/prettier/compare/2.8.1...2.8.2) --- updated-dependencies: - dependency-name: prettier dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Bump @babel/core from 7.20.7 to 7.20.12 (#3043) Bumps [@babel/core](https://github.com/babel/babel/tree/HEAD/packages/babel-core) from 7.20.7 to 7.20.12. - [Release notes](https://github.com/babel/babel/releases) - [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md) - [Commits](https://github.com/babel/babel/commits/v7.20.12/packages/babel-core) --- updated-dependencies: - dependency-name: "@babel/core" dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Bump babel-loader from 9.1.0 to 9.1.2 (#3042) Bumps [babel-loader](https://github.com/babel/babel-loader) from 9.1.0 to 9.1.2. - [Release notes](https://github.com/babel/babel-loader/releases) - [Changelog](https://github.com/babel/babel-loader/blob/main/CHANGELOG.md) - [Commits](https://github.com/babel/babel-loader/compare/v9.1.0...v9.1.2) --- updated-dependencies: - dependency-name: babel-loader dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Bump eslint-plugin-jsonc from 2.5.0 to 2.6.0 (#3040) Bumps [eslint-plugin-jsonc](https://github.com/ota-meshi/eslint-plugin-jsonc) from 2.5.0 to 2.6.0. - [Release notes](https://github.com/ota-meshi/eslint-plugin-jsonc/releases) - [Changelog](https://github.com/ota-meshi/eslint-plugin-jsonc/blob/master/CHANGELOG.md) - [Commits](https://github.com/ota-meshi/eslint-plugin-jsonc/compare/v2.5.0...v2.6.0) --- updated-dependencies: - dependency-name: eslint-plugin-jsonc dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Bump postcss from 8.4.20 to 8.4.21 (#3041) Bumps [postcss](https://github.com/postcss/postcss) from 8.4.20 to 8.4.21. - [Release notes](https://github.com/postcss/postcss/releases) - [Changelog](https://github.com/postcss/postcss/blob/main/CHANGELOG.md) - [Commits](https://github.com/postcss/postcss/compare/8.4.20...8.4.21) --- updated-dependencies: - dependency-name: postcss dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Translated using Weblate (Portuguese) Currently translated at 100.0% (659 of 659 strings) Translation: FreeTube/Translations Translate-URL: https://hosted.weblate.org/projects/free-tube/translations/pt/ * Generate local API sessions locally (#3052) * Move invidious out of stores (#3045) * move invidious out of stores * fix getting more replies & local api fallback for comments * throw error if error message found in invidious response * fix issue with data setting * fix issue with replacing thumbnails * add bank line, remove slash * Apply suggestions from code review Co-authored-by: absidue <48293849+absidue@users.noreply.github.com> Co-authored-by: absidue <48293849+absidue@users.noreply.github.com> * small accessibility improvements (#3033) * small accessibility improvements * switch to routerlink for side-nav-more-options * Translated using Weblate (English (United Kingdom)) Currently translated at 100.0% (659 of 659 strings) Translation: FreeTube/Translations Translate-URL: https://hosted.weblate.org/projects/free-tube/translations/en_GB/ * Add an IS_ELECTRON_MAIN define instead of runtime detection (#3056) * Migrate live chat to YouTube.js (#3054) * Decipher format URLs with the local API (#3053) * ! Fix code for getting event.key lowercase value (#3061) * Use ES6 classes for video.js components instead of videojs.extend (#3060) * ! Fix searching in channel view broken (#3062) * Translated using Weblate (Norwegian Nynorsk) Currently translated at 91.9% (606 of 659 strings) Translation: FreeTube/Translations Translate-URL: https://hosted.weblate.org/projects/free-tube/translations/nn/ * Import store modules with ES6 imports instead of dynamically (#3064) * Translated using Weblate (Norwegian Bokmål) Currently translated at 89.8% (592 of 659 strings) Translation: FreeTube/Translations Translate-URL: https://hosted.weblate.org/projects/free-tube/translations/nb_NO/ * Translated using Weblate (Norwegian Nynorsk) Currently translated at 95.4% (629 of 659 strings) Translation: FreeTube/Translations Translate-URL: https://hosted.weblate.org/projects/free-tube/translations/nn/ * Add a formatNumber helper and other i18n cleanup (#3055) * Add a formatNumber helper and other i18n cleanup * Fix import order * Translated using Weblate (Portuguese (Brazil)) Currently translated at 100.0% (659 of 659 strings) Translation: FreeTube/Translations Translate-URL: https://hosted.weblate.org/projects/free-tube/translations/pt_BR/ * Translated using Weblate (Norwegian Nynorsk) Currently translated at 97.1% (640 of 659 strings) Translation: FreeTube/Translations Translate-URL: https://hosted.weblate.org/projects/free-tube/translations/nn/ * Translated using Weblate (Spanish (Mexico)) Currently translated at 84.2% (555 of 659 strings) Translation: FreeTube/Translations Translate-URL: https://hosted.weblate.org/projects/free-tube/translations/es_MX/ * Translated using Weblate (Norwegian Nynorsk) Currently translated at 98.7% (651 of 659 strings) Translation: FreeTube/Translations Translate-URL: https://hosted.weblate.org/projects/free-tube/translations/nn/ * Bump prettier from 2.8.2 to 2.8.3 (#3085) Bumps [prettier](https://github.com/prettier/prettier) from 2.8.2 to 2.8.3. - [Release notes](https://github.com/prettier/prettier/releases) - [Changelog](https://github.com/prettier/prettier/blob/main/CHANGELOG.md) - [Commits](https://github.com/prettier/prettier/compare/2.8.2...2.8.3) --- updated-dependencies: - dependency-name: prettier dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Bump marked from 4.2.5 to 4.2.12 (#3076) Bumps [marked](https://github.com/markedjs/marked) from 4.2.5 to 4.2.12. - [Release notes](https://github.com/markedjs/marked/releases) - [Changelog](https://github.com/markedjs/marked/blob/master/.releaserc.json) - [Commits](https://github.com/markedjs/marked/compare/v4.2.5...v4.2.12) --- updated-dependencies: - dependency-name: marked dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Bump eslint-plugin-vue from 9.8.0 to 9.9.0 (#3077) Bumps [eslint-plugin-vue](https://github.com/vuejs/eslint-plugin-vue) from 9.8.0 to 9.9.0. - [Release notes](https://github.com/vuejs/eslint-plugin-vue/releases) - [Commits](https://github.com/vuejs/eslint-plugin-vue/compare/v9.8.0...v9.9.0) --- updated-dependencies: - dependency-name: eslint-plugin-vue dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Bump eslint-plugin-import from 2.26.0 to 2.27.4 (#3081) Bumps [eslint-plugin-import](https://github.com/import-js/eslint-plugin-import) from 2.26.0 to 2.27.4. - [Release notes](https://github.com/import-js/eslint-plugin-import/releases) - [Changelog](https://github.com/import-js/eslint-plugin-import/blob/main/CHANGELOG.md) - [Commits](https://github.com/import-js/eslint-plugin-import/compare/v2.26.0...v2.27.4) --- updated-dependencies: - dependency-name: eslint-plugin-import dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Bump lefthook from 1.2.6 to 1.2.7 (#3078) Bumps [lefthook](https://github.com/evilmartians/lefthook) from 1.2.6 to 1.2.7. - [Release notes](https://github.com/evilmartians/lefthook/releases) - [Changelog](https://github.com/evilmartians/lefthook/blob/master/CHANGELOG.md) - [Commits](https://github.com/evilmartians/lefthook/compare/v1.2.6...v1.2.7) --- updated-dependencies: - dependency-name: lefthook dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Bump rimraf from 3.0.2 to 4.1.0 (#3088) Bumps [rimraf](https://github.com/isaacs/rimraf) from 3.0.2 to 4.1.0. - [Release notes](https://github.com/isaacs/rimraf/releases) - [Changelog](https://github.com/isaacs/rimraf/blob/main/CHANGELOG.md) - [Commits](https://github.com/isaacs/rimraf/compare/v3.0.2...v4.1.0) --- updated-dependencies: - dependency-name: rimraf dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Bump eslint-plugin-n from 15.6.0 to 15.6.1 (#3080) Bumps [eslint-plugin-n](https://github.com/eslint-community/eslint-plugin-n) from 15.6.0 to 15.6.1. - [Release notes](https://github.com/eslint-community/eslint-plugin-n/releases) - [Commits](https://github.com/eslint-community/eslint-plugin-n/compare/15.6.0...15.6.1) --- updated-dependencies: - dependency-name: eslint-plugin-n dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Bump eslint-plugin-vuejs-accessibility from 2.0.0 to 2.1.0 (#3082) Bumps [eslint-plugin-vuejs-accessibility](https://github.com/vue-a11y/eslint-plugin-vuejs-accessibility) from 2.0.0 to 2.1.0. - [Release notes](https://github.com/vue-a11y/eslint-plugin-vuejs-accessibility/releases) - [Changelog](https://github.com/vue-a11y/eslint-plugin-vuejs-accessibility/blob/main/CHANGELOG.md) - [Commits](https://github.com/vue-a11y/eslint-plugin-vuejs-accessibility/compare/v2.0.0...v2.1.0) --- updated-dependencies: - dependency-name: eslint-plugin-vuejs-accessibility dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Bump electron from 22.0.0 to 22.0.2 (#3084) Bumps [electron](https://github.com/electron/electron) from 22.0.0 to 22.0.2. - [Release notes](https://github.com/electron/electron/releases) - [Changelog](https://github.com/electron/electron/blob/main/docs/breaking-changes.md) - [Commits](https://github.com/electron/electron/compare/v22.0.0...v22.0.2) --- updated-dependencies: - dependency-name: electron dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Bump eslint from 8.31.0 to 8.32.0 (#3083) Bumps [eslint](https://github.com/eslint/eslint) from 8.31.0 to 8.32.0. - [Release notes](https://github.com/eslint/eslint/releases) - [Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md) - [Commits](https://github.com/eslint/eslint/compare/v8.31.0...v8.32.0) --- updated-dependencies: - dependency-name: eslint dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Translated using Weblate (Polish) Currently translated at 100.0% (659 of 659 strings) Translation: FreeTube/Translations Translate-URL: https://hosted.weblate.org/projects/free-tube/translations/pl/ * Switch from Vue.extend to defineComponent (#3066) * Don't react when selecting the current trending tab (#3070) * Correct argument on `invidiousGetPlaylistInfo` (#3089) `invidiousGetPlaylistInfo` expects a `string`. * ! Fix search view not loading result starting 2nd page via local API (#3074) * Translated using Weblate (Norwegian Bokmål) Currently translated at 89.9% (593 of 659 strings) Translation: FreeTube/Translations Translate-URL: https://hosted.weblate.org/projects/free-tube/translations/nb_NO/ * Translated using Weblate (Norwegian Nynorsk) Currently translated at 100.0% (659 of 659 strings) Translation: FreeTube/Translations Translate-URL: https://hosted.weblate.org/projects/free-tube/translations/nn/ * Map `videos` property on Invidious API response (#3090) `invidiousAPICall` is returning an object with a `videos` property here instead of a list. * Bind directive to function instead of function call (#3091) v-if="showResult(data)" evaluates once v-if="showResult" is reactive; `data` also doesn't need to be passed here because it is already accessible from `this`. * Translated using Weblate (Russian) Currently translated at 100.0% (659 of 659 strings) Translation: FreeTube/Translations Translate-URL: https://hosted.weblate.org/projects/free-tube/translations/ru/ * ! Fix searching (2nd time onward) in channel view broken (#3073) * Use a single import for the brand icons (#3094) * Translated using Weblate (Russian) Currently translated at 100.0% (659 of 659 strings) Translation: FreeTube/Translations Translate-URL: https://hosted.weblate.org/projects/free-tube/translations/ru/ * Change `color: white` to `color: inherit` (I was assuming dark mode) * Add logic for setting proper android version codes * Reduce the space between the top of the screen and the routerView * Make the splash screen look better in dark mode My logic is that light mode users won't mind a dark splash screen, but that dark mode users will definitely mind a light mode splash screen. * Set the splashscreen icon * Add check for un-updated invidious version * Add `Cordova Settings` section ; right now, it 🏠houses a setting to enable the `silent` mode in the background mode plugin. I am leaving it off by default because disabling this notification may cause Android to more readily close the application in the background. * Switch from exact wait time to `splashscreen.show()` This is so much better than just having an arbitrary delay for the splash screen. * Float the share links to the middle on small 📱displays * Remove no longer necessary `if`s * Add `fs-extra` to the dev-dependencies ; also, reorganize the build workflow * Update reference to `move` from `fs-extra` * Add a `shortcut icon` to `index.ejs` * Move icon into `static` * Explicitly set node-version * Update gh actions * Attempt to extract the package JSON data manually * Add step to echo back json * 🤷‍♀️ * Attempt json parse inside gh script * Remove part of the read_package step * 🤷‍♀️ * Replace percent signs with space * Trying a multi-line string * Try `GITHUB_ENV` * Try a new gh action * Rename step * Fix naming * Differentiate development build name * Change `name` to `productName` * Forgot the `Name` in `productName` * Remove hyphen from app name * Testing out modified release pipeline * Fix indenting * Correct the environment * Switch release to environment named release * Fix `asset_path` in upload * remove development suffix from releaseCordova * Adjust release `asset_path` * Adjust `yarn build:cordova` arguments to allow --release * Add log to tell if `release` is being set * Add `--stacktrace` argument * Troubleshoot signing * 🤔 unsure why building the `--release` flag causes signing to fail * 🔨Troubleshoot signing issues * Remove `--release` flag from release for now ; I am having trouble with getting a release build signed. * Modify update checking logic slightly * Switch the development and nightly labels; `development` is now `nightly` and what was `nightly` is now `release`. * Set `buildCordova.yml` to point back to `development` --- .eslintrc.js | 42 +- .github/dependabot.yml | 1 + .github/workflows/autoMerge.yml | 4 +- .github/workflows/build.yml | 4 +- .github/workflows/buildCordova.yml | 106 +- .github/workflows/conflicts.yml | 4 +- .github/workflows/dummy-conflicts.yml | 9 - .github/workflows/release.yml | 2 +- .github/workflows/releaseCordova.yml | 149 +- .github/workflows/stale.yml | 8 +- .gitignore | 3 + .stylelintignore | 7 + .stylelintrc.json | 33 + .vscode/extensions.json | 11 + .vscode/settings.json | 16 + README.md | 32 +- _scripts/CordovaPlugin.js | 44 + _scripts/ProcessLocalesPlugin.js | 15 +- _scripts/build.js | 9 +- _scripts/cordova-build.js | 552 +-- _scripts/cordova-run.js | 35 - _scripts/helpers.js | 32 + _scripts/webpack.cordova.config.js | 195 ++ _scripts/webpack.main.config.js | 6 +- _scripts/webpack.renderer.browser.config.js | 199 -- _scripts/webpack.renderer.config.js | 59 +- _scripts/webpack.web.config.js | 36 +- _scripts/webpack.workers.config.js | 66 - jsconfig.json | 5 + lefthook-local.yml.example | 7 + lefthook.yml | 3 - package.json | 99 +- src/cordova/config.xml | 22 + src/cordova/config.xml.js | 30 + src/cordova/package.js | 40 + src/datastores/handlers/base.js | 8 + src/datastores/index.js | 3 +- src/index.ejs | 12 +- src/main/ImageCache.js | 2 +- src/main/index.js | 263 +- src/renderer/App.js | 105 +- src/renderer/App.vue | 10 +- .../cordova-settings/cordova-settings.js | 23 + .../cordova-settings/cordova-settings.vue | 17 + .../components/data-settings/data-settings.js | 32 +- .../data-settings/data-settings.vue | 12 +- .../distraction-settings.css | 5 + .../distraction-settings.js | 23 +- .../distraction-settings.vue | 18 + .../download-settings/download-settings.js | 4 +- .../download-settings/download-settings.sass | 2 - .../download-settings/download-settings.scss | 3 + .../download-settings/download-settings.vue | 2 +- .../experimental-settings.js | 41 +- .../external-player-settings.js | 4 +- .../external-player-settings.vue | 42 +- .../ft-age-restricted/ft-age-restricted.js | 4 +- .../ft-age-restricted/ft-age-restricted.sass | 14 - .../ft-age-restricted/ft-age-restricted.scss | 19 + .../ft-age-restricted/ft-age-restricted.vue | 2 +- .../components/ft-auto-grid/ft-auto-grid.js | 4 +- .../components/ft-auto-grid/ft-auto-grid.sass | 10 - .../components/ft-auto-grid/ft-auto-grid.scss | 13 + .../components/ft-auto-grid/ft-auto-grid.vue | 2 +- .../components/ft-button/ft-button.js | 8 +- .../components/ft-button/ft-button.vue | 1 + src/renderer/components/ft-card/ft-card.js | 4 +- .../ft-channel-bubble/ft-channel-bubble.js | 19 +- .../ft-channel-bubble/ft-channel-bubble.vue | 9 +- .../ft-element-list/ft-element-list.js | 4 +- .../components/ft-flex-box/ft-flex-box.js | 4 +- .../ft-icon-button/ft-icon-button.js | 6 +- .../ft-icon-button/ft-icon-button.sass | 109 - .../ft-icon-button/ft-icon-button.scss | 131 + .../ft-icon-button/ft-icon-button.vue | 16 +- .../ft-input-tags/ft-input-tags.css | 55 + .../components/ft-input-tags/ft-input-tags.js | 55 + .../ft-input-tags/ft-input-tags.vue | 39 + src/renderer/components/ft-input/ft-input.css | 20 +- src/renderer/components/ft-input/ft-input.js | 107 +- src/renderer/components/ft-input/ft-input.vue | 59 +- .../ft-list-channel/ft-list-channel.js | 34 +- .../ft-list-channel/ft-list-channel.sass | 1 - .../ft-list-channel/ft-list-channel.scss | 6 + .../ft-list-channel/ft-list-channel.vue | 10 +- .../ft-list-dropdown/ft-list-dropdown.css | 18 +- .../ft-list-dropdown/ft-list-dropdown.js | 26 +- .../ft-list-dropdown/ft-list-dropdown.vue | 19 +- .../ft-list-lazy-wrapper.js | 30 +- .../ft-list-lazy-wrapper.vue | 2 +- .../ft-list-playlist/ft-list-playlist.js | 20 +- .../ft-list-playlist/ft-list-playlist.sass | 1 - .../ft-list-playlist/ft-list-playlist.scss | 1 + .../ft-list-playlist/ft-list-playlist.vue | 2 +- .../ft-list-video-lazy/ft-list-video-lazy.js | 55 + .../ft-list-video-lazy/ft-list-video-lazy.vue | 23 + .../components/ft-list-video/ft-list-video.js | 49 +- .../ft-list-video/ft-list-video.sass | 4 - .../ft-list-video/ft-list-video.scss | 5 + .../ft-list-video/ft-list-video.vue | 11 +- .../components/ft-loader/ft-loader.js | 4 +- .../ft-notification-banner.css | 4 + .../ft-notification-banner.js | 7 +- .../ft-notification-banner.vue | 11 +- .../ft-profile-bubble/ft-profile-bubble.js | 4 +- .../ft-profile-channel-list.js | 9 +- .../ft-profile-edit/ft-profile-edit.js | 4 +- .../ft-profile-filter-channels-list.js | 9 +- .../ft-profile-selector.css | 5 +- .../ft-profile-selector.js | 4 +- .../ft-progress-bar/ft-progress-bar.js | 4 +- .../components/ft-prompt/ft-prompt.js | 67 +- .../components/ft-prompt/ft-prompt.vue | 22 +- .../ft-radio-button/ft-radio-button.js | 4 +- .../ft-search-filters/ft-search-filters.js | 4 +- .../components/ft-select/ft-select.css | 1 - .../components/ft-select/ft-select.js | 18 +- .../components/ft-select/ft-select.vue | 4 + .../ft-settings-section.js | 4 +- .../ft-settings-section.sass | 82 - .../ft-settings-section.scss | 116 + .../ft-settings-section.vue | 2 +- .../ft-share-button/ft-share-button.js | 5 +- .../ft-share-button/ft-share-button.sass | 64 - .../ft-share-button/ft-share-button.scss | 82 + .../ft-share-button/ft-share-button.vue | 16 +- .../components/ft-slider/ft-slider.js | 4 +- .../ft-sponsor-block-category.js | 11 +- .../ft-sponsor-block-category.sass | 7 - .../ft-sponsor-block-category.scss | 12 + .../ft-sponsor-block-category.vue | 13 +- .../ft-timestamp-catcher.js | 6 +- src/renderer/components/ft-toast/ft-toast.js | 4 +- src/renderer/components/ft-toast/ft-toast.vue | 4 + .../ft-toggle-switch/ft-toggle-switch.js | 4 +- .../ft-toggle-switch/ft-toggle-switch.sass | 81 - .../ft-toggle-switch/ft-toggle-switch.scss | 88 + .../ft-toggle-switch/ft-toggle-switch.vue | 2 +- .../components/ft-tooltip/ft-tooltip.js | 4 +- .../ft-video-player/ft-video-player.js | 244 +- .../ft-video-player/ft-video-player.vue | 3 + .../general-settings/general-settings.js | 25 +- .../general-settings/general-settings.sass | 8 - .../general-settings/general-settings.scss | 9 + .../general-settings/general-settings.vue | 2 +- .../parental-control-settings.js | 4 +- .../password-dialog/password-dialog.css | 9 + .../password-dialog/password-dialog.js | 27 + .../password-dialog/password-dialog.vue | 19 + .../password-settings/password-settings.css | 3 + .../password-settings/password-settings.js | 42 + .../password-settings/password-settings.vue | 36 + .../player-settings/player-settings.js | 13 +- .../player-settings/player-settings.sass | 12 - .../player-settings/player-settings.scss | 19 + .../player-settings/player-settings.vue | 11 +- .../components/playlist-info/playlist-info.js | 35 +- .../playlist-info/playlist-info.sass | 46 - .../playlist-info/playlist-info.scss | 55 + .../playlist-info/playlist-info.vue | 23 +- .../privacy-settings/privacy-settings.js | 15 +- .../proxy-settings/proxy-settings.js | 6 +- .../side-nav-more-options.css | 3 + .../side-nav-more-options.js | 10 +- .../side-nav-more-options.vue | 96 +- src/renderer/components/side-nav/side-nav.css | 3 +- src/renderer/components/side-nav/side-nav.js | 12 +- src/renderer/components/side-nav/side-nav.vue | 68 +- .../sponsor-block-settings.js | 4 +- .../subscription-settings.js | 4 +- .../theme-settings/theme-settings.js | 6 +- .../theme-settings/theme-settings.vue | 70 +- src/renderer/components/top-nav/top-nav.js | 25 +- src/renderer/components/top-nav/top-nav.sass | 166 - src/renderer/components/top-nav/top-nav.scss | 212 ++ src/renderer/components/top-nav/top-nav.vue | 3 +- .../watch-video-chapters.js | 4 +- .../watch-video-chapters.vue | 1 + .../watch-video-comments.css | 4 +- .../watch-video-comments.js | 106 +- .../watch-video-comments.vue | 73 +- .../watch-video-description.js | 40 +- .../watch-video-info/watch-video-info.js | 23 +- .../watch-video-info/watch-video-info.sass | 92 - .../watch-video-info/watch-video-info.scss | 123 + .../watch-video-info/watch-video-info.vue | 7 +- .../watch-video-live-chat.css | 3 +- .../watch-video-live-chat.js | 216 +- .../watch-video-live-chat.vue | 73 +- .../watch-video-playlist.css | 6 +- .../watch-video-playlist.js | 119 +- .../watch-video-playlist.vue | 40 +- .../watch-video-recommendations.js | 8 +- .../watch-video-recommendations.vue | 2 +- src/renderer/helpers/accessibility.js | 44 + src/renderer/helpers/api/PlayerCache.js | 43 + src/renderer/helpers/api/invidious.js | 112 + src/renderer/helpers/api/local.js | 439 +++ src/renderer/helpers/filesystem.js | 14 + src/renderer/helpers/utils.js | 261 +- src/renderer/i18n/index.js | 41 +- src/renderer/main.js | 12 +- src/renderer/router/index.js | 48 +- src/renderer/sass-partials/_ft-list-item.sass | 237 -- src/renderer/scss-partials/_ft-list-item.scss | 294 ++ src/renderer/store/modules/index.js | 26 +- src/renderer/store/modules/invidious.js | 88 +- src/renderer/store/modules/settings.js | 28 +- src/renderer/store/modules/utils.js | 248 +- src/renderer/store/modules/ytdl.js | 254 -- src/renderer/themes.css | 56 +- src/renderer/views/About/About.js | 6 +- src/renderer/views/About/About.sass | 50 - src/renderer/views/About/About.scss | 62 + src/renderer/views/About/About.vue | 3 +- src/renderer/views/Channel/Channel.css | 53 +- src/renderer/views/Channel/Channel.js | 81 +- src/renderer/views/Channel/Channel.vue | 112 +- src/renderer/views/History/History.js | 8 +- src/renderer/views/Playlist/Playlist.css | 5 + src/renderer/views/Playlist/Playlist.js | 134 +- src/renderer/views/Playlist/Playlist.vue | 18 +- src/renderer/views/Popular/Popular.js | 15 +- src/renderer/views/ProfileEdit/ProfileEdit.js | 4 +- .../views/ProfileSettings/ProfileSettings.js | 4 +- src/renderer/views/Search/Search.css | 4 + src/renderer/views/Search/Search.js | 134 +- src/renderer/views/Search/Search.vue | 4 + src/renderer/views/Settings/Settings.js | 30 +- src/renderer/views/Settings/Settings.vue | 60 +- .../SubscribedChannels/SubscribedChannels.css | 5 +- .../SubscribedChannels/SubscribedChannels.js | 28 +- .../SubscribedChannels/SubscribedChannels.vue | 21 +- .../views/Subscriptions/Subscriptions.js | 20 +- src/renderer/views/Trending/Trending.js | 47 +- .../views/UserPlaylists/UserPlaylists.js | 8 +- src/renderer/views/Watch/Watch.js | 964 +++--- src/renderer/views/Watch/Watch.sass | 110 - src/renderer/views/Watch/Watch.scss | 149 + src/renderer/views/Watch/Watch.vue | 8 +- static/geolocations/bg/countries.json | 2 - static/geolocations/ja/countries.json | 2 +- static/geolocations/uk/countries.json | 2 +- static/geolocations/zh/countries.json | 2 +- {_icons => static}/icon.ico | Bin static/locales/ar.yaml | 27 + static/locales/bg.yaml | 43 +- static/locales/cs.yaml | 162 +- static/locales/de-DE.yaml | 34 +- static/locales/el.yaml | 147 +- static/locales/en-US.yaml | 33 +- static/locales/en_GB.yaml | 27 + static/locales/es-MX.yaml | 5 + static/locales/es.yaml | 30 + static/locales/fi.yaml | 25 + static/locales/fr-FR.yaml | 30 + static/locales/gl.yaml | 35 +- static/locales/he.yaml | 27 + static/locales/hi.yaml | 257 +- static/locales/hr.yaml | 30 +- static/locales/hu.yaml | 28 + static/locales/is.yaml | 26 + static/locales/it.yaml | 62 +- static/locales/ja.yaml | 33 +- static/locales/lt.yaml | 2 +- static/locales/nb_NO.yaml | 118 +- static/locales/nn.yaml | 309 +- static/locales/pl.yaml | 47 +- static/locales/pt-BR.yaml | 28 + static/locales/pt-PT.yaml | 41 +- static/locales/pt.yaml | 33 +- static/locales/ru.yaml | 125 +- static/locales/tr.yaml | 27 + static/locales/uk.yaml | 27 + static/locales/zh-CN.yaml | 26 +- static/locales/zh-TW.yaml | 24 + static/manifest.json | 19 - static/manifest.webmanifest | 15 + yarn.lock | 3023 ++++++++++------- 279 files changed, 9755 insertions(+), 6201 deletions(-) delete mode 100644 .github/workflows/dummy-conflicts.yml create mode 100644 .stylelintignore create mode 100644 .stylelintrc.json create mode 100644 .vscode/extensions.json create mode 100644 .vscode/settings.json create mode 100644 _scripts/CordovaPlugin.js delete mode 100644 _scripts/cordova-run.js create mode 100644 _scripts/helpers.js create mode 100644 _scripts/webpack.cordova.config.js delete mode 100644 _scripts/webpack.renderer.browser.config.js delete mode 100644 _scripts/webpack.workers.config.js create mode 100644 jsconfig.json create mode 100644 lefthook-local.yml.example create mode 100644 src/cordova/config.xml create mode 100644 src/cordova/config.xml.js create mode 100644 src/cordova/package.js create mode 100644 src/renderer/components/cordova-settings/cordova-settings.js create mode 100644 src/renderer/components/cordova-settings/cordova-settings.vue create mode 100644 src/renderer/components/distraction-settings/distraction-settings.css delete mode 100644 src/renderer/components/download-settings/download-settings.sass create mode 100644 src/renderer/components/download-settings/download-settings.scss delete mode 100644 src/renderer/components/ft-age-restricted/ft-age-restricted.sass create mode 100644 src/renderer/components/ft-age-restricted/ft-age-restricted.scss delete mode 100644 src/renderer/components/ft-auto-grid/ft-auto-grid.sass create mode 100644 src/renderer/components/ft-auto-grid/ft-auto-grid.scss delete mode 100644 src/renderer/components/ft-icon-button/ft-icon-button.sass create mode 100644 src/renderer/components/ft-icon-button/ft-icon-button.scss create mode 100644 src/renderer/components/ft-input-tags/ft-input-tags.css create mode 100644 src/renderer/components/ft-input-tags/ft-input-tags.js create mode 100644 src/renderer/components/ft-input-tags/ft-input-tags.vue delete mode 100644 src/renderer/components/ft-list-channel/ft-list-channel.sass create mode 100644 src/renderer/components/ft-list-channel/ft-list-channel.scss delete mode 100644 src/renderer/components/ft-list-playlist/ft-list-playlist.sass create mode 100644 src/renderer/components/ft-list-playlist/ft-list-playlist.scss create mode 100644 src/renderer/components/ft-list-video-lazy/ft-list-video-lazy.js create mode 100644 src/renderer/components/ft-list-video-lazy/ft-list-video-lazy.vue delete mode 100644 src/renderer/components/ft-list-video/ft-list-video.sass create mode 100644 src/renderer/components/ft-list-video/ft-list-video.scss delete mode 100644 src/renderer/components/ft-settings-section/ft-settings-section.sass create mode 100644 src/renderer/components/ft-settings-section/ft-settings-section.scss delete mode 100644 src/renderer/components/ft-share-button/ft-share-button.sass create mode 100644 src/renderer/components/ft-share-button/ft-share-button.scss delete mode 100644 src/renderer/components/ft-sponsor-block-category/ft-sponsor-block-category.sass create mode 100644 src/renderer/components/ft-sponsor-block-category/ft-sponsor-block-category.scss delete mode 100644 src/renderer/components/ft-toggle-switch/ft-toggle-switch.sass create mode 100644 src/renderer/components/ft-toggle-switch/ft-toggle-switch.scss delete mode 100644 src/renderer/components/general-settings/general-settings.sass create mode 100644 src/renderer/components/general-settings/general-settings.scss create mode 100644 src/renderer/components/password-dialog/password-dialog.css create mode 100644 src/renderer/components/password-dialog/password-dialog.js create mode 100644 src/renderer/components/password-dialog/password-dialog.vue create mode 100644 src/renderer/components/password-settings/password-settings.css create mode 100644 src/renderer/components/password-settings/password-settings.js create mode 100644 src/renderer/components/password-settings/password-settings.vue delete mode 100644 src/renderer/components/player-settings/player-settings.sass create mode 100644 src/renderer/components/player-settings/player-settings.scss delete mode 100644 src/renderer/components/playlist-info/playlist-info.sass create mode 100644 src/renderer/components/playlist-info/playlist-info.scss delete mode 100644 src/renderer/components/top-nav/top-nav.sass create mode 100644 src/renderer/components/top-nav/top-nav.scss delete mode 100644 src/renderer/components/watch-video-info/watch-video-info.sass create mode 100644 src/renderer/components/watch-video-info/watch-video-info.scss create mode 100644 src/renderer/helpers/accessibility.js create mode 100644 src/renderer/helpers/api/PlayerCache.js create mode 100644 src/renderer/helpers/api/invidious.js create mode 100644 src/renderer/helpers/api/local.js create mode 100644 src/renderer/helpers/filesystem.js delete mode 100644 src/renderer/sass-partials/_ft-list-item.sass create mode 100644 src/renderer/scss-partials/_ft-list-item.scss delete mode 100644 src/renderer/store/modules/ytdl.js delete mode 100644 src/renderer/views/About/About.sass create mode 100644 src/renderer/views/About/About.scss delete mode 100644 src/renderer/views/Watch/Watch.sass create mode 100644 src/renderer/views/Watch/Watch.scss rename {_icons => static}/icon.ico (100%) delete mode 100644 static/manifest.json create mode 100644 static/manifest.webmanifest diff --git a/.eslintrc.js b/.eslintrc.js index be982e46f7c8b..f50b44c1d73ff 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -11,13 +11,25 @@ module.exports = { // https://eslint.org/docs/user-guide/configuring#specifying-parser parser: 'vue-eslint-parser', - // https://vuejs.github.io/eslint-plugin-vue/user-guide/#faq + // https://eslint.vuejs.org/user-guide/#faq parserOptions: { - parser: 'babel-eslint', - ecmaVersion: 2018, - sourceType: 'module' + parser: '@babel/eslint-parser', + ecmaVersion: 2022, + sourceType: 'module', + requireConfigFile: false }, + overrides: [ + { + files: ['*.json'], + parser: 'jsonc-eslint-parser', + rules: { + 'no-tabs': 'off', + 'comma-spacing': 'off' + } + } + ], + // https://eslint.org/docs/user-guide/configuring#extending-configuration-files // order matters: from least important to most important in terms of overriding // Prettier + Vue: https://medium.com/@gogl.alex/how-to-properly-set-up-eslint-with-prettier-for-vue-or-nuxt-in-vscode-e42532099a9c @@ -25,11 +37,13 @@ module.exports = { 'prettier', 'eslint:recommended', 'plugin:vue/recommended', - 'standard' + 'standard', + 'plugin:jsonc/recommended-with-json', + // 'plugin:vuejs-accessibility/recommended' // uncomment once issues are fixed ], // https://eslint.org/docs/user-guide/configuring#configuring-plugins - plugins: ['vue'], + plugins: ['vue', 'vuejs-accessibility', 'n', 'unicorn'], rules: { 'space-before-function-paren': 'off', @@ -38,8 +52,22 @@ module.exports = { 'no-console': ['error', { allow: ['warn', 'error'] }], 'no-unused-vars': 'warn', 'no-undef': 'warn', + 'object-shorthand': 'off', 'vue/no-template-key': 'warn', 'vue/no-useless-template-attributes': 'off', - 'vue/multi-word-component-names': 'off' + 'vue/multi-word-component-names': 'off', + 'vuejs-accessibility/no-onchange': 'off', + 'vuejs-accessibility/label-has-for': ['error', { + required: { + some: ['nesting', 'id'] + } + }], + 'n/no-callback-literal': 'warn', + 'n/no-path-concat': 'warn', + 'unicorn/better-regex': 'error', + 'unicorn/no-array-push-push': 'error', + 'unicorn/prefer-keyboard-event-key': 'error', + 'unicorn/prefer-regexp-test': 'error', + 'unicorn/prefer-string-replace-all': 'error' } } diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 396fc42b3a9af..c90fb342d97af 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -7,6 +7,7 @@ updates: labels: - "PR: waiting for review" - "PR: dependencies" + open-pull-requests-limit: 10 - package-ecosystem: "github-actions" directory: "/" schedule: diff --git a/.github/workflows/autoMerge.yml b/.github/workflows/autoMerge.yml index dc6ced128b2cf..a5391e7245551 100644 --- a/.github/workflows/autoMerge.yml +++ b/.github/workflows/autoMerge.yml @@ -1,7 +1,7 @@ name: Auto Merge PR on: pull_request_target: - types: [opened, synchronize, reopened, auto_merge_disabled] + types: [opened, synchronize, reopened, auto_merge_disabled, ready_for_review] jobs: build: @@ -9,7 +9,7 @@ jobs: steps: - name: Auto Merge PR - if: contains(${{ github.event.pull_request.base.ref }}, 'development') || contains(${{ github.event.pull_request.base.ref }}, 'RC') + if: github.event.pull_request.draft == false && (contains(${{ github.event.pull_request.base.ref }}, 'development') || contains(${{ github.event.pull_request.base.ref }}, 'RC')) run: | echo ${{ secrets.PUSH_TOKEN }} >> auth.txt gh auth login --with-token < auth.txt diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 077dad8147e4e..f6a14f1678372 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -57,7 +57,7 @@ jobs: - run: yarn run ci - run: yarn run lint - name: Get Version Number - uses: nyaayaya/package-version@v1 + uses: jozsefsallai/node-package-version@v1.0.4 with: path: 'package.json' follow-symlinks: false @@ -84,7 +84,7 @@ jobs: # script: if ${{ env.IS_DEV }} then echo "::set-output name=VERSION_NUMBER::${{ env.VERSION_NUMBER_NIGHTLY }}" else echo "::set-output name=VERSION_NUMBER::${{ env.VERSION_NUMBER }}" fi - name: Update package.json version - uses: jossef/action-set-json-field@v2 + uses: jossef/action-set-json-field@v2.1 with: file: package.json field: version diff --git a/.github/workflows/buildCordova.yml b/.github/workflows/buildCordova.yml index b6cadeeff7e8a..7bb71e4d17016 100644 --- a/.github/workflows/buildCordova.yml +++ b/.github/workflows/buildCordova.yml @@ -9,66 +9,79 @@ on: jobs: build: - strategy: - matrix: - node-version: [16.x] - runtime: [ linux-x64 ] - include: - - runtime: linux-x64 - os: ubuntu-18.04 - - runs-on: ${{ matrix.os }} + runs-on: ubuntu-latest environment: development steps: - - uses: actions/checkout@v2 - - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v2 + - uses: actions/checkout@v3 + - name: Use Node.js + uses: actions/setup-node@v3 with: - node-version: ${{ matrix.node-version }} + node-version: 16.x cache: "yarn" - - run: npm run ci + - name: 🧶 Yarn install + run: yarn ci - - name: Lint code - run: npm run lint + - name: 🔍Lint code + run: yarn lint - - name: Get Version Number - uses: nyaayaya/package-version@v1 - with: - path: 'package.json' - follow-symlinks: false + - name: 📚Read package.json + id: pkg + uses: jaywcjlove/github-action-package@v1.3.0 - name: Set Version Number Variable id: versionNumber uses: actions/github-script@v6 - env: - VERSION_NUMBER_DEVELOPMENT: ${{ env.PACKAGE_VERSION }}-development-${{ github.run_number }} with: result-encoding: string script: | - return "${{ env.VERSION_NUMBER_DEVELOPMENT }}" + return '${{ steps.pkg.outputs.version }}-nightly-${{ github.run_number }}' + - name: Set App ID Variable + id: appId + uses: actions/github-script@v6 + with: + result-encoding: string + script: | + return '${{ steps.pkg.outputs.name }}.nightly' - - name: Update package.json version - uses: jossef/action-set-json-field@v2 + - name: ⬆ Update package.json version + uses: jossef/action-set-json-field@v2.1 with: file: package.json field: version value: ${{ steps.versionNumber.outputs.result }} + - name: ⬆ Update package.json app environment + uses: jossef/action-set-json-field@v2.1 + with: + file: package.json + field: name + value: ${{ steps.appId.outputs.result }} + + + - name: ⬆ Update package.json product name + uses: jossef/action-set-json-field@v2.1 + with: + file: package.json + field: productName + value: ${{ steps.pkg.outputs.productName }} Nightly - - name: Install libarchive-tools - if: startsWith(matrix.os, 'ubuntu') && startsWith(matrix.runtime, 'linux-x64') - run: sudo apt -y install libarchive-tools; echo "Version Number ${{ toJson(job) }} ${{ toJson(needs) }}" + - name: 📦 Pack for 🕸web with Node.js + run: yarn pack:web - - name: Pack with Node.js ${{ matrix.node-version}} - if: contains(matrix.runtime, 'x64') - run: npm run pack:browser + - name: 📡 Upload PWA Artifact + uses: actions/upload-artifact@v3 + with: + name: freetube-${{ steps.versionNumber.outputs.result }}-PWA + path: dist/web - - name: Setup Android SDK Tools - if: contains(matrix.runtime, 'x64') + - name: 🚧 Setup Android SDK Tools uses: android-actions/setup-android@v2.0.9 - - name: Fetch keystore from secrets + - name: 📦 Pack for 📱Android with Node.js & Cordova + run: yarn pack:cordova + + - name: 🦴 Fetch keystore from secrets run: | while read -r line; do @@ -76,24 +89,11 @@ jobs: done <<< '${{ secrets.KEYSTORE }}' gpg -d --passphrase '${{ secrets.KEYSTORE_PASSWORD }}' --batch freetube.keystore.asc >> freetube.keystore - - name: Build APK with Cordova with Node.js ${{ matrix.node-version}} - if: startsWith(matrix.os, 'ubuntu') && startsWith(matrix.runtime, 'linux-x64') - run: npm run build:cordova freetube-${{ steps.versionNumber.outputs.result }}.apk cordova ./freetube.keystore ${{ secrets.KEYSTORE_PASSWORD }} - - - name: Upload Cordova APK Artifact - uses: actions/upload-artifact@v3 - if: startsWith(matrix.os, 'ubuntu') && startsWith(matrix.runtime, 'linux-x64') - with: - name: freetube-${{ steps.versionNumber.outputs.result }}.apk - path: build/freetube-${{ steps.versionNumber.outputs.result }}.apk - - - name: Build HTML5 with Node.js ${{ matrix.node-version}} - if: startsWith(matrix.os, 'ubuntu') && startsWith(matrix.runtime, 'linux-x64') - run: npm run build:cordova freetube browser + - name: 👷‍♀️ Build APK with Cordova with Node.js + run: yarn build:cordova freetube-${{ steps.versionNumber.outputs.result }}.apk ./freetube.keystore ${{ secrets.KEYSTORE_PASSWORD }} - - name: Upload Cordova HTML5 Artifact + - name: 📡 Upload Cordova APK Artifact uses: actions/upload-artifact@v3 - if: startsWith(matrix.os, 'ubuntu') && startsWith(matrix.runtime, 'linux-x64') with: - name: freetube-${{ steps.versionNumber.outputs.result }}-HTML5 - path: build/freetube + name: freetube-${{ steps.versionNumber.outputs.result }}-Android.apk + path: dist/freetube-${{ steps.versionNumber.outputs.result }}.apk diff --git a/.github/workflows/conflicts.yml b/.github/workflows/conflicts.yml index c0f2210258c8d..cceb14d31c46f 100644 --- a/.github/workflows/conflicts.yml +++ b/.github/workflows/conflicts.yml @@ -7,9 +7,6 @@ on: # In `pull_request` we wouldn't be able to change labels of fork PRs pull_request_target: types: [synchronize] - workflow_run: - workflows: ['Dummy workflow for conflicts'] - types: [requested] jobs: main: @@ -23,3 +20,4 @@ jobs: repoToken: "${{ secrets.GITHUB_TOKEN }}" commentOnDirty: "This pull request has conflicts, please resolve those before we can evaluate the pull request." commentOnClean: "Conflicts have been resolved. A maintainer will review the pull request shortly." + diff --git a/.github/workflows/dummy-conflicts.yml b/.github/workflows/dummy-conflicts.yml deleted file mode 100644 index cc4ba42504ee4..0000000000000 --- a/.github/workflows/dummy-conflicts.yml +++ /dev/null @@ -1,9 +0,0 @@ -name: Dummy workflow for conflicts -on: - pull_request_review: - types: [submitted] -jobs: - dummy: - runs-on: ubuntu-latest - steps: - - run: echo "this is a dummy workflow that triggers a workflow_run; it's necessary because otherwise the repo secrets will not be in scope for externally forked pull requests" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 000f9172a39b1..a2b08ad6e3e3b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -58,7 +58,7 @@ jobs: - run: yarn run lint - name: Get Version Number - uses: nyaayaya/package-version@v1 + uses: jozsefsallai/node-package-version@v1.0.4 with: path: 'package.json' follow-symlinks: false diff --git a/.github/workflows/releaseCordova.yml b/.github/workflows/releaseCordova.yml index fa06c020f216f..59706302f11b2 100644 --- a/.github/workflows/releaseCordova.yml +++ b/.github/workflows/releaseCordova.yml @@ -2,82 +2,86 @@ # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions name: Release Cordova + on: push: branches: [ release ] jobs: - build: - strategy: - matrix: - node-version: [16.x] - runtime: [ linux-x64 ] - include: - - runtime: linux-x64 - os: ubuntu-18.04 - - runs-on: ${{ matrix.os }} - environment: nightly + release: + runs-on: ubuntu-latest + environment: release steps: - - uses: actions/checkout@v2 - - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v2 + - uses: actions/checkout@v3 + - name: Use Node.js + uses: actions/setup-node@v3 with: - node-version: ${{ matrix.node-version }} + node-version: 16.x cache: "yarn" - - run: npm run ci - - name: Get Version Number - uses: nyaayaya/package-version@v1 - with: - path: 'package.json' - follow-symlinks: false + - name: 🧶 Yarn install + run: yarn ci + + - name: 🔍Lint code + run: yarn lint + + - name: 📚Read package.json + id: pkg + uses: jaywcjlove/github-action-package@v1.3.0 - name: Set Version Number Variable id: versionNumber uses: actions/github-script@v6 - env: - IS_DEV: ${{ contains(github.ref, 'development') }} - IS_NIGHTLY: ${{ contains(github.ref, 'release') }} - VERSION_NUMBER_DEVELOPMENT: ${{ env.PACKAGE_VERSION }}-development-${{ github.run_number }} - VERSION_NUMBER_NIGHTLY: ${{ env.PACKAGE_VERSION }}-nightly-${{ github.run_number }} - VERSION_NUMBER_RELEASE: ${{ env.PACKAGE_VERSION }}.${{ github.run_number }} with: result-encoding: string script: | - if (${{ env.IS_DEV }}) { - return "${{ env.VERSION_NUMBER_DEVELOPMENT }}" - } else if (${{ env.IS_NIGHTLY }}) { - return "${{ env.VERSION_NUMBER_NIGHTLY }}" - } else { - return "${{env.VERSION_NUMBER_RELEASE }}" - } - # script: if ${{ env.IS_DEV }} then echo ":token :set-output name=VERSION_NUMBER::${{ env.VERSION_NUMBER_NIGHTLY }}" else echo "::set-output name=VERSION_NUMBER::${{ env.VERSION_NUMBER }}" fi - - - name: Update package.json version - uses: jossef/action-set-json-field@v2 + return '${{ steps.pkg.outputs.version }}.${{ github.run_number }}' + - name: Set App ID Variable + id: appId + uses: actions/github-script@v6 + with: + result-encoding: string + script: | + return '${{ steps.pkg.outputs.name }}' + + - name: ⬆ Update package.json version + uses: jossef/action-set-json-field@v2.1 with: file: package.json field: version value: ${{ steps.versionNumber.outputs.result }} + - name: ⬆ Update package.json app environment + uses: jossef/action-set-json-field@v2.1 + with: + file: package.json + field: name + value: ${{ steps.appId.outputs.result }} + - - name: Install libarchive-tools - if: startsWith(matrix.os, 'ubuntu') && startsWith(matrix.runtime, 'linux-x64') - run: sudo apt -y install libarchive-tools; echo "Version Number ${{ toJson(job) }} ${{ toJson(needs) }}" + - name: ⬆ Update package.json product name + uses: jossef/action-set-json-field@v2.1 + with: + file: package.json + field: productName + value: ${{ steps.pkg.outputs.productName }} - - name: Lint code - run: npm run lint + - name: 📦 Pack for 🕸web with Node.js + run: yarn pack:web - - name: Pack with Node.js ${{ matrix.node-version}} - if: contains(matrix.runtime, 'x64') - run: npm run pack:browser + - name: 📡 Upload PWA Artifact + uses: actions/upload-artifact@v3 + with: + name: freetube-${{ steps.versionNumber.outputs.result }}-PWA + path: dist/web - - name: Setup Android SDK Tools - if: contains(matrix.runtime, 'x64') + - name: 🚧 Setup Android SDK Tools uses: android-actions/setup-android@v2.0.9 - - name: Fetch keystore from secrets + - name: 📦 Pack for 📱Android with Node.js & Cordova + run: yarn pack:cordova + + - name: 🦴 Fetch keystore from secrets run: | while read -r line; do @@ -85,43 +89,28 @@ jobs: done <<< '${{ secrets.KEYSTORE }}' gpg -d --passphrase '${{ secrets.KEYSTORE_PASSWORD }}' --batch freetube.keystore.asc >> freetube.keystore - - name: Build APK with Cordova with Node.js ${{ matrix.node-version}} - if: startsWith(matrix.os, 'ubuntu') && startsWith(matrix.runtime, 'linux-x64') - run: npm run build:cordova freetube-${{ steps.versionNumber.outputs.result }}.apk cordova ./freetube.keystore ${{ secrets.KEYSTORE_PASSWORD }} + - name: 👷‍♀️ Build APK with Cordova with Node.js + run: yarn build:cordova freetube-${{ steps.versionNumber.outputs.result }}.apk ./freetube.keystore ${{ secrets.KEYSTORE_PASSWORD }} - - name: Upload Cordova APK Artifact + - name: 📡 Upload Cordova APK Artifact uses: actions/upload-artifact@v3 - if: startsWith(matrix.os, 'ubuntu') && startsWith(matrix.runtime, 'linux-x64') with: - name: freetube-${{ steps.versionNumber.outputs.result }}.apk - path: build/freetube-${{ steps.versionNumber.outputs.result }}.apk - - - name: Build HTML5 with Node.js ${{ matrix.node-version}} - if: startsWith(matrix.os, 'ubuntu') && startsWith(matrix.runtime, 'linux-x64') - run: npm run build:cordova freetube browser + name: freetube-${{ steps.versionNumber.outputs.result }}-Android.apk + path: dist/freetube-${{ steps.versionNumber.outputs.result }}.apk - - name: Setup Zip Action - if: startsWith(matrix.os, 'ubuntu') && startsWith(matrix.runtime, 'linux-x64') + - name: 🔨 Setup Zip Action uses: montudor/action-zip@v1.0.0 - - name: Zip output - if: startsWith(matrix.os, 'ubuntu') && startsWith(matrix.runtime, 'linux-x64') + - name: 🤐 Zip output run: zip -qq -r freetube-${{ steps.versionNumber.outputs.result }}.zip * - working-directory: build/freetube/ - - - name: Upload Cordova HTML5 Artifact - uses: actions/upload-artifact@v3 - if: startsWith(matrix.os, 'ubuntu') && startsWith(matrix.runtime, 'linux-x64') - with: - name: freetube-${{ steps.versionNumber.outputs.result }}.zip - path: build/freetube/freetube-${{ steps.versionNumber.outputs.result }}.zip + working-directory: dist/web/ - name: Create release body run: | echo "${{ github.event.head_commit.message }}" >> release.txt echo "" >> release.txt - - name: Create Draft Release + - name: 📝 Create Draft Release id: create_release uses: actions/create-release@v1 env: @@ -133,28 +122,22 @@ jobs: prerelease: false body_path: release.txt - - name: Upload HTML5 Artifact to Release + - name: ⬆ Upload HTML5 Artifact to Release uses: actions/upload-release-asset@v1.0.1 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: upload_url: ${{ steps.create_release.outputs.upload_url }} - asset_path: build/freetube/freetube-${{ steps.versionNumber.outputs.result }}.zip + asset_path: dist/web/freetube-${{ steps.versionNumber.outputs.result }}.zip asset_name: freetube-${{ steps.versionNumber.outputs.result }}-pwa.zip asset_content_type: application/zip - - name: Upload Android APK Artifact to Release + - name: ⬆ Upload Android APK Artifact to Release uses: actions/upload-release-asset@v1.0.1 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: upload_url: ${{ steps.create_release.outputs.upload_url }} - asset_path: build/freetube-${{ steps.versionNumber.outputs.result }}.apk + asset_path: dist/freetube-${{ steps.versionNumber.outputs.result }}.apk asset_name: freetube-${{ steps.versionNumber.outputs.result }}-android.apk asset_content_type: application/apk - - - uses: eregon/publish-release@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - release_id: ${{ steps.create_release.outputs.id }} diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index 02a937fe39dfe..04fa47539ab2e 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -3,11 +3,15 @@ on: schedule: - cron: '30 1 * * *' +permissions: + issues: write + pull-requests: write + jobs: stale: runs-on: ubuntu-latest steps: - - uses: actions/stale@v6 + - uses: actions/stale@v7 with: stale-issue-message: 'This issue is stale because it has been open 28 days with no activity. Remove stale label or comment or this will be closed in 7 days.' stale-pr-message: 'This PR is stale because it has been open 28 days with no activity. Remove stale label or comment or this will be closed in 14 days.' @@ -19,3 +23,5 @@ jobs: days-before-pr-close: 14 stale-issue-label: 'U: stale' stale-pr-label: 'PR: stale' + exempt-pr-labels: 'PR: WIP' + exempt-issue-labels: 'enhancement' diff --git a/.gitignore b/.gitignore index 79dfea94c23f0..2dbcd0748b75a 100644 --- a/.gitignore +++ b/.gitignore @@ -25,3 +25,6 @@ __coverage__ csak-timelog.json .idea/ debug/ + +# Lefthook +lefthook-local.yml diff --git a/.stylelintignore b/.stylelintignore new file mode 100644 index 0000000000000..18256bd0f5487 --- /dev/null +++ b/.stylelintignore @@ -0,0 +1,7 @@ +src/data/ +src/datastores/ +src/main/ +src/renderer/videoJS.css +dist/ +static/ +node_modules/ diff --git a/.stylelintrc.json b/.stylelintrc.json new file mode 100644 index 0000000000000..bf2da3b109eeb --- /dev/null +++ b/.stylelintrc.json @@ -0,0 +1,33 @@ +{ + "plugins": ["stylelint-high-performance-animation", "@double-great/stylelint-a11y"], + "extends": ["stylelint-config-standard", "stylelint-config-sass-guidelines"], + "overrides": [ + { + "files": ["**/*.scss"], + "customSyntax": "postcss-scss", + "rules": { + "max-nesting-depth": null, + "selector-max-compound-selectors": null + } + }, + { + "files": ["**/*.css"], + "rules": { + } + } + ], + "rules": { + "selector-class-pattern": null, + "selector-id-pattern": null, + "plugin/no-low-performance-animation-properties": null, + "selector-pseudo-class-no-unknown": [ + true, + { + "ignorePseudoClasses": ["deep"] + } + ], + "a11y/no-outline-none": true, + "a11y/selector-pseudo-class-focus": true, + "a11y/font-size-is-readable": true + } +} diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000000000..5dc5d72c0a43a --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,11 @@ +{ + "recommendations": [ + "editorconfig.editorconfig", + "dbaeumer.vscode-eslint", + "stylelint.vscode-stylelint", + "syler.sass-indented", + "redhat.vscode-yaml", + "vue.volar", + "eamodio.gitlens" + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000000000..a48f03e30aeb2 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,16 @@ +{ + "stylelint.packageManager": "yarn", + "stylelint.snippet": [ + "css", + "less", + "postcss", + "sass", + "scss" + ], + "stylelint.validate": [ + "css", + "less", + "postcss", + "scss" + ] +} diff --git a/README.md b/README.md index 0ce199f377edf..02a38f0791aaf 100644 --- a/README.md +++ b/README.md @@ -77,26 +77,24 @@ Builds are automatically created from changes to our development branch via [Git The first build with a green check mark is the latest build. You will need to have a GitHub account to download these builds. ## How to build and test - +### Commands for the Android APK ```bash - yarn pack:browser - # This creates the cordova project, - # copies the dist folder, browserifies - # it, and replaces a bunch of functions - # with cordova equivalents. - # After your first build, most of the - # build components will be recycled - # from that build. + # 📦 Packs the project using `webpack.cordova.config.js` + yarn pack:cordova + # 🏗 Builds the debug APK and launches it on a connected device + yarn run:cordova + # 🚧 Builds the development APK yarn build:cordova - # This opens up the cordova application - # in a web browser - yarn run:cordova browser - # This opens up the cordova application - # on an android device connected with - # debugging enabled - yarn run:cordova android + # 🏦 Builds the release APK + yarn build:cordova --release +``` +### Commands for the PWA (progressive web app) +```bash + # 🐛 Debugs the project using `webpack.web.config.js` + yarn dev:web + # 📦 Packs the project using `webpack.web.config.js` + yarn pack:web ``` - ## Contributing **NOTICE: MOST CHANGES SHOULD PROBABLY BE MADE TO [UPSTREAM](https://www.github.com/freetubeapp/freetube) UNLESS DIRECTLY RELATED TO CORDOVA CODE OR WORKFLOWS.** diff --git a/_scripts/CordovaPlugin.js b/_scripts/CordovaPlugin.js new file mode 100644 index 0000000000000..7c10dae48ac60 --- /dev/null +++ b/_scripts/CordovaPlugin.js @@ -0,0 +1,44 @@ + +// #region Imports +const { mkdir, writeFile } = require('fs/promises') +const fse = require('fs-extra') +const path = require('path') +const util = require('util') +const copy = util.promisify(fse.cp) +const exists = fse.existsSync +const { execWithLiveOutput } = require('./helpers') +// #endregion + +class CordovaPlugin { + apply(compiler) { + compiler.hooks.afterDone.tap('CordovaPlugin', async (afterDone) => { + const wwwRoot = afterDone.compilation.options.output.path + const outputDirectory = path.join(wwwRoot, '..') + const configXML = await require('../src/cordova/config.xml.js') + const packageJSON = require('../src/cordova/package') + + if (!exists(path.join(outputDirectory, 'node_modules'))) { + await writeFile(path.join(outputDirectory, 'package.json'), JSON.stringify(packageJSON, null, 2)) + await writeFile(path.join(outputDirectory, 'config.xml'), configXML.string) + // Copy the icons into the cordova directory + await mkdir(path.join(outputDirectory, 'res')) + await mkdir(path.join(outputDirectory, 'res', 'icon')) + await copy(path.join(__dirname, '..', '_icons', '.icon-set'), path.join(outputDirectory, 'res', 'icon', 'android'), { recursive: true, force: true }) + await copy(path.join(__dirname, '..', '_icons', 'icon.svg'), path.join(outputDirectory, 'res', 'icon', 'android', 'background.xml')) + // These next commands require the environment to be development + const environment = process.env.NODE_ENV + process.env.NODE_ENV = 'development' + // Install all of the cordova plugins + await execWithLiveOutput(`cd ${outputDirectory} && yarn install`) + // Restore the platform specific data + await execWithLiveOutput(`cd ${outputDirectory} && yarn restore`) + process.env.NODE_ENV = environment + } else { + await copy(path.join(__dirname, '..', '_icons', '.icon-set'), path.join(outputDirectory, 'res', 'icon', 'android'), { recursive: true, force: true }) + await copy(path.join(__dirname, '..', '_icons', 'icon.svg'), path.join(outputDirectory, 'res', 'icon', 'android', 'background.xml')) + } + }) + } +} + +module.exports = CordovaPlugin diff --git a/_scripts/ProcessLocalesPlugin.js b/_scripts/ProcessLocalesPlugin.js index 4d471cd21014e..988d9e4df9f84 100644 --- a/_scripts/ProcessLocalesPlugin.js +++ b/_scripts/ProcessLocalesPlugin.js @@ -1,7 +1,10 @@ const { existsSync, readFileSync } = require('fs') -const { brotliCompressSync, constants } = require('zlib') +const { brotliCompress, constants } = require('zlib') +const { promisify } = require('util') const { load: loadYaml } = require('js-yaml') +const brotliCompressAsync = promisify(brotliCompress) + class ProcessLocalesPlugin { constructor(options = {}) { this.compress = !!options.compress @@ -34,9 +37,9 @@ class ProcessLocalesPlugin { }, async (_assets) => { const promises = [] - + for (const { locale, data } of this.locales) { - promises.push(new Promise((resolve) => { + promises.push(new Promise(async (resolve) => { if (Object.prototype.hasOwnProperty.call(data, 'Locale Name')) { delete data['Locale Name'] } @@ -46,7 +49,7 @@ class ProcessLocalesPlugin { if (this.compress) { filename += '.br' - output = this.compressLocale(output) + output = await this.compressLocale(output) } compilation.emitAsset( @@ -78,10 +81,10 @@ class ProcessLocalesPlugin { } } - compressLocale(data) { + async compressLocale(data) { const buffer = Buffer.from(data, 'utf-8') - return brotliCompressSync(buffer, { + return await brotliCompressAsync(buffer, { params: { [constants.BROTLI_PARAM_MODE]: constants.BROTLI_MODE_TEXT, [constants.BROTLI_PARAM_QUALITY]: constants.BROTLI_MAX_QUALITY, diff --git a/_scripts/build.js b/_scripts/build.js index 78b1ed622ee5a..035f986c97dbe 100644 --- a/_scripts/build.js +++ b/_scripts/build.js @@ -41,7 +41,7 @@ if (platform === 'darwin') { const config = { appId: `io.freetubeapp.${name}`, - copyright: 'Copyleft © 2020-2022 freetubeapp@protonmail.com', + copyright: 'Copyleft © 2020-2023 freetubeapp@protonmail.com', // asar: false, // compression: 'store', productName, @@ -62,13 +62,6 @@ const config = { './dist/**/*', '!dist/web/*', '!node_modules/**/*', - - // renderer - 'node_modules/{miniget,ytpl,ytsr}/**/*', - - '!**/README.md', - '!**/*.js.map', - '!**/*.d.ts', ], dmg: { contents: [ diff --git a/_scripts/cordova-build.js b/_scripts/cordova-build.js index 3b68defc6a471..042cae9ae91f5 100644 --- a/_scripts/cordova-build.js +++ b/_scripts/cordova-build.js @@ -1,500 +1,60 @@ - -const DIST_FOLDER_NAME = 'android-dist' +const { writeFile, copyFile, stat } = require('fs/promises') +const { move } = require('fs-extra') const path = require('path') -const fs = require('fs') -const fse = require('fs-extra') -const util = require('util') -const fsExists = function (path) { - return new Promise(function (resolve, reject) { - fs.access(path, function (err, stat) { - if (err) { - resolve(false) - } else { - resolve(true) - } - }) - }) -} -const fsMkdir = util.promisify(fs.mkdir) -const fsReadFile = util.promisify(fs.readFile) -const fsWriteFile = util.promisify(fs.writeFile) -const fsMove = util.promisify(fs.rename) -const fsRm = util.promisify(fs.rm) -const fsCopy = util.promisify(fse.cp) -const exec = util.promisify(require('child_process').exec) -const xml2js = require('xml2js') -const parseXMLString = util.promisify(xml2js.parseString) -const createXMLStringFromObject = function (obj) { - const builder = new xml2js.Builder() - return builder.buildObject(obj) -} -const archiver = require('archiver'); - -(async function () { +const pkg = require('../package.json') +const exec = require('./helpers').execWithLiveOutput +;(async () => { + const log = (message, level = 'INFO') => { + // 🤷‍♀️ idk if there is a better way to implement logging here + // eslint-disable-next-line + console.log(`(${new Date().toISOString()})[${level}]: ${message}`) + } + const distDirectory = 'dist/cordova' try { - const sourceDirectory = path.join(__dirname, '..') - console.log('Using source directory: ' + sourceDirectory) - // Remove the dist folder if it already exists - const buildDirectory = path.join(sourceDirectory, 'build') - console.log('Using build directory: ' + buildDirectory) - if (!await fsExists(buildDirectory)) { - await fsMkdir(buildDirectory) - } - const distDirectory = path.join(buildDirectory, DIST_FOLDER_NAME) - console.log('Using dist directory: ' + distDirectory) - if (await fsExists(distDirectory)) { - await fsRm(distDirectory, { recursive: true, force: true }) - } - - const wwwroot = path.join(distDirectory, 'www') - console.log('Using wwwroot: ' + wwwroot) - - // Create the outline of the cordova project - console.log('Creating cordova outline') - const cordovaTemplateDirectory = path.join(sourceDirectory, 'node_modules/cordova-template') - if (await fsExists(cordovaTemplateDirectory)) { - await fsCopy(cordovaTemplateDirectory, distDirectory, { recursive: true, force: true }) - console.log('Was able to recycle previously built outline') - } - if (!await fsExists(cordovaTemplateDirectory)) { - const addCordovaPlugin = async function (pluginName) { - await exec('cd ' + distDirectory + ' && npx cordova plugin add ' + pluginName) - console.log('Installed ' + pluginName) - } - const addNpmPackage = async function (packageName) { - await exec(' cd ' + distDirectory + ' && npm install ' + packageName) - console.log('Installed ' + packageName) - } - await exec('cd ' + buildDirectory + ' && npx cordova create ' + DIST_FOLDER_NAME) - await addCordovaPlugin('cordova-plugin-background-mode') - await addCordovaPlugin('cordova-plugin-theme-detection') - await addCordovaPlugin('cordova-plugin-advanced-background-mode') - await addCordovaPlugin('cordova-plugin-media') - await addCordovaPlugin('cordova-plugin-music-controls2@3.0.5') - await addCordovaPlugin('cordova-plugin-save-dialog') - await addCordovaPlugin('cordova-plugin-android-permissions') - await addCordovaPlugin('cordova-clipboard') - - await addNpmPackage('browserify') - - if (await fsExists(wwwroot)) { - await fsRm(wwwroot, { recursive: true, force: true }) - } - try { - await fsCopy(distDirectory, cordovaTemplateDirectory, { recursive: true, force: true }) - } catch (exception) { - console.log(exception) - } - } - const sourcePackageUri = path.join(sourceDirectory, 'package.json') - const sourcePackage = JSON.parse((await fsReadFile(sourcePackageUri)).toString()) - - const destinationPackageUri = path.join(distDirectory, 'package.json') - const destinationPackage = JSON.parse((await fsReadFile(destinationPackageUri)).toString()) - - destinationPackage.name = 'io.freetubeapp.' + sourcePackage.name - destinationPackage.displayName = sourcePackage.productName - destinationPackage.version = sourcePackage.version - destinationPackage.author = sourcePackage.author - destinationPackage.repository = sourcePackage.repository - destinationPackage.bugs = sourcePackage.bugs - destinationPackage.license = sourcePackage.license - destinationPackage.description = sourcePackage.description - destinationPackage.private = sourcePackage.private - - let apkName = sourcePackage.name + '-' + sourcePackage.version + '.apk' - let exportType = 'cordova' - let keystorePath = null//if null, don't sign the apk - let keystorePassphrase = null - if (process.argv.length > 2) { - apkName = process.argv[2] - } - if (process.argv.length > 3) { - exportType = process.argv[3] - } - if (process.argv.length > 4) { - keystorePath = process.argv[4] - } - if (process.argv.length > 5) { - keystorePassphrase = process.argv[5] - } - // Copy dist folder into cordova project; - console.log('Copying dist output to cordova outline') - await fsCopy(path.join(sourceDirectory, 'dist', 'web'), wwwroot, { recursive: true, force: true }) - - console.log('Writing package.json in cordova project') - await fsWriteFile(destinationPackageUri, JSON.stringify(destinationPackage, null, 2)) - - // Running browserify on the renderer to remove to allow app to run in browser frame - console.log('Running browserify on cordova project') - await exec('cd ' + distDirectory + '/' + ' && npx browserify www/renderer.js -o www/renderer.js') - - let rendererContent = (await fsReadFile(path.join(wwwroot, 'renderer.js'))).toString() - // These escaped characters need to be escaped - // because they are part of a regular expression - // and they do not refer to a group - // they refer to the literal characters '(' and ')' - /* eslint-disable no-useless-escape */ - // this is a POC, random changes to the codebase break these regex all the time - rendererContent = rendererContent.replace(/([^(){}?.;:=,`&]*?)\(\)(\.(readFile|readFileSync|readdirSync|writeFileSync|writeFile|existsSync)\((.[^\)]*)\))/g, 'fileSystem$2') - rendererContent = rendererContent.replace(/\)([^(){}?.;:=,`&]*?)\(\)(\.(readFile|readFileSync|readdirSync|writeFileSync|writeFile|existsSync)\((.[^\)]*)\))/g, ';fileSystem$2') - //rendererContent = rendererContent.replace(/(this.showSaveDialog)\(([^\(\)]*?)\)/g, 'showFileSaveDialog($2);') - rendererContent = rendererContent.replace(/([a-zA-Z]*)=([a-zA-Z]*\([1-9]*\))\.createInstance/g, '$1=window.dataStore=$2.createInstance') - if (exportType === 'cordova') { - rendererContent = rendererContent.replace(/this.invidiousGetVideoInformation\(this.videoId\).then\(/g, 'this.invidiousGetVideoInformation(this.videoId).then(updatePlayingVideo);this.invidiousGetVideoInformation\(this.videoId\).then(') - rendererContent = rendererContent.replace('systemTheme:function(){return window.matchMedia("(prefers-color-scheme: dark)").matches?"dark":"light"}', 'systemTheme:function () { return window.isDarkMode }') - } else { - rendererContent = rendererContent.replaceAll("createNewWindow:function(){", "createNewWindow: window.createNewWindow, electronNewWindow:function(){") - } - /* eslint-enable no-useless-escape */ - console.log('Setting up the renderer') - await fsWriteFile(path.join(wwwroot, 'renderer.js'), `(async function () { - ` + ((exportType === 'cordova') - ? ` - (() => { - Object.defineProperties(Array.prototype, { - at: { - value(index) { - if (this.length > index) { - if (index >= 0) { - return this[index] - } else { - if (this.length + index >= 0) { - if (this.length + index < this.length) { - return this[this.length + index] - } - } - } - } - } - } - }) - })(); - var createControls = function (object = {}, success = function () {}) { - MusicControls.create(object, success); - var listeners = {}; - var addListener = function (type, funct) { - var listenerTypes = Object.keys(listeners); - if (listenerTypes.indexOf(type) === -1) { - listeners[type] = []; - } - listeners[type].push(funct); - } - var triggerListeners = function (type, value) { - var listenerTypes = Object.keys(listeners); - if (listenerTypes.indexOf(type) !== -1) { - for (var i = 0; i < listeners[type].length; i++) { - var listener = listeners[type][i]; - listener(value); - } - } - } - function events(action) { - const message = JSON.parse(action).message; - switch(message) { - case 'music-controls-next': - triggerListeners(message); - break; - case 'music-controls-previous': - triggerListeners(message); - // Do something - break; - case 'music-controls-pause': - triggerListeners(message); - // Do something - break; - case 'music-controls-play': - triggerListeners(message); - // Do something - break; - case 'music-controls-destroy': - triggerListeners(message); - // Do something - break; - case 'music-controls-toggle-play-pause' : - triggerListeners(message); - // Do something - break; - // Lockscreen seek controls (iOS only) - case 'music-controls-seek-to': - const seekToInSeconds = JSON.parse(action).position; - MusicControls.updateElapsed({ - elapsed: seekToInSeconds, - isPlaying: true - }); - triggerListeners(message); - // Do something - break; - - // Headset events (Android only) - // All media button events are listed below - case 'music-controls-media-button' : - // Do something - triggerListeners(message); - break; - case 'music-controls-headset-unplugged': - // Do something - triggerListeners(message); - break; - case 'music-controls-headset-plugged': - // Do something - triggerListeners(message); - break; - default: - triggerListeners(message); - break; - } - } - - MusicControls.subscribe(events); - MusicControls.listen(); - return { - addListener: addListener, - updateData: function (newObject) { - MusicControls.destroy(function () { - var newKeys = Object.keys(newObject); - for (var i = 0; i < newKeys.length; i++) { - object[newKeys[i]] = newObject[newKeys[i]]; - } - MusicControls.create(object); - }, function (error) { - console.log(error); - }) - } - } - } - var currentVideo = null; - window.currentControls = null; - var setupControlsListeners = function (controls) { - controls.addListener("music-controls-play", function () { - if (currentVideo !== null) { - if (currentVideo.paused) { - currentVideo.play(); - } - } - }); - var pauseListener = function () { - if (currentVideo !== null) { - if (!currentVideo.paused) { - currentVideo.pause(); - } - } - }; - controls.addListener("music-controls-pause", pauseListener); - controls.addListener("music-controls-headset-unplugged", pauseListener); - controls.addListener("music-controls-next", function () { - try { - window.location.href = document.querySelector(".playlistItem .watchPlaylistItem .router-link-active").parentNode.parentNode.parentNode.nextElementSibling.querySelector("a").href; - } catch { - try { - window.location.href = document.body.querySelector(".recommendation a").href; - } catch (exception) { - console.log(exception); - } - } - }); - controls.addListener("music-controls-previous", function () { - try { - window.location.href = document.querySelector(".playlistItem .watchPlaylistItem").parentNode.previousElementSibling.querySelector("a").href; - } catch { - try { - history.back(); - } catch (exception) { - console.log(exception); - } - } - }); - } - window.updatePlayingVideo = function (videoObject) { - var videoObject = { track: videoObject.title, artist: videoObject.author, cover: videoObject.videoThumbnails[videoObject.videoThumbnails.length - 4].url }; - currentControls.updateData(videoObject) - window.currentVideoData = videoObject; - } - try { - // trying to fix the issue where the first controls object created does not have any data - currentControls = createControls(window.currentVideoData, function () { - setupControlsListeners(currentControls); - // Destroy any rouge controls that have popped up - MusicControls.destroy(); - }); - } catch { - - } - window.currentVideoData = {}; - setInterval(function () { - // Check for current video - var video = document.querySelector('video'); - if (video !== currentVideo) { - currentVideo = video; - // setup media controls - if (video === null || video === undefined) { - MusicControls.destroy(); - } - if (video.getAttribute("data-music-controls-loaded") !== "true") { - if (currentControls === null) { - currentControls = createControls(window.currentVideoData); - setupControlsListeners(currentControls); - } - video.setAttribute("data-music-controls-loaded", "true"); - video.onplay = function () { - MusicControls.updateIsPlaying(true); - } - video.onpause = function () { - MusicControls.updateIsPlaying(false); - } - - } - } - - }, 500);` - : '') + - ` - window.play = function () { - if (currentVideo !== null) { - currentVideo.play(); - } - }; - Object.defineProperty(window, 'player', { - get: function () { - return currentVideo; - } - }); - ` + ((exportType === 'cordova') - ? ` - window.isDarkMode = "light"; - if (await new Promise(function (resolve, reject) { cordova.plugins.ThemeDetection.isAvailable(resolve, reject) }) ) { - var isDarkMode = await new Promise(function (resolve, reject) { cordova.plugins.ThemeDetection.isDarkModeEnabled(function (result) { resolve(result.value) },reject) }); - if (isDarkMode) { - window.isDarkMode = "dark"; - } - } - var removeNewWindowIconStyle = document.createElement('style'); - removeNewWindowIconStyle.innerHTML = ".navNewWindowIcon { display: none !important; }" - document.head.appendChild(removeNewWindowIconStyle); - ` - : ` - window.createNewWindow = function () { - window.open(window.location.pathname, "_blank") - }; - `) + ` - ` + rendererContent + ` - }()); - `) - // Commenting out the electron exports because they will not exist in cordova - let content = (await fsReadFile(path.join(wwwroot, 'renderer.js'))).toString() - content = content.toString().replace('module.exports = getElectronPath();', "// commenting out this line because it doesn't work in cordova\r\n// module.exports = getElectronPath();") - await fsWriteFile(path.join(wwwroot, 'renderer.js'), content) - - let indexContent = (await fsReadFile(path.join(wwwroot, 'index.html'))).toString() - indexContent = indexContent.replace('', '') - if (exportType === 'cordova') { - indexContent = indexContent.replace('', '') - } - if (exportType === 'browser') { - indexContent = indexContent.replace('', '') - } - await fsWriteFile(path.join(wwwroot, 'index.html'), indexContent) - // Copy the icons to the cordova directory - console.log('Copying icons into cordova project') - await fsMkdir(path.join(distDirectory, 'res')) - await fsMkdir(path.join(distDirectory, 'res/icon')) - await fsCopy(path.join(sourceDirectory, '_icons/.icon-set'), path.join(distDirectory, 'res/icon/android'), { recursive: true, force: true }) - await fsCopy(path.join(sourceDirectory, '_icons/icon.svg'), path.join(distDirectory, 'res/icon/android/background.xml')) - - // Copy the values from the package.json into the config.xml - const configAddon = ` - - - - - - - - - ` - const configXML = await parseXMLString(await fsReadFile(path.join(distDirectory, 'config.xml'))) - configXML.widget.$.id = `io.freetubeapp.${sourcePackage.name}` - configXML.widget.$.version = sourcePackage.version - const sourcePackageParts = sourcePackage.version.split('-') - const [major, minor, patch] = sourcePackageParts[0].split('.') - let build = 0 - if (sourcePackageParts.length > 1) { - // Seperate environments by ID instead of version code - configXML.widget.$.id += `.${sourcePackageParts[1]}` - } - // disable environment based package id - /* if (sourcePackageParts.length > 2) { - build = sourcePackageParts[2] - } */ - configXML.widget.$['android-versionCode'] = `${major * 10000000 + minor * 100000 + patch * 1000 + build}` - configXML.widget.author[0].$.email = sourcePackage.author.email - configXML.widget.author[0]._ = sourcePackage.author.name - configXML.widget.author[0].$.href = sourcePackage.author.url - configXML.widget.name[0] = sourcePackage.productName - configXML.widget.description[0] = sourcePackage.description - const xmlString = createXMLStringFromObject(configXML) - console.log('Writing config.xml to cordova project') - await fsWriteFile(path.join(distDirectory, 'config.xml'), xmlString.replace('', configAddon + '')) + await stat(distDirectory) + } catch { + log(`The dist directory \`${distDirectory}\` cannot be found. This build *will* fail. \`pack:cordova\` did not complete.`, 'WARN') + } + let apkName = `${pkg.name}-${pkg.version}.apk` + let keystorePath = null + let keystorePassphrase = null + let release = false + const args = Array.from(process.argv) + if (args.indexOf('--release') !== -1) { + release = true + args.splice(args.indexOf('--release'), 1) + } + if (args.length > 2) { + apkName = args[2] + } + if (args.length > 3) { + keystorePath = args[3] + } + if (args.length > 4) { + keystorePassphrase = args[4] + } - // Adding export platforms to cordova - console.log('Adding platforms to cordova project') - if (exportType === 'cordova') { - await exec('cd ' + distDirectory + ' && npx cordova platform add android') - } - await exec('cd ' + distDirectory + ' && npx cordova platform add browser') - if (exportType === 'cordova') { - let buildArguments = '' - if (keystorePassphrase !== null) { - // the apk needs to be signed - buildArguments = '--buildConfig --warning-mode-all' - await fsMove(keystorePath, path.join(distDirectory, 'freetubecordova.keystore')); - await fsWriteFile(path.join(distDirectory, 'build.json'), JSON.stringify({ - "android": { - "debug": { - "keystore": "./freetubecordova.keystore", - "storePassword": keystorePassphrase, - "alias": "freetubecordova", - "password" : keystorePassphrase, - "keystoreType": "jks" - }, - "release": { - "keystore": "./freetubecordova.keystore", - "storePassword": keystorePassphrase, - "alias": "freetubecordova", - "password" : keystorePassphrase, - "keystoreType": "jks" - } - } - }, null, 4)); - } - // Run the apk build - console.log('Building apk file') - await exec('cd ' + distDirectory + ' && npx cordova build android ' + buildArguments) - // Copy the apk to the build dir - console.log('Copying apk file to build directory') - await fsCopy(path.join(distDirectory, 'platforms/android/app/build/outputs/apk/debug/app-debug.apk'), path.join(buildDirectory, apkName)) - } else if (exportType === 'browser') { - console.log('Copying output directory to build directory') - await fsCopy(wwwroot, path.join(buildDirectory, apkName), { recursive: true, force: true }) - const manifest = { - background_color: 'white', - description: sourcePackage.description, - display: 'standalone', - icons: [ - { - src: '_icons/icon.ico', - sizes: '256x256', - type: 'image/ico' - } - ], - name: sourcePackage.productName, - short_name: sourcePackage.productName, - start_url: './index.html' - } - await fsWriteFile(path.join(buildDirectory, apkName, 'manifest.webmanifest'), JSON.stringify(manifest, null, 2)) - } - } catch (exception) { - const g = 'see not useless' - throw exception + let buildArguments = '' + if (keystorePassphrase !== null) { + // the apk needs to be signed + buildArguments = `--stacktrace --buildConfig --warning-mode-all ${release ? '--release' : ''}` + await move(keystorePath, path.join(distDirectory, 'freetubecordova.keystore')) + const buildJSON = { + android: {} + } + buildJSON.android[release ? 'release' : 'debug'] = { + keystore: './freetubecordova.keystore', + storePassword: keystorePassphrase, + alias: 'freetubecordova', + password: keystorePassphrase, + keystoreType: 'jks' + } + await writeFile(path.join(distDirectory, 'build.json'), JSON.stringify(buildJSON, null, 4)) } -}()) + // 🏃‍♀️ Run the apk build + log(`Building apk file for ${release ? 'release' : 'development'}`) + await exec(`cd ${distDirectory} && npx cordova build android ${buildArguments}`) + // 📋 Copy the apk to the build dir + log('Copying apk file to build directory') + await copyFile(path.join(distDirectory, 'platforms/android/app/build/outputs/apk/debug/app-debug.apk'), path.join(distDirectory, '..', apkName)) +})() diff --git a/_scripts/cordova-run.js b/_scripts/cordova-run.js deleted file mode 100644 index fb1dab2998a2b..0000000000000 --- a/_scripts/cordova-run.js +++ /dev/null @@ -1,35 +0,0 @@ - - -const DIST_FOLDER_NAME = "android-dist"; -const fs = require('fs'); -const fse = require('fs-extra'); -const util = require('util'); -const fsExists = util.promisify(fs.exists); -const fsMkdir = util.promisify(fs.mkdir); -const fsReadFile = util.promisify(fs.readFile); -const fsWriteFile = util.promisify(fs.writeFile); -const fsRm = util.promisify(fs.rm); -const fsCopy = util.promisify(fse.cp); -const exec = util.promisify(require('child_process').exec); - -(async function () { - try { - // Remove the dist folder if it already exists - if (!await fsExists(__dirname + "/../build")) { - await fsMkdir(__dirname + "/../build"); - } - if (await fsExists(__dirname + "/../build/" + DIST_FOLDER_NAME)) { - var type = "browser"; - if (process.argv.length > 2) { - type = process.argv[2]; - } - await exec("npx cordova run " + type, { cwd: __dirname + "/../build/" + DIST_FOLDER_NAME }); - } else { - console.log("No cordova build found."); - console.log("Run: "); - console.log(" npm run build:cordova"); - } - } catch (exception) { - console.log(exception); - } -}()); \ No newline at end of file diff --git a/_scripts/helpers.js b/_scripts/helpers.js new file mode 100644 index 0000000000000..8b70fea38bd62 --- /dev/null +++ b/_scripts/helpers.js @@ -0,0 +1,32 @@ + +const exec = require('child_process').exec + +/** + * Calls `child_process`.exec, but it outputs + * all of the stdout live and can be awaited + * @param {string} command The command to be executed + * @returns + */ +function execWithLiveOutput (command) { + return new Promise((resolve, reject) => { + const execCall = exec(command, (error, stdout, stderr) => { + if (error) { + reject(error) + } + resolve() + }) + execCall.stdout.on('data', (data) => { + process.stdout.write(data) + }) + execCall.stderr.on('data', (data) => { + console.error(data) + }) + execCall.on('close', () => { + resolve() + }) + }) +} + +module.exports = { + execWithLiveOutput +} diff --git a/_scripts/webpack.cordova.config.js b/_scripts/webpack.cordova.config.js new file mode 100644 index 0000000000000..96da473f9807c --- /dev/null +++ b/_scripts/webpack.cordova.config.js @@ -0,0 +1,195 @@ +const path = require('path') +const fs = require('fs') +const webpack = require('webpack') +const HtmlWebpackPlugin = require('html-webpack-plugin') +const VueLoaderPlugin = require('vue-loader/lib/plugin') +const CopyWebpackPlugin = require('copy-webpack-plugin') +const MiniCssExtractPlugin = require('mini-css-extract-plugin') +const JsonMinimizerPlugin = require('json-minimizer-webpack-plugin') +const CssMinimizerPlugin = require('css-minimizer-webpack-plugin') +const ProcessLocalesPlugin = require('./ProcessLocalesPlugin') +const CordovaPlugin = require('./CordovaPlugin') + +const isDevMode = process.env.NODE_ENV === 'development' + +const config = { + name: 'cordova', + mode: process.env.NODE_ENV, + devtool: isDevMode ? 'eval-cheap-module-source-map' : false, + entry: { + web: path.join(__dirname, '../src/renderer/main.js'), + }, + output: { + path: path.join(__dirname, '../dist/cordova/www'), + filename: '[name].js', + }, + externals: [ + { + electron: '{}', + cordova: 'cordova', + 'music-controls': 'MusicControls' + }, + ({ request }, callback) => { + if (request.startsWith('youtubei.js')) { + return callback(null, '{}') + } + callback() + } + ], + module: { + rules: [ + { + test: /\.js$/, + use: 'babel-loader', + exclude: /node_modules/, + }, + { + test: /\.vue$/, + loader: 'vue-loader' + }, + { + test: /\.scss$/, + use: [ + { + loader: MiniCssExtractPlugin.loader, + }, + { + loader: 'css-loader', + options: { + esModule: false + } + }, + { + loader: 'sass-loader', + options: { + implementation: require('sass') + } + }, + ], + }, + { + test: /\.css$/, + use: [ + { + loader: MiniCssExtractPlugin.loader + }, + { + loader: 'css-loader', + options: { + esModule: false + } + } + ], + }, + { + test: /\.html$/, + use: 'vue-html-loader', + }, + { + test: /\.(png|jpe?g|gif|tif?f|bmp|webp|svg)(\?.*)?$/, + type: 'asset/resource', + generator: { + filename: 'imgs/[name][ext]' + } + }, + { + test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/, + type: 'asset/resource', + generator: { + filename: 'fonts/[name][ext]' + } + }, + ], + }, + // webpack defaults to only optimising the production builds, so having this here is fine + optimization: { + minimizer: [ + '...', // extend webpack's list instead of overwriting it + new JsonMinimizerPlugin({ + exclude: /\/locales\/.*\.json/ + }), + new CssMinimizerPlugin() + ] + }, + node: { + __dirname: true, + __filename: isDevMode, + }, + plugins: [ + new webpack.DefinePlugin({ + 'process.env.IS_ELECTRON': false, + 'process.env.IS_ELECTRON_MAIN': false, + 'process.env.IS_CORDOVA': true + }), + new webpack.ProvidePlugin({ + process: 'process/browser', + Buffer: ['buffer', 'Buffer'], + }), + new HtmlWebpackPlugin({ + excludeChunks: ['processTaskWorker'], + filename: 'index.html', + template: path.resolve(__dirname, '../src/index.ejs'), + nodeModules: false, + }), + new VueLoaderPlugin(), + new MiniCssExtractPlugin({ + filename: isDevMode ? '[name].css' : '[name].[contenthash].css', + chunkFilename: isDevMode ? '[id].css' : '[id].[contenthash].css', + }) + ], + resolve: { + alias: { + vue$: 'vue/dist/vue.esm.js' + }, + fallback: { + buffer: require.resolve('buffer/'), + dns: require.resolve('browserify/lib/_empty.js'), + 'fs/promises': require.resolve('browserify/lib/_empty.js'), + http: require.resolve('stream-http'), + https: require.resolve('https-browserify'), + net: require.resolve('browserify/lib/_empty.js'), + os: require.resolve('os-browserify/browser.js'), + path: require.resolve('path-browserify'), + stream: require.resolve('stream-browserify'), + timers: require.resolve('timers-browserify'), + tls: require.resolve('browserify/lib/_empty.js'), + vm: require.resolve('vm-browserify'), + zlib: require.resolve('browserify-zlib') + }, + extensions: ['.js', '.vue'] + }, + target: 'web', +} + +const processLocalesPlugin = new ProcessLocalesPlugin({ + compress: false, + inputDir: path.join(__dirname, '../static/locales'), + outputDir: 'static/locales', +}) + +config.plugins.push( + new CordovaPlugin(), + processLocalesPlugin, + new webpack.DefinePlugin({ + 'process.env.LOCALE_NAMES': JSON.stringify(processLocalesPlugin.localeNames), + 'process.env.GEOLOCATION_NAMES': JSON.stringify(fs.readdirSync(path.join(__dirname, '..', 'static', 'geolocations'))) + }), + new CopyWebpackPlugin({ + patterns: [ + { + from: path.join(__dirname, '../static/pwabuilder-sw.js'), + to: path.join(__dirname, '../dist/cordova/www/pwabuilder-sw.js'), + }, + { + from: path.join(__dirname, '../static'), + to: path.join(__dirname, '../dist/cordova/www/static'), + globOptions: { + dot: true, + ignore: ['**/.*', '**/locales/**', '**/pwabuilder-sw.js', '**/dashFiles/**', '**/storyboards/**'], + }, + }, + ] + }) +) + +module.exports = config diff --git a/_scripts/webpack.main.config.js b/_scripts/webpack.main.config.js index f48ceca476616..52b357afe9bd5 100644 --- a/_scripts/webpack.main.config.js +++ b/_scripts/webpack.main.config.js @@ -3,8 +3,6 @@ const webpack = require('webpack') const CopyWebpackPlugin = require('copy-webpack-plugin') const JsonMinimizerPlugin = require('json-minimizer-webpack-plugin') -const { productName } = require('../package.json') - const isDevMode = process.env.NODE_ENV === 'development' const config = { @@ -38,8 +36,8 @@ const config = { }, plugins: [ new webpack.DefinePlugin({ - 'process.env.PRODUCT_NAME': JSON.stringify(productName), - }), + 'process.env.IS_ELECTRON_MAIN': true + }) ], output: { filename: '[name].js', diff --git a/_scripts/webpack.renderer.browser.config.js b/_scripts/webpack.renderer.browser.config.js deleted file mode 100644 index 41fc48f35af5d..0000000000000 --- a/_scripts/webpack.renderer.browser.config.js +++ /dev/null @@ -1,199 +0,0 @@ -const path = require('path') -const fs = require('fs') -const webpack = require('webpack') -const HtmlWebpackPlugin = require('html-webpack-plugin') -const VueLoaderPlugin = require('vue-loader/lib/plugin') -const MiniCssExtractPlugin = require('mini-css-extract-plugin') -const CssMinimizerPlugin = require('css-minimizer-webpack-plugin') -const CopyWebpackPlugin = require('copy-webpack-plugin') -const JsonMinimizerPlugin = require('json-minimizer-webpack-plugin') -const ProcessLocalesPlugin = require('./ProcessLocalesPlugin') -const { productName } = require('../package.json') - -const isDevMode = process.env.NODE_ENV === 'development' - -const config = { - name: 'renderer', - mode: process.env.NODE_ENV, - devtool: isDevMode ? 'eval-cheap-module-source-map' : false, - entry: { - renderer: path.join(__dirname, '../src/renderer/main.js'), - }, - infrastructureLogging: { - // Only warnings and errors - // level: 'none' disable logging - // Please read https://webpack.js.org/configuration/other-options/#infrastructurelogginglevel - level: isDevMode ? 'info' : 'none' - }, - output: { - publicPath: '', - libraryTarget: 'commonjs2', - path: path.join(__dirname, '../dist/web'), - filename: '[name].js', - }, - // webpack spits out errors while inlining ytpl and ytsr as - // they dynamically import their package.json file to extract the bug report URL - // the error: "Critical dependency: the request of a dependency is an expression" - externals: ['ytpl', 'ytsr'], - module: { - rules: [ - { - test: /\.js$/, - use: 'babel-loader', - exclude: /node_modules/, - }, - { - test: /\.vue$/, - loader: 'vue-loader', - }, - { - test: /\.s(c|a)ss$/, - use: [ - { - loader: MiniCssExtractPlugin.loader, - }, - { - loader: 'css-loader', - options: { - esModule: false - } - }, - { - loader: 'sass-loader', - options: { - // eslint-disable-next-line - implementation: require('sass'), - sassOptions: { - indentedSyntax: true - } - } - }, - ], - }, - { - test: /\.css$/, - use: [ - { - loader: MiniCssExtractPlugin.loader - }, - { - loader: 'css-loader', - options: { - esModule: false - } - } - ], - }, - { - test: /\.(png|jpe?g|gif|tif?f|bmp|webp|svg)(\?.*)?$/, - type: 'asset/resource', - generator: { - filename: 'imgs/[name][ext]' - } - }, - { - test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/, - type: 'asset/resource', - generator: { - filename: 'fonts/[name][ext]' - } - }, - ], - }, - // webpack defaults to only optimising the production builds, so having this here is fine - optimization: { - minimizer: [ - '...', // extend webpack's list instead of overwriting it - new JsonMinimizerPlugin({ - exclude: /\/locales\/.*\.json/ - }), - new CssMinimizerPlugin() - ] - }, - node: { - __dirname: isDevMode, - __filename: isDevMode, - global: isDevMode, - }, - plugins: [ - new webpack.DefinePlugin({ - 'process.env.PRODUCT_NAME': JSON.stringify(productName), - 'process.env.IS_ELECTRON': false - }), - new HtmlWebpackPlugin({ - excludeChunks: ['processTaskWorker'], - filename: 'index.html', - template: path.resolve(__dirname, '../src/index.ejs'), - nodeModules: isDevMode - ? path.resolve(__dirname, '../node_modules') - : false, - }), - new VueLoaderPlugin(), - new MiniCssExtractPlugin({ - filename: isDevMode ? '[name].css' : '[name].[contenthash].css', - chunkFilename: isDevMode ? '[id].css' : '[id].[contenthash].css', - }), - ], - resolve: { - alias: { - vue$: 'vue/dist/vue.common.js', - '@': path.join(__dirname, '../src/'), - src: path.join(__dirname, '../src/'), - icons: path.join(__dirname, '../_icons/'), - images: path.join(__dirname, '../src/renderer/assets/img/'), - static: path.join(__dirname, '../static/'), - }, - extensions: ['.js', '.vue', '.json'], - }, - target: 'electron-renderer', -} - -/** - * Adjust rendererConfig for production settings - */ -if (isDevMode) { - // any dev only config - config.plugins.push( - new webpack.DefinePlugin({ - __static: `"${path.join(__dirname, '../static').replace(/\\/g, '\\\\')}"`, - }) - ) -} else { - const processLocalesPlugin = new ProcessLocalesPlugin({ - compress: false, - inputDir: path.join(__dirname, '../static/locales'), - outputDir: 'static/locales', - }) - - config.plugins.push( - processLocalesPlugin, - new webpack.DefinePlugin({ - 'process.env.LOCALE_NAMES': JSON.stringify(processLocalesPlugin.localeNames), - 'process.env.GEOLOCATION_NAMES': JSON.stringify(fs.readdirSync(path.join(__dirname, '..', 'static', 'geolocations'))) - }), - new CopyWebpackPlugin({ - patterns: [ - { - from: path.join(__dirname, '../static/pwabuilder-sw.js'), - to: path.join(__dirname, '../dist/web/pwabuilder-sw.js'), - }, - { - from: path.join(__dirname, '../static'), - to: path.join(__dirname, '../dist/web/static'), - globOptions: { - dot: true, - ignore: ['**/.*', '**/locales/**', '**/pwabuilder-sw.js', '**/dashFiles/**', '**/storyboards/**'], - }, - }, - ] - }), - // webpack doesn't get rid of js-yaml even though it isn't used in the production builds - // so we need to manually tell it to ignore any imports for `js-yaml` - new webpack.IgnorePlugin({ - resourceRegExp: /^js-yaml$/, - contextRegExp: /i18n$/ - }) - ) -} - -module.exports = config diff --git a/_scripts/webpack.renderer.config.js b/_scripts/webpack.renderer.config.js index e994fb626aa82..7325a7e8bb91a 100644 --- a/_scripts/webpack.renderer.config.js +++ b/_scripts/webpack.renderer.config.js @@ -6,10 +6,14 @@ const MiniCssExtractPlugin = require('mini-css-extract-plugin') const CssMinimizerPlugin = require('css-minimizer-webpack-plugin') const ProcessLocalesPlugin = require('./ProcessLocalesPlugin') -const { productName } = require('../package.json') - const isDevMode = process.env.NODE_ENV === 'development' +const processLocalesPlugin = new ProcessLocalesPlugin({ + compress: !isDevMode, + inputDir: path.join(__dirname, '../static/locales'), + outputDir: 'static/locales', +}) + const config = { name: 'renderer', mode: process.env.NODE_ENV, @@ -29,10 +33,12 @@ const config = { path: path.join(__dirname, '../dist'), filename: '[name].js', }, - // webpack spits out errors while inlining ytpl and ytsr as - // they dynamically import their package.json file to extract the bug report URL - // the error: "Critical dependency: the request of a dependency is an expression" - externals: ['ytpl', 'ytsr'], + externals: { + // ignore linkedom's unnecessary broken canvas import, as youtubei.js only uses linkedom to generate DASH manifests + canvas: '{}', + 'cordova': 'browserify/lib/_empty.js', + 'music-controls': 'browserify/lib/_empty.js' + }, module: { rules: [ { @@ -45,7 +51,7 @@ const config = { loader: 'vue-loader', }, { - test: /\.s(c|a)ss$/, + test: /\.scss$/, use: [ { loader: MiniCssExtractPlugin.loader, @@ -59,11 +65,7 @@ const config = { { loader: 'sass-loader', options: { - // eslint-disable-next-line - implementation: require('sass'), - sassOptions: { - indentedSyntax: true - } + implementation: require('sass') } }, ], @@ -110,9 +112,11 @@ const config = { __filename: isDevMode }, plugins: [ + processLocalesPlugin, new webpack.DefinePlugin({ - 'process.env.PRODUCT_NAME': JSON.stringify(productName), - 'process.env.IS_ELECTRON': true + 'process.env.IS_ELECTRON': true, + 'process.env.IS_ELECTRON_MAIN': false, + 'process.env.LOCALE_NAMES': JSON.stringify(processLocalesPlugin.localeNames) }), new HtmlWebpackPlugin({ excludeChunks: ['processTaskWorker'], @@ -126,33 +130,20 @@ const config = { new MiniCssExtractPlugin({ filename: isDevMode ? '[name].css' : '[name].[contenthash].css', chunkFilename: isDevMode ? '[id].css' : '[id].[contenthash].css', - }), + }) ], resolve: { alias: { - vue$: 'vue/dist/vue.common.js' + vue$: 'vue/dist/vue.common.js', + + // defaults to the prebundled browser version which causes webpack to error with: + // "Critical dependency: require function is used in a way in which dependencies cannot be statically extracted" + // webpack likes to bundle the dependencies itself, could really have a better error message though + 'youtubei.js$': 'youtubei.js/dist/browser.js', }, extensions: ['.js', '.vue'] }, target: 'electron-renderer', } -/** - * Adjust rendererConfig for production settings - */ -if (!isDevMode) { - const processLocalesPlugin = new ProcessLocalesPlugin({ - compress: true, - inputDir: path.join(__dirname, '../static/locales'), - outputDir: 'static/locales', - }) - - config.plugins.push( - processLocalesPlugin, - new webpack.DefinePlugin({ - 'process.env.LOCALE_NAMES': JSON.stringify(processLocalesPlugin.localeNames) - }) - ) -} - module.exports = config diff --git a/_scripts/webpack.web.config.js b/_scripts/webpack.web.config.js index cf3a7ee1840b8..8f0bdd92fa820 100644 --- a/_scripts/webpack.web.config.js +++ b/_scripts/webpack.web.config.js @@ -9,8 +9,6 @@ const JsonMinimizerPlugin = require('json-minimizer-webpack-plugin') const CssMinimizerPlugin = require('css-minimizer-webpack-plugin') const ProcessLocalesPlugin = require('./ProcessLocalesPlugin') -const { productName } = require('../package.json') - const isDevMode = process.env.NODE_ENV === 'development' const config = { @@ -25,11 +23,19 @@ const config = { path: path.join(__dirname, '../dist/web'), filename: '[name].js', }, - externals: { - electron: '{}', - ytpl: '{}', - ytsr: '{}' - }, + externals: [ + { + electron: '{}', + cordova: '{}', + 'music-controls': '{}' + }, + ({ request }, callback) => { + if (request.startsWith('youtubei.js')) { + return callback(null, '{}') + } + callback() + } + ], module: { rules: [ { @@ -42,7 +48,7 @@ const config = { loader: 'vue-loader' }, { - test: /\.s(c|a)ss$/, + test: /\.scss$/, use: [ { loader: MiniCssExtractPlugin.loader, @@ -56,11 +62,7 @@ const config = { { loader: 'sass-loader', options: { - // eslint-disable-next-line - implementation: require('sass'), - sassOptions: { - indentedSyntax: true - } + implementation: require('sass') } }, ], @@ -115,8 +117,8 @@ const config = { }, plugins: [ new webpack.DefinePlugin({ - 'process.env.PRODUCT_NAME': JSON.stringify(productName), - 'process.env.IS_ELECTRON': false + 'process.env.IS_ELECTRON': false, + 'process.env.IS_ELECTRON_MAIN': false }), new webpack.ProvidePlugin({ process: 'process/browser', @@ -132,7 +134,7 @@ const config = { new MiniCssExtractPlugin({ filename: isDevMode ? '[name].css' : '[name].[contenthash].css', chunkFilename: isDevMode ? '[id].css' : '[id].[contenthash].css', - }), + }) ], resolve: { alias: { @@ -141,7 +143,7 @@ const config = { fallback: { buffer: require.resolve('buffer/'), dns: require.resolve('browserify/lib/_empty.js'), - fs: require.resolve('browserify/lib/_empty.js'), + 'fs/promises': require.resolve('browserify/lib/_empty.js'), http: require.resolve('stream-http'), https: require.resolve('https-browserify'), net: require.resolve('browserify/lib/_empty.js'), diff --git a/_scripts/webpack.workers.config.js b/_scripts/webpack.workers.config.js deleted file mode 100644 index 65412b94d282d..0000000000000 --- a/_scripts/webpack.workers.config.js +++ /dev/null @@ -1,66 +0,0 @@ -const path = require('path') -const webpack = require('webpack') - -const { - dependencies, - devDependencies, - productName, -} = require('../package.json') - -const externals = Object.keys(dependencies).concat(Object.keys(devDependencies)) -const isDevMode = process.env.NODE_ENV === 'development' - -const config = { - name: 'workers', - mode: process.env.NODE_ENV, - devtool: isDevMode ? 'eval-cheap-module-source-map' : false, - entry: { - workerSample: path.join(__dirname, '../src/utilities/workerSample.js'), - }, - output: { - publicPath: '', - libraryTarget: 'commonjs2', - path: path.join(__dirname, '../dist'), - filename: '[name].js', - }, - externals: externals, - module: { - rules: [ - { - test: /\.js$/, - use: 'babel-loader', - exclude: /node_modules/, - }, - ], - }, - node: { - __dirname: isDevMode, - __filename: isDevMode, - global: isDevMode, - }, - plugins: [ - // new WriteFilePlugin(), - new webpack.DefinePlugin({ - 'process.env.PRODUCT_NAME': JSON.stringify(productName), - }), - ], - resolve: { - alias: { - '@': path.join(__dirname, '../src/'), - src: path.join(__dirname, '../src/'), - }, - extensions: ['.js', '.json'], - }, - target: 'node', -} - -/** - * Adjust rendererConfig for production settings - */ -if (isDevMode) { - // any dev only config -} else { - // any producation only config -} - -module.exports = config diff --git a/jsconfig.json b/jsconfig.json new file mode 100644 index 0000000000000..8f5ea8a228e3c --- /dev/null +++ b/jsconfig.json @@ -0,0 +1,5 @@ +{ + "vueCompilerOptions": { + "target": 2.7 + } +} diff --git a/lefthook-local.yml.example b/lefthook-local.yml.example new file mode 100644 index 0000000000000..db5a35b2f9a9a --- /dev/null +++ b/lefthook-local.yml.example @@ -0,0 +1,7 @@ +# See following doc for details +# https://github.com/evilmartians/lefthook/blob/master/docs/configuration.md#rc + +# You can choose whatever name/path you want for `~/.lefthookrc`. +# You can share it between projects where you use lefthook. +# Make sure the path is absolute. +rc: ~/.lefthookrc diff --git a/lefthook.yml b/lefthook.yml index 7f6f4e3d68548..6a09c9530afc2 100644 --- a/lefthook.yml +++ b/lefthook.yml @@ -1,4 +1,3 @@ - # Refer for explanation to following link: # https://github.com/evilmartians/lefthook/blob/master/docs/full_guide.md pre-commit: @@ -12,8 +11,6 @@ pre-commit: skip: - rebase - - # EXAMPLE USAGE # # pre-push: diff --git a/package.json b/package.json index c8279957ed289..4f4a114163b55 100644 --- a/package.json +++ b/package.json @@ -19,11 +19,10 @@ "url": "https://github.com/FreeTubeApp/FreeTube/issues" }, "scripts": { - "build:cordova": "node _scripts/cordova-build.js", - "run:cordova": "node _scripts/cordova-run.js", "build": "run-s rebuild:electron pack build-release", "build:arm64": "run-s rebuild:electron pack build-release:arm64", "build:arm32": "run-s rebuild:electron pack build-release:arm32", + "build:cordova": "node _scripts/cordova-build.js", "build-release": "node _scripts/build.js", "build-release:arm64": "node _scripts/build.js arm64", "build-release:arm32": "node _scripts/build.js arm32", @@ -33,95 +32,103 @@ "dev": "run-s rebuild:electron dev-runner", "dev:web": "node _scripts/dev-runner.js --web", "dev-runner": "node _scripts/dev-runner.js", + "lint-all": "run-p lint lint-json lint-style", "lint-fix": "eslint --fix --ext .js,.vue ./", "lint": "eslint --ext .js,.vue ./", + "lint-json": "eslint --ext .json ./", + "lint-style": "run-p lint-style:scss lint-style:css", + "lint-style:scss": "stylelint \"**/*.scss\"", + "lint-style:css": "stylelint \"**/*.css\"", + "lint-style-fix:scss": "stylelint --fix \"**/*.scss\"", + "lint-style-fix:css": "stylelint --fix \"**/*.css\"", "pack": "run-p pack:main pack:renderer", - "pack:browser": "webpack --mode=production --node-env=production --config _scripts/webpack.renderer.browser.config.js", "pack:main": "webpack --mode=production --node-env=production --config _scripts/webpack.main.config.js", "pack:renderer": "webpack --mode=production --node-env=production --config _scripts/webpack.renderer.config.js", + "pack:cordova": "webpack --mode=production --node-env=production --config _scripts/webpack.cordova.config.js", "pack:web": "webpack --mode=production --node-env=production --config _scripts/webpack.web.config.js", "postinstall": "yarn run --silent rebuild:electron", "prettier": "prettier --write \"{src,_scripts}/**/*.{js,vue}\"", "rebuild:electron": "electron-builder install-app-deps", "release": "run-s test build", + "run:cordova": "cd dist/cordova && npx cordova run android", "ci": "yarn install --silent --frozen-lockfile" }, "dependencies": { - "@fortawesome/fontawesome-svg-core": "^6.2.0", - "@fortawesome/free-brands-svg-icons": "^6.2.0", - "@fortawesome/free-solid-svg-icons": "^6.2.0", - "@fortawesome/vue-fontawesome": "^2.0.8", - "@freetube/youtube-chat": "^1.1.2", + "@fortawesome/fontawesome-svg-core": "^6.2.1", + "@fortawesome/free-brands-svg-icons": "^6.2.1", + "@fortawesome/free-solid-svg-icons": "^6.2.1", + "@fortawesome/vue-fontawesome": "^2.0.9", "@freetube/yt-comment-scraper": "^6.2.0", - "@freetube/yt-trending-scraper": "^3.1.1", "@silvermine/videojs-quality-selector": "^1.2.5", "autolinker": "^4.0.0", "browserify": "^17.0.0", "browserify-zlib": "^0.2.0", - "electron-context-menu": "^3.5.0", - "http-proxy-agent": "^5.0.0", - "https-proxy-agent": "^5.0.0", + "electron-context-menu": "^3.6.1", "lodash.debounce": "^4.0.8", - "marked": "^4.1.1", + "marked": "^4.2.12", "nedb-promises": "^6.2.1", "process": "^0.11.10", - "socks-proxy-agent": "^7.0.0", - "video.js": "7.18.1", - "videojs-contrib-quality-levels": "^2.1.0", + "video.js": "7.20.3", + "videojs-contrib-quality-levels": "^3.0.0", "videojs-http-source-selector": "^1.1.6", "videojs-mobile-ui": "^0.8.0", - "videojs-overlay": "^2.1.4", + "videojs-overlay": "^3.0.0", "videojs-vtt-thumbnails-freetube": "0.0.15", - "vue": "^2.7.13", - "vue-i18n": "^8.28.1", + "vue": "^2.7.14", + "vue-i18n": "^8.28.2", "vue-observe-visibility": "^1.0.0", "vue-router": "^3.6.5", "vuex": "^3.6.2", - "youtube-suggest": "^1.2.0", - "yt-channel-info": "^3.2.1", - "yt-dash-manifest-generator": "1.1.0", - "ytdl-core": "https://github.com/absidue/node-ytdl-core#fix-likes-extraction", - "ytpl": "^2.3.0", - "ytsr": "^3.8.0" + "youtubei.js": "^2.9.0", + "yt-channel-info": "^3.2.1" }, "devDependencies": { - "@babel/core": "^7.20.2", + "@babel/core": "^7.20.12", + "@babel/eslint-parser": "^7.19.1", "@babel/plugin-proposal-class-properties": "^7.18.6", - "@babel/preset-env": "^7.18.10", - "archiver": "^5.3.1", - "babel-eslint": "^10.1.0", - "babel-loader": "^9.1.0", + "@babel/preset-env": "^7.20.2", + "@double-great/stylelint-a11y": "^2.0.2", + "babel-loader": "^9.1.2", "copy-webpack-plugin": "^11.0.0", "cordova": "^11.0.0", - "css-loader": "^6.7.2", + "css-loader": "^6.7.3", "css-minimizer-webpack-plugin": "^4.2.2", - "electron": "^21.2.3", + "electron": "^22.0.2", "electron-builder": "^23.6.0", - "eslint": "^7.32.0", - "eslint-config-prettier": "^8.3.0", - "eslint-config-standard": "^16.0.3", - "eslint-plugin-import": "^2.24.2", - "eslint-plugin-node": "^11.1.0", + "eslint": "^8.32.0", + "eslint-config-prettier": "^8.6.0", + "eslint-config-standard": "^17.0.0", + "eslint-plugin-import": "^2.27.4", + "eslint-plugin-jsonc": "^2.6.0", + "eslint-plugin-n": "^15.6.1", "eslint-plugin-prettier": "^4.2.1", - "eslint-plugin-promise": "^5.1.0", - "eslint-plugin-standard": "^5.0.0", - "eslint-plugin-vue": "^9.7.0", + "eslint-plugin-promise": "^6.1.1", + "eslint-plugin-unicorn": "^45.0.2", + "eslint-plugin-vue": "^9.9.0", + "eslint-plugin-vuejs-accessibility": "^2.1.0", + "fs-extra": "^11.1.0", "html-webpack-plugin": "^5.3.2", "js-yaml": "^4.1.0", "json-minimizer-webpack-plugin": "^4.0.0", - "lefthook": "^1.1.4", - "mini-css-extract-plugin": "^2.7.0", + "lefthook": "^1.2.7", + "mini-css-extract-plugin": "^2.7.2", "npm-run-all": "^4.1.5", - "prettier": "^2.7.1", - "rimraf": "^3.0.2", - "sass": "^1.56.0", + "postcss": "^8.4.21", + "postcss-scss": "^4.0.6", + "prettier": "^2.8.3", + "rimraf": "^4.1.0", + "sass": "^1.57.1", "sass-loader": "^13.2.0", + "stylelint": "^14.16.1", + "stylelint-config-sass-guidelines": "^9.0.1", + "stylelint-config-standard": "^29.0.0", + "stylelint-high-performance-animation": "^1.7.0", "tree-kill": "1.2.2", "vue-devtools": "^5.1.4", "vue-eslint-parser": "^9.1.0", "vue-loader": "^15.10.0", "webpack": "^5.75.0", - "webpack-cli": "^4.10.0", + "webpack-cli": "^5.0.1", "webpack-dev-server": "^4.11.1", "xml2js": "^0.4.23" } diff --git a/src/cordova/config.xml b/src/cordova/config.xml new file mode 100644 index 0000000000000..2419a575c2413 --- /dev/null +++ b/src/cordova/config.xml @@ -0,0 +1,22 @@ + + + FreeTube + A private YouTube client + PrestonN + + + + + + + + + + + + + + + + + diff --git a/src/cordova/config.xml.js b/src/cordova/config.xml.js new file mode 100644 index 0000000000000..bf80fe1c79e45 --- /dev/null +++ b/src/cordova/config.xml.js @@ -0,0 +1,30 @@ + +const pkg = require('../../package.json') +const fs = require('fs') +const util = require('util') +const xml2js = require('xml2js') +const parseXMLString = util.promisify(xml2js.parseString) +const createXMLStringFromObject = (obj) => { + const builder = new xml2js.Builder() + return builder.buildObject(obj) +} + +module.exports = (async () => { + const configXML = await parseXMLString((await util.promisify(fs.readFile)('./src/cordova/config.xml')).toString()) + configXML.widget.$.id = `io.freetubeapp.${pkg.name}` + const versionParts = pkg.version.split('-') + const [major, minor, patch] = versionParts[0].split('.') + let build = 0 + if (versionParts.length > 2) { + build = versionParts[2] + } + configXML.widget.$['android-versionCode'] = `${major * 10000000 + minor * 100000 + patch * 1000 + build}` + configXML.widget.$.version = pkg.version + configXML.widget.author[0].$.email = pkg.author.email + configXML.widget.author[0]._ = pkg.author.name + configXML.widget.author[0].$.href = pkg.author.url + configXML.widget.name[0] = pkg.productName + configXML.widget.description[0] = pkg.description + const configXMLString = createXMLStringFromObject(configXML) + return { string: configXMLString, object: configXML } +})() diff --git a/src/cordova/package.js b/src/cordova/package.js new file mode 100644 index 0000000000000..90ec05a73559b --- /dev/null +++ b/src/cordova/package.js @@ -0,0 +1,40 @@ + +const pkg = require('../../package.json') + +module.exports = { + name: pkg.name, + displayName: pkg.productName, + version: pkg.version, + author: pkg.author, + repository: pkg.repository, + bugs: pkg.bugs, + license: pkg.license, + description: pkg.description, + private: pkg.private, + scripts: { + restore: 'npx cordova platform add android' + }, + devDependencies: { + 'cordova-android': '^11.0.0', + 'cordova-clipboard': '^1.3.0', + 'cordova-plugin-advanced-background-mode': '^1.1.1', + 'cordova-plugin-android-permissions': '^1.1.4', + 'cordova-plugin-music-controls2': '3.0.5', + 'cordova-plugin-save-dialog': '^1.1.1', + 'cordova-plugin-theme-detection': '^1.3.0', + 'cordova-plugin-device': '^2.1.0' + }, + cordova: { + platforms: [ + 'android' + ], + plugins: { + 'cordova-plugin-android-permissions': {}, + 'cordova-plugin-music-controls2': {}, + 'cordova-clipboard': {}, + 'cordova-plugin-advanced-background-mode': {}, + 'cordova-plugin-theme-detection': {}, + 'cordova-plugin-save-dialog': {} + } + } +} diff --git a/src/datastores/handlers/base.js b/src/datastores/handlers/base.js index 1250052510cb1..285307017abf9 100644 --- a/src/datastores/handlers/base.js +++ b/src/datastores/handlers/base.js @@ -31,6 +31,14 @@ class Settings { return db.settings.findOne({ _id: 'baseTheme' }) } + static _findSidenavSettings() { + return { + hideTrendingVideos: db.settings.findOne({ _id: 'hideTrendingVideos' }), + hidePopularVideos: db.settings.findOne({ _id: 'hidePopularVideos' }), + hidePlaylists: db.settings.findOne({ _id: 'hidePlaylists' }), + } + } + static _updateBounds(value) { return db.settings.update({ _id: 'bounds' }, { _id: 'bounds', value }, { upsert: true }) } diff --git a/src/datastores/index.js b/src/datastores/index.js index 713a0ea304641..a54fd0b4ad3c0 100644 --- a/src/datastores/index.js +++ b/src/datastores/index.js @@ -2,8 +2,7 @@ import Datastore from 'nedb-promises' let dbPath = null -const isElectronMain = !!process?.versions?.electron -if (isElectronMain) { +if (process.env.IS_ELECTRON_MAIN) { const { app } = require('electron') const { join } = require('path') const userDataPath = app.getPath('userData') // This is based on the user's OS diff --git a/src/index.ejs b/src/index.ejs index 9fd3136e3ffae..312ee4b4177ad 100644 --- a/src/index.ejs +++ b/src/index.ejs @@ -5,7 +5,10 @@ - + <% if (!process.env.IS_ELECTRON) { %> + + + <% } %> <% if (htmlWebpackPlugin.options.nodeModules) { %> + <% } %> + <% if (process.env.IS_CORDOVA) { %> + + <% } %> diff --git a/src/main/ImageCache.js b/src/main/ImageCache.js index 4a635b7d92e9f..8228f38dc63ed 100644 --- a/src/main/ImageCache.js +++ b/src/main/ImageCache.js @@ -51,7 +51,7 @@ export class ImageCache { * @returns a timestamp in seconds */ export function extractExpiryTimestamp(headers) { - const maxAgeRegex = /max-age=([0-9]+)/ + const maxAgeRegex = /max-age=(\d+)/ const cacheControl = headers['cache-control'] if (cacheControl && maxAgeRegex.test(cacheControl)) { diff --git a/src/main/index.js b/src/main/index.js index 15a38879d6b21..6bdff568c4709 100644 --- a/src/main/index.js +++ b/src/main/index.js @@ -1,6 +1,7 @@ import { app, BrowserWindow, dialog, Menu, ipcMain, - powerSaveBlocker, screen, session, shell, nativeTheme, net, protocol + powerSaveBlocker, screen, session, shell, + nativeTheme, net, protocol, clipboard } from 'electron' import path from 'path' import cp from 'child_process' @@ -21,6 +22,8 @@ function runApp() { showSearchWithGoogle: false, showSaveImageAs: true, showCopyImageAddress: true, + showSelectAll: false, + showCopyLink: false, prepend: (defaultActions, parameters, browserWindow) => [ { label: 'Show / Hide Video Statistics', @@ -36,8 +39,122 @@ function runApp() { click: () => { createWindow({ replaceMainWindow: false, windowStartupUrl: parameters.linkURL, showWindowNow: true }) } + }, + // Only show select all in text fields + { + label: 'Select All', + enabled: parameters.editFlags.canSelectAll, + visible: parameters.isEditable, + click: () => { + browserWindow.webContents.selectAll() + } } - ] + ], + // only show the copy link entry for external links and the /playlist, /channel and /watch in-app URLs + // the /playlist, /channel and /watch in-app URLs get transformed to their equivalent YouTube or Invidious URLs + append: (defaultActions, parameters, browserWindow) => { + let visible = false + const urlParts = parameters.linkURL.split('#') + const isInAppUrl = urlParts[0] === browserWindow.webContents.getURL().split('#')[0] + + if (parameters.linkURL.length > 0) { + if (isInAppUrl) { + const path = urlParts[1] + + if (path) { + visible = ['/playlist', '/channel', '/watch'].some(p => path.startsWith(p)) + } + } else { + visible = true + } + } + + const copy = (url) => { + if (parameters.linkText) { + clipboard.write({ + bookmark: parameters.linkText, + text: url + }) + } else { + clipboard.writeText(url) + } + } + + const transformURL = (toYouTube) => { + let origin + + if (toYouTube) { + origin = 'https://www.youtube.com' + } else { + origin = 'https://redirect.invidious.io' + } + + const [path, query] = urlParts[1].split('?') + const [route, id] = path.split('/').filter(p => p) + + switch (route) { + case 'playlist': + return `${origin}/playlist?list=${id}` + case 'channel': + return `${origin}/channel/${id}` + case 'watch': { + let url + + if (toYouTube) { + url = `https://youtu.be/${id}` + } else { + url = `https://redirect.invidious.io/watch?v=${id}` + } + + if (query) { + const params = new URLSearchParams(query) + const newParams = new URLSearchParams() + let hasParams = false + + if (params.has('playlistId')) { + newParams.set('list', params.get('playlistId')) + hasParams = true + } + + if (params.has('timestamp')) { + newParams.set('t', params.get('timestamp')) + hasParams = true + } + + if (hasParams) { + url += '?' + newParams.toString() + } + } + + return url + } + } + } + + return [ + { + label: 'Copy Lin&k', + visible: visible && !isInAppUrl, + click: () => { + copy(parameters.linkURL) + } + }, + { + label: 'Copy YouTube Link', + visible: visible && isInAppUrl, + click: () => { + copy(transformURL(true)) + } + }, + { + label: 'Copy Invidious Link', + visible: visible && isInAppUrl, + click: () => { + copy(transformURL(false)) + } + } + ] + } }) // disable electron warning @@ -148,9 +265,7 @@ function runApp() { // Set CONSENT cookie on reasonable domains const consentCookieDomains = [ - 'http://www.youtube.com', 'https://www.youtube.com', - 'http://youtube.com', 'https://youtube.com' ] consentCookieDomains.forEach(url => { @@ -162,6 +277,16 @@ function runApp() { }) }) + // make InnerTube requests work with the fetch function + // InnerTube rejects requests if the referer isn't YouTube or empty + const innertubeRequestFilter = { urls: ['https://www.youtube.com/youtubei/*'] } + + session.defaultSession.webRequest.onBeforeSendHeaders(innertubeRequestFilter, ({ requestHeaders }, callback) => { + requestHeaders.referer = 'https://www.youtube.com' + // eslint-disable-next-line n/no-callback-literal + callback({ requestHeaders }) + }) + if (replaceHttpCache) { // in-memory image cache @@ -173,7 +298,7 @@ function runApp() { if (imageCache.has(url)) { const cached = imageCache.get(url) - // eslint-disable-next-line node/no-callback-literal + // eslint-disable-next-line n/no-callback-literal callback({ mimeType: cached.mimeType, data: cached.data @@ -211,7 +336,7 @@ function runApp() { imageCache.add(url, mimeType, data, expiryTimestamp) - // eslint-disable-next-line node/no-callback-literal + // eslint-disable-next-line n/no-callback-literal callback({ mimeType, data: data @@ -239,7 +364,7 @@ function runApp() { return value }) - // eslint-disable-next-line node/no-callback-literal + // eslint-disable-next-line n/no-callback-literal callback({ statusCode: response.statusCode ?? 400, mimeType: 'application/json', @@ -248,6 +373,10 @@ function runApp() { }) }) + newRequest.on('error', (err) => { + console.error(err) + }) + newRequest.end() }) @@ -256,12 +385,12 @@ function runApp() { // the requests made by the imagecache:// handler to fetch the image, // are allowed through, as their resourceType is 'other' if (details.resourceType === 'image') { - // eslint-disable-next-line node/no-callback-literal + // eslint-disable-next-line n/no-callback-literal callback({ redirectURL: `imagecache://${encodeURIComponent(details.url)}` }) } else { - // eslint-disable-next-line node/no-callback-literal + // eslint-disable-next-line n/no-callback-literal callback({}) } }) @@ -328,7 +457,7 @@ function runApp() { darkTheme: nativeTheme.shouldUseDarkColors, icon: process.env.NODE_ENV === 'development' ? path.join(__dirname, '../../_icons/iconColor.png') - /* eslint-disable-next-line */ + /* eslint-disable-next-line n/no-path-concat */ : `${__dirname}/_icons/iconColor.png`, autoHideMenuBar: true, // useContentSize: true, @@ -419,7 +548,7 @@ function runApp() { if (windowStartupUrl != null) { newWindow.loadURL(windowStartupUrl) } else { - /* eslint-disable-next-line */ + /* eslint-disable-next-line n/no-path-concat */ newWindow.loadFile(`${__dirname}/index.html`) } } @@ -611,6 +740,17 @@ function runApp() { event, { event: SyncEvents.GENERAL.UPSERT, data } ) + switch (data._id) { + // Update app menu on related setting update + case 'hideTrendingVideos': + case 'hidePopularVideos': + case 'hidePlaylists': + await setMenu() + break + + default: + // Do nothing for unmatched settings + } return null default: @@ -888,7 +1028,7 @@ function runApp() { } } - /** + /* * Auto Updater * * Uncomment the following code below and install `electron-updater` to @@ -907,12 +1047,23 @@ function runApp() { }) */ - /* eslint-disable-next-line */ - const sendMenuEvent = async data => { - mainWindow.webContents.send('change-view', data) + function navigateTo(path, browserWindow) { + if (browserWindow == null) { + return + } + + browserWindow.webContents.send( + 'change-view', + { route: path } + ) } - function setMenu() { + async function setMenu() { + const sidenavSettings = baseHandlers.settings._findSidenavSettings() + const hideTrendingVideos = (await sidenavSettings.hideTrendingVideos)?.value + const hidePopularVideos = (await sidenavSettings.hidePopularVideos)?.value + const hidePlaylists = (await sidenavSettings.hidePlaylists)?.value + const template = [ { label: 'File', @@ -933,12 +1084,7 @@ function runApp() { label: 'Preferences', accelerator: 'CmdOrCtrl+,', click: (_menuItem, browserWindow, _event) => { - if (browserWindow == null) { return } - - browserWindow.webContents.send( - 'change-view', - { route: '/settings' } - ) + navigateTo('/settings', browserWindow) }, type: 'normal' }, @@ -1001,22 +1147,81 @@ function runApp() { { role: 'togglefullscreen' }, { type: 'separator' }, { - label: 'History', - // MacOS: Command + Y - // Other OS: Ctrl + H - accelerator: process.platform === 'darwin' ? 'Cmd+Y' : 'Ctrl+H', + label: 'Back', + accelerator: 'Alt+Left', click: (_menuItem, browserWindow, _event) => { if (browserWindow == null) { return } browserWindow.webContents.send( - 'change-view', - { route: '/history' } + 'history-back', ) }, - type: 'normal' + type: 'normal', + }, + { + label: 'Forward', + accelerator: 'Alt+Right', + click: (_menuItem, browserWindow, _event) => { + if (browserWindow == null) { return } + + browserWindow.webContents.send( + 'history-forward', + ) + }, + type: 'normal', }, ] }, + { + label: 'Navigate', + submenu: [ + { + label: 'Subscriptions', + click: (_menuItem, browserWindow, _event) => { + navigateTo('/subscriptions', browserWindow) + }, + type: 'normal' + }, + { + label: 'Channels', + click: (_menuItem, browserWindow, _event) => { + navigateTo('/subscribedchannels', browserWindow) + }, + type: 'normal' + }, + !hideTrendingVideos && { + label: 'Trending', + click: (_menuItem, browserWindow, _event) => { + navigateTo('/trending', browserWindow) + }, + type: 'normal' + }, + !hidePopularVideos && { + label: 'Most Popular', + click: (_menuItem, browserWindow, _event) => { + navigateTo('/popular', browserWindow) + }, + type: 'normal' + }, + !hidePlaylists && { + label: 'Playlists', + click: (_menuItem, browserWindow, _event) => { + navigateTo('/userplaylists', browserWindow) + }, + type: 'normal' + }, + { + label: 'History', + // MacOS: Command + Y + // Other OS: Ctrl + H + accelerator: process.platform === 'darwin' ? 'Cmd+Y' : 'Ctrl+H', + click: (_menuItem, browserWindow, _event) => { + navigateTo('/history', browserWindow) + }, + type: 'normal' + }, + ].filter((v) => v !== false), + }, { role: 'window', submenu: [ diff --git a/src/renderer/App.js b/src/renderer/App.js index da24cab6e4ac4..c05bf89cef89a 100644 --- a/src/renderer/App.js +++ b/src/renderer/App.js @@ -1,4 +1,4 @@ -import Vue from 'vue' +import Vue, { defineComponent } from 'vue' import { mapActions, mapMutations } from 'vuex' import { ObserveVisibility } from 'vue-observe-visibility' import FtFlexBox from './components/ft-flex-box/ft-flex-box.vue' @@ -13,12 +13,13 @@ import { marked } from 'marked' import { IpcChannels } from '../constants' import packageDetails from '../../package.json' import { openExternalLink, openInternalPath, showToast } from './helpers/utils' +import cordova from 'cordova' let ipcRenderer = null Vue.directive('observe-visibility', ObserveVisibility) -export default Vue.extend({ +export default defineComponent({ name: 'App', components: { FtFlexBox, @@ -76,10 +77,10 @@ export default Vue.extend({ if (this.$route.meta.title !== 'Channel' && this.$route.meta.title !== 'Watch') { let title = this.$route.meta.path === '/home' - ? process.env.PRODUCT_NAME - : `${this.$t(this.$route.meta.title)} - ${process.env.PRODUCT_NAME}` + ? packageDetails.productName + : `${this.$t(this.$route.meta.title)} - ${packageDetails.productName}` if (!title) { - title = process.env.PRODUCT_NAME + title = packageDetails.productName } return title } else { @@ -143,6 +144,17 @@ export default Vue.extend({ this.setWindowTitle() }, mounted: function () { + if (process.env.IS_CORDOVA) { + const { backgroundMode } = cordova.plugins + backgroundMode.setDefaults({ + title: 'FreeTube' + }) + backgroundMode.enable() + backgroundMode.on('activate', () => { + // By default android wants to pause videos in the background, this disables that "optimization" + backgroundMode.disableWebViewOptimizations() + }) + } this.grabUserSettings().then(async () => { this.checkThemeSettings() @@ -154,20 +166,30 @@ export default Vue.extend({ this.grabAllProfiles(this.$t('Profile.All Channels')).then(async () => { this.grabHistory() this.grabAllPlaylists() - + this.watchSystemTheme() + document.addEventListener('visibilitychange', () => { + if (!document.hidden) { // if the window was unfocused, the system theme might have changed + this.watchSystemTheme() + } + }) if (process.env.IS_ELECTRON) { ipcRenderer = require('electron').ipcRenderer this.setupListenersToSyncWindows() this.activateKeyboardShortcuts() + this.activateIPCListeners() this.openAllLinksExternally() this.enableSetSearchQueryText() this.enableOpenUrl() - this.watchSystemTheme() await this.checkExternalPlayer() } this.dataReady = true - + setTimeout(() => { + if (process.env.IS_CORDOVA) { + // hide the splashscreen + navigator.splashscreen.hide() + } + }, 250) setTimeout(() => { this.checkForNewUpdates() this.checkForNewBlogPosts() @@ -204,13 +226,22 @@ export default Vue.extend({ .then((response) => response.json()) .then((json) => { const tagName = json[0].tag_name - const versionNumber = tagName.split('-nightly-')[1] + const tagNameParts = tagName.split('.') + let versionNumber = tagNameParts[tagNameParts.length - 1] + // if the tag is a nightly release + if (tagName.indexOf('-nightly-') !== -1) { + versionNumber = tagName.split('-nightly-')[1] + } this.updateChangelog = marked.parse(json[0].body) this.changeLogTitle = json[0].name const message = this.$t('Version $ is now available! Click for more details') this.updateBannerMessage = message.replace('$', versionNumber) - const appVersion = packageDetails.version.split('-nightly-')[1] + const versionParts = packageDetails.version.split('.') + let appVersion = versionParts[versionParts.length - 1] + if (appVersion.indexOf('-nightly-') !== -1) { + appVersion = appVersion.split('-nightly-')[1] + } if (parseInt(versionNumber) > parseInt(appVersion)) { this.showUpdatesBanner = true } @@ -288,15 +319,19 @@ export default Vue.extend({ }) }, + activateIPCListeners: function () { + // handle menu event updates from main script + ipcRenderer.on('history-back', (_event) => { + this.$refs.topNav.historyBack() + }) + ipcRenderer.on('history-forward', (_event) => { + this.$refs.topNav.historyForward() + }) + }, + handleKeyboardShortcuts: function (event) { if (event.altKey) { switch (event.key) { - case 'ArrowRight': - this.$refs.topNav.historyForward() - break - case 'ArrowLeft': - this.$refs.topNav.historyBack() - break case 'D': case 'd': this.$refs.topNav.focusSearch() @@ -453,9 +488,41 @@ export default Vue.extend({ * all systems running the electron app. */ watchSystemTheme: function () { - ipcRenderer.on(IpcChannels.NATIVE_THEME_UPDATE, (event, shouldUseDarkColors) => { - document.body.dataset.systemTheme = shouldUseDarkColors ? 'dark' : 'light' - }) + if (process.env.IS_ELECTRON) { + ipcRenderer.on(IpcChannels.NATIVE_THEME_UPDATE, (event, shouldUseDarkColors) => { + document.body.dataset.systemTheme = shouldUseDarkColors ? 'dark' : 'light' + }) + } else if (process.env.IS_CORDOVA) { + cordova.plugins.ThemeDetection.isAvailable((isThemeDetectionAvailable) => { + if (isThemeDetectionAvailable) { + cordova.plugins.ThemeDetection.isDarkModeEnabled((message) => { + document.body.dataset.systemTheme = message.value ? 'dark' : 'light' + }) + } + }, console.error) + } + }, + + openInternalPath: function({ path, doCreateNewWindow, query = {}, searchQueryText = null }) { + if (process.env.IS_ELECTRON && doCreateNewWindow) { + const { ipcRenderer } = require('electron') + + // Combine current document path and new "hash" as new window startup URL + const newWindowStartupURL = [ + window.location.href.split('#')[0], + `#${path}?${(new URLSearchParams(query)).toString()}` + ].join('') + ipcRenderer.send(IpcChannels.CREATE_NEW_WINDOW, { + windowStartupUrl: newWindowStartupURL, + searchQueryText + }) + } else { + // Web + this.$router.push({ + path, + query + }) + } }, enableSetSearchQueryText: function () { diff --git a/src/renderer/App.vue b/src/renderer/App.vue index a1a3f925fb38c..b4d7faa4ef45d 100644 --- a/src/renderer/App.vue +++ b/src/renderer/App.vue @@ -20,12 +20,14 @@ v-if="showUpdatesBanner" class="banner" :message="updateBannerMessage" + role="link" @click="handleUpdateBannerClick" />