forked from EVOgeek/Vmeda.Online
		
	Merge pull request #2557 from NoelDeMartin/MOBILE-3320
MOBILE-3320 Updated linting rules + add Error tests
This commit is contained in:
		
						commit
						1ab5c78491
					
				
							
								
								
									
										102
									
								
								.eslintrc.js
									
									
									
									
									
								
							
							
						
						
									
										102
									
								
								.eslintrc.js
									
									
									
									
									
								
							| @ -1,33 +1,30 @@ | ||||
| module.exports = { | ||||
|     root: true, | ||||
|     overrides: [ | ||||
|         { | ||||
|             files: ['*.ts'], | ||||
| var appConfig = { | ||||
|     env: { | ||||
|         browser: true, | ||||
|         es6: true, | ||||
|         node: true, | ||||
|     }, | ||||
|     plugins: [ | ||||
|         '@typescript-eslint', | ||||
|         'header', | ||||
|         'jsdoc', | ||||
|         'prefer-arrow', | ||||
|         'promise', | ||||
|     ], | ||||
|     extends: [ | ||||
|         'eslint:recommended', | ||||
|         'plugin:@typescript-eslint/recommended', | ||||
|         'prettier', | ||||
|         'prettier/@typescript-eslint', | ||||
|                 'plugin:jest/recommended', | ||||
|         'plugin:@angular-eslint/recommended', | ||||
|         'plugin:promise/recommended', | ||||
|     ], | ||||
|     parser: '@typescript-eslint/parser', | ||||
|     parserOptions: { | ||||
|         project: 'tsconfig.json', | ||||
|         sourceType: 'module', | ||||
|     }, | ||||
|             plugins: [ | ||||
|                 'eslint-plugin-prefer-arrow', | ||||
|                 'eslint-plugin-jsdoc', | ||||
|                 '@typescript-eslint', | ||||
|                 'header', | ||||
|                 'jest', | ||||
|             ], | ||||
|     reportUnusedDisableDirectives: true, | ||||
|     rules: { | ||||
|         '@angular-eslint/component-class-suffix': ['error', { suffixes: ['Component', 'Page'] }], | ||||
|         '@typescript-eslint/adjacent-overload-signatures': 'error', | ||||
| @ -56,7 +53,29 @@ module.exports = { | ||||
|                 accessibility: 'no-public', | ||||
|             }, | ||||
|         ], | ||||
|                 '@typescript-eslint/indent': 'error', | ||||
|         '@typescript-eslint/explicit-module-boundary-types': [ | ||||
|             'error', | ||||
|             { | ||||
|                 allowArgumentsExplicitlyTypedAsAny: true, | ||||
|             }, | ||||
|         ], | ||||
|         '@typescript-eslint/indent': [ | ||||
|             'error', | ||||
|             4, | ||||
|             { | ||||
|                 SwitchCase: 1, | ||||
|                 ignoredNodes: [ | ||||
|                     'ClassProperty *', | ||||
|                 ], | ||||
|             }, | ||||
|         ], | ||||
|         '@typescript-eslint/lines-between-class-members': [ | ||||
|             'error', | ||||
|             'always', | ||||
|             { | ||||
|                 exceptAfterSingleLine: true, | ||||
|             }, | ||||
|         ], | ||||
|         '@typescript-eslint/member-delimiter-style': [ | ||||
|             'error', | ||||
|             { | ||||
| @ -85,19 +104,14 @@ module.exports = { | ||||
|         ], | ||||
|         '@typescript-eslint/no-empty-function': 'error', | ||||
|         '@typescript-eslint/no-empty-interface': 'off', | ||||
|                 '@typescript-eslint/no-explicit-any': [ | ||||
|                     'warn', | ||||
|                     { | ||||
|                         fixToUnknown: true, | ||||
|                     }, | ||||
|                 ], | ||||
|         '@typescript-eslint/no-explicit-any': 'warn', | ||||
|         '@typescript-eslint/no-inferrable-types': [ | ||||
|             'error', | ||||
|             { | ||||
|                 ignoreParameters: true, | ||||
|             }, | ||||
|         ], | ||||
|                 '@typescript-eslint/no-non-null-assertion': 'error', | ||||
|         '@typescript-eslint/no-non-null-assertion': 'off', | ||||
|         '@typescript-eslint/no-this-alias': 'error', | ||||
|         '@typescript-eslint/no-unused-vars': 'error', | ||||
|         '@typescript-eslint/quotes': [ | ||||
| @ -109,18 +123,6 @@ module.exports = { | ||||
|             'always', | ||||
|         ], | ||||
|         '@typescript-eslint/type-annotation-spacing': 'error', | ||||
|                 '@typescript-eslint/typedef': [ | ||||
|                     'error', | ||||
|                     { | ||||
|                         arrayDestructuring: false, | ||||
|                         arrowParameter: false, | ||||
|                         memberVariableDeclaration: true, | ||||
|                         objectDestructuring: false, | ||||
|                         parameter: true, | ||||
|                         propertyDeclaration: true, | ||||
|                         variableDeclaration: false, | ||||
|                     }, | ||||
|                 ], | ||||
|         '@typescript-eslint/unified-signatures': 'error', | ||||
|         'header/header': [ | ||||
|             2, | ||||
| @ -142,13 +144,20 @@ module.exports = { | ||||
|             ], | ||||
|             1, | ||||
|         ], | ||||
|         'promise/catch-or-return': [ | ||||
|             'warn', | ||||
|             { | ||||
|                 allowFinally: true, | ||||
|             }, | ||||
|         ], | ||||
|         'arrow-body-style': ['error', 'as-needed'], | ||||
|         'array-bracket-spacing': ['error', 'never'], | ||||
|         'comma-dangle': ['error', 'always-multiline'], | ||||
|         'constructor-super': 'error', | ||||
|         'curly': 'error', | ||||
|                 'default-case': 'error', | ||||
|         'eol-last': 'error', | ||||
|         'function-call-argument-newline': ['error', 'consistent'], | ||||
|         'function-paren-newline': ['error', 'multiline-arguments'], | ||||
|         'id-blacklist': [ | ||||
|             'error', | ||||
|             'any', | ||||
| @ -163,13 +172,17 @@ module.exports = { | ||||
|         ], | ||||
|         'id-match': 'error', | ||||
|         'jsdoc/check-alignment': 'error', | ||||
|                 'jsdoc/check-indentation': 'error', | ||||
|         'jsdoc/check-indentation': [ | ||||
|             'error', | ||||
|             { | ||||
|                 excludeTags: ['param'], | ||||
|             }, | ||||
|         ], | ||||
|         'jsdoc/newline-after-description': 'error', | ||||
|         'linebreak-style': [ | ||||
|             'error', | ||||
|             'unix', | ||||
|         ], | ||||
|                 'lines-between-class-members': ['error', 'always'], | ||||
|         'max-len': [ | ||||
|             'error', | ||||
|             { | ||||
| @ -238,7 +251,24 @@ module.exports = { | ||||
|         'use-isnan': 'error', | ||||
|         'yoda': 'error', | ||||
|     }, | ||||
| }; | ||||
| 
 | ||||
| var testsConfig = Object.assign({}, appConfig); | ||||
| testsConfig['rules']['padded-blocks'] = [ | ||||
|     'error', | ||||
|     { | ||||
|         classes: 'always', | ||||
|         switches: 'never', | ||||
|     }, | ||||
| ]; | ||||
| testsConfig['plugins'].push('jest'); | ||||
| testsConfig['extends'].push('plugin:jest/recommended'); | ||||
| 
 | ||||
| module.exports = { | ||||
|     root: true, | ||||
|     overrides: [ | ||||
|         Object.assign({ files: ['*.ts'] }, appConfig), | ||||
|         Object.assign({ files: ['*.test.ts'] }, testsConfig), | ||||
|         { | ||||
|             files: ['*.html'], | ||||
|             extends: ['plugin:@angular-eslint/template/recommended'], | ||||
|  | ||||
							
								
								
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @ -22,7 +22,6 @@ npm-debug.log* | ||||
| /.sass-cache | ||||
| /.sourcemaps | ||||
| /.versions | ||||
| /.vscode | ||||
| /coverage | ||||
| /dist | ||||
| /node_modules | ||||
|  | ||||
							
								
								
									
										35
									
								
								.vscode/launch.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								.vscode/launch.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,35 @@ | ||||
| { | ||||
|     "version": "0.2.0", | ||||
|     "configurations": [ | ||||
|         { | ||||
|             "type": "node", | ||||
|             "request": "launch", | ||||
|             "name": "Jest All", | ||||
|             "program": "${workspaceFolder}/node_modules/.bin/jest", | ||||
|             "args": ["--runInBand"], | ||||
|             "console": "integratedTerminal", | ||||
|             "internalConsoleOptions": "neverOpen", | ||||
|             "disableOptimisticBPs": true, | ||||
|             "windows": { | ||||
|                 "program": "${workspaceFolder}/node_modules/jest/bin/jest", | ||||
|             } | ||||
|         }, | ||||
|         { | ||||
|             "type": "node", | ||||
|             "request": "launch", | ||||
|             "name": "Jest Current File", | ||||
|             "program": "${workspaceFolder}/node_modules/.bin/jest", | ||||
|             "args": [ | ||||
|                 "${fileBasenameNoExtension}", | ||||
|                 "--config", | ||||
|                 "jest.config.js" | ||||
|             ], | ||||
|             "console": "integratedTerminal", | ||||
|             "internalConsoleOptions": "neverOpen", | ||||
|             "disableOptimisticBPs": true, | ||||
|             "windows": { | ||||
|                 "program": "${workspaceFolder}/node_modules/jest/bin/jest", | ||||
|             } | ||||
|         } | ||||
|     ] | ||||
| } | ||||
							
								
								
									
										145
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										145
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							| @ -3849,12 +3849,6 @@ | ||||
|         "picomatch": "^2.0.4" | ||||
|       } | ||||
|     }, | ||||
|     "app-root-path": { | ||||
|       "version": "3.0.0", | ||||
|       "resolved": "https://registry.npmjs.org/app-root-path/-/app-root-path-3.0.0.tgz", | ||||
|       "integrity": "sha512-qMcx+Gy2UZynHjOHOIXPNvpf+9cjvk3cWrBBK7zg4gH9+clobJRb9NGzcT7mQTcV/6Gm/1WelUtqxVXnNlrwcw==", | ||||
|       "dev": true | ||||
|     }, | ||||
|     "aproba": { | ||||
|       "version": "1.2.0", | ||||
|       "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", | ||||
| @ -3876,16 +3870,6 @@ | ||||
|         "sprintf-js": "~1.0.2" | ||||
|       } | ||||
|     }, | ||||
|     "aria-query": { | ||||
|       "version": "3.0.0", | ||||
|       "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-3.0.0.tgz", | ||||
|       "integrity": "sha1-ZbP8wcoRVajJrmTW7uKX8V1RM8w=", | ||||
|       "dev": true, | ||||
|       "requires": { | ||||
|         "ast-types-flow": "0.0.7", | ||||
|         "commander": "^2.11.0" | ||||
|       } | ||||
|     }, | ||||
|     "arity-n": { | ||||
|       "version": "1.0.4", | ||||
|       "resolved": "https://registry.npmjs.org/arity-n/-/arity-n-1.0.4.tgz", | ||||
| @ -4059,12 +4043,6 @@ | ||||
|       "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", | ||||
|       "dev": true | ||||
|     }, | ||||
|     "ast-types-flow": { | ||||
|       "version": "0.0.7", | ||||
|       "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.7.tgz", | ||||
|       "integrity": "sha1-9wtzXGvKGlycItmCw+Oef+ujva0=", | ||||
|       "dev": true | ||||
|     }, | ||||
|     "astral-regex": { | ||||
|       "version": "1.0.0", | ||||
|       "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", | ||||
| @ -4184,15 +4162,6 @@ | ||||
|       "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.10.1.tgz", | ||||
|       "integrity": "sha512-zg7Hz2k5lI8kb7U32998pRRFin7zJlkfezGJjUc2heaD4Pw2wObakCDVzkKztTm/Ln7eiVvYsjqak0Ed4LkMDA==" | ||||
|     }, | ||||
|     "axobject-query": { | ||||
|       "version": "2.0.2", | ||||
|       "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-2.0.2.tgz", | ||||
|       "integrity": "sha512-MCeek8ZH7hKyO1rWUbKNQBbl4l2eY0ntk7OGi+q0RlafrCnfPxC06WZA+uebCfmYp4mNU9jRBP1AhGyf8+W3ww==", | ||||
|       "dev": true, | ||||
|       "requires": { | ||||
|         "ast-types-flow": "0.0.7" | ||||
|       } | ||||
|     }, | ||||
|     "babel-jest": { | ||||
|       "version": "26.5.2", | ||||
|       "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-26.5.2.tgz", | ||||
| @ -5291,60 +5260,6 @@ | ||||
|         } | ||||
|       } | ||||
|     }, | ||||
|     "codelyzer": { | ||||
|       "version": "6.0.1", | ||||
|       "resolved": "https://registry.npmjs.org/codelyzer/-/codelyzer-6.0.1.tgz", | ||||
|       "integrity": "sha512-cOyGQgMdhnRYtW2xrJUNrNYDjEgwQ+BrE2y93Bwz3h4DJ6vJRLfupemU5N3pbYsUlBHJf0u1j1UGk+NLW4d97g==", | ||||
|       "dev": true, | ||||
|       "requires": { | ||||
|         "@angular/compiler": "9.0.0", | ||||
|         "@angular/core": "9.0.0", | ||||
|         "app-root-path": "^3.0.0", | ||||
|         "aria-query": "^3.0.0", | ||||
|         "axobject-query": "2.0.2", | ||||
|         "css-selector-tokenizer": "^0.7.1", | ||||
|         "cssauron": "^1.4.0", | ||||
|         "damerau-levenshtein": "^1.0.4", | ||||
|         "rxjs": "^6.5.3", | ||||
|         "semver-dsl": "^1.0.1", | ||||
|         "source-map": "^0.5.7", | ||||
|         "sprintf-js": "^1.1.2", | ||||
|         "tslib": "^1.10.0", | ||||
|         "zone.js": "~0.10.3" | ||||
|       }, | ||||
|       "dependencies": { | ||||
|         "@angular/compiler": { | ||||
|           "version": "9.0.0", | ||||
|           "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-9.0.0.tgz", | ||||
|           "integrity": "sha512-ctjwuntPfZZT2mNj2NDIVu51t9cvbhl/16epc5xEwyzyDt76pX9UgwvY+MbXrf/C/FWwdtmNtfP698BKI+9leQ==", | ||||
|           "dev": true | ||||
|         }, | ||||
|         "@angular/core": { | ||||
|           "version": "9.0.0", | ||||
|           "resolved": "https://registry.npmjs.org/@angular/core/-/core-9.0.0.tgz", | ||||
|           "integrity": "sha512-6Pxgsrf0qF9iFFqmIcWmjJGkkCaCm6V5QNnxMy2KloO3SDq6QuMVRbN9RtC8Urmo25LP+eZ6ZgYqFYpdD8Hd9w==", | ||||
|           "dev": true | ||||
|         }, | ||||
|         "source-map": { | ||||
|           "version": "0.5.7", | ||||
|           "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", | ||||
|           "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", | ||||
|           "dev": true | ||||
|         }, | ||||
|         "sprintf-js": { | ||||
|           "version": "1.1.2", | ||||
|           "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.2.tgz", | ||||
|           "integrity": "sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug==", | ||||
|           "dev": true | ||||
|         }, | ||||
|         "tslib": { | ||||
|           "version": "1.14.0", | ||||
|           "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.0.tgz", | ||||
|           "integrity": "sha512-+Zw5lu0D9tvBMjGP8LpvMb0u2WW2QV3y+D8mO6J+cNzCYIN4sVy43Bf9vl92nqFahutN0I8zHa7cc4vihIshnw==", | ||||
|           "dev": true | ||||
|         } | ||||
|       } | ||||
|     }, | ||||
|     "collect-v8-coverage": { | ||||
|       "version": "1.0.1", | ||||
|       "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz", | ||||
| @ -6559,16 +6474,6 @@ | ||||
|       "integrity": "sha512-jQVeeRG70QI08vSTwf1jHxp74JoZsr2XSgETae8/xC8ovSnL2WF87GTLO86Sbwdt2lK4Umg4HnnwMO4YF3Ce7w==", | ||||
|       "dev": true | ||||
|     }, | ||||
|     "css-selector-tokenizer": { | ||||
|       "version": "0.7.3", | ||||
|       "resolved": "https://registry.npmjs.org/css-selector-tokenizer/-/css-selector-tokenizer-0.7.3.tgz", | ||||
|       "integrity": "sha512-jWQv3oCEL5kMErj4wRnK/OPoBi0D+P1FR2cDCKYPaMeD2eW3/mttav8HT4hT1CKopiJI/psEULjkClhvJo4Lvg==", | ||||
|       "dev": true, | ||||
|       "requires": { | ||||
|         "cssesc": "^3.0.0", | ||||
|         "fastparse": "^1.1.2" | ||||
|       } | ||||
|     }, | ||||
|     "css-tree": { | ||||
|       "version": "1.0.0-alpha.37", | ||||
|       "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.0.0-alpha.37.tgz", | ||||
| @ -6593,15 +6498,6 @@ | ||||
|       "integrity": "sha512-wHOppVDKl4vTAOWzJt5Ek37Sgd9qq1Bmj/T1OjvicWbU5W7ru7Pqbn0Jdqii3Drx/h+dixHKXNhZYx7blthL7g==", | ||||
|       "dev": true | ||||
|     }, | ||||
|     "cssauron": { | ||||
|       "version": "1.4.0", | ||||
|       "resolved": "https://registry.npmjs.org/cssauron/-/cssauron-1.4.0.tgz", | ||||
|       "integrity": "sha1-pmAt/34EqDBtwNuaVR6S6LVmKtg=", | ||||
|       "dev": true, | ||||
|       "requires": { | ||||
|         "through": "X.X.X" | ||||
|       } | ||||
|     }, | ||||
|     "cssesc": { | ||||
|       "version": "3.0.0", | ||||
|       "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", | ||||
| @ -6765,12 +6661,6 @@ | ||||
|         "type": "^1.0.1" | ||||
|       } | ||||
|     }, | ||||
|     "damerau-levenshtein": { | ||||
|       "version": "1.0.6", | ||||
|       "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.6.tgz", | ||||
|       "integrity": "sha512-JVrozIeElnj3QzfUIt8tB8YMluBJom4Vw9qTPpjGYQ9fYlB3D/rb6OordUxf3xeFB35LKWs0xqcO5U6ySvBtug==", | ||||
|       "dev": true | ||||
|     }, | ||||
|     "dashdash": { | ||||
|       "version": "1.14.1", | ||||
|       "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", | ||||
| @ -7936,6 +7826,12 @@ | ||||
|       "integrity": "sha512-C8YMhL+r8RMeMdYAw/rQtE6xNdMulj+zGWud/qIGnlmomiPRaLDGLMeskZ3alN6uMBojmooRimtdrXebLN4svQ==", | ||||
|       "dev": true | ||||
|     }, | ||||
|     "eslint-plugin-promise": { | ||||
|       "version": "4.2.1", | ||||
|       "resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-4.2.1.tgz", | ||||
|       "integrity": "sha512-VoM09vT7bfA7D+upt+FjeBO5eHIJQBUWki1aPvB+vbNiHS3+oGIJGIeyBtKQTME6UPXXy3vV07OL1tHd3ANuDw==", | ||||
|       "dev": true | ||||
|     }, | ||||
|     "eslint-scope": { | ||||
|       "version": "4.0.3", | ||||
|       "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.3.tgz", | ||||
| @ -8329,6 +8225,12 @@ | ||||
|       "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.4.0.tgz", | ||||
|       "integrity": "sha1-4mifjzVvrWLMplo6kcXfX5VRaS8=" | ||||
|     }, | ||||
|     "faker": { | ||||
|       "version": "5.1.0", | ||||
|       "resolved": "https://registry.npmjs.org/faker/-/faker-5.1.0.tgz", | ||||
|       "integrity": "sha512-RrWKFSSA/aNLP0g3o2WW1Zez7/MnMr7xkiZmoCfAGZmdkDQZ6l2KtuXHN5XjdvpRjDl8+3vf+Rrtl06Z352+Mw==", | ||||
|       "dev": true | ||||
|     }, | ||||
|     "fast-deep-equal": { | ||||
|       "version": "3.1.3", | ||||
|       "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", | ||||
| @ -8363,12 +8265,6 @@ | ||||
|       "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", | ||||
|       "dev": true | ||||
|     }, | ||||
|     "fastparse": { | ||||
|       "version": "1.1.2", | ||||
|       "resolved": "https://registry.npmjs.org/fastparse/-/fastparse-1.1.2.tgz", | ||||
|       "integrity": "sha512-483XLLxTVIwWK3QTrMGRqUfUpoOs/0hbQrl2oz4J0pAcm3A3bu84wxTFqGqkJzewCLdME38xJLJAxBABfQT8sQ==", | ||||
|       "dev": true | ||||
|     }, | ||||
|     "fastq": { | ||||
|       "version": "1.8.0", | ||||
|       "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.8.0.tgz", | ||||
| @ -15642,23 +15538,6 @@ | ||||
|         } | ||||
|       } | ||||
|     }, | ||||
|     "semver-dsl": { | ||||
|       "version": "1.0.1", | ||||
|       "resolved": "https://registry.npmjs.org/semver-dsl/-/semver-dsl-1.0.1.tgz", | ||||
|       "integrity": "sha1-02eN5VVeimH2Ke7QJTZq5fJzQKA=", | ||||
|       "dev": true, | ||||
|       "requires": { | ||||
|         "semver": "^5.3.0" | ||||
|       }, | ||||
|       "dependencies": { | ||||
|         "semver": { | ||||
|           "version": "5.7.1", | ||||
|           "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", | ||||
|           "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", | ||||
|           "dev": true | ||||
|         } | ||||
|       } | ||||
|     }, | ||||
|     "semver-intersect": { | ||||
|       "version": "1.4.0", | ||||
|       "resolved": "https://registry.npmjs.org/semver-intersect/-/semver-intersect-1.4.0.tgz", | ||||
|  | ||||
| @ -121,7 +121,6 @@ | ||||
|     "@angular/language-service": "~10.0.0", | ||||
|     "@ionic/angular-toolkit": "^2.3.0", | ||||
|     "@types/node": "^12.12.64", | ||||
|     "codelyzer": "^6.0.0", | ||||
|     "@typescript-eslint/eslint-plugin": "4.3.0", | ||||
|     "@typescript-eslint/parser": "4.3.0", | ||||
|     "eslint": "^7.6.0", | ||||
| @ -131,6 +130,8 @@ | ||||
|     "eslint-plugin-jest": "^24.1.0", | ||||
|     "eslint-plugin-jsdoc": "^30.6.3", | ||||
|     "eslint-plugin-prefer-arrow": "^1.2.2", | ||||
|     "eslint-plugin-promise": "^4.2.1", | ||||
|     "faker": "^5.1.0", | ||||
|     "jest": "^26.5.0", | ||||
|     "jest-preset-angular": "^8.3.1", | ||||
|     "ts-jest": "^26.4.1", | ||||
|  | ||||
							
								
								
									
										75
									
								
								src/app/classes/error.test.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										75
									
								
								src/app/classes/error.test.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,75 @@ | ||||
| // (C) Copyright 2015 Moodle Pty Ltd.
 | ||||
| //
 | ||||
| // Licensed under the Apache License, Version 2.0 (the "License");
 | ||||
| // you may not use this file except in compliance with the License.
 | ||||
| // You may obtain a copy of the License at
 | ||||
| //
 | ||||
| //     http://www.apache.org/licenses/LICENSE-2.0
 | ||||
| //
 | ||||
| // Unless required by applicable law or agreed to in writing, software
 | ||||
| // distributed under the License is distributed on an "AS IS" BASIS,
 | ||||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | ||||
| // See the License for the specific language governing permissions and
 | ||||
| // limitations under the License.
 | ||||
| import Faker from 'faker'; | ||||
| 
 | ||||
| import { CoreError } from './error'; | ||||
| 
 | ||||
| describe('CoreError', () => { | ||||
| 
 | ||||
|     it('behaves like an error', () => { | ||||
|         // Arrange
 | ||||
|         const message = Faker.lorem.sentence(); | ||||
| 
 | ||||
|         let error: CoreError | null = null; | ||||
| 
 | ||||
|         // Act
 | ||||
|         try { | ||||
|             throw new CoreError(message); | ||||
|         } catch (e) { | ||||
|             error = e; | ||||
|         } | ||||
| 
 | ||||
|         // Assert
 | ||||
|         expect(error).not.toBeNull(); | ||||
|         expect(error).toBeInstanceOf(Error); | ||||
|         expect(error).toBeInstanceOf(CoreError); | ||||
|         expect(error!.name).toEqual('CoreError'); | ||||
|         expect(error!.message).toEqual(message); | ||||
|         expect(error!.stack).not.toBeNull(); | ||||
|         expect(error!.stack).toContain('src/app/classes/error.test.ts'); | ||||
|     }); | ||||
| 
 | ||||
|     it('can be subclassed', () => { | ||||
|         // Arrange
 | ||||
|         class CustomCoreError extends CoreError { | ||||
| 
 | ||||
|             constructor(m: string) { | ||||
|                 super(`Custom message: ${m}`); | ||||
|             } | ||||
| 
 | ||||
|         } | ||||
| 
 | ||||
|         const message = Faker.lorem.sentence(); | ||||
| 
 | ||||
|         let error: CustomCoreError | null = null; | ||||
| 
 | ||||
|         // Act
 | ||||
|         try { | ||||
|             throw new CustomCoreError(message); | ||||
|         } catch (e) { | ||||
|             error = e; | ||||
|         } | ||||
| 
 | ||||
|         // Assert
 | ||||
|         expect(error).not.toBeNull(); | ||||
|         expect(error).toBeInstanceOf(Error); | ||||
|         expect(error).toBeInstanceOf(CoreError); | ||||
|         expect(error).toBeInstanceOf(CustomCoreError); | ||||
|         expect(error!.name).toEqual('CustomCoreError'); | ||||
|         expect(error!.message).toEqual(`Custom message: ${message}`); | ||||
|         expect(error!.stack).not.toBeNull(); | ||||
|         expect(error!.stack).toContain('src/app/classes/error.test.ts'); | ||||
|     }); | ||||
| 
 | ||||
| }); | ||||
| @ -13,9 +13,10 @@ | ||||
| // limitations under the License.
 | ||||
| 
 | ||||
| import { Injectable } from '@angular/core'; | ||||
| import { NavController } from '@ionic/angular'; | ||||
| import { WKUserScriptWindow, WKUserScriptInjectionTime } from 'cordova-plugin-wkuserscript'; | ||||
| 
 | ||||
| import { CoreApp, CoreAppProvider } from '@services/app'; | ||||
| import { CoreApp } from '@services/app'; | ||||
| import { CoreFile } from '@services/file'; | ||||
| import { CoreFileHelper } from '@services/file-helper'; | ||||
| import { CoreSites } from '@services/sites'; | ||||
| @ -24,42 +25,35 @@ import { CoreTextUtils } from '@services/utils/text'; | ||||
| import { CoreUrlUtils } from '@services/utils/url'; | ||||
| import { CoreUtils } from '@services/utils/utils'; | ||||
| 
 | ||||
| import { makeSingleton, Translate, Network, Platform, NgZone } from '@singletons/core.singletons'; | ||||
| import { makeSingleton, Network, Platform, NgZone } from '@singletons/core.singletons'; | ||||
| import { CoreLogger } from '@singletons/logger'; | ||||
| import { CoreUrl } from '@singletons/url'; | ||||
| import { CoreWindow } from '@singletons/window'; | ||||
| 
 | ||||
| /** | ||||
|  * Possible types of frame elements. | ||||
|  */ | ||||
| type CoreFrameElement = (HTMLIFrameElement | HTMLFrameElement | HTMLObjectElement | HTMLEmbedElement) & { | ||||
|     window?: Window; | ||||
|     getWindow?(): Window; | ||||
| }; | ||||
| 
 | ||||
| /* | ||||
|  * "Utils" service with helper functions for iframes, embed and similar. | ||||
|  */ | ||||
| @Injectable() | ||||
| export class CoreIframeUtilsProvider { | ||||
|     static FRAME_TAGS = ['iframe', 'frame', 'object', 'embed']; | ||||
| 
 | ||||
|     static readonly FRAME_TAGS = ['iframe', 'frame', 'object', 'embed']; | ||||
| 
 | ||||
|     protected logger: CoreLogger; | ||||
| 
 | ||||
|     constructor() { | ||||
|         this.logger = CoreLogger.getInstance('CoreUtilsProvider'); | ||||
| 
 | ||||
|         const win = <WKUserScriptWindow> window; | ||||
| 
 | ||||
|         if (CoreApp.instance.isIOS() && win.WKUserScript) { | ||||
|             Platform.instance.ready().then(() => { | ||||
|                 // Inject code to the iframes because we cannot access the online ones.
 | ||||
|                 const wwwPath = CoreFile.instance.getWWWAbsolutePath(); | ||||
|                 const linksPath = CoreTextUtils.instance.concatenatePaths(wwwPath, 'assets/js/iframe-treat-links.js'); | ||||
|                 const recaptchaPath = CoreTextUtils.instance.concatenatePaths(wwwPath, 'assets/js/iframe-recaptcha.js'); | ||||
| 
 | ||||
|                 win.WKUserScript.addScript({id: 'CoreIframeUtilsLinksScript', file: linksPath}); | ||||
|                 win.WKUserScript.addScript({ | ||||
|                     id: 'CoreIframeUtilsRecaptchaScript', | ||||
|                     file: recaptchaPath, | ||||
|                     injectionTime: WKUserScriptInjectionTime.END, | ||||
|                 }); | ||||
| 
 | ||||
|                 // Handle post messages received by iframes.
 | ||||
|                 window.addEventListener('message', this.handleIframeMessage.bind(this)); | ||||
|             }); | ||||
|         if (CoreApp.instance.isIOS() && 'WKUserScript' in window) { | ||||
|             // eslint-disable-next-line promise/catch-or-return
 | ||||
|             Platform.instance.ready().then(() => this.injectiOSScripts(window)); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
| @ -70,8 +64,8 @@ export class CoreIframeUtilsProvider { | ||||
|      * @param isSubframe Whether it's a frame inside another frame. | ||||
|      * @return True if frame is online and the app is offline, false otherwise. | ||||
|      */ | ||||
|     checkOnlineFrameInOffline(element: any, isSubframe?: boolean): boolean { | ||||
|         const src = element.src || element.data; | ||||
|     checkOnlineFrameInOffline(element: CoreFrameElement, isSubframe?: boolean): boolean { | ||||
|         const src = 'src' in element ? element.src : element.data; | ||||
| 
 | ||||
|         if (src && src != 'about:blank' && !CoreUrlUtils.instance.isLocalFileUrl(src) && !CoreApp.instance.isOnline()) { | ||||
|             if (element.classList.contains('core-iframe-offline-disabled')) { | ||||
| @ -86,8 +80,6 @@ export class CoreIframeUtilsProvider { | ||||
|             div.setAttribute('padding', ''); | ||||
|             div.classList.add('core-iframe-offline-warning'); | ||||
| 
 | ||||
|             const site = CoreSites.instance.getCurrentSite(); | ||||
|             const username = site ? site.getInfo().username : undefined; | ||||
|             // @todo Handle link
 | ||||
| 
 | ||||
|             // Add a class to specify that the iframe is hidden.
 | ||||
| @ -112,8 +104,13 @@ export class CoreIframeUtilsProvider { | ||||
|             return true; | ||||
|         } else if (element.classList.contains('core-iframe-offline-disabled')) { | ||||
|             // Reload the frame.
 | ||||
|             if ('src' in element) { | ||||
|                 // eslint-disable-next-line no-self-assign
 | ||||
|                 element.src = element.src; | ||||
|             } else { | ||||
|                 // eslint-disable-next-line no-self-assign
 | ||||
|                 element.data = element.data; | ||||
|             } | ||||
| 
 | ||||
|             // Remove the warning and show the iframe
 | ||||
|             CoreDomUtils.instance.removeElement(element.parentElement, 'div.core-iframe-offline-warning'); | ||||
| @ -134,12 +131,14 @@ export class CoreIframeUtilsProvider { | ||||
|      * @param element Element to treat (iframe, embed, ...). | ||||
|      * @return Window and Document. | ||||
|      */ | ||||
|     getContentWindowAndDocument(element: any): { window: Window, document: Document } { | ||||
|         let contentWindow: Window = element.contentWindow; | ||||
|     getContentWindowAndDocument(element: CoreFrameElement): { window: Window; document: Document } { | ||||
|         let contentWindow: Window = 'contentWindow' in element ? element.contentWindow : undefined; | ||||
|         let contentDocument: Document; | ||||
| 
 | ||||
|         try { | ||||
|             contentDocument = element.contentDocument || (contentWindow && contentWindow.document); | ||||
|             contentDocument = 'contentDocument' in element && element.contentDocument | ||||
|                 ? element.contentDocument | ||||
|                 : contentWindow && contentWindow.document; | ||||
|         } catch (ex) { | ||||
|             // Ignore errors.
 | ||||
|         } | ||||
| @ -149,7 +148,7 @@ export class CoreIframeUtilsProvider { | ||||
|             contentWindow = contentDocument.defaultView; | ||||
|         } | ||||
| 
 | ||||
|         if (!contentWindow && element.getSVGDocument) { | ||||
|         if (!contentWindow && 'getSVGDocument' in element) { | ||||
|             // It's probably an <embed>. Try to get the window and the document.
 | ||||
|             try { | ||||
|                 contentDocument = element.getSVGDocument(); | ||||
| @ -187,9 +186,6 @@ export class CoreIframeUtilsProvider { | ||||
|             case 'link_clicked': | ||||
|                 this.linkClicked(event.data.link); | ||||
|                 break; | ||||
| 
 | ||||
|             default: | ||||
|                 break; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
| @ -202,10 +198,15 @@ export class CoreIframeUtilsProvider { | ||||
|      * @param contentDocument The document of the element contents. | ||||
|      * @param navCtrl NavController to use if a link can be opened in the app. | ||||
|      */ | ||||
|     redefineWindowOpen(element: any, contentWindow: Window, contentDocument: Document, navCtrl?: any): void { | ||||
|     redefineWindowOpen( | ||||
|         element: CoreFrameElement, | ||||
|         contentWindow: Window, | ||||
|         contentDocument: Document, | ||||
|         navCtrl?: NavController, | ||||
|     ): void { | ||||
|         if (contentWindow) { | ||||
|             // Intercept window.open.
 | ||||
|             (<any> contentWindow).open = (url: string, name: string): Window => { | ||||
|             contentWindow.open = (url: string, name: string) => { | ||||
|                 this.windowOpen(url, name, element, navCtrl); | ||||
| 
 | ||||
|                 return null; | ||||
| @ -216,7 +217,7 @@ export class CoreIframeUtilsProvider { | ||||
|             // Search sub frames.
 | ||||
|             CoreIframeUtilsProvider.FRAME_TAGS.forEach((tag) => { | ||||
|                 const elements = Array.from(contentDocument.querySelectorAll(tag)); | ||||
|                 elements.forEach((subElement) => { | ||||
|                 elements.forEach((subElement: CoreFrameElement) => { | ||||
|                     this.treatFrame(subElement, true, navCtrl); | ||||
|                 }); | ||||
|             }); | ||||
| @ -231,7 +232,7 @@ export class CoreIframeUtilsProvider { | ||||
|      * @param isSubframe Whether it's a frame inside another frame. | ||||
|      * @param navCtrl NavController to use if a link can be opened in the app. | ||||
|      */ | ||||
|     treatFrame(element: any, isSubframe?: boolean, navCtrl?: any): void { | ||||
|     treatFrame(element: CoreFrameElement, isSubframe?: boolean, navCtrl?: NavController): void { | ||||
|         if (element) { | ||||
|             this.checkOnlineFrameInOffline(element, isSubframe); | ||||
| 
 | ||||
| @ -266,7 +267,7 @@ export class CoreIframeUtilsProvider { | ||||
|      * @param element Element to treat (iframe, embed, ...). | ||||
|      * @param contentDocument The document of the element contents. | ||||
|      */ | ||||
|     treatFrameLinks(element: any, contentDocument: Document): void { | ||||
|     treatFrameLinks(element: CoreFrameElement, contentDocument: Document): void { | ||||
|         if (!contentDocument) { | ||||
|             return; | ||||
|         } | ||||
| @ -292,7 +293,7 @@ export class CoreIframeUtilsProvider { | ||||
|             link.treated = true; | ||||
|             link.addEventListener('click', this.linkClicked.bind(this, link, element)); | ||||
|         }, { | ||||
|             capture: true // Use capture to fix this listener not called if the element clicked is too deep in the DOM.
 | ||||
|             capture: true, // Use capture to fix this listener not called if the element clicked is too deep in the DOM.
 | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
| @ -305,11 +306,13 @@ export class CoreIframeUtilsProvider { | ||||
|      * @param navCtrl NavController to use if a link can be opened in the app. | ||||
|      * @return Promise resolved when done. | ||||
|      */ | ||||
|     protected async windowOpen(url: string, name: string, element?: any, navCtrl?: any): Promise<void> { | ||||
|     protected async windowOpen(url: string, name: string, element?: CoreFrameElement, navCtrl?: NavController): Promise<void> { | ||||
|         const scheme = CoreUrlUtils.instance.getUrlScheme(url); | ||||
|         if (!scheme) { | ||||
|             // It's a relative URL, use the frame src to create the full URL.
 | ||||
|             const src = element && (element.src || element.data); | ||||
|             const src = element | ||||
|                 ? ('src' in element ? element.src : element.data) | ||||
|                 : null; | ||||
|             if (src) { | ||||
|                 const dirAndFile = CoreFile.instance.getFileAndDirectoryFromPath(src); | ||||
|                 if (dirAndFile.directory) { | ||||
| @ -372,8 +375,11 @@ export class CoreIframeUtilsProvider { | ||||
|      * @param event Click event. | ||||
|      * @return Promise resolved when done. | ||||
|      */ | ||||
|     protected async linkClicked(link: {href: string, target?: string}, element?: HTMLFrameElement | HTMLObjectElement, | ||||
|             event?: Event): Promise<void> { | ||||
|     protected async linkClicked( | ||||
|         link: {href: string; target?: string}, | ||||
|         element?: HTMLFrameElement | HTMLObjectElement, | ||||
|         event?: Event, | ||||
|     ): Promise<void> { | ||||
|         if (event && event.defaultPrevented) { | ||||
|             // Event already prevented by some other code.
 | ||||
|             return; | ||||
| @ -438,6 +444,28 @@ export class CoreIframeUtilsProvider { | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Inject code to the iframes because we cannot access the online ones. | ||||
|      * | ||||
|      * @param userScriptWindow Window. | ||||
|      */ | ||||
|     private injectiOSScripts(userScriptWindow: WKUserScriptWindow) { | ||||
|         const wwwPath = CoreFile.instance.getWWWAbsolutePath(); | ||||
|         const linksPath = CoreTextUtils.instance.concatenatePaths(wwwPath, 'assets/js/iframe-treat-links.js'); | ||||
|         const recaptchaPath = CoreTextUtils.instance.concatenatePaths(wwwPath, 'assets/js/iframe-recaptcha.js'); | ||||
| 
 | ||||
|         userScriptWindow.WKUserScript.addScript({ id: 'CoreIframeUtilsLinksScript', file: linksPath }); | ||||
|         userScriptWindow.WKUserScript.addScript({ | ||||
|             id: 'CoreIframeUtilsRecaptchaScript', | ||||
|             file: recaptchaPath, | ||||
|             injectionTime: WKUserScriptInjectionTime.END, | ||||
|         }); | ||||
| 
 | ||||
|         // Handle post messages received by iframes.
 | ||||
|         window.addEventListener('message', this.handleIframeMessage.bind(this)); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| export class CoreIframeUtils extends makeSingleton(CoreIframeUtilsProvider) {} | ||||
|  | ||||
| @ -13,36 +13,55 @@ | ||||
| // limitations under the License.
 | ||||
| 
 | ||||
| import { Injectable } from '@angular/core'; | ||||
| import { FileEntry } from '@ionic-native/file'; | ||||
| 
 | ||||
| import { CoreFile } from '@services/file'; | ||||
| import { CoreTextUtils } from '@services/utils/text'; | ||||
| import { makeSingleton, Translate, Http } from '@singletons/core.singletons'; | ||||
| import { CoreLogger } from '@singletons/logger'; | ||||
| import { CoreWSExternalFile } from '@services/ws'; | ||||
| import { CoreUtils } from '@services/utils/utils'; | ||||
| 
 | ||||
| interface MimeTypeInfo { | ||||
|     type: string; | ||||
|     icon?: string; | ||||
|     groups?: string[]; | ||||
| 
 | ||||
|     // eslint-disable-next-line id-blacklist
 | ||||
|     string?: string; | ||||
| } | ||||
| 
 | ||||
| interface MimeTypeGroupInfo { | ||||
|     mimetypes: string[]; | ||||
|     extensions: string[]; | ||||
| } | ||||
| 
 | ||||
| const EXTENSION_REGEX = /^[a-z0-9]+$/; | ||||
| 
 | ||||
| /* | ||||
|  * "Utils" service with helper functions for mimetypes and extensions. | ||||
|  */ | ||||
| @Injectable() | ||||
| export class CoreMimetypeUtilsProvider { | ||||
| 
 | ||||
|     protected logger: CoreLogger; | ||||
|     protected extToMime = {}; // Object to map extensions -> mimetypes.
 | ||||
|     protected mimeToExt = {}; // Object to map mimetypes -> extensions.
 | ||||
|     protected groupsMimeInfo = {}; // Object to hold extensions and mimetypes that belong to a certain "group" (audio, video, ...).
 | ||||
|     protected extensionRegex = /^[a-z0-9]+$/; | ||||
|     protected extToMime: Record<string, MimeTypeInfo> = {}; | ||||
|     protected mimeToExt: Record<string, string[]> = {}; | ||||
|     protected groupsMimeInfo: Record<string, MimeTypeGroupInfo> = {}; | ||||
| 
 | ||||
|     constructor() { | ||||
|         this.logger = CoreLogger.getInstance('CoreMimetypeUtilsProvider'); | ||||
| 
 | ||||
|         Http.instance.get('assets/exttomime.json').subscribe((result) => { | ||||
|         Http.instance.get('assets/exttomime.json').subscribe((result: Record<string, MimeTypeInfo>) => { | ||||
|             this.extToMime = result; | ||||
|         }, (err) => { | ||||
|         }, () => { | ||||
|             // Error, shouldn't happen.
 | ||||
|         }); | ||||
| 
 | ||||
|         Http.instance.get('assets/mimetoext.json').subscribe((result) => { | ||||
|         Http.instance.get('assets/mimetoext.json').subscribe((result: Record<string, string[]>) => { | ||||
|             this.mimeToExt = result; | ||||
|         }, (err) => { | ||||
|             // Error, shouldn't happen.
 | ||||
|         }, () => { | ||||
|             // Error, shouldn't happen
 | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
| @ -148,31 +167,38 @@ export class CoreMimetypeUtilsProvider { | ||||
|      * Set the embed type to display an embedded file and mimetype if not found. | ||||
|      * | ||||
|      * @param file File object. | ||||
|      * @paran path Alternative path that will override fileurl from file object. | ||||
|      * @param path Alternative path that will override fileurl from file object. | ||||
|      */ | ||||
|     getEmbeddedHtml(file: any, path?: string): string { | ||||
|         let ext; | ||||
|         const filename = file.filename || file.name; | ||||
|     getEmbeddedHtml(file: CoreWSExternalFile | FileEntry, path?: string): string { | ||||
|         const filename = CoreUtils.instance.isFileEntry(file) ? (file as FileEntry).name : file.filename; | ||||
|         const extension = !CoreUtils.instance.isFileEntry(file) && file.mimetype | ||||
|             ? this.getExtension(file.mimetype) | ||||
|             : this.getFileExtension(filename); | ||||
|         const mimeType = !CoreUtils.instance.isFileEntry(file) && file.mimetype ? file.mimetype : this.getMimeType(extension); | ||||
| 
 | ||||
|         if (file.mimetype) { | ||||
|             ext = this.getExtension(file.mimetype); | ||||
|         } else { | ||||
|             ext = this.getFileExtension(filename); | ||||
|             file.mimetype = this.getMimeType(ext); | ||||
|         } | ||||
|         // @todo linting: See if this can be removed
 | ||||
|         (file as CoreWSExternalFile).mimetype = mimeType; | ||||
| 
 | ||||
|         if (this.canBeEmbedded(ext)) { | ||||
|             file.embedType = this.getExtensionType(ext); | ||||
|         if (this.canBeEmbedded(extension)) { | ||||
|             const embedType = this.getExtensionType(extension); | ||||
| 
 | ||||
|             path = CoreFile.instance.convertFileSrc(path || file.fileurl || file.url || (file.toURL && file.toURL())); | ||||
|             // @todo linting: See if this can be removed
 | ||||
|             (file as { embedType: string }).embedType = embedType; | ||||
| 
 | ||||
|             if (file.embedType == 'image') { | ||||
|                 return '<img src="' + path + '">'; | ||||
|             } | ||||
|             if (file.embedType == 'audio' || file.embedType == 'video') { | ||||
|                 return '<' + file.embedType + ' controls title="' + filename + '" src="' + path + '">' + | ||||
|                     '<source src="' + path + '" type="' + file.mimetype + '">' + | ||||
|                     '</' + file.embedType + '>'; | ||||
|             path = CoreFile.instance.convertFileSrc(path ?? (CoreUtils.instance.isFileEntry(file) ? file.toURL() : file.fileurl)); | ||||
| 
 | ||||
|             switch (embedType) { | ||||
|                 case 'image': | ||||
|                     return `<img src="${path}">`; | ||||
|                 case 'audio': | ||||
|                 case 'video': | ||||
|                     return [ | ||||
|                         `<${embedType} controls title="${filename}" src="${path}">`, | ||||
|                         `<source src="${path}" type="${mimeType}">`, | ||||
|                         `</${embedType}>`, | ||||
|                     ].join(''); | ||||
|                 default: | ||||
|                     return ''; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
| @ -290,7 +316,7 @@ export class CoreMimetypeUtilsProvider { | ||||
|                 candidate = candidate.substr(0, position); | ||||
|             } | ||||
| 
 | ||||
|             if (this.extensionRegex.test(candidate)) { | ||||
|             if (EXTENSION_REGEX.test(candidate)) { | ||||
|                 extension = candidate; | ||||
|             } | ||||
|         } | ||||
| @ -338,7 +364,7 @@ export class CoreMimetypeUtilsProvider { | ||||
|      * @param field The field to get. If not supplied, all the info will be returned. | ||||
|      * @return Info for the group. | ||||
|      */ | ||||
|     getGroupMimeInfo(group: string, field?: string): any { | ||||
|     getGroupMimeInfo(group: string, field?: string): MimeTypeGroupInfo { | ||||
|         if (typeof this.groupsMimeInfo[group] == 'undefined') { | ||||
|             this.fillGroupMimeInfo(group); | ||||
|         } | ||||
| @ -372,13 +398,13 @@ export class CoreMimetypeUtilsProvider { | ||||
|      * @param capitalise If true, capitalises first character of result. | ||||
|      * @return Type description. | ||||
|      */ | ||||
|     getMimetypeDescription(obj: any, capitalise?: boolean): string { | ||||
|     getMimetypeDescription(obj: FileEntry | { filename: string; mimetype: string } | string, capitalise?: boolean): string { | ||||
|         const langPrefix = 'assets.mimetypes.'; | ||||
|         let filename = ''; | ||||
|         let mimetype = ''; | ||||
|         let extension = ''; | ||||
| 
 | ||||
|         if (typeof obj == 'object' && typeof obj.file == 'function') { | ||||
|         if (typeof obj == 'object' && CoreUtils.instance.isFileEntry(obj)) { | ||||
|             // It's a FileEntry. Don't use the file function because it's asynchronous and the type isn't reliable.
 | ||||
|             filename = obj.name; | ||||
|         } else if (typeof obj == 'object') { | ||||
| @ -548,6 +574,7 @@ export class CoreMimetypeUtilsProvider { | ||||
| 
 | ||||
|         return path; | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| export class CoreMimetypeUtils extends makeSingleton(CoreMimetypeUtilsProvider) {} | ||||
|  | ||||
| @ -18,6 +18,8 @@ import { DomSanitizer, SafeUrl } from '@angular/platform-browser'; | ||||
| import { CoreApp } from '@services/app'; | ||||
| import { CoreLang } from '@services/lang'; | ||||
| import { makeSingleton, Translate } from '@singletons/core.singletons'; | ||||
| import { CoreWSExternalFile } from '@services/ws'; | ||||
| import { Locutus } from '@singletons/locutus'; | ||||
| 
 | ||||
| /** | ||||
|  * Different type of errors the app can treat. | ||||
| @ -36,7 +38,7 @@ export type CoreTextErrorObject = { | ||||
| export class CoreTextUtilsProvider { | ||||
| 
 | ||||
|     // List of regular expressions to convert the old nomenclature to new nomenclature for disabled features.
 | ||||
|     protected DISABLED_FEATURES_COMPAT_REGEXPS = [ | ||||
|     protected readonly DISABLED_FEATURES_COMPAT_REGEXPS: { old: RegExp; new: string }[] = [ | ||||
|         { old: /\$mmLoginEmailSignup/g, new: 'CoreLoginEmailSignup' }, | ||||
|         { old: /\$mmSideMenuDelegate/g, new: 'CoreMainMenuDelegate' }, | ||||
|         { old: /\$mmCoursesDelegate/g, new: 'CoreCourseOptionsDelegate' }, | ||||
| @ -82,7 +84,7 @@ export class CoreTextUtilsProvider { | ||||
|         { old: /remoteAddOn_/g, new: 'sitePlugin_' }, | ||||
|     ]; | ||||
| 
 | ||||
|     protected template = document.createElement('template'); // A template element to convert HTML to element.
 | ||||
|     protected template: HTMLTemplateElement = document.createElement('template'); // A template element to convert HTML to element.
 | ||||
| 
 | ||||
|     constructor(private sanitizer: DomSanitizer) { } | ||||
| 
 | ||||
| @ -200,7 +202,6 @@ export class CoreTextUtilsProvider { | ||||
|      * @return Size in human readable format. | ||||
|      */ | ||||
|     bytesToSize(bytes: number, precision: number = 2): string { | ||||
| 
 | ||||
|         if (typeof bytes == 'undefined' || bytes === null || bytes < 0) { | ||||
|             return Translate.instance.instant('core.notapplicable'); | ||||
|         } | ||||
| @ -449,9 +450,17 @@ export class CoreTextUtilsProvider { | ||||
|      * @param courseId Course ID the text belongs to. It can be used to improve performance with filters. | ||||
|      * @deprecated since 3.8.3. Please use viewText instead. | ||||
|      */ | ||||
|     expandText(title: string, text: string, component?: string, componentId?: string | number, files?: any[], | ||||
|             filter?: boolean, contextLevel?: string, instanceId?: number, courseId?: number): void { | ||||
| 
 | ||||
|     expandText( | ||||
|         title: string, | ||||
|         text: string, | ||||
|         component?: string, | ||||
|         componentId?: string | number, | ||||
|         files?: CoreWSExternalFile[], | ||||
|         filter?: boolean, | ||||
|         contextLevel?: string, | ||||
|         instanceId?: number, | ||||
|         courseId?: number, | ||||
|     ): void { | ||||
|         return this.viewText(title, text, { | ||||
|             component, | ||||
|             componentId, | ||||
| @ -531,12 +540,12 @@ export class CoreTextUtilsProvider { | ||||
|      * @param files Files to extract the URL from. They need to have the URL in a 'url' or 'fileurl' attribute. | ||||
|      * @return Pluginfile URL, undefined if no files found. | ||||
|      */ | ||||
|     getTextPluginfileUrl(files: any[]): string { | ||||
|     getTextPluginfileUrl(files: CoreWSExternalFile[]): string { | ||||
|         if (files && files.length) { | ||||
|             const fileURL = files[0].url || files[0].fileurl; | ||||
|             const url = files[0].fileurl; | ||||
| 
 | ||||
|             // Remove text after last slash (encoded or not).
 | ||||
|             return fileURL.substr(0, Math.max(fileURL.lastIndexOf('/'), fileURL.lastIndexOf('%2F'))); | ||||
|             return url.substr(0, Math.max(url.lastIndexOf('/'), url.lastIndexOf('%2F'))); | ||||
|         } | ||||
| 
 | ||||
|         return undefined; | ||||
| @ -610,13 +619,17 @@ export class CoreTextUtilsProvider { | ||||
|      * @param data Object to be checked. | ||||
|      * @return If the data has any long Unicode char on it. | ||||
|      */ | ||||
|     hasUnicodeData(data: object): boolean { | ||||
|     hasUnicodeData(data: Record<string, unknown>): boolean { | ||||
|         for (const el in data) { | ||||
|             if (typeof data[el] == 'object') { | ||||
|                 if (this.hasUnicodeData(data[el])) { | ||||
|                 if (this.hasUnicodeData(data[el] as Record<string, unknown>)) { | ||||
|                     return true; | ||||
|                 } | ||||
|             } else if (typeof data[el] == 'string' && this.hasUnicode(data[el])) { | ||||
| 
 | ||||
|                 continue; | ||||
|             } | ||||
| 
 | ||||
|             if (typeof data[el] == 'string' && this.hasUnicode(data[el] as string)) { | ||||
|                 return true; | ||||
|             } | ||||
|         } | ||||
| @ -632,13 +645,13 @@ export class CoreTextUtilsProvider { | ||||
|      * @param logErrorFn An error to call with the exception to log the error. If not supplied, no error. | ||||
|      * @return JSON parsed as object or what it gets. | ||||
|      */ | ||||
|     parseJSON(json: string, defaultValue?: any, logErrorFn?: (error?: any) => void): any { | ||||
|     parseJSON<T>(json: string, defaultValue?: T, logErrorFn?: (error?: Error) => void): T | string { | ||||
|         try { | ||||
|             return JSON.parse(json); | ||||
|         } catch (ex) { | ||||
|         } catch (error) { | ||||
|             // Error, log the error if needed.
 | ||||
|             if (logErrorFn) { | ||||
|                 logErrorFn(ex); | ||||
|                 logErrorFn(error); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
| @ -675,7 +688,7 @@ export class CoreTextUtilsProvider { | ||||
|             return ''; | ||||
|         } | ||||
| 
 | ||||
|         return text.replace(/[#:\/\?\\]+/g, '_'); | ||||
|         return text.replace(/[#:/?\\]+/g, '_'); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
| @ -700,7 +713,7 @@ export class CoreTextUtilsProvider { | ||||
|      * @param files Files to extract the pluginfile URL from. They need to have the URL in a url or fileurl attribute. | ||||
|      * @return Treated text. | ||||
|      */ | ||||
|     replacePluginfileUrls(text: string, files: any[]): string { | ||||
|     replacePluginfileUrls(text: string, files: CoreWSExternalFile[]): string { | ||||
|         if (text && typeof text == 'string') { | ||||
|             const fileURL = this.getTextPluginfileUrl(files); | ||||
|             if (fileURL) { | ||||
| @ -718,7 +731,7 @@ export class CoreTextUtilsProvider { | ||||
|      * @param files Files to extract the pluginfile URL from. They need to have the URL in a url or fileurl attribute. | ||||
|      * @return Treated text. | ||||
|      */ | ||||
|     restorePluginfileUrls(text: string, files: any[]): string { | ||||
|     restorePluginfileUrls(text: string, files: CoreWSExternalFile[]): string { | ||||
|         if (text && typeof text == 'string') { | ||||
|             const fileURL = this.getTextPluginfileUrl(files); | ||||
|             if (fileURL) { | ||||
| @ -804,7 +817,6 @@ export class CoreTextUtilsProvider { | ||||
| 
 | ||||
|     /** | ||||
|      * Replace text within a portion of a string. Equivalent to PHP's substr_replace. | ||||
|      * Credits to http://locutus.io/php/strings/substr_replace/
 | ||||
|      * | ||||
|      * @param str The string to treat. | ||||
|      * @param replace The value to put inside the string. | ||||
| @ -814,22 +826,7 @@ export class CoreTextUtilsProvider { | ||||
|      * @return Treated string. | ||||
|      */ | ||||
|     substrReplace(str: string, replace: string, start: number, length?: number): string { | ||||
|         length = typeof length != 'undefined' ? length : str.length; | ||||
| 
 | ||||
|         if (start < 0) { | ||||
|             start = start + str.length; | ||||
|         } | ||||
| 
 | ||||
|         if (length < 0) { | ||||
|             length = length + str.length - start; | ||||
|         } | ||||
| 
 | ||||
|         return [ | ||||
|             str.slice(0, start), | ||||
|             replace.substr(0, length), | ||||
|             replace.slice(length), | ||||
|             str.slice(start + length) | ||||
|         ].join(''); | ||||
|         return Locutus.substrReplace(str, replace, start, length); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
| @ -867,14 +864,14 @@ export class CoreTextUtilsProvider { | ||||
|         return CoreLang.instance.getCurrentLanguage().then((language) => { | ||||
|             // Match the current language.
 | ||||
|             const anyLangRegEx = /<(?:lang|span)[^>]+lang="[a-zA-Z0-9_-]+"[^>]*>(.*?)<\/(?:lang|span)>/g; | ||||
|             let currentLangRegEx = new RegExp('<(?:lang|span)[^>]+lang="' + language + '"[^>]*>(.*?)<\/(?:lang|span)>', 'g'); | ||||
|             let currentLangRegEx = new RegExp('<(?:lang|span)[^>]+lang="' + language + '"[^>]*>(.*?)</(?:lang|span)>', 'g'); | ||||
| 
 | ||||
|             if (!text.match(currentLangRegEx)) { | ||||
|                 // Current lang not found. Try to find the first language.
 | ||||
|                 const matches = text.match(anyLangRegEx); | ||||
|                 if (matches && matches[0]) { | ||||
|                     language = matches[0].match(/lang="([a-zA-Z0-9_-]+)"/)[1]; | ||||
|                     currentLangRegEx = new RegExp('<(?:lang|span)[^>]+lang="' + language + '"[^>]*>(.*?)<\/(?:lang|span)>', 'g'); | ||||
|                     currentLangRegEx = new RegExp('<(?:lang|span)[^>]+lang="' + language + '"[^>]*>(.*?)</(?:lang|span)>', 'g'); | ||||
|                 } else { | ||||
|                     // No multi-lang tag found, stop.
 | ||||
|                     return text; | ||||
| @ -915,221 +912,12 @@ export class CoreTextUtilsProvider { | ||||
| 
 | ||||
|     /** | ||||
|      * Unserialize Array from PHP. | ||||
|      * Taken from: https://github.com/kvz/locutus/blob/master/src/php/var/unserialize.js
 | ||||
|      * | ||||
|      * @param data String to unserialize. | ||||
|      * @param logErrorFn An error to call with the exception to log the error. If not supplied, no error. | ||||
|      * @return Unserialized data. | ||||
|      */ | ||||
|     unserialize(data: string, logErrorFn?: (error?: string) => void): any { | ||||
|         //  Discuss at: http://locutus.io/php/unserialize/
 | ||||
|         // Original by: Arpad Ray (mailto:arpad@php.net)
 | ||||
|         // Improved by: Pedro Tainha (http://www.pedrotainha.com)
 | ||||
|         // Improved by: Kevin van Zonneveld (http://kvz.io)
 | ||||
|         // Improved by: Kevin van Zonneveld (http://kvz.io)
 | ||||
|         // Improved by: Chris
 | ||||
|         // Improved by: James
 | ||||
|         // Improved by: Le Torbi
 | ||||
|         // Improved by: Eli Skeggs
 | ||||
|         // Bugfixed by: dptr1988
 | ||||
|         // Bugfixed by: Kevin van Zonneveld (http://kvz.io)
 | ||||
|         // Bugfixed by: Brett Zamir (http://brett-zamir.me)
 | ||||
|         // Bugfixed by: philippsimon (https://github.com/philippsimon/)
 | ||||
|         //  Revised by: d3x
 | ||||
|         //    Input by: Brett Zamir (http://brett-zamir.me)
 | ||||
|         //    Input by: Martin (http://www.erlenwiese.de/)
 | ||||
|         //    Input by: kilops
 | ||||
|         //    Input by: Jaroslaw Czarniak
 | ||||
|         //    Input by: lovasoa (https://github.com/lovasoa/)
 | ||||
|         //      Note 1: We feel the main purpose of this function should be
 | ||||
|         //      Note 1: to ease the transport of data between php & js
 | ||||
|         //      Note 1: Aiming for PHP-compatibility, we have to translate objects to arrays
 | ||||
|         //   Example 1: unserialize('a:3:{i:0;s:5:"Kevin";i:1;s:3:"van";i:2;s:9:"Zonneveld";}')
 | ||||
|         //   Returns 1: ['Kevin', 'van', 'Zonneveld']
 | ||||
|         //   Example 2: unserialize('a:2:{s:9:"firstName";s:5:"Kevin";s:7:"midName";s:3:"van";}')
 | ||||
|         //   Returns 2: {firstName: 'Kevin', midName: 'van'}
 | ||||
|         //   Example 3: unserialize('a:3:{s:2:"ü";s:2:"ü";s:3:"四";s:3:"四";s:4:"𠜎";s:4:"𠜎";}')
 | ||||
|         //   Returns 3: {'ü': 'ü', '四': '四', '𠜎': '𠜎'}
 | ||||
| 
 | ||||
|         const utf8Overhead = (str: string): number => { | ||||
|             let s = str.length; | ||||
| 
 | ||||
|             for (let i = str.length - 1; i >= 0; i--) { | ||||
|                 const code = str.charCodeAt(i); | ||||
|                 if (code > 0x7f && code <= 0x7ff) { | ||||
|                     s++; | ||||
|                 } else if (code > 0x7ff && code <= 0xffff) { | ||||
|                     s += 2; | ||||
|                 } | ||||
|                 // Trail surrogate.
 | ||||
|                 if (code >= 0xDC00 && code <= 0xDFFF) { | ||||
|                     i--; | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             return s - 1; | ||||
|         }; | ||||
| 
 | ||||
|         const error = (type: string, msg: string): void => { | ||||
|             if (logErrorFn) { | ||||
|                 logErrorFn(type + msg); | ||||
|             } | ||||
|         }; | ||||
| 
 | ||||
|         const readUntil = (data: string, offset: number, stopchr: string): Array<any> => { | ||||
|             let i = 2; | ||||
|             const buf = []; | ||||
|             let chr = data.slice(offset, offset + 1); | ||||
| 
 | ||||
|             while (chr !== stopchr) { | ||||
|                 if ((i + offset) > data.length) { | ||||
|                     error('Error', 'Invalid'); | ||||
|                 } | ||||
|                 buf.push(chr); | ||||
|                 chr = data.slice(offset + (i - 1), offset + i); | ||||
|                 i += 1; | ||||
|             } | ||||
| 
 | ||||
|             return [buf.length, buf.join('')]; | ||||
|         }; | ||||
| 
 | ||||
|         const readChrs = (data: string, offset: number, length: number): Array<any> => { | ||||
|             let chr; | ||||
|             const buf = []; | ||||
| 
 | ||||
|             for (let i = 0; i < length; i++) { | ||||
|                 chr = data.slice(offset + (i - 1), offset + i); | ||||
|                 buf.push(chr); | ||||
|                 length -= utf8Overhead(chr); | ||||
|             } | ||||
| 
 | ||||
|             return [buf.length, buf.join('')]; | ||||
|         }; | ||||
| 
 | ||||
|         const _unserialize = (data: string, offset: number): any => { | ||||
|             let dtype, | ||||
|                 dataoffset, | ||||
|                 keyandchrs, | ||||
|                 keys, | ||||
|                 contig, | ||||
|                 length, | ||||
|                 array, | ||||
|                 readdata, | ||||
|                 readData, | ||||
|                 ccount, | ||||
|                 stringlength, | ||||
|                 i, | ||||
|                 key, | ||||
|                 kprops, | ||||
|                 kchrs, | ||||
|                 vprops, | ||||
|                 vchrs, | ||||
|                 value, | ||||
|                 chrs = 0, | ||||
|                 typeconvert = (x: any): any => { | ||||
|                     return x; | ||||
|                 }; | ||||
| 
 | ||||
|             if (!offset) { | ||||
|                 offset = 0; | ||||
|             } | ||||
|             dtype = (data.slice(offset, offset + 1)).toLowerCase(); | ||||
| 
 | ||||
|             dataoffset = offset + 2; | ||||
| 
 | ||||
|             switch (dtype) { | ||||
|                 case 'i': | ||||
|                     typeconvert = (x: any): number => { | ||||
|                         return parseInt(x, 10); | ||||
|                     }; | ||||
|                     readData = readUntil(data, dataoffset, ';'); | ||||
|                     chrs = readData[0]; | ||||
|                     readdata = readData[1]; | ||||
|                     dataoffset += chrs + 1; | ||||
|                     break; | ||||
|                 case 'b': | ||||
|                     typeconvert = (x: any): boolean => { | ||||
|                         return parseInt(x, 10) !== 0; | ||||
|                     }; | ||||
|                     readData = readUntil(data, dataoffset, ';'); | ||||
|                     chrs = readData[0]; | ||||
|                     readdata = readData[1]; | ||||
|                     dataoffset += chrs + 1; | ||||
|                     break; | ||||
|                 case 'd': | ||||
|                     typeconvert = (x: any): number => { | ||||
|                         return parseFloat(x); | ||||
|                     }; | ||||
|                     readData = readUntil(data, dataoffset, ';'); | ||||
|                     chrs = readData[0]; | ||||
|                     readdata = readData[1]; | ||||
|                     dataoffset += chrs + 1; | ||||
|                     break; | ||||
|                 case 'n': | ||||
|                     readdata = null; | ||||
|                     break; | ||||
|                 case 's': | ||||
|                     ccount = readUntil(data, dataoffset, ':'); | ||||
|                     chrs = ccount[0]; | ||||
|                     stringlength = ccount[1]; | ||||
|                     dataoffset += chrs + 2; | ||||
| 
 | ||||
|                     readData = readChrs(data, dataoffset + 1, parseInt(stringlength, 10)); | ||||
|                     chrs = readData[0]; | ||||
|                     readdata = readData[1]; | ||||
|                     dataoffset += chrs + 2; | ||||
|                     if (chrs !== parseInt(stringlength, 10) && chrs !== readdata.length) { | ||||
|                         error('SyntaxError', 'String length mismatch'); | ||||
|                     } | ||||
|                     break; | ||||
|                 case 'a': | ||||
|                     readdata = {}; | ||||
| 
 | ||||
|                     keyandchrs = readUntil(data, dataoffset, ':'); | ||||
|                     chrs = keyandchrs[0]; | ||||
|                     keys = keyandchrs[1]; | ||||
|                     dataoffset += chrs + 2; | ||||
| 
 | ||||
|                     length = parseInt(keys, 10); | ||||
|                     contig = true; | ||||
| 
 | ||||
|                     for (let i = 0; i < length; i++) { | ||||
|                         kprops = _unserialize(data, dataoffset); | ||||
|                         kchrs = kprops[1]; | ||||
|                         key = kprops[2]; | ||||
|                         dataoffset += kchrs; | ||||
| 
 | ||||
|                         vprops = _unserialize(data, dataoffset); | ||||
|                         vchrs = vprops[1]; | ||||
|                         value = vprops[2]; | ||||
|                         dataoffset += vchrs; | ||||
| 
 | ||||
|                         if (key !== i) { | ||||
|                             contig = false; | ||||
|                         } | ||||
| 
 | ||||
|                         readdata[key] = value; | ||||
|                     } | ||||
| 
 | ||||
|                     if (contig) { | ||||
|                         array = new Array(length); | ||||
|                         for (i = 0; i < length; i++) { | ||||
|                             array[i] = readdata[i]; | ||||
|                         } | ||||
|                         readdata = array; | ||||
|                     } | ||||
| 
 | ||||
|                     dataoffset += 1; | ||||
|                     break; | ||||
|                 default: | ||||
|                     error('SyntaxError', 'Unknown / Unhandled data type(s): ' + dtype); | ||||
|                     break; | ||||
|             } | ||||
| 
 | ||||
|             return [dtype, dataoffset - offset, typeconvert(readdata)]; | ||||
|         }; | ||||
| 
 | ||||
|         return _unserialize((data + ''), 0)[2]; | ||||
|     unserialize<T = unknown>(data: string): T { | ||||
|         return Locutus.unserialize<T>(data); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
| @ -1138,16 +926,13 @@ export class CoreTextUtilsProvider { | ||||
|      * @param title Title of the new state. | ||||
|      * @param text Content of the text to be expanded. | ||||
|      * @param component Component to link the embedded files to. | ||||
|      * @param componentId An ID to use in conjunction with the component. | ||||
|      * @param files List of files to display along with the text. | ||||
|      * @param filter Whether the text should be filtered. | ||||
|      * @param contextLevel The context level. | ||||
|      * @param instanceId The instance ID related to the context. | ||||
|      * @param courseId Course ID the text belongs to. It can be used to improve performance with filters. | ||||
|      * @param options Options. | ||||
|      */ | ||||
|     // eslint-disable-next-line @typescript-eslint/no-unused-vars
 | ||||
|     viewText(title: string, text: string, options?: CoreTextUtilsViewTextOptions): void { | ||||
|         // @todo
 | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| /** | ||||
| @ -1156,7 +941,7 @@ export class CoreTextUtilsProvider { | ||||
| export type CoreTextUtilsViewTextOptions = { | ||||
|     component?: string; // Component to link the embedded files to.
 | ||||
|     componentId?: string | number; // An ID to use in conjunction with the component.
 | ||||
|     files?: any[]; // List of files to display along with the text.
 | ||||
|     files?: CoreWSExternalFile[]; // List of files to display along with the text.
 | ||||
|     filter?: boolean; // Whether the text should be filtered.
 | ||||
|     contextLevel?: string; // The context level.
 | ||||
|     instanceId?: number; // The instance ID related to the context.
 | ||||
|  | ||||
| @ -13,44 +13,43 @@ | ||||
| // limitations under the License.
 | ||||
| 
 | ||||
| import { Injectable, NgZone } from '@angular/core'; | ||||
| import { InAppBrowserObject } from '@ionic-native/in-app-browser'; | ||||
| import { InAppBrowserObject, InAppBrowserOptions } from '@ionic-native/in-app-browser'; | ||||
| import { FileEntry } from '@ionic-native/file'; | ||||
| import { Subscription } from 'rxjs'; | ||||
| 
 | ||||
| import { CoreApp } from '@services/app'; | ||||
| import { CoreEvents, CoreEventsProvider } from '@services/events'; | ||||
| import { CoreFile } from '@services/file'; | ||||
| import { CoreLang } from '@services/lang'; | ||||
| import { CoreWS, CoreWSError } from '@services/ws'; | ||||
| import { CoreWS, CoreWSError, CoreWSExternalFile } from '@services/ws'; | ||||
| import { CoreDomUtils } from '@services/utils/dom'; | ||||
| import { CoreMimetypeUtils } from '@services/utils/mimetype'; | ||||
| import { CoreTextUtils } from '@services/utils/text'; | ||||
| import { | ||||
|     makeSingleton, Clipboard, InAppBrowser, Platform, FileOpener, WebIntent, QRScanner, Translate | ||||
|     makeSingleton, Clipboard, InAppBrowser, Platform, FileOpener, WebIntent, QRScanner, Translate, | ||||
| } from '@singletons/core.singletons'; | ||||
| import { CoreLogger } from '@singletons/logger'; | ||||
| 
 | ||||
| type TreeNode<T> = T & { children: TreeNode<T>[] }; | ||||
| 
 | ||||
| /* | ||||
|  * "Utils" service with helper functions. | ||||
|  */ | ||||
| @Injectable() | ||||
| export class CoreUtilsProvider { | ||||
|     protected DONT_CLONE = ['[object FileEntry]', '[object DirectoryEntry]', '[object DOMFileSystem]']; | ||||
| 
 | ||||
|     protected readonly DONT_CLONE = ['[object FileEntry]', '[object DirectoryEntry]', '[object DOMFileSystem]']; | ||||
| 
 | ||||
|     protected logger: CoreLogger; | ||||
|     protected iabInstance: InAppBrowserObject; | ||||
|     protected uniqueIds: {[name: string]: number} = {}; | ||||
|     protected qrScanData: {deferred: PromiseDefer<any>, observable: Subscription}; | ||||
|     protected qrScanData: {deferred: PromiseDefer<string>; observable: Subscription}; | ||||
| 
 | ||||
|     constructor(protected zone: NgZone) { | ||||
|         this.logger = CoreLogger.getInstance('CoreUtilsProvider'); | ||||
| 
 | ||||
|         Platform.instance.ready().then(() => { | ||||
|             const win = <any> window; | ||||
| 
 | ||||
|             if (win.cordova && win.cordova.InAppBrowser) { | ||||
|                 // Override the default window.open with the InAppBrowser one.
 | ||||
|                 win.open = win.cordova.InAppBrowser.open; | ||||
|             } | ||||
|         }); | ||||
|         // eslint-disable-next-line promise/catch-or-return
 | ||||
|         Platform.instance.ready().then(() => this.overrideWindowOpen()); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
| @ -60,7 +59,7 @@ export class CoreUtilsProvider { | ||||
|      * @param defaultError Message to show if the error is not a string. | ||||
|      * @return New error message. | ||||
|      */ | ||||
|     addDataNotDownloadedError(error: any, defaultError?: string): string { | ||||
|     addDataNotDownloadedError(error: Error | string, defaultError?: string): string { | ||||
|         let errorMessage = error; | ||||
| 
 | ||||
|         if (error && typeof error != 'string') { | ||||
| @ -85,35 +84,25 @@ export class CoreUtilsProvider { | ||||
|      * @param promises Promises. | ||||
|      * @return Promise resolved if all promises are resolved and rejected if at least 1 promise fails. | ||||
|      */ | ||||
|     allPromises(promises: Promise<unknown>[]): Promise<void> { | ||||
|     async allPromises(promises: Promise<unknown>[]): Promise<void> { | ||||
|         if (!promises || !promises.length) { | ||||
|             return Promise.resolve(); | ||||
|         } | ||||
| 
 | ||||
|         return new Promise((resolve, reject): void => { | ||||
|             const total = promises.length; | ||||
|             let count = 0; | ||||
|             let hasFailed = false; | ||||
|             let error; | ||||
| 
 | ||||
|             promises.forEach((promise) => { | ||||
|                 promise.catch((err) => { | ||||
|                     hasFailed = true; | ||||
|                     error = err; | ||||
|                 }).finally(() => { | ||||
|                     count++; | ||||
| 
 | ||||
|                     if (count === total) { | ||||
|                         // All promises have finished, reject/resolve.
 | ||||
|                         if (hasFailed) { | ||||
|                             reject(error); | ||||
|                         } else { | ||||
|                             resolve(); | ||||
|         const getPromiseError = async (promise): Promise<Error | void> => { | ||||
|             try { | ||||
|                 await promise; | ||||
|             } catch (error) { | ||||
|                 return error; | ||||
|             } | ||||
|         }; | ||||
| 
 | ||||
|         const errors = await Promise.all(promises.map(getPromiseError)); | ||||
|         const error = errors.find(error => !!error); | ||||
| 
 | ||||
|         if (error) { | ||||
|             throw error; | ||||
|         } | ||||
|                 }); | ||||
|             }); | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
| @ -126,7 +115,7 @@ export class CoreUtilsProvider { | ||||
|      * @param result Object where to put the properties. If not defined, a new object will be created. | ||||
|      * @return The object. | ||||
|      */ | ||||
|     arrayToObject(array: any[], propertyName?: string, result?: any): any { | ||||
|     arrayToObject(array: unknown[], propertyName?: string, result?: unknown): unknown { | ||||
|         result = result || {}; | ||||
|         array.forEach((entry) => { | ||||
|             const key = propertyName ? entry[propertyName] : entry; | ||||
| @ -148,7 +137,13 @@ export class CoreUtilsProvider { | ||||
|      * @param undefinedIsNull True if undefined is equal to null. Defaults to true. | ||||
|      * @return Whether both items are equal. | ||||
|      */ | ||||
|     basicLeftCompare(itemA: any, itemB: any, maxLevels: number = 0, level: number = 0, undefinedIsNull: boolean = true): boolean { | ||||
|     basicLeftCompare( | ||||
|         itemA: any, // eslint-disable-line @typescript-eslint/no-explicit-any
 | ||||
|         itemB: any, // eslint-disable-line @typescript-eslint/no-explicit-any
 | ||||
|         maxLevels: number = 0, | ||||
|         level: number = 0, | ||||
|         undefinedIsNull: boolean = true, | ||||
|     ): boolean { | ||||
|         if (typeof itemA == 'function' || typeof itemB == 'function') { | ||||
|             return true; // Don't compare functions.
 | ||||
|         } else if (typeof itemA == 'object' && typeof itemB == 'object') { | ||||
| @ -190,6 +185,7 @@ export class CoreUtilsProvider { | ||||
| 
 | ||||
|     /** | ||||
|      * Blocks leaving a view. | ||||
|      * | ||||
|      * @deprecated, use ionViewCanLeave instead. | ||||
|      */ | ||||
|     blockLeaveView(): void { | ||||
| @ -202,24 +198,26 @@ export class CoreUtilsProvider { | ||||
|      * @param url The URL to check. | ||||
|      * @return Promise resolved with boolean_ whether there is a redirect. | ||||
|      */ | ||||
|     checkRedirect(url: string): Promise<boolean> { | ||||
|         if (window.fetch) { | ||||
|             const win = <any> window; // Convert to <any> to be able to use AbortController (not supported by our TS version).
 | ||||
|             const initOptions: any = { | ||||
|                 redirect: 'follow', | ||||
|             }; | ||||
|             let controller; | ||||
|     async checkRedirect(url: string): Promise<boolean> { | ||||
|         if (!window.fetch) { | ||||
|             // Cannot check if there is a redirect, assume it's false.
 | ||||
|             return false; | ||||
|         } | ||||
| 
 | ||||
|         const initOptions: RequestInit = { redirect: 'follow' }; | ||||
| 
 | ||||
|         // Some browsers implement fetch but no AbortController.
 | ||||
|             if (win.AbortController) { | ||||
|                 controller = new win.AbortController(); | ||||
|         const controller = AbortController ? new AbortController() : false; | ||||
| 
 | ||||
|         if (controller) { | ||||
|             initOptions.signal = controller.signal; | ||||
|         } | ||||
| 
 | ||||
|             return this.timeoutPromise(window.fetch(url, initOptions), CoreWS.instance.getRequestTimeout()) | ||||
|                     .then((response: Response) => { | ||||
|         try { | ||||
|             const response = await this.timeoutPromise(window.fetch(url, initOptions), CoreWS.instance.getRequestTimeout()); | ||||
| 
 | ||||
|             return response.redirected; | ||||
|             }).catch((error) => { | ||||
|         } catch (error) { | ||||
|             if (error.timeout && controller) { | ||||
|                 // Timeout, abort the request.
 | ||||
|                 controller.abort(); | ||||
| @ -227,10 +225,6 @@ export class CoreUtilsProvider { | ||||
| 
 | ||||
|             // There was a timeout, cannot determine if there's a redirect. Assume it's false.
 | ||||
|             return false; | ||||
|             }); | ||||
|         } else { | ||||
|             // Cannot check if there is a redirect, assume it's false.
 | ||||
|             return Promise.resolve(false); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
| @ -255,7 +249,7 @@ export class CoreUtilsProvider { | ||||
|      * @param level Depth we are right now inside a cloned object. It's used to prevent reaching max call stack size. | ||||
|      * @return Cloned variable. | ||||
|      */ | ||||
|     clone(source: any, level: number = 0): any { | ||||
|     clone<T>(source: T, level: number = 0): T { | ||||
|         if (level >= 20) { | ||||
|             // Max 20 levels.
 | ||||
|             this.logger.error('Max depth reached when cloning object.', source); | ||||
| @ -265,7 +259,7 @@ export class CoreUtilsProvider { | ||||
| 
 | ||||
|         if (Array.isArray(source)) { | ||||
|             // Clone the array and all the entries.
 | ||||
|             const newArray = []; | ||||
|             const newArray = [] as unknown as T; | ||||
|             for (let i = 0; i < source.length; i++) { | ||||
|                 newArray[i] = this.clone(source[i], level + 1); | ||||
|             } | ||||
| @ -279,7 +273,7 @@ export class CoreUtilsProvider { | ||||
|             } | ||||
| 
 | ||||
|             // Clone the object and all the subproperties.
 | ||||
|             const newObject = {}; | ||||
|             const newObject = {} as T; | ||||
|             for (const name in source) { | ||||
|                 newObject[name] = this.clone(source[name], level + 1); | ||||
|             } | ||||
| @ -298,7 +292,7 @@ export class CoreUtilsProvider { | ||||
|      * @param to Object where to store the properties. | ||||
|      * @param clone Whether the properties should be cloned (so they are different instances). | ||||
|      */ | ||||
|     copyProperties(from: any, to: any, clone: boolean = true): void { | ||||
|     copyProperties(from: Record<string, unknown>, to: Record<string, unknown>, clone: boolean = true): void { | ||||
|         for (const name in from) { | ||||
|             if (clone) { | ||||
|                 to[name] = this.clone(from[name]); | ||||
| @ -314,13 +308,15 @@ export class CoreUtilsProvider { | ||||
|      * @param text Text to be copied | ||||
|      * @return Promise resolved when text is copied. | ||||
|      */ | ||||
|     copyToClipboard(text: string): Promise<any> { | ||||
|         return Clipboard.instance.copy(text).then(() => { | ||||
|     async copyToClipboard(text: string): Promise<void> { | ||||
|         try { | ||||
|             await Clipboard.instance.copy(text); | ||||
| 
 | ||||
|             // Show toast using ionicLoading.
 | ||||
|             return CoreDomUtils.instance.showToast('core.copiedtoclipboard', true); | ||||
|         }).catch(() => { | ||||
|             CoreDomUtils.instance.showToast('core.copiedtoclipboard', true); | ||||
|         } catch { | ||||
|             // Ignore errors.
 | ||||
|         }); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
| @ -339,7 +335,7 @@ export class CoreUtilsProvider { | ||||
|      * | ||||
|      * @param array Array to empty. | ||||
|      */ | ||||
|     emptyArray(array: any[]): void { | ||||
|     emptyArray(array: unknown[]): void { | ||||
|         array.length = 0; // Empty array without losing its reference.
 | ||||
|     } | ||||
| 
 | ||||
| @ -348,9 +344,9 @@ export class CoreUtilsProvider { | ||||
|      * | ||||
|      * @param object Object to remove the properties. | ||||
|      */ | ||||
|     emptyObject(object: object): void { | ||||
|     emptyObject(object: Record<string, unknown>): void { | ||||
|         for (const key in object) { | ||||
|             if (object.hasOwnProperty(key)) { | ||||
|             if (Object.prototype.hasOwnProperty.call(object, key)) { | ||||
|                 delete object[key]; | ||||
|             } | ||||
|         } | ||||
| @ -373,10 +369,8 @@ export class CoreUtilsProvider { | ||||
|         // Execute all the processes in order.
 | ||||
|         for (const i in orderedPromisesData) { | ||||
|             const data = orderedPromisesData[i]; | ||||
|             let promise; | ||||
| 
 | ||||
|             // Add the process to the dependency stack.
 | ||||
|             promise = dependency.finally(() => { | ||||
|             const promise = dependency.finally(() => { | ||||
|                 try { | ||||
|                     return data.function(); | ||||
|                 } catch (e) { | ||||
| @ -406,19 +400,19 @@ export class CoreUtilsProvider { | ||||
|      * @param useDotNotation Whether to use dot notation '.' or square brackets '['. | ||||
|      * @return Flattened object. | ||||
|      */ | ||||
|     flattenObject(obj: object, useDotNotation?: boolean): object { | ||||
|     flattenObject(obj: Record<string, unknown>, useDotNotation?: boolean): Record<string, unknown> { | ||||
|         const toReturn = {}; | ||||
| 
 | ||||
|         for (const name in obj) { | ||||
|             if (!obj.hasOwnProperty(name)) { | ||||
|             if (!Object.prototype.hasOwnProperty.call(obj, name)) { | ||||
|                 continue; | ||||
|             } | ||||
| 
 | ||||
|             const value = obj[name]; | ||||
|             if (typeof value == 'object' && !Array.isArray(value)) { | ||||
|                 const flatObject = this.flattenObject(value); | ||||
|                 const flatObject = this.flattenObject(value as Record<string, unknown>); | ||||
|                 for (const subName in flatObject) { | ||||
|                     if (!flatObject.hasOwnProperty(subName)) { | ||||
|                     if (!Object.prototype.hasOwnProperty.call(flatObject, subName)) { | ||||
|                         continue; | ||||
|                     } | ||||
| 
 | ||||
| @ -463,19 +457,24 @@ export class CoreUtilsProvider { | ||||
|      * @param ...args All the params sent after checkAll will be passed to isEnabledFn. | ||||
|      * @return Promise resolved with the list of enabled sites. | ||||
|      */ | ||||
|     filterEnabledSites(siteIds: string[], isEnabledFn: (siteId, ...args: any[]) => boolean | Promise<boolean>, checkAll?: boolean, | ||||
|             ...args: any[]): Promise<string[]> { | ||||
|     filterEnabledSites<P extends unknown[]>( | ||||
|         siteIds: string[], | ||||
|         isEnabledFn: (siteId, ...args: P) => boolean | Promise<boolean>, | ||||
|         checkAll?: boolean, | ||||
|         ...args: P | ||||
|     ): Promise<string[]> { | ||||
|         const promises = []; | ||||
|         const enabledSites = []; | ||||
| 
 | ||||
|         for (const i in siteIds) { | ||||
|             const siteId = siteIds[i]; | ||||
|             const pushIfEnabled = enabled => enabled && enabledSites.push(siteId); | ||||
|             if (checkAll || !promises.length) { | ||||
|                 promises.push(Promise.resolve(isEnabledFn.apply(isEnabledFn, [siteId].concat(args))).then((enabled) => { | ||||
|                     if (enabled) { | ||||
|                         enabledSites.push(siteId); | ||||
|                     } | ||||
|                 })); | ||||
|                 promises.push( | ||||
|                     Promise | ||||
|                         .resolve(isEnabledFn(siteId, ...args)) | ||||
|                         .then(pushIfEnabled), | ||||
|                 ); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
| @ -498,7 +497,7 @@ export class CoreUtilsProvider { | ||||
|      * @param float The float to print. | ||||
|      * @return Locale float. | ||||
|      */ | ||||
|     formatFloat(float: any): string { | ||||
|     formatFloat(float: unknown): string { | ||||
|         if (typeof float == 'undefined' || float === null || typeof float == 'boolean') { | ||||
|             return ''; | ||||
|         } | ||||
| @ -506,9 +505,9 @@ export class CoreUtilsProvider { | ||||
|         const localeSeparator = Translate.instance.instant('core.decsep'); | ||||
| 
 | ||||
|         // Convert float to string.
 | ||||
|         float += ''; | ||||
|         const floatString = float + ''; | ||||
| 
 | ||||
|         return float.replace('.', localeSeparator); | ||||
|         return floatString.replace('.', localeSeparator); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
| @ -523,17 +522,20 @@ export class CoreUtilsProvider { | ||||
|      * @param maxDepth Max Depth to convert to tree. Children found will be in the last level of depth. | ||||
|      * @return Array with the formatted tree, children will be on each node under children field. | ||||
|      */ | ||||
|     formatTree(list: any[], parentFieldName: string = 'parent', idFieldName: string = 'id', rootParentId: number = 0, | ||||
|             maxDepth: number = 5): any[] { | ||||
|     formatTree<T>( | ||||
|         list: T[], | ||||
|         parentFieldName: string = 'parent', | ||||
|         idFieldName: string = 'id', | ||||
|         rootParentId: number = 0, | ||||
|         maxDepth: number = 5, | ||||
|     ): TreeNode<T>[] { | ||||
|         const map = {}; | ||||
|         const mapDepth = {}; | ||||
|         const tree = []; | ||||
|         let parent; | ||||
|         let id; | ||||
|         const tree: TreeNode<T>[] = []; | ||||
| 
 | ||||
|         list.forEach((node, index): void => { | ||||
|             id = node[idFieldName]; | ||||
|             parent = node[parentFieldName]; | ||||
|         list.forEach((node: TreeNode<T>, index): void => { | ||||
|             const id = node[idFieldName]; | ||||
|             const parent = node[parentFieldName]; | ||||
|             node.children = []; | ||||
| 
 | ||||
|             if (!id || !parent) { | ||||
| @ -543,7 +545,7 @@ export class CoreUtilsProvider { | ||||
|             // Use map to look-up the parents.
 | ||||
|             map[id] = index; | ||||
|             if (parent != rootParentId) { | ||||
|                 const parentNode = list[map[parent]]; | ||||
|                 const parentNode = list[map[parent]] as TreeNode<T>; | ||||
|                 if (parentNode) { | ||||
|                     if (mapDepth[parent] == maxDepth) { | ||||
|                         // Reached max level of depth. Proceed with flat order. Find parent object of the current node.
 | ||||
| @ -551,11 +553,11 @@ export class CoreUtilsProvider { | ||||
|                         if (parentOfParent) { | ||||
|                             // This element will be the child of the node that is two levels up the hierarchy
 | ||||
|                             // (i.e. the child of node.parent.parent).
 | ||||
|                             list[map[parentOfParent]].children.push(node); | ||||
|                             (list[map[parentOfParent]] as TreeNode<T>).children.push(node); | ||||
|                             // Assign depth level to the same depth as the parent (i.e. max depth level).
 | ||||
|                             mapDepth[id] = mapDepth[parent]; | ||||
|                             // Change the parent to be the one that is two levels up the hierarchy.
 | ||||
|                             node.parent = parentOfParent; | ||||
|                             node[parentFieldName] = parentOfParent; | ||||
|                         } else { | ||||
|                             this.logger.error(`Node parent of parent:${parentOfParent} not found on formatTree`); | ||||
|                         } | ||||
| @ -596,7 +598,7 @@ export class CoreUtilsProvider { | ||||
|      * | ||||
|      * @return Promise resolved with the list of countries. | ||||
|      */ | ||||
|     getCountryList(): Promise<any> { | ||||
|     getCountryList(): Promise<Record<string, string>> { | ||||
|         // Get the keys of the countries.
 | ||||
|         return this.getCountryKeysList().then((keys) => { | ||||
|             // Now get the code and the translated name.
 | ||||
| @ -618,7 +620,7 @@ export class CoreUtilsProvider { | ||||
|      * | ||||
|      * @return Promise resolved with the list of countries. | ||||
|      */ | ||||
|     getCountryListSorted(): Promise<any[]> { | ||||
|     getCountryListSorted(): Promise<{ code: string; name: string }[]> { | ||||
|         // Get the keys of the countries.
 | ||||
|         return this.getCountryList().then((countries) => { | ||||
|             // Sort translations.
 | ||||
| @ -647,7 +649,7 @@ export class CoreUtilsProvider { | ||||
| 
 | ||||
|             if (fallbackLang === defaultLang) { | ||||
|                 // Same language, just reject.
 | ||||
|                 return Promise.reject('Countries not found.'); | ||||
|                 throw new Error('Countries not found.'); | ||||
|             } | ||||
| 
 | ||||
|             return this.getCountryKeysListForLanguage(fallbackLang); | ||||
| @ -660,9 +662,10 @@ export class CoreUtilsProvider { | ||||
|      * @param lang Language to check. | ||||
|      * @return Promise resolved with the countries list. Rejected if not translated. | ||||
|      */ | ||||
|     protected getCountryKeysListForLanguage(lang: string): Promise<string[]> { | ||||
|     protected async getCountryKeysListForLanguage(lang: string): Promise<string[]> { | ||||
|         // Get the translation table for the language.
 | ||||
|         return CoreLang.instance.getTranslationTable(lang).then((table): any => { | ||||
|         const table = await CoreLang.instance.getTranslationTable(lang); | ||||
| 
 | ||||
|         // Gather all the keys for countries,
 | ||||
|         const keys = []; | ||||
| 
 | ||||
| @ -674,11 +677,10 @@ export class CoreUtilsProvider { | ||||
| 
 | ||||
|         if (keys.length === 0) { | ||||
|             // Not translated, reject.
 | ||||
|                 return Promise.reject('Countries not found.'); | ||||
|             throw new Error('Countries not found.'); | ||||
|         } | ||||
| 
 | ||||
|         return keys; | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
| @ -699,9 +701,7 @@ export class CoreUtilsProvider { | ||||
|         } | ||||
| 
 | ||||
|         // Can't be guessed, get the remote mimetype.
 | ||||
|         return CoreWS.instance.getRemoteFileMimeType(url).then((mimetype) => { | ||||
|             return mimetype || ''; | ||||
|         }); | ||||
|         return CoreWS.instance.getRemoteFileMimeType(url).then(mimetype => mimetype || ''); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
| @ -718,13 +718,23 @@ export class CoreUtilsProvider { | ||||
|         return ++this.uniqueIds[name]; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Check if a file is a FileEntry | ||||
|      * | ||||
|      * @param file File. | ||||
|      * @return Type guard indicating if the file is a FileEntry. | ||||
|      */ | ||||
|     isFileEntry(file: FileEntry | CoreWSExternalFile): file is FileEntry { | ||||
|         return 'isFile' in file; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Given a list of files, check if there are repeated names. | ||||
|      * | ||||
|      * @param files List of files. | ||||
|      * @return String with error message if repeated, false if no repeated. | ||||
|      */ | ||||
|     hasRepeatedFilenames(files: any[]): string | boolean { | ||||
|     hasRepeatedFilenames(files: (FileEntry | CoreWSExternalFile)[]): string | false { | ||||
|         if (!files || !files.length) { | ||||
|             return false; | ||||
|         } | ||||
| @ -733,12 +743,14 @@ export class CoreUtilsProvider { | ||||
| 
 | ||||
|         // Check if there are 2 files with the same name.
 | ||||
|         for (let i = 0; i < files.length; i++) { | ||||
|             const name = files[i].filename || files[i].name; | ||||
|             const file = files[i]; | ||||
|             const name = this.isFileEntry(file) ? file.name : file.filename; | ||||
| 
 | ||||
|             if (names.indexOf(name) > -1) { | ||||
|                 return Translate.instance.instant('core.filenameexist', { $a: name }); | ||||
|             } else { | ||||
|                 names.push(name); | ||||
|             } | ||||
| 
 | ||||
|             names.push(name); | ||||
|         } | ||||
| 
 | ||||
|         return false; | ||||
| @ -774,6 +786,7 @@ export class CoreUtilsProvider { | ||||
|      * @param value Value to check. | ||||
|      * @return Whether the value is false, 0 or "0". | ||||
|      */ | ||||
|     // eslint-disable-next-line @typescript-eslint/no-explicit-any
 | ||||
|     isFalseOrZero(value: any): boolean { | ||||
|         return typeof value != 'undefined' && (value === false || value === 'false' || parseInt(value, 10) === 0); | ||||
|     } | ||||
| @ -784,6 +797,7 @@ export class CoreUtilsProvider { | ||||
|      * @param value Value to check. | ||||
|      * @return Whether the value is true, 1 or "1". | ||||
|      */ | ||||
|     // eslint-disable-next-line @typescript-eslint/no-explicit-any
 | ||||
|     isTrueOrOne(value: any): boolean { | ||||
|         return typeof value != 'undefined' && (value === true || value === 'true' || parseInt(value, 10) === 1); | ||||
|     } | ||||
| @ -794,6 +808,7 @@ export class CoreUtilsProvider { | ||||
|      * @param error Error to check. | ||||
|      * @return Whether the error was returned by the WebService. | ||||
|      */ | ||||
|     // eslint-disable-next-line @typescript-eslint/no-explicit-any
 | ||||
|     isWebServiceError(error: any): boolean { | ||||
|         return error && (typeof error.warningcode != 'undefined' || (typeof error.errorcode != 'undefined' && | ||||
|                 error.errorcode != 'invalidtoken' && error.errorcode != 'userdeleted' && error.errorcode != 'upgraderunning' && | ||||
| @ -812,19 +827,22 @@ export class CoreUtilsProvider { | ||||
|      * @param defaultValue Element that will become default option value. Default 0. | ||||
|      * @return The now assembled array | ||||
|      */ | ||||
|     makeMenuFromList(list: string, defaultLabel?: string, separator: string = ',', defaultValue?: any): any[] { | ||||
|     makeMenuFromList<T>( | ||||
|         list: string, | ||||
|         defaultLabel?: string, | ||||
|         separator: string = ',', | ||||
|         defaultValue?: T, | ||||
|     ): { label: string; value: T | number }[] { | ||||
|         // Split and format the list.
 | ||||
|         const split = list.split(separator).map((label, index) => { | ||||
|             return { | ||||
|         const split = list.split(separator).map((label, index) => ({ | ||||
|             label: label.trim(), | ||||
|                 value: index + 1 | ||||
|             }; | ||||
|         }); | ||||
|             value: index + 1, | ||||
|         })) as { label: string; value: T | number }[]; | ||||
| 
 | ||||
|         if (defaultLabel) { | ||||
|             split.unshift({ | ||||
|                 label: defaultLabel, | ||||
|                 value: defaultValue || 0 | ||||
|                 value: defaultValue || 0, | ||||
|             }); | ||||
|         } | ||||
| 
 | ||||
| @ -839,8 +857,8 @@ export class CoreUtilsProvider { | ||||
|      * @param [key] Key of the property that must be unique. If not specified, the whole entry. | ||||
|      * @return Merged array. | ||||
|      */ | ||||
|     mergeArraysWithoutDuplicates(array1: any[], array2: any[], key?: string): any[] { | ||||
|         return this.uniqueArray(array1.concat(array2), key); | ||||
|     mergeArraysWithoutDuplicates<T>(array1: T[], array2: T[], key?: string): T[] { | ||||
|         return this.uniqueArray(array1.concat(array2), key) as T[]; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
| @ -849,6 +867,7 @@ export class CoreUtilsProvider { | ||||
|      * @param value Value to check. | ||||
|      * @return True if not null and not undefined. | ||||
|      */ | ||||
|     // eslint-disable-next-line @typescript-eslint/no-explicit-any
 | ||||
|     notNullOrUndefined(value: any): boolean { | ||||
|         return typeof value != 'undefined' && value !== null; | ||||
|     } | ||||
| @ -888,12 +907,10 @@ export class CoreUtilsProvider { | ||||
| 
 | ||||
|             if (!extension || extension.indexOf('/') > -1 || extension.indexOf('\\') > -1) { | ||||
|                 // Extension not found.
 | ||||
|                 error = Translate.instance.instant('core.erroropenfilenoextension'); | ||||
|             } else { | ||||
|                 error = Translate.instance.instant('core.erroropenfilenoapp'); | ||||
|                 throw new Error(Translate.instance.instant('core.erroropenfilenoextension')); | ||||
|             } | ||||
| 
 | ||||
|             throw error; | ||||
|             throw new Error(Translate.instance.instant('core.erroropenfilenoapp')); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
| @ -905,7 +922,7 @@ export class CoreUtilsProvider { | ||||
|      * @param options Override default options passed to InAppBrowser. | ||||
|      * @return The opened window. | ||||
|      */ | ||||
|     openInApp(url: string, options?: any): InAppBrowserObject { | ||||
|     openInApp(url: string, options?: InAppBrowserOptions): InAppBrowserObject { | ||||
|         if (!url) { | ||||
|             return; | ||||
|         } | ||||
| @ -995,36 +1012,32 @@ export class CoreUtilsProvider { | ||||
|      * @param url The URL of the file. | ||||
|      * @return Promise resolved when opened. | ||||
|      */ | ||||
|     openOnlineFile(url: string): Promise<void> { | ||||
|     async openOnlineFile(url: string): Promise<void> { | ||||
|         if (CoreApp.instance.isAndroid()) { | ||||
|             // In Android we need the mimetype to open it.
 | ||||
|             return this.getMimeTypeFromUrl(url).catch(() => { | ||||
|                 // Error getting mimetype, return undefined.
 | ||||
|             }).then((mimetype) => { | ||||
|             const mimetype = await this.ignoreErrors(this.getMimeTypeFromUrl(url)); | ||||
| 
 | ||||
|             if (!mimetype) { | ||||
|                 // Couldn't retrieve mimetype. Return error.
 | ||||
|                     return Promise.reject(Translate.instance.instant('core.erroropenfilenoextension')); | ||||
|                 throw new Error(Translate.instance.instant('core.erroropenfilenoextension')); | ||||
|             } | ||||
| 
 | ||||
|             const options = { | ||||
|                 action: WebIntent.instance.ACTION_VIEW, | ||||
|                 url, | ||||
|                     type: mimetype | ||||
|                 type: mimetype, | ||||
|             }; | ||||
| 
 | ||||
|             return WebIntent.instance.startActivity(options).catch((error) => { | ||||
|                 this.logger.error('Error opening online file ' + url + ' with mimetype ' + mimetype); | ||||
|                 this.logger.error('Error: ', JSON.stringify(error)); | ||||
| 
 | ||||
|                     return Promise.reject(Translate.instance.instant('core.erroropenfilenoapp')); | ||||
|                 }); | ||||
|                 throw new Error(Translate.instance.instant('core.erroropenfilenoapp')); | ||||
|             }); | ||||
|         } | ||||
| 
 | ||||
|         // In the rest of platforms we need to open them in InAppBrowser.
 | ||||
|         this.openInApp(url); | ||||
| 
 | ||||
|         return Promise.resolve(); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
| @ -1033,10 +1046,8 @@ export class CoreUtilsProvider { | ||||
|      * @param obj Object to convert. | ||||
|      * @return Array with the values of the object but losing the keys. | ||||
|      */ | ||||
|     objectToArray(obj: object): any[] { | ||||
|         return Object.keys(obj).map((key) => { | ||||
|             return obj[key]; | ||||
|         }); | ||||
|     objectToArray<T>(obj: Record<string, T>): T[] { | ||||
|         return Object.keys(obj).map((key) => obj[key]); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
| @ -1051,9 +1062,15 @@ export class CoreUtilsProvider { | ||||
|      * @param sortByValue True to sort values alphabetically, false otherwise. | ||||
|      * @return Array of objects with the name & value of each property. | ||||
|      */ | ||||
|     objectToArrayOfObjects(obj: object, keyName: string, valueName: string, sortByKey?: boolean, sortByValue?: boolean): any[] { | ||||
|     objectToArrayOfObjects( | ||||
|         obj: Record<string, unknown>, | ||||
|         keyName: string, | ||||
|         valueName: string, | ||||
|         sortByKey?: boolean, | ||||
|         sortByValue?: boolean, | ||||
|     ): Record<string, unknown>[] { | ||||
|         // Get the entries from an object or primitive value.
 | ||||
|         const getEntries = (elKey, value): any[] | any => { | ||||
|         const getEntries = (elKey: string, value: unknown): Record<string, unknown>[] | unknown => { | ||||
|             if (typeof value == 'undefined' || value == null) { | ||||
|                 // Filter undefined and null values.
 | ||||
|                 return; | ||||
| @ -1087,7 +1104,7 @@ export class CoreUtilsProvider { | ||||
|         } | ||||
| 
 | ||||
|         // "obj" will always be an object, so "entries" will always be an array.
 | ||||
|         const entries = <any[]> getEntries('', obj); | ||||
|         const entries = getEntries('', obj) as Record<string, unknown>[]; | ||||
|         if (sortByKey || sortByValue) { | ||||
|             return entries.sort((a, b) => { | ||||
|                 if (sortByKey) { | ||||
| @ -1111,7 +1128,12 @@ export class CoreUtilsProvider { | ||||
|      * @param keyPrefix Key prefix if neededs to delete it. | ||||
|      * @return Object. | ||||
|      */ | ||||
|     objectToKeyValueMap(objects: object[], keyName: string, valueName: string, keyPrefix?: string): {[name: string]: any} { | ||||
|     objectToKeyValueMap( | ||||
|         objects: Record<string, unknown>[], | ||||
|         keyName: string, | ||||
|         valueName: string, | ||||
|         keyPrefix?: string, | ||||
|     ): {[name: string]: unknown} { | ||||
|         if (!objects) { | ||||
|             return; | ||||
|         } | ||||
| @ -1119,7 +1141,8 @@ export class CoreUtilsProvider { | ||||
|         const prefixSubstr = keyPrefix ? keyPrefix.length : 0; | ||||
|         const mapped = {}; | ||||
|         objects.forEach((item) => { | ||||
|             const key = prefixSubstr > 0 ? item[keyName].substr(prefixSubstr) : item[keyName]; | ||||
|             const keyValue = item[keyName] as string; | ||||
|             const key = prefixSubstr > 0 ? keyValue.substr(prefixSubstr) : keyValue; | ||||
|             mapped[key] = item[valueName]; | ||||
|         }); | ||||
| 
 | ||||
| @ -1133,7 +1156,7 @@ export class CoreUtilsProvider { | ||||
|      * @param removeEmpty Whether to remove params whose value is null/undefined. | ||||
|      * @return GET params. | ||||
|      */ | ||||
|     objectToGetParams(object: any, removeEmpty: boolean = true): string { | ||||
|     objectToGetParams(object: Record<string, unknown>, removeEmpty: boolean = true): string { | ||||
|         // First of all, flatten the object so all properties are in the first level.
 | ||||
|         const flattened = this.flattenObject(object); | ||||
|         let result = ''; | ||||
| @ -1164,7 +1187,7 @@ export class CoreUtilsProvider { | ||||
|      * @param prefix Prefix to add. | ||||
|      * @return Prefixed object. | ||||
|      */ | ||||
|     prefixKeys(data: any, prefix: string): any { | ||||
|     prefixKeys(data: Record<string, unknown>, prefix: string): Record<string, unknown> { | ||||
|         const newObj = {}; | ||||
|         const keys = Object.keys(data); | ||||
| 
 | ||||
| @ -1196,12 +1219,14 @@ export class CoreUtilsProvider { | ||||
|      * @param promise Promise to check | ||||
|      * @return Promise resolved with boolean: true if the promise is rejected or false if it's resolved. | ||||
|      */ | ||||
|     promiseFails(promise: Promise<any>): Promise<boolean> { | ||||
|         return promise.then(() => { | ||||
|     async promiseFails(promise: Promise<unknown>): Promise<boolean> { | ||||
|         try { | ||||
|             await promise; | ||||
| 
 | ||||
|             return false; | ||||
|         }).catch(() => { | ||||
|         } catch { | ||||
|             return true; | ||||
|         }); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
| @ -1210,12 +1235,14 @@ export class CoreUtilsProvider { | ||||
|      * @param promise Promise to check | ||||
|      * @return Promise resolved with boolean: true if the promise it's resolved or false if it's rejected. | ||||
|      */ | ||||
|     promiseWorks(promise: Promise<any>): Promise<boolean> { | ||||
|         return promise.then(() => { | ||||
|     async promiseWorks(promise: Promise<unknown>): Promise<boolean> { | ||||
|         try { | ||||
|             await promise; | ||||
| 
 | ||||
|             return true; | ||||
|         }).catch(() => { | ||||
|         } catch { | ||||
|             return false; | ||||
|         }); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
| @ -1228,7 +1255,7 @@ export class CoreUtilsProvider { | ||||
|      * @param key Key to check. | ||||
|      * @return Whether the two objects/arrays have the same value (or lack of one) for a given key. | ||||
|      */ | ||||
|     sameAtKeyMissingIsBlank(obj1: any, obj2: any, key: string): boolean { | ||||
|     sameAtKeyMissingIsBlank(obj1: unknown, obj2: unknown, key: string): boolean { | ||||
|         let value1 = typeof obj1[key] != 'undefined' ? obj1[key] : ''; | ||||
|         let value2 = typeof obj2[key] != 'undefined' ? obj2[key] : ''; | ||||
| 
 | ||||
| @ -1249,7 +1276,7 @@ export class CoreUtilsProvider { | ||||
|      * @param obj Object to stringify. | ||||
|      * @return Stringified object. | ||||
|      */ | ||||
|     sortAndStringify(obj: object): string { | ||||
|     sortAndStringify(obj: Record<string, unknown>): string { | ||||
|         return JSON.stringify(this.sortProperties(obj)); | ||||
|     } | ||||
| 
 | ||||
| @ -1259,7 +1286,7 @@ export class CoreUtilsProvider { | ||||
|      * @param obj The object to sort. If it isn't an object, the original value will be returned. | ||||
|      * @return Sorted object. | ||||
|      */ | ||||
|     sortProperties(obj: object): object { | ||||
|     sortProperties<T>(obj: T): T { | ||||
|         if (obj != null && typeof obj == 'object' && !Array.isArray(obj)) { | ||||
|             // It's an object, sort it.
 | ||||
|             return Object.keys(obj).sort().reduce((accumulator, key) => { | ||||
| @ -1267,7 +1294,7 @@ export class CoreUtilsProvider { | ||||
|                 accumulator[key] = this.sortProperties(obj[key]); | ||||
| 
 | ||||
|                 return accumulator; | ||||
|             }, {}); | ||||
|             }, {} as T); | ||||
|         } else { | ||||
|             return obj; | ||||
|         } | ||||
| @ -1279,12 +1306,12 @@ export class CoreUtilsProvider { | ||||
|      * @param obj The object to sort. If it isn't an object, the original value will be returned. | ||||
|      * @return Sorted object. | ||||
|      */ | ||||
|     sortValues(obj: object): object { | ||||
|     sortValues<T>(obj: T): T { | ||||
|         if (typeof obj == 'object' && !Array.isArray(obj)) { | ||||
|             // It's an object, sort it. Convert it to an array to be able to sort it and then convert it back to object.
 | ||||
|             const array = this.objectToArrayOfObjects(obj, 'name', 'value', false, true); | ||||
|             const array = this.objectToArrayOfObjects(obj as Record<string, unknown>, 'name', 'value', false, true); | ||||
| 
 | ||||
|             return this.objectToKeyValueMap(array, 'name', 'value'); | ||||
|             return this.objectToKeyValueMap(array, 'name', 'value') as unknown as T; | ||||
|         } else { | ||||
|             return obj; | ||||
|         } | ||||
| @ -1297,10 +1324,10 @@ export class CoreUtilsProvider { | ||||
|      * @return File size and a boolean to indicate if it is the total size or only partial. | ||||
|      * @deprecated since 3.8.0. Use CorePluginFileDelegate.getFilesSize instead. | ||||
|      */ | ||||
|     sumFileSizes(files: any[]): { size: number, total: boolean } { | ||||
|     sumFileSizes(files: CoreWSExternalFile[]): { size: number; total: boolean } { | ||||
|         const result = { | ||||
|             size: 0, | ||||
|             total: true | ||||
|             total: true, | ||||
|         }; | ||||
| 
 | ||||
|         files.forEach((file) => { | ||||
| @ -1325,13 +1352,25 @@ export class CoreUtilsProvider { | ||||
|      */ | ||||
|     timeoutPromise<T>(promise: Promise<T>, time: number): Promise<T> { | ||||
|         return new Promise((resolve, reject): void => { | ||||
|             const timeout = setTimeout(() => { | ||||
|             let timedOut = false; | ||||
|             const resolveBeforeTimeout = () => { | ||||
|                 if (timedOut) { | ||||
|                     return; | ||||
|                 } | ||||
|                 resolve(); | ||||
|             }; | ||||
|             const timeout = setTimeout( | ||||
|                 () => { | ||||
|                     reject({ timeout: true }); | ||||
|             }, time); | ||||
|                     timedOut = true; | ||||
|                 }, | ||||
|                 time, | ||||
|             ); | ||||
| 
 | ||||
|             promise.then(resolve).catch(reject).finally(() => { | ||||
|                 clearTimeout(timeout); | ||||
|             }); | ||||
|             promise | ||||
|                 .then(resolveBeforeTimeout) | ||||
|                 .catch(reject) | ||||
|                 .finally(() => clearTimeout(timeout)); | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
| @ -1344,7 +1383,8 @@ export class CoreUtilsProvider { | ||||
|      * @param strict If true, then check the input and return false if it is not a valid number. | ||||
|      * @return False if bad format, empty string if empty value or the parsed float if not. | ||||
|      */ | ||||
|     unformatFloat(localeFloat: any, strict?: boolean): any { | ||||
|     // eslint-disable-next-line @typescript-eslint/no-explicit-any
 | ||||
|     unformatFloat(localeFloat: any, strict?: boolean): false | '' | number { | ||||
|         // Bad format on input type number.
 | ||||
|         if (typeof localeFloat == 'undefined') { | ||||
|             return false; | ||||
| @ -1383,7 +1423,7 @@ export class CoreUtilsProvider { | ||||
|      * @param [key] Key of the property that must be unique. If not specified, the whole entry. | ||||
|      * @return Array without duplicate values. | ||||
|      */ | ||||
|     uniqueArray(array: any[], key?: string): any[] { | ||||
|     uniqueArray<T>(array: T[], key?: string): T[] { | ||||
|         const filtered = []; | ||||
|         const unique = {}; // Use an object to make it faster to check if it's duplicate.
 | ||||
| 
 | ||||
| @ -1407,16 +1447,13 @@ export class CoreUtilsProvider { | ||||
|      * @param delay Time that must pass until the function is called. | ||||
|      * @return Debounced function. | ||||
|      */ | ||||
|     debounce(fn: (...args: any[]) => any, delay: number): (...args: any[]) => void { | ||||
| 
 | ||||
|     debounce<T extends unknown[]>(fn: (...args: T) => unknown, delay: number): (...args: T) => void { | ||||
|         let timeoutID: number; | ||||
| 
 | ||||
|         const debounced = (...args: any[]): void => { | ||||
|         const debounced = (...args: unknown[]): void => { | ||||
|             clearTimeout(timeoutID); | ||||
| 
 | ||||
|             timeoutID = window.setTimeout(() => { | ||||
|                 fn.apply(null, args); | ||||
|             }, delay); | ||||
|             timeoutID = window.setTimeout(() => fn.apply(null, args), delay); | ||||
|         }; | ||||
| 
 | ||||
|         return debounced; | ||||
| @ -1448,18 +1485,19 @@ export class CoreUtilsProvider { | ||||
|      * | ||||
|      * @return Promise resolved with the QR string, rejected if error or cancelled. | ||||
|      */ | ||||
|     startScanQR(): Promise<string> { | ||||
|     async startScanQR(): Promise<string> { | ||||
|         if (!CoreApp.instance.isMobile()) { | ||||
|             return Promise.reject('QRScanner isn\'t available in desktop apps.'); | ||||
|         } | ||||
| 
 | ||||
|         // Ask the user for permission to use the camera.
 | ||||
|         // The scan method also does this, but since it returns an Observable we wouldn't be able to detect if the user denied.
 | ||||
|         return QRScanner.instance.prepare().then((status) => { | ||||
|         try { | ||||
|             const status = await QRScanner.instance.prepare(); | ||||
| 
 | ||||
|             if (!status.authorized) { | ||||
|                 // No access to the camera, reject. In android this shouldn't happen, denying access passes through catch.
 | ||||
|                 return Promise.reject('The user denied camera access.'); | ||||
|                 throw new Error('The user denied camera access.'); | ||||
|             } | ||||
| 
 | ||||
|             if (this.qrScanData && this.qrScanData.deferred) { | ||||
| @ -1470,29 +1508,29 @@ export class CoreUtilsProvider { | ||||
|             // Start scanning.
 | ||||
|             this.qrScanData = { | ||||
|                 deferred: this.promiseDefer(), | ||||
|                 observable: QRScanner.instance.scan().subscribe((text) => { | ||||
| 
 | ||||
|                     // Text received, stop scanning and return the text.
 | ||||
|                     this.stopScanQR(text, false); | ||||
|                 }) | ||||
|                 // When text is received, stop scanning and return the text.
 | ||||
|                 observable: QRScanner.instance.scan().subscribe(text => this.stopScanQR(text, false)), | ||||
|             }; | ||||
| 
 | ||||
|             // Show the camera.
 | ||||
|             return QRScanner.instance.show().then(() => { | ||||
|             try { | ||||
|                 await QRScanner.instance.show(); | ||||
| 
 | ||||
|                 document.body.classList.add('core-scanning-qr'); | ||||
| 
 | ||||
|                 return this.qrScanData.deferred.promise; | ||||
|             }, (err) => { | ||||
|                 this.stopScanQR(err, true); | ||||
|             } catch (e) { | ||||
|                 this.stopScanQR(e, true); | ||||
| 
 | ||||
|                 return Promise.reject(err); | ||||
|             }); | ||||
|                 throw e; | ||||
|             } | ||||
|         } catch (error) { | ||||
|             // eslint-disable-next-line no-underscore-dangle, @typescript-eslint/naming-convention
 | ||||
|             error.message = error.message || (error as { _message?: string })._message; | ||||
| 
 | ||||
|         }).catch((err) => { | ||||
|             err.message = err.message || err._message; | ||||
| 
 | ||||
|             return Promise.reject(err); | ||||
|         }); | ||||
|             throw error; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
| @ -1501,8 +1539,7 @@ export class CoreUtilsProvider { | ||||
|      * @param data If success, the text of the QR code. If error, the error object or message. Undefined for cancelled. | ||||
|      * @param error True if the data belongs to an error, false otherwise. | ||||
|      */ | ||||
|     stopScanQR(data?: any, error?: boolean): void { | ||||
| 
 | ||||
|     stopScanQR(data?: string | Error, error?: boolean): void { | ||||
|         if (!this.qrScanData) { | ||||
|             // Not scanning.
 | ||||
|             return; | ||||
| @ -1518,7 +1555,7 @@ export class CoreUtilsProvider { | ||||
|         if (error) { | ||||
|             this.qrScanData.deferred.reject(data); | ||||
|         } else if (typeof data != 'undefined') { | ||||
|             this.qrScanData.deferred.resolve(data); | ||||
|             this.qrScanData.deferred.resolve(data as string); | ||||
|         } else { | ||||
|             this.qrScanData.deferred.reject(CoreDomUtils.instance.createCanceledError()); | ||||
|         } | ||||
| @ -1549,10 +1586,20 @@ export class CoreUtilsProvider { | ||||
|      * @return Promise resolved after the time has passed. | ||||
|      */ | ||||
|     wait(milliseconds: number): Promise<void> { | ||||
|         return new Promise((resolve, reject): void => { | ||||
|             setTimeout(resolve, milliseconds); | ||||
|         }); | ||||
|         return new Promise(resolve => setTimeout(resolve, milliseconds)); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Override native window.open with InAppBrowser if available. | ||||
|      */ | ||||
|     private overrideWindowOpen() { | ||||
|         if (!window.cordova?.InAppBrowser) { | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         window.open = window.cordova.InAppBrowser.open; | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| export class CoreUtils extends makeSingleton(CoreUtilsProvider) {} | ||||
|  | ||||
							
								
								
									
										447
									
								
								src/app/singletons/locutus.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										447
									
								
								src/app/singletons/locutus.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,447 @@ | ||||
| /* eslint-disable */ | ||||
| 
 | ||||
| /** | ||||
|  * Original code taken from https://github.com/kvz/locutus
 | ||||
|  */ | ||||
| 
 | ||||
| function initCache () { | ||||
|   const store = [] | ||||
|   // cache only first element, second is length to jump ahead for the parser
 | ||||
|   const cache = function cache (value) { | ||||
|     store.push(value[0]) | ||||
|     return value | ||||
|   } | ||||
| 
 | ||||
|   cache.get = (index) => { | ||||
|     if (index >= store.length) { | ||||
|       throw RangeError(`Can't resolve reference ${index + 1}`) | ||||
|     } | ||||
| 
 | ||||
|     return store[index] | ||||
|   } | ||||
| 
 | ||||
|   return cache | ||||
| } | ||||
| 
 | ||||
| function expectType (str, cache) { | ||||
|   const types = /^(?:N(?=;)|[bidsSaOCrR](?=:)|[^:]+(?=:))/g | ||||
|   const type = (types.exec(str) || [])[0] | ||||
| 
 | ||||
|   if (!type) { | ||||
|     throw SyntaxError('Invalid input: ' + str) | ||||
|   } | ||||
| 
 | ||||
|   switch (type) { | ||||
|     case 'N': | ||||
|       return cache([ null, 2 ]) | ||||
|     case 'b': | ||||
|       return cache(expectBool(str)) | ||||
|     case 'i': | ||||
|       return cache(expectInt(str)) | ||||
|     case 'd': | ||||
|       return cache(expectFloat(str)) | ||||
|     case 's': | ||||
|       return cache(expectString(str)) | ||||
|     case 'S': | ||||
|       return cache(expectEscapedString(str)) | ||||
|     case 'a': | ||||
|       return expectArray(str, cache) | ||||
|     case 'O': | ||||
|       return expectObject(str, cache) | ||||
|     case 'C': | ||||
|       return expectClass(str, cache) | ||||
|     case 'r': | ||||
|     case 'R': | ||||
|       return expectReference(str, cache) | ||||
|     default: | ||||
|       throw SyntaxError(`Invalid or unsupported data type: ${type}`) | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| function expectBool (str) { | ||||
|   const reBool = /^b:([01]);/ | ||||
|   const [ match, boolMatch ] = reBool.exec(str) || [] | ||||
| 
 | ||||
|   if (!boolMatch) { | ||||
|     throw SyntaxError('Invalid bool value, expected 0 or 1') | ||||
|   } | ||||
| 
 | ||||
|   return [ boolMatch === '1', match.length ] | ||||
| } | ||||
| 
 | ||||
| function expectInt (str) { | ||||
|   const reInt = /^i:([+-]?\d+);/ | ||||
|   const [ match, intMatch ] = reInt.exec(str) || [] | ||||
| 
 | ||||
|   if (!intMatch) { | ||||
|     throw SyntaxError('Expected an integer value') | ||||
|   } | ||||
| 
 | ||||
|   return [ parseInt(intMatch, 10), match.length ] | ||||
| } | ||||
| 
 | ||||
| function expectFloat (str) { | ||||
|   const reFloat = /^d:(NAN|-?INF|(?:\d+\.\d*|\d*\.\d+|\d+)(?:[eE][+-]\d+)?);/ | ||||
|   const [ match, floatMatch ] = reFloat.exec(str) || [] | ||||
| 
 | ||||
|   if (!floatMatch) { | ||||
|     throw SyntaxError('Expected a float value') | ||||
|   } | ||||
| 
 | ||||
|   let floatValue | ||||
| 
 | ||||
|   switch (floatMatch) { | ||||
|     case 'NAN': | ||||
|       floatValue = Number.NaN | ||||
|       break | ||||
|     case '-INF': | ||||
|       floatValue = Number.NEGATIVE_INFINITY | ||||
|       break | ||||
|     case 'INF': | ||||
|       floatValue = Number.POSITIVE_INFINITY | ||||
|       break | ||||
|     default: | ||||
|       floatValue = parseFloat(floatMatch) | ||||
|       break | ||||
|   } | ||||
| 
 | ||||
|   return [ floatValue, match.length ] | ||||
| } | ||||
| 
 | ||||
| function readBytes (str, len, escapedString = false) { | ||||
|   let bytes = 0 | ||||
|   let out = '' | ||||
|   let c = 0 | ||||
|   const strLen = str.length | ||||
|   let wasHighSurrogate = false | ||||
|   let escapedChars = 0 | ||||
| 
 | ||||
|   while (bytes < len && c < strLen) { | ||||
|     let chr = str.charAt(c) | ||||
|     const code = chr.charCodeAt(0) | ||||
|     const isHighSurrogate = code >= 0xd800 && code <= 0xdbff | ||||
|     const isLowSurrogate = code >= 0xdc00 && code <= 0xdfff | ||||
| 
 | ||||
|     if (escapedString && chr === '\\') { | ||||
|       chr = String.fromCharCode(parseInt(str.substr(c + 1, 2), 16)) | ||||
|       escapedChars++ | ||||
| 
 | ||||
|       // each escaped sequence is 3 characters. Go 2 chars ahead.
 | ||||
|       // third character will be jumped over a few lines later
 | ||||
|       c += 2 | ||||
|     } | ||||
| 
 | ||||
|     c++ | ||||
| 
 | ||||
|     bytes += isHighSurrogate || (isLowSurrogate && wasHighSurrogate) | ||||
|       // if high surrogate, count 2 bytes, as expectation is to be followed by low surrogate
 | ||||
|       // if low surrogate preceded by high surrogate, add 2 bytes
 | ||||
|       ? 2 | ||||
|       : code > 0x7ff | ||||
|         // otherwise low surrogate falls into this part
 | ||||
|         ? 3 | ||||
|         : code > 0x7f | ||||
|           ? 2 | ||||
|           : 1 | ||||
| 
 | ||||
|     // if high surrogate is not followed by low surrogate, add 1 more byte
 | ||||
|     bytes += wasHighSurrogate && !isLowSurrogate ? 1 : 0 | ||||
| 
 | ||||
|     out += chr | ||||
|     wasHighSurrogate = isHighSurrogate | ||||
|   } | ||||
| 
 | ||||
|   return [ out, bytes, escapedChars ] | ||||
| } | ||||
| 
 | ||||
| function expectString (str) { | ||||
|   // PHP strings consist of one-byte characters.
 | ||||
|   // JS uses 2 bytes with possible surrogate pairs.
 | ||||
|   // Serialized length of 2 is still 1 JS string character
 | ||||
|   const reStrLength = /^s:(\d+):"/g // also match the opening " char
 | ||||
|   const [ match, byteLenMatch ] = reStrLength.exec(str) || [] | ||||
| 
 | ||||
|   if (!match) { | ||||
|     throw SyntaxError('Expected a string value') | ||||
|   } | ||||
| 
 | ||||
|   const len = parseInt(byteLenMatch, 10) | ||||
| 
 | ||||
|   str = str.substr(match.length) | ||||
| 
 | ||||
|   let [ strMatch, bytes ] = readBytes(str, len) | ||||
| 
 | ||||
|   if (bytes !== len) { | ||||
|     throw SyntaxError(`Expected string of ${len} bytes, but got ${bytes}`) | ||||
|   } | ||||
| 
 | ||||
|   str = str.substr((strMatch as string).length) | ||||
| 
 | ||||
|   // strict parsing, match closing "; chars
 | ||||
|   if (!str.startsWith('";')) { | ||||
|     throw SyntaxError('Expected ";') | ||||
|   } | ||||
| 
 | ||||
|   return [ strMatch, match.length + (strMatch as string).length + 2 ] // skip last ";
 | ||||
| } | ||||
| 
 | ||||
| function expectEscapedString (str) { | ||||
|   const reStrLength = /^S:(\d+):"/g // also match the opening " char
 | ||||
|   const [ match, strLenMatch ] = reStrLength.exec(str) || [] | ||||
| 
 | ||||
|   if (!match) { | ||||
|     throw SyntaxError('Expected an escaped string value') | ||||
|   } | ||||
| 
 | ||||
|   const len = parseInt(strLenMatch, 10) | ||||
| 
 | ||||
|   str = str.substr(match.length) | ||||
| 
 | ||||
|   let [ strMatch, bytes, escapedChars ] = readBytes(str, len, true) | ||||
| 
 | ||||
|   if (bytes !== len) { | ||||
|     throw SyntaxError(`Expected escaped string of ${len} bytes, but got ${bytes}`) | ||||
|   } | ||||
| 
 | ||||
|   str = str.substr((strMatch as string).length + (escapedChars as number) * 2) | ||||
| 
 | ||||
|   // strict parsing, match closing "; chars
 | ||||
|   if (!str.startsWith('";')) { | ||||
|     throw SyntaxError('Expected ";') | ||||
|   } | ||||
| 
 | ||||
|   return [ strMatch, match.length + (strMatch as string).length + 2 ] // skip last ";
 | ||||
| } | ||||
| 
 | ||||
| function expectKeyOrIndex (str) { | ||||
|   try { | ||||
|     return expectString(str) | ||||
|   } catch (err) {} | ||||
| 
 | ||||
|   try { | ||||
|     return expectEscapedString(str) | ||||
|   } catch (err) {} | ||||
| 
 | ||||
|   try { | ||||
|     return expectInt(str) | ||||
|   } catch (err) { | ||||
|     throw SyntaxError('Expected key or index') | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| function expectObject (str, cache) { | ||||
|   // O:<class name length>:"class name":<prop count>:{<props and values>}
 | ||||
|   // O:8:"stdClass":2:{s:3:"foo";s:3:"bar";s:3:"bar";s:3:"baz";}
 | ||||
|   const reObjectLiteral = /^O:(\d+):"([^"]+)":(\d+):\{/ | ||||
|   const [ objectLiteralBeginMatch, /* classNameLengthMatch */, className, propCountMatch ] = reObjectLiteral.exec(str) || [] | ||||
| 
 | ||||
|   if (!objectLiteralBeginMatch) { | ||||
|     throw SyntaxError('Invalid input') | ||||
|   } | ||||
| 
 | ||||
|   if (className !== 'stdClass') { | ||||
|     throw SyntaxError(`Unsupported object type: ${className}`) | ||||
|   } | ||||
| 
 | ||||
|   let totalOffset = objectLiteralBeginMatch.length | ||||
| 
 | ||||
|   const propCount = parseInt(propCountMatch, 10) | ||||
|   const obj = {} | ||||
|   cache([obj]) | ||||
| 
 | ||||
|   str = str.substr(totalOffset) | ||||
| 
 | ||||
|   for (let i = 0; i < propCount; i++) { | ||||
|     const prop = expectKeyOrIndex(str) | ||||
|     str = str.substr(prop[1]) | ||||
|     totalOffset += prop[1] as number | ||||
| 
 | ||||
|     const value = expectType(str, cache) | ||||
|     str = str.substr(value[1]) | ||||
|     totalOffset += value[1] | ||||
| 
 | ||||
|     obj[prop[0]] = value[0] | ||||
|   } | ||||
| 
 | ||||
|   // strict parsing, expect } after object literal
 | ||||
|   if (str.charAt(0) !== '}') { | ||||
|     throw SyntaxError('Expected }') | ||||
|   } | ||||
| 
 | ||||
|   return [ obj, totalOffset + 1 ] // skip final }
 | ||||
| } | ||||
| 
 | ||||
| function expectClass (str, cache) { | ||||
|   // can't be well supported, because requires calling eval (or similar)
 | ||||
|   // in order to call serialized constructor name
 | ||||
|   // which is unsafe
 | ||||
|   // or assume that constructor is defined in global scope
 | ||||
|   // but this is too much limiting
 | ||||
|   throw Error('Not yet implemented') | ||||
| } | ||||
| 
 | ||||
| function expectReference (str, cache) { | ||||
|   const reRef = /^[rR]:([1-9]\d*);/ | ||||
|   const [ match, refIndex ] = reRef.exec(str) || [] | ||||
| 
 | ||||
|   if (!match) { | ||||
|     throw SyntaxError('Expected reference value') | ||||
|   } | ||||
| 
 | ||||
|   return [ cache.get(parseInt(refIndex, 10) - 1), match.length ] | ||||
| } | ||||
| 
 | ||||
| function expectArray (str, cache) { | ||||
|   const reArrayLength = /^a:(\d+):{/ | ||||
|   const [ arrayLiteralBeginMatch, arrayLengthMatch ] = reArrayLength.exec(str) || [] | ||||
| 
 | ||||
|   if (!arrayLengthMatch) { | ||||
|     throw SyntaxError('Expected array length annotation') | ||||
|   } | ||||
| 
 | ||||
|   str = str.substr(arrayLiteralBeginMatch.length) | ||||
| 
 | ||||
|   const array = expectArrayItems(str, parseInt(arrayLengthMatch, 10), cache) | ||||
| 
 | ||||
|   // strict parsing, expect closing } brace after array literal
 | ||||
|   if (str.charAt(array[1]) !== '}') { | ||||
|     throw SyntaxError('Expected }') | ||||
|   } | ||||
| 
 | ||||
|   return [ array[0], arrayLiteralBeginMatch.length + (array[1] as number) + 1 ] // jump over }
 | ||||
| } | ||||
| 
 | ||||
| function expectArrayItems (str, expectedItems = 0, cache) { | ||||
|   let key | ||||
|   let hasStringKeys = false | ||||
|   let item | ||||
|   let totalOffset = 0 | ||||
|   let items = [] | ||||
|   cache([items]) | ||||
| 
 | ||||
|   for (let i = 0; i < expectedItems; i++) { | ||||
|     key = expectKeyOrIndex(str) | ||||
| 
 | ||||
|     // this is for backward compatibility with previous implementation
 | ||||
|     if (!hasStringKeys) { | ||||
|       hasStringKeys = (typeof key[0] === 'string') | ||||
|     } | ||||
| 
 | ||||
|     str = str.substr(key[1]) | ||||
|     totalOffset += key[1] | ||||
| 
 | ||||
|     // references are resolved immediately, so if duplicate key overwrites previous array index
 | ||||
|     // the old value is anyway resolved
 | ||||
|     // fixme: but next time the same reference should point to the new value
 | ||||
|     item = expectType(str, cache) | ||||
|     str = str.substr(item[1]) | ||||
|     totalOffset += item[1] | ||||
| 
 | ||||
|     items[key[0]] = item[0] | ||||
|   } | ||||
| 
 | ||||
|   // this is for backward compatibility with previous implementation
 | ||||
|   if (hasStringKeys) { | ||||
|     items = Object.assign({}, items) | ||||
|   } | ||||
| 
 | ||||
|   return [ items, totalOffset ] | ||||
| } | ||||
| 
 | ||||
| function unserialize (str) { | ||||
|   //       discuss at: https://locutus.io/php/unserialize/
 | ||||
|   //      original by: Arpad Ray (mailto:arpad@php.net)
 | ||||
|   //      improved by: Pedro Tainha (https://www.pedrotainha.com)
 | ||||
|   //      improved by: Kevin van Zonneveld (https://kvz.io)
 | ||||
|   //      improved by: Kevin van Zonneveld (https://kvz.io)
 | ||||
|   //      improved by: Chris
 | ||||
|   //      improved by: James
 | ||||
|   //      improved by: Le Torbi
 | ||||
|   //      improved by: Eli Skeggs
 | ||||
|   //      bugfixed by: dptr1988
 | ||||
|   //      bugfixed by: Kevin van Zonneveld (https://kvz.io)
 | ||||
|   //      bugfixed by: Brett Zamir (https://brett-zamir.me)
 | ||||
|   //      bugfixed by: philippsimon (https://github.com/philippsimon/)
 | ||||
|   //       revised by: d3x
 | ||||
|   //         input by: Brett Zamir (https://brett-zamir.me)
 | ||||
|   //         input by: Martin (https://www.erlenwiese.de/)
 | ||||
|   //         input by: kilops
 | ||||
|   //         input by: Jaroslaw Czarniak
 | ||||
|   //         input by: lovasoa (https://github.com/lovasoa/)
 | ||||
|   //      improved by: Rafał Kukawski
 | ||||
|   // reimplemented by: Rafał Kukawski
 | ||||
|   //           note 1: We feel the main purpose of this function should be
 | ||||
|   //           note 1: to ease the transport of data between php & js
 | ||||
|   //           note 1: Aiming for PHP-compatibility, we have to translate objects to arrays
 | ||||
|   //        example 1: unserialize('a:3:{i:0;s:5:"Kevin";i:1;s:3:"van";i:2;s:9:"Zonneveld";}')
 | ||||
|   //        returns 1: ['Kevin', 'van', 'Zonneveld']
 | ||||
|   //        example 2: unserialize('a:2:{s:9:"firstName";s:5:"Kevin";s:7:"midName";s:3:"van";}')
 | ||||
|   //        returns 2: {firstName: 'Kevin', midName: 'van'}
 | ||||
|   //        example 3: unserialize('a:3:{s:2:"ü";s:2:"ü";s:3:"四";s:3:"四";s:4:"𠜎";s:4:"𠜎";}')
 | ||||
|   //        returns 3: {'ü': 'ü', '四': '四', '𠜎': '𠜎'}
 | ||||
|   //        example 4: unserialize(undefined)
 | ||||
|   //        returns 4: false
 | ||||
|   //        example 5: unserialize('O:8:"stdClass":1:{s:3:"foo";b:1;}')
 | ||||
|   //        returns 5: { foo: true }
 | ||||
|   //        example 6: unserialize('a:2:{i:0;N;i:1;s:0:"";}')
 | ||||
|   //        returns 6: [null, ""]
 | ||||
|   //        example 7: unserialize('S:7:"\\65\\73\\63\\61\\70\\65\\64";')
 | ||||
|   //        returns 7: 'escaped'
 | ||||
| 
 | ||||
|   try { | ||||
|     if (typeof str !== 'string') { | ||||
|       return false | ||||
|     } | ||||
| 
 | ||||
|     return expectType(str, initCache())[0] | ||||
|   } catch (err) { | ||||
|     console.error(err) | ||||
|     return false | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| function substr_replace (str, replace, start, length) { // eslint-disable-line camelcase
 | ||||
|   //  discuss at: https://locutus.io/php/substr_replace/
 | ||||
|   // original by: Brett Zamir (https://brett-zamir.me)
 | ||||
|   //   example 1: substr_replace('ABCDEFGH:/MNRPQR/', 'bob', 0)
 | ||||
|   //   returns 1: 'bob'
 | ||||
|   //   example 2: var $var = 'ABCDEFGH:/MNRPQR/'
 | ||||
|   //   example 2: substr_replace($var, 'bob', 0, $var.length)
 | ||||
|   //   returns 2: 'bob'
 | ||||
|   //   example 3: substr_replace('ABCDEFGH:/MNRPQR/', 'bob', 0, 0)
 | ||||
|   //   returns 3: 'bobABCDEFGH:/MNRPQR/'
 | ||||
|   //   example 4: substr_replace('ABCDEFGH:/MNRPQR/', 'bob', 10, -1)
 | ||||
|   //   returns 4: 'ABCDEFGH:/bob/'
 | ||||
|   //   example 5: substr_replace('ABCDEFGH:/MNRPQR/', 'bob', -7, -1)
 | ||||
|   //   returns 5: 'ABCDEFGH:/bob/'
 | ||||
|   //   example 6: substr_replace('ABCDEFGH:/MNRPQR/', '', 10, -1)
 | ||||
|   //   returns 6: 'ABCDEFGH://'
 | ||||
| 
 | ||||
|   if (start < 0) { | ||||
|     // start position in str
 | ||||
|     start = start + str.length | ||||
|   } | ||||
|   length = length !== undefined ? length : str.length | ||||
|   if (length < 0) { | ||||
|     length = length + str.length - start | ||||
|   } | ||||
| 
 | ||||
|   return [ | ||||
|     str.slice(0, start), | ||||
|     replace.substr(0, length), | ||||
|     replace.slice(length), | ||||
|     str.slice(start + length) | ||||
|   ].join('') | ||||
| } | ||||
| 
 | ||||
| export class Locutus { | ||||
| 
 | ||||
|     static unserialize<T = unknown>(data: string): T { | ||||
|         return unserialize(data); | ||||
|     } | ||||
| 
 | ||||
|     static substrReplace(str: string, replace: string, start: number, length?: number): string { | ||||
|         return substr_replace(str, replace, start, length); | ||||
|     } | ||||
| 
 | ||||
| }; | ||||
							
								
								
									
										1272
									
								
								src/assets/exttomime.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1272
									
								
								src/assets/exttomime.json
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										1095
									
								
								src/assets/mimetoext.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1095
									
								
								src/assets/mimetoext.json
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @ -3,8 +3,9 @@ | ||||
|   "compilerOptions": { | ||||
|     "outDir": "./out-tsc/app", | ||||
|     "types": [ | ||||
|       "cordova", | ||||
|       "cordova-plugin-file-transfer", | ||||
|       "cordova-plugin-inappbrowser", | ||||
|       "cordova", | ||||
|       "node" | ||||
|     ], | ||||
|     "paths": { | ||||
|  | ||||
| @ -18,6 +18,9 @@ | ||||
|       "dom" | ||||
|     ], | ||||
|     "types": [ | ||||
|         "cordova-plugin-file-transfer", | ||||
|         "cordova-plugin-inappbrowser", | ||||
|         "cordova", | ||||
|         "jest", | ||||
|         "node" | ||||
|     ], | ||||
|  | ||||
| @ -6,6 +6,9 @@ | ||||
|     "emitDecoratorMetadata": true, | ||||
|     "outDir": "./out-tsc/tests", | ||||
|     "types": [ | ||||
|       "cordova-plugin-file-transfer", | ||||
|       "cordova-plugin-inappbrowser", | ||||
|       "cordova", | ||||
|       "jest", | ||||
|       "node" | ||||
|     ], | ||||
|  | ||||
							
								
								
									
										144
									
								
								tslint.json
									
									
									
									
									
								
							
							
						
						
									
										144
									
								
								tslint.json
									
									
									
									
									
								
							| @ -1,144 +0,0 @@ | ||||
| { | ||||
|   "extends": "tslint:recommended", | ||||
|   "rules": { | ||||
|     "array-type": false, | ||||
|     "arrow-return-shorthand": true, | ||||
|     "curly": true, | ||||
|     "deprecation": { | ||||
|       "severity": "warning" | ||||
|     }, | ||||
|     "component-class-suffix": [true, "Page", "Component"], | ||||
|     "contextual-lifecycle": true, | ||||
|     "directive-class-suffix": true, | ||||
|     "directive-selector": [ | ||||
|       true, | ||||
|       "attribute", | ||||
|       "app", | ||||
|       "camelCase" | ||||
|     ], | ||||
|     "eofline": true, | ||||
|     "import-blacklist": [ | ||||
|       true, | ||||
|       "rxjs/Rx" | ||||
|     ], | ||||
|     "import-spacing": true, | ||||
|     "indent": { | ||||
|       "options": [ | ||||
|         "spaces" | ||||
|       ] | ||||
|     }, | ||||
|     "max-classes-per-file": false, | ||||
|     "max-line-length": [ | ||||
|       true, | ||||
|       140 | ||||
|     ], | ||||
|     "member-ordering": [ | ||||
|       true, | ||||
|       { | ||||
|         "order": [ | ||||
|           "static-field", | ||||
|           "instance-field", | ||||
|           "static-method", | ||||
|           "instance-method" | ||||
|         ] | ||||
|       } | ||||
|     ], | ||||
|     "no-angle-bracket-type-assertion": false, | ||||
|     "no-console": [ | ||||
|       true, | ||||
|       "debug", | ||||
|       "info", | ||||
|       "time", | ||||
|       "timeEnd", | ||||
|       "trace" | ||||
|     ], | ||||
|     "no-empty": false, | ||||
|     "no-inferrable-types": [ | ||||
|       true, | ||||
|       "ignore-params" | ||||
|     ], | ||||
|     "no-non-null-assertion": true, | ||||
|     "no-redundant-jsdoc": true, | ||||
|     "no-shadowed-variable": false, | ||||
|     "no-switch-case-fall-through": true, | ||||
|     "no-unused-expression": false, | ||||
|     "no-var-requires": false, | ||||
|     "object-literal-key-quotes": [ | ||||
|       true, | ||||
|       "as-needed" | ||||
|     ], | ||||
|     "prefer-for-of": false, | ||||
|     "quotemark": [ | ||||
|       true, | ||||
|       "single" | ||||
|     ], | ||||
|     "semicolon": { | ||||
|       "options": [ | ||||
|         "always" | ||||
|       ] | ||||
|     }, | ||||
|     "space-before-function-paren": { | ||||
|       "options": { | ||||
|         "anonymous": "never", | ||||
|         "asyncArrow": "always", | ||||
|         "constructor": "never", | ||||
|         "method": "never", | ||||
|         "named": "never" | ||||
|       } | ||||
|     }, | ||||
|     "typedef-whitespace": { | ||||
|       "options": [ | ||||
|         { | ||||
|           "call-signature": "nospace", | ||||
|           "index-signature": "nospace", | ||||
|           "parameter": "nospace", | ||||
|           "property-declaration": "nospace", | ||||
|           "variable-declaration": "nospace" | ||||
|         }, | ||||
|         { | ||||
|           "call-signature": "onespace", | ||||
|           "index-signature": "onespace", | ||||
|           "parameter": "onespace", | ||||
|           "property-declaration": "onespace", | ||||
|           "variable-declaration": "onespace" | ||||
|         } | ||||
|       ] | ||||
|     }, | ||||
|     "variable-name": { | ||||
|       "options": [ | ||||
|         "ban-keywords", | ||||
|         "check-format", | ||||
|         "allow-pascal-case", | ||||
|         "allow-leading-underscore" | ||||
|       ] | ||||
|     }, | ||||
|     "whitespace": { | ||||
|       "options": [ | ||||
|         "check-branch", | ||||
|         "check-decl", | ||||
|         "check-operator", | ||||
|         "check-separator", | ||||
|         "check-type", | ||||
|         "check-typecast" | ||||
|       ] | ||||
|     }, | ||||
|     "no-conflicting-lifecycle": true, | ||||
|     "no-host-metadata-property": true, | ||||
|     "no-input-rename": true, | ||||
|     "no-inputs-metadata-property": true, | ||||
|     "no-output-native": true, | ||||
|     "no-output-on-prefix": true, | ||||
|     "no-output-rename": true, | ||||
|     "no-outputs-metadata-property": true, | ||||
|     "template-banana-in-box": true, | ||||
|     "template-no-negated-async": true, | ||||
|     "use-lifecycle-interface": true, | ||||
|     "use-pipe-transform-interface": true, | ||||
|     "object-literal-sort-keys": false, | ||||
|     "forin": false, | ||||
|     "triple-equals": false | ||||
|   }, | ||||
|   "rulesDirectory": [ | ||||
|     "codelyzer" | ||||
|   ] | ||||
| } | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user