diff --git a/MainActivity.java b/MainActivity.java new file mode 100755 index 000000000..92a34c3c0 --- /dev/null +++ b/MainActivity.java @@ -0,0 +1,56 @@ +// (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. + +// Based on the template node_modules/cordova-android/bin/templates/project/Activity.java + +package com.moodle.moodlemobile; + +import android.os.Bundle; +import android.view.KeyEvent; +import android.view.View; +import org.apache.cordova.*; + +public class MainActivity extends CordovaActivity +{ + @Override + public void onCreate(Bundle savedInstanceState) + { + super.onCreate(savedInstanceState); + + // enable Cordova apps to be started in the background + Bundle extras = getIntent().getExtras(); + if (extras != null && extras.getBoolean("cdvStartInBackground", false)) { + moveTaskToBack(true); + } + + // Set by in config.xml + loadUrl(launchUrl); + } + + @Override + public boolean dispatchKeyEvent(KeyEvent event) { + // Forward back key events to the web view. + if (this.appView != null && event.getKeyCode() == KeyEvent.KEYCODE_BACK) { + View webview = this.appView.getView(); + + if (webview != null) { + webview.dispatchKeyEvent(event); + } + + return true; + } + + return super.dispatchKeyEvent(event); + } +} diff --git a/NOTICE b/NOTICE index f8dca79e3..64a4824f2 100644 --- a/NOTICE +++ b/NOTICE @@ -1,4 +1,4 @@ -(C) Copyright 2015 Martin Dougiamas +(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. diff --git a/config.xml b/config.xml index 27e82659b..ed799b098 100644 --- a/config.xml +++ b/config.xml @@ -1,5 +1,5 @@ - + Moodle Moodle official app Moodle Mobile team @@ -42,25 +42,20 @@ + - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + @@ -77,6 +72,18 @@ + + + + + + + + + + + + @@ -112,8 +119,29 @@ + + We need your location so you can attach it as part of your submissions. + + + + + + + + + + + + + + + + + + + - + @@ -125,8 +153,11 @@ - + + + + @@ -138,7 +169,7 @@ - + @@ -156,6 +187,6 @@ YES - - + + diff --git a/config/copy.config.js b/config/copy.config.js index 7f0f0686f..cf81edd35 100644 --- a/config/copy.config.js +++ b/config/copy.config.js @@ -12,5 +12,37 @@ module.exports = { copyConfig: { src: ['{{ROOT}}/src/config.json'], dest: '{{WWW}}/' - } + }, + copyMathJaxMain: { + src: ['{{ROOT}}/node_modules/mathjax/MathJax.js'], + dest: '{{WWW}}/lib/mathjax' + }, + copyMathJaxExtensions: { + src: ['{{ROOT}}/node_modules/mathjax/extensions/**/*'], + dest: '{{WWW}}/lib/mathjax/extensions' + }, + copyMathJaxElement: { + src: ['{{ROOT}}/node_modules/mathjax/jax/element/**/*'], + dest: '{{WWW}}/lib/mathjax/jax/element' + }, + copyMathJaxInput: { + src: ['{{ROOT}}/node_modules/mathjax/jax/input/**/*'], + dest: '{{WWW}}/lib/mathjax/jax/input' + }, + copyMathJaxSVGOutput: { + src: ['{{ROOT}}/node_modules/mathjax/jax/output/SVG/**/*'], + dest: '{{WWW}}/lib/mathjax/jax/output/SVG' + }, + copyMathJaxPreviewHTMLOutput: { + src: ['{{ROOT}}/node_modules/mathjax/jax/output/PreviewHTML/**/*'], + dest: '{{WWW}}/lib/mathjax/jax/output/PreviewHTML' + }, + copyMathJaxLocalization: { + src: ['{{ROOT}}/node_modules/mathjax/localization/**/*'], + dest: '{{WWW}}/lib/mathjax/localization' + }, + copyH5P: { + src: ['{{ROOT}}/src/core/h5p/assets/**/*'], + dest: '{{WWW}}/h5p/' + }, }; diff --git a/desktop/assets/windows/AppXManifest.xml b/desktop/assets/windows/AppXManifest.xml index 74ed3b65b..4923f504d 100644 --- a/desktop/assets/windows/AppXManifest.xml +++ b/desktop/assets/windows/AppXManifest.xml @@ -6,7 +6,7 @@ + Version="3.8.0.0" /> Moodle Desktop Moodle Pty Ltd. diff --git a/gulpfile.js b/gulpfile.js index f0bc12a16..426ea8cc2 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -13,7 +13,7 @@ var gulp = require('gulp'), File = gutil.File, exec = require('child_process').exec, license = '' + - '// (C) Copyright 2015 Martin Dougiamas\n' + + '// (C) Copyright 2015 Moodle Pty Ltd.\n' + '//\n' + '// Licensed under the Apache License, Version 2.0 (the "License");\n' + '// you may not use this file except in compliance with the License.\n' + diff --git a/package-lock.json b/package-lock.json index c82b49deb..15de2157d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "moodlemobile", - "version": "3.7.1", + "version": "3.8.0", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -145,6 +145,11 @@ "resolved": "https://registry.npmjs.org/@ionic-native/file-transfer/-/file-transfer-4.17.0.tgz", "integrity": "sha512-GtCnjCMH02r5BLONYkPjcYDAXSHmrO9t8jPY5LoI29bx2B6vwX+NFBARHPNSFPEwRi1zZKs/J0uJ1hkhDzOvxg==" }, + "@ionic-native/geolocation": { + "version": "4.17.0", + "resolved": "https://registry.npmjs.org/@ionic-native/geolocation/-/geolocation-4.17.0.tgz", + "integrity": "sha512-VgMOAP/Kjnpx4cwufFbx/DeoSoERfHt7X+I5aiOVo3J9w2rICUK3jyEccWVP0FPAfcIMrJoofpFK88TeY7O+tg==" + }, "@ionic-native/globalization": { "version": "4.17.0", "resolved": "https://registry.npmjs.org/@ionic-native/globalization/-/globalization-4.17.0.tgz", @@ -1144,6 +1149,15 @@ } } }, + "@mrmlnc/readdir-enhanced": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@mrmlnc/readdir-enhanced/-/readdir-enhanced-2.2.1.tgz", + "integrity": "sha512-bPHp6Ji8b41szTOcaP63VlnbbO5Ny6dwAATtY6JTjh5N2OLrb5Qk/Th5cRkRQhkWCt+EJsYrNB0MiL+Gpn6e3g==", + "requires": { + "call-me-maybe": "^1.0.1", + "glob-to-regexp": "^0.3.0" + } + }, "@ngx-translate/core": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/@ngx-translate/core/-/core-8.0.0.tgz", @@ -1154,6 +1168,11 @@ "resolved": "https://registry.npmjs.org/@ngx-translate/http-loader/-/http-loader-2.0.1.tgz", "integrity": "sha1-qmd4jmS/qGUmkad7Ais7QDEgkRM=" }, + "@nodelib/fs.stat": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-1.1.3.tgz", + "integrity": "sha512-shAmDyaQC4H92APFoIaVDHCx5bStIocgvbwQyxPRrbUY20V1EYTbSDchWbuwlMG3V17cprZhA6+78JfB+3DTPw==" + }, "@types/cordova": { "version": "0.0.34", "resolved": "https://registry.npmjs.org/@types/cordova/-/cordova-0.0.34.tgz", @@ -1185,6 +1204,26 @@ "resolved": "https://registry.npmjs.org/@types/cordova-plugin-network-information/-/cordova-plugin-network-information-0.0.3.tgz", "integrity": "sha1-+iycaufkxX8Tt39pXaTtuzr6oBY=" }, + "@types/events": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/events/-/events-3.0.0.tgz", + "integrity": "sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g==" + }, + "@types/glob": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.1.tgz", + "integrity": "sha512-1Bh06cbWJUHMC97acuD6UMG29nMt0Aqz1vF3guLfG+kHHJhy3AyohZFFxYk2f7Q1SQIrNwvncxAE0N/9s70F2w==", + "requires": { + "@types/events": "*", + "@types/minimatch": "*", + "@types/node": "*" + } + }, + "@types/minimatch": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz", + "integrity": "sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==" + }, "@types/node": { "version": "8.10.19", "resolved": "https://registry.npmjs.org/@types/node/-/node-8.10.19.tgz", @@ -1198,14 +1237,12 @@ "abbrev": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", - "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", - "dev": true + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" }, "accepts": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.5.tgz", "integrity": "sha1-63d99gEXI6OxTopywIBcjoZ0a9I=", - "dev": true, "requires": { "mime-types": "~2.1.18", "negotiator": "0.6.1" @@ -1235,15 +1272,14 @@ } }, "ajv": { - "version": "5.5.2", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", - "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", - "dev": true, + "version": "6.10.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.2.tgz", + "integrity": "sha512-TXtUUEYHuaTEbLZWIKUr5pmBuhDLy+8KYtPYdcV8qC+pOZL+NKqYwvWSRrVXHn+ZmRRAu8vJTAznH7Oag6RVRw==", "requires": { - "co": "^4.6.0", - "fast-deep-equal": "^1.0.0", + "fast-deep-equal": "^2.0.1", "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.3.0" + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" } }, "ajv-keywords": { @@ -1269,6 +1305,56 @@ "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=", "dev": true }, + "android-versions": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/android-versions/-/android-versions-1.4.0.tgz", + "integrity": "sha512-GnomfYsBq+nZh3c3UH/4r9Jt6FuTxdhUJbeHIdYOH5xBhQ8I0ZzC2/RM5IFFIjrzuNWSHb8JWP1lPK0/a26jrg==", + "requires": { + "semver": "^5.4.1" + } + }, + "ansi": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/ansi/-/ansi-0.3.1.tgz", + "integrity": "sha1-DELU+xcWDVqa8eSEus4cZpIsGyE=" + }, + "ansi-align": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-2.0.0.tgz", + "integrity": "sha1-w2rsy6VjuJzrVW82kPCx2eNUf38=", + "requires": { + "string-width": "^2.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=" + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=" + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + } + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "requires": { + "ansi-regex": "^3.0.0" + } + } + } + }, "ansi-colors": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-1.1.0.tgz", @@ -1287,6 +1373,11 @@ "ansi-wrap": "0.1.0" } }, + "ansi-escapes": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz", + "integrity": "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==" + }, "ansi-gray": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/ansi-gray/-/ansi-gray-0.1.1.tgz", @@ -1315,7 +1406,6 @@ "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, "requires": { "color-convert": "^1.9.0" }, @@ -1324,7 +1414,6 @@ "version": "1.9.2", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.2.tgz", "integrity": "sha512-3NUJZdhMhcdPn8vJ9v2UQJoH0qqoGUkYTgFEPZaPjEtwmmKUfNV46zZmgB2M5M4DCEQHMaCfWHCxiBflLm04Tg==", - "dev": true, "requires": { "color-name": "1.1.1" } @@ -1332,8 +1421,7 @@ "color-name": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.1.tgz", - "integrity": "sha1-SxQVMEz1ACjqgWQ2Q72C6gWANok=", - "dev": true + "integrity": "sha1-SxQVMEz1ACjqgWQ2Q72C6gWANok=" } } }, @@ -1426,8 +1514,7 @@ "arr-union": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", - "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=", - "dev": true + "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=" }, "array-differ": { "version": "1.0.0", @@ -1444,14 +1531,12 @@ "array-find-index": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz", - "integrity": "sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E=", - "dev": true + "integrity": "sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E=" }, "array-flatten": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=", - "dev": true + "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" }, "array-initial": { "version": "1.1.0", @@ -1513,11 +1598,18 @@ } } }, + "array-union": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", + "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=", + "requires": { + "array-uniq": "^1.0.1" + } + }, "array-uniq": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", - "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=", - "dev": true + "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=" }, "array-unique": { "version": "0.2.1", @@ -1527,8 +1619,7 @@ "asn1": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz", - "integrity": "sha1-2sh4dxPJlmhJ/IGAd36+nB3fO4Y=", - "dev": true + "integrity": "sha1-2sh4dxPJlmhJ/IGAd36+nB3fO4Y=" }, "asn1.js": { "version": "4.10.1", @@ -1570,20 +1661,17 @@ "assert-plus": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", - "dev": true + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" }, "assign-symbols": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", - "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", - "dev": true + "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=" }, "async": { "version": "2.6.2", "resolved": "https://registry.npmjs.org/async/-/async-2.6.2.tgz", "integrity": "sha512-H1qVYh1MYhEEFLsP97cVKqCGo7KfCyTt6uEWqsTBr9SO84oK9Uwbyd/yCW+6rKJLHksBNUVWZDAjfS+Ccx0Bbg==", - "dev": true, "requires": { "lodash": "^4.17.11" } @@ -1635,14 +1723,12 @@ "asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", - "dev": true + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" }, "atob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.1.tgz", - "integrity": "sha1-ri1acpR38onWDdf5amMUoi3Wwio=", - "dev": true + "integrity": "sha1-ri1acpR38onWDdf5amMUoi3Wwio=" }, "autoprefixer": { "version": "7.2.6", @@ -1661,14 +1747,12 @@ "aws-sign2": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", - "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=", - "dev": true + "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" }, "aws4": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz", - "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==", - "dev": true + "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==" }, "babel-code-frame": { "version": "6.26.0", @@ -1739,7 +1823,6 @@ "version": "0.11.2", "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", - "dev": true, "requires": { "cache-base": "^1.0.1", "class-utils": "^0.3.5", @@ -1754,7 +1837,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, "requires": { "is-descriptor": "^1.0.0" } @@ -1763,7 +1845,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, "requires": { "kind-of": "^6.0.0" } @@ -1772,7 +1853,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, "requires": { "kind-of": "^6.0.0" } @@ -1781,7 +1861,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, "requires": { "is-accessor-descriptor": "^1.0.0", "is-data-descriptor": "^1.0.0", @@ -1791,28 +1870,24 @@ "isobject": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=" }, "kind-of": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", - "dev": true + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==" } } }, "base64-js": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.0.tgz", - "integrity": "sha512-ccav/yGvoa80BQDljCxsmmQ3Xvx60/UpBIij5QN21W3wBi/hhIC9OoO+KLpu9IJTS9j4DRVJ3aDDF9cMSoa2lw==", - "dev": true + "integrity": "sha512-ccav/yGvoa80BQDljCxsmmQ3Xvx60/UpBIij5QN21W3wBi/hhIC9OoO+KLpu9IJTS9j4DRVJ3aDDF9cMSoa2lw==" }, "bcrypt-pbkdf": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz", "integrity": "sha1-Y7xdy2EzG5K8Bf1SiVPDNGKgb40=", - "dev": true, "optional": true, "requires": { "tweetnacl": "^0.14.3" @@ -1824,6 +1899,11 @@ "integrity": "sha1-5tXqjF2tABMEpwsiY4RH9pyy+Ak=", "dev": true }, + "big-integer": { + "version": "1.6.44", + "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.44.tgz", + "integrity": "sha512-7MzElZPTyJ2fNvBkPxtFQ2fWIkVmuzw41+BZHSzpEq3ymB2MfeKp1+yXl/tS75xCx+WnyV+yb0kp+K1C3UNwmQ==" + }, "big.js": { "version": "5.2.2", "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", @@ -1905,7 +1985,6 @@ "version": "1.18.3", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.3.tgz", "integrity": "sha1-WykhmP/dVTs6DyDe0FkrlWlVyLQ=", - "dev": true, "requires": { "bytes": "3.0.0", "content-type": "~1.0.4", @@ -1919,6 +1998,70 @@ "type-is": "~1.6.16" } }, + "boxen": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/boxen/-/boxen-1.3.0.tgz", + "integrity": "sha512-TNPjfTr432qx7yOjQyaXm3dSR0MH9vXp7eT1BFSl/C51g+EFnOR9hTg1IreahGBmDNCehscshe45f+C1TBZbLw==", + "requires": { + "ansi-align": "^2.0.0", + "camelcase": "^4.0.0", + "chalk": "^2.0.1", + "cli-boxes": "^1.0.0", + "string-width": "^2.0.0", + "term-size": "^1.2.0", + "widest-line": "^2.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=" + }, + "camelcase": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", + "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=" + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=" + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + } + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "requires": { + "ansi-regex": "^3.0.0" + } + } + } + }, + "bplist-creator": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/bplist-creator/-/bplist-creator-0.0.7.tgz", + "integrity": "sha1-N98VNgkoJLh8QvlXsBNEEXNyrkU=", + "requires": { + "stream-buffers": "~2.2.0" + } + }, + "bplist-parser": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/bplist-parser/-/bplist-parser-0.1.1.tgz", + "integrity": "sha1-1g1dzCDLptx+HymbNdPh+V2vuuY=", + "requires": { + "big-integer": "^1.6.7" + } + }, "brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -2152,8 +2295,7 @@ "builtin-modules": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", - "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=", - "dev": true + "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=" }, "builtin-status-codes": { "version": "3.0.0", @@ -2161,17 +2303,20 @@ "integrity": "sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug=", "dev": true }, + "builtins": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/builtins/-/builtins-1.0.3.tgz", + "integrity": "sha1-y5T662HIaWRR2zZTThQi+U8K7og=" + }, "bytes": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", - "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=", - "dev": true + "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=" }, "cache-base": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", - "dev": true, "requires": { "collection-visit": "^1.0.0", "component-emitter": "^1.2.1", @@ -2187,11 +2332,20 @@ "isobject": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=" } } }, + "call-me-maybe": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.1.tgz", + "integrity": "sha1-JtII6onje1y95gJQoV8DHBak1ms=" + }, + "callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==" + }, "camelcase": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-2.1.1.tgz", @@ -2214,11 +2368,15 @@ "integrity": "sha512-ekW8NQ3/FvokviDxhdKLZZAx7PptXNwxKgXtnR5y+PR3hckwuP3yJ1Ir+4/c97dsHNqtAyfKUGdw8P4EYzBNgw==", "dev": true }, + "capture-stack-trace": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/capture-stack-trace/-/capture-stack-trace-1.0.1.tgz", + "integrity": "sha512-mYQLZnx5Qt1JgB1WEiMCf2647plpGeQ2NMR/5L0HNZzGQo4fuSPnK+wjfPnKZV0aiJDgzmWqqkV/g7JD+DW0qw==" + }, "caseless": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", - "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", - "dev": true + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" }, "center-align": { "version": "0.1.3", @@ -2234,13 +2392,17 @@ "version": "2.4.1", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", - "dev": true, "requires": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", "supports-color": "^5.3.0" } }, + "chardet": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", + "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==" + }, "chart.js": { "version": "2.7.2", "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-2.7.2.tgz", @@ -2292,8 +2454,7 @@ "ci-info": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-1.1.3.tgz", - "integrity": "sha512-SK/846h/Rcy8q9Z9CAwGBLfCJ6EkjJWdpelWDufQpqVDYq2Wnnv8zlSO6AMQap02jvhVruKKpEtQOufo3pFhLg==", - "dev": true + "integrity": "sha512-SK/846h/Rcy8q9Z9CAwGBLfCJ6EkjJWdpelWDufQpqVDYq2Wnnv8zlSO6AMQap02jvhVruKKpEtQOufo3pFhLg==" }, "cipher-base": { "version": "1.0.4", @@ -2309,7 +2470,6 @@ "version": "0.3.6", "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", - "dev": true, "requires": { "arr-union": "^3.1.0", "define-property": "^0.2.5", @@ -2321,7 +2481,6 @@ "version": "0.2.5", "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, "requires": { "is-descriptor": "^0.1.0" } @@ -2329,8 +2488,7 @@ "isobject": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=" } } }, @@ -2343,11 +2501,15 @@ "source-map": "~0.6.0" } }, + "cli-boxes": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-1.0.0.tgz", + "integrity": "sha1-T6kXw+WclKAEzWH47lCdplFocUM=" + }, "cli-cursor": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", "integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=", - "dev": true, "requires": { "restore-cursor": "^2.0.0" } @@ -2358,6 +2520,11 @@ "integrity": "sha512-1QL4544moEsDVH9T/l6Cemov/37iv1RtoKf7NJ04A60+4MREXNfx/QvavbH6QoGdsD4N4Mwy49cmaINR/o2mdg==", "dev": true }, + "cli-width": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz", + "integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=" + }, "cliui": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz", @@ -2436,7 +2603,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", - "dev": true, "requires": { "map-visit": "^1.0.0", "object-visit": "^1.0.0" @@ -2465,15 +2631,14 @@ "dev": true }, "com-darryncampbell-cordova-plugin-intent": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/com-darryncampbell-cordova-plugin-intent/-/com-darryncampbell-cordova-plugin-intent-1.1.7.tgz", - "integrity": "sha512-e+CIaOTpZ7r178tmCijZcm/o5nJIWVnQaUrwm5xwX1zc5zutVCtz1oH3xqq6gzNk05C9i7n96xdenODHMYpiMw==" + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/com-darryncampbell-cordova-plugin-intent/-/com-darryncampbell-cordova-plugin-intent-1.3.0.tgz", + "integrity": "sha512-JXslndd4UiRHmirGZrwrHZHczoZ5sxM7zAylm4bPX7ZDwD4FdCHhILgDA8AeaG8wc11e0A7OEAFo0Esgc0M4yA==" }, "combined-stream": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.6.tgz", "integrity": "sha1-cj599ugBrFYTETp+RFqbactjKBg=", - "dev": true, "requires": { "delayed-stream": "~1.0.0" } @@ -2493,8 +2658,36 @@ "component-emitter": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", - "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=", - "dev": true + "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=" + }, + "compressible": { + "version": "2.0.17", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.17.tgz", + "integrity": "sha512-BGHeLCK1GV7j1bSmQQAi26X+GgWcTjLr/0tzSvMCl3LH1w1IJ4PFSPoV5316b30cneTziC+B1a+3OjoSUcQYmw==", + "requires": { + "mime-db": ">= 1.40.0 < 2" + }, + "dependencies": { + "mime-db": { + "version": "1.42.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.42.0.tgz", + "integrity": "sha512-UbfJCR4UAVRNgMpfImz05smAXK7+c+ZntjaA26ANtkXLlOe947Aag5zdIcKQULAiF9Cq4WxBi9jUs5zkA84bYQ==" + } + } + }, + "compression": { + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.4.tgz", + "integrity": "sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==", + "requires": { + "accepts": "~1.3.5", + "bytes": "3.0.0", + "compressible": "~2.0.16", + "debug": "2.6.9", + "on-headers": "~1.0.2", + "safe-buffer": "5.1.2", + "vary": "~1.1.2" + } }, "concat-map": { "version": "0.0.1", @@ -2522,6 +2715,31 @@ "source-map": "^0.6.1" } }, + "conf": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/conf/-/conf-1.4.0.tgz", + "integrity": "sha512-bzlVWS2THbMetHqXKB8ypsXN4DQ/1qopGwNJi1eYbpwesJcd86FBjFciCQX/YwAhp9bM7NVnPFqZ5LpV7gP0Dg==", + "requires": { + "dot-prop": "^4.1.0", + "env-paths": "^1.0.0", + "make-dir": "^1.0.0", + "pkg-up": "^2.0.0", + "write-file-atomic": "^2.3.0" + } + }, + "configstore": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/configstore/-/configstore-4.0.0.tgz", + "integrity": "sha512-CmquAXFBocrzaSM8mtGPMM/HiWmyIpr4CcJl/rgY2uCObZ/S7cKU0silxslqJejl+t/T9HS8E0PUNQD81JGUEQ==", + "requires": { + "dot-prop": "^4.1.0", + "graceful-fs": "^4.1.2", + "make-dir": "^1.0.0", + "unique-string": "^1.0.0", + "write-file-atomic": "^2.0.0", + "xdg-basedir": "^3.0.0" + } + }, "console-browserify": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.1.0.tgz", @@ -2546,14 +2764,12 @@ "content-disposition": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", - "integrity": "sha1-DPaLud318r55YcOoUXjLhdunjLQ=", - "dev": true + "integrity": "sha1-DPaLud318r55YcOoUXjLhdunjLQ=" }, "content-type": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", - "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", - "dev": true + "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" }, "continuable-cache": { "version": "0.3.1", @@ -2573,20 +2789,17 @@ "cookie": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", - "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=", - "dev": true + "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=" }, "cookie-signature": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=", - "dev": true + "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" }, "copy-descriptor": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", - "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=", - "dev": true + "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=" }, "copy-props": { "version": "2.0.4", @@ -2599,3045 +2812,29 @@ } }, "cordova": { - "version": "8.1.2", - "resolved": "https://registry.npmjs.org/cordova/-/cordova-8.1.2.tgz", - "integrity": "sha512-IfslM3MP42CA/ebVJVlit6FhQ2P6Fercwx9NNQjkVs0wahEwqamL4bcqh1gKiTti7+/ZsDtBRSVmRv+y7LcTbg==", + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/cordova/-/cordova-9.0.0.tgz", + "integrity": "sha512-zWEPo9uGj9KNcEhU2Lpo3r4HYK21tL+at496N2LLnuCWuWVndv6QWed8+EYl/08rrcNshrEtfzXj9Ux6vQm2PQ==", "requires": { - "configstore": "^3.1.2", - "cordova-common": "^2.2.0", - "cordova-lib": "8.1.1", - "editor": "1.0.0", - "insight": "^0.8.4", - "loud-rejection": "^1.6.0", + "configstore": "^4.0.0", + "cordova-common": "^3.1.0", + "cordova-lib": "^9.0.0", + "editor": "^1.0.0", + "insight": "^0.10.1", + "loud-rejection": "^2.0.0", "nopt": "^4.0.1", "update-notifier": "^2.5.0" }, "dependencies": { - "JSONStream": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.4.tgz", - "integrity": "sha512-Y7vfi3I5oMOYIr+WxV8NZxDSwcbNgzdKYsTNInmycOq9bUYwGg9ryu57Wg5NLmCjqdFPNUmpMBo3kSJN9tCbXg==", - "requires": { - "jsonparse": "^1.2.0", - "through": ">=2.2.7 <3" - } - }, - "abbrev": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", - "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" - }, - "accepts": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.5.tgz", - "integrity": "sha1-63d99gEXI6OxTopywIBcjoZ0a9I=", - "requires": { - "mime-types": "~2.1.18", - "negotiator": "0.6.1" - } - }, - "acorn": { - "version": "5.7.3", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.3.tgz", - "integrity": "sha512-T/zvzYRfbVojPWahDsE5evJdHb3oJoQfFbsrKM7w5Zcs++Tr257tia3BmMP8XYVjp1S9RZXQMh7gao96BlqZOw==" - }, - "acorn-jsx": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-3.0.1.tgz", - "integrity": "sha1-r9+UiPsezvyDSPb7IvRk4ypYs2s=", - "requires": { - "acorn": "^3.0.4" - }, - "dependencies": { - "acorn": { - "version": "3.3.0", - "resolved": "http://registry.npmjs.org/acorn/-/acorn-3.3.0.tgz", - "integrity": "sha1-ReN/s56No/JbruP/U2niu18iAXo=" - } - } - }, - "acorn-node": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/acorn-node/-/acorn-node-1.6.0.tgz", - "integrity": "sha512-ZsysjEh+Y3i14f7YXCAKJy99RXbd56wHKYBzN4FlFtICIZyFpYwK6OwNJhcz8A/FMtxoUZkJofH1v9KIfNgWmw==", - "requires": { - "acorn": "^6.0.1", - "acorn-walk": "^6.0.1", - "xtend": "^4.0.1" - }, - "dependencies": { - "acorn": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.0.2.tgz", - "integrity": "sha512-GXmKIvbrN3TV7aVqAzVFaMW8F8wzVX7voEBRO3bDA64+EX37YSayggRJP5Xig6HYHBkWKpFg9W5gg6orklubhg==" - } - } - }, - "acorn-walk": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-6.1.0.tgz", - "integrity": "sha512-ugTb7Lq7u4GfWSqqpwE0bGyoBZNMTok/zDBXxfEG0QM50jNlGhIWjRC1pPN7bvV1anhF+bs+/gNcRw+o55Evbg==" - }, - "ajv": { - "version": "5.5.2", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", - "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", - "requires": { - "co": "^4.6.0", - "fast-deep-equal": "^1.0.0", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.3.0" - } - }, - "ajv-keywords": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-2.1.1.tgz", - "integrity": "sha1-YXmX/F9gV2iUxDX5QNgZ4TW4B2I=" - }, - "aliasify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/aliasify/-/aliasify-2.1.0.tgz", - "integrity": "sha1-fDCCW5RQueYYW6J1M+r24gZ9S0I=", - "requires": { - "browserify-transform-tools": "~1.7.0" - } - }, - "amdefine": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", - "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=", - "optional": true - }, - "ansi": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/ansi/-/ansi-0.3.1.tgz", - "integrity": "sha1-DELU+xcWDVqa8eSEus4cZpIsGyE=" - }, - "ansi-align": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-2.0.0.tgz", - "integrity": "sha1-w2rsy6VjuJzrVW82kPCx2eNUf38=", - "requires": { - "string-width": "^2.0.0" - } - }, - "ansi-escapes": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.1.0.tgz", - "integrity": "sha512-UgAb8H9D41AQnu/PbWlCofQVcnV4Gs2bBJi9eZPxfU/hgglFh3SMDMENRIqdr7H6XFnXdoknctFByVsCOotTVw==" - }, - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" - }, - "ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=" - }, - "argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "requires": { - "sprintf-js": "~1.0.2" - } - }, - "array-filter": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/array-filter/-/array-filter-0.0.1.tgz", - "integrity": "sha1-fajPLiZijtcygDWB/SH2fKzS7uw=" - }, - "array-find-index": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz", - "integrity": "sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E=" - }, - "array-flatten": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" - }, - "array-map": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/array-map/-/array-map-0.0.0.tgz", - "integrity": "sha1-iKK6tz0c97zVwbEYoAP2b2ZfpmI=" - }, - "array-reduce": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/array-reduce/-/array-reduce-0.0.0.tgz", - "integrity": "sha1-FziZ0//Rx9k4PkR5Ul2+J4yrXys=" - }, - "array-union": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", - "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=", - "requires": { - "array-uniq": "^1.0.1" - } - }, - "array-uniq": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", - "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=" - }, - "arrify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", - "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=" - }, - "asn1": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", - "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", - "requires": { - "safer-buffer": "~2.1.0" - } - }, - "asn1.js": { - "version": "4.10.1", - "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-4.10.1.tgz", - "integrity": "sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw==", - "requires": { - "bn.js": "^4.0.0", - "inherits": "^2.0.1", - "minimalistic-assert": "^1.0.0" - } - }, - "assert": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/assert/-/assert-1.4.1.tgz", - "integrity": "sha1-mZEtWRg2tab1s0XA8H7vwI/GXZE=", - "requires": { - "util": "0.10.3" - }, - "dependencies": { - "inherits": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz", - "integrity": "sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=" - }, - "util": { - "version": "0.10.3", - "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz", - "integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=", - "requires": { - "inherits": "2.0.1" - } - } - } - }, - "assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" - }, - "async": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", - "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=" - }, - "asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" - }, - "aws-sign2": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", - "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" - }, - "aws4": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz", - "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==" - }, - "babel-code-frame": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", - "integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=", - "requires": { - "chalk": "^1.1.3", - "esutils": "^2.0.2", - "js-tokens": "^3.0.2" - } - }, - "balanced-match": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" - }, - "base64-js": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.2.0.tgz", - "integrity": "sha1-o5mS1yNYSBGYK+XikLtqU9hnAPE=" - }, - "bcrypt-pbkdf": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", - "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", - "optional": true, - "requires": { - "tweetnacl": "^0.14.3" - } - }, - "big-integer": { - "version": "1.6.36", - "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.36.tgz", - "integrity": "sha512-t70bfa7HYEA1D9idDbmuv7YbsbVkQ+Hp+8KFSul4aE5e/i1bjCNIRYJZlA8Q8p0r9T8cF/RVvwUgRA//FydEyg==" - }, - "block-stream": { - "version": "0.0.9", - "resolved": "https://registry.npmjs.org/block-stream/-/block-stream-0.0.9.tgz", - "integrity": "sha1-E+v+d4oDIFz+A3UUgeu0szAMEmo=", - "requires": { - "inherits": "~2.0.0" - } - }, - "bn.js": { - "version": "4.11.8", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz", - "integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==" - }, - "body-parser": { - "version": "1.18.2", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.2.tgz", - "integrity": "sha1-h2eKGdhLR9hZuDGZvVm84iKxBFQ=", - "requires": { - "bytes": "3.0.0", - "content-type": "~1.0.4", - "debug": "2.6.9", - "depd": "~1.1.1", - "http-errors": "~1.6.2", - "iconv-lite": "0.4.19", - "on-finished": "~2.3.0", - "qs": "6.5.1", - "raw-body": "2.3.2", - "type-is": "~1.6.15" - } - }, - "boxen": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/boxen/-/boxen-1.3.0.tgz", - "integrity": "sha512-TNPjfTr432qx7yOjQyaXm3dSR0MH9vXp7eT1BFSl/C51g+EFnOR9hTg1IreahGBmDNCehscshe45f+C1TBZbLw==", - "requires": { - "ansi-align": "^2.0.0", - "camelcase": "^4.0.0", - "chalk": "^2.0.1", - "cli-boxes": "^1.0.0", - "string-width": "^2.0.0", - "term-size": "^1.2.0", - "widest-line": "^2.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "requires": { - "color-convert": "^1.9.0" - } - }, - "chalk": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", - "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "requires": { - "has-flag": "^3.0.0" - } - } - } - }, - "bplist-creator": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/bplist-creator/-/bplist-creator-0.0.7.tgz", - "integrity": "sha1-N98VNgkoJLh8QvlXsBNEEXNyrkU=", - "requires": { - "stream-buffers": "~2.2.0" - } - }, - "bplist-parser": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/bplist-parser/-/bplist-parser-0.1.1.tgz", - "integrity": "sha1-1g1dzCDLptx+HymbNdPh+V2vuuY=", - "requires": { - "big-integer": "^1.6.7" - } - }, - "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "brorand": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", - "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=" - }, - "browser-pack": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/browser-pack/-/browser-pack-6.1.0.tgz", - "integrity": "sha512-erYug8XoqzU3IfcU8fUgyHqyOXqIE4tUTTQ+7mqUjQlvnXkOO6OlT9c/ZoJVHYoAaqGxr09CN53G7XIsO4KtWA==", - "requires": { - "JSONStream": "^1.0.3", - "combine-source-map": "~0.8.0", - "defined": "^1.0.0", - "safe-buffer": "^5.1.1", - "through2": "^2.0.0", - "umd": "^3.0.0" - } - }, - "browser-resolve": { - "version": "1.11.3", - "resolved": "https://registry.npmjs.org/browser-resolve/-/browser-resolve-1.11.3.tgz", - "integrity": "sha512-exDi1BYWB/6raKHmDTCicQfTkqwN5fioMFV4j8BsfMU4R2DK/QfZfK7kOVkmWCNANf0snkBzqGqAJBao9gZMdQ==", - "requires": { - "resolve": "1.1.7" - }, - "dependencies": { - "resolve": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz", - "integrity": "sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=" - } - } - }, - "browserify": { - "version": "14.4.0", - "resolved": "https://registry.npmjs.org/browserify/-/browserify-14.4.0.tgz", - "integrity": "sha1-CJo0Y69Y0OSNjNQHCz90ZU1avKk=", - "requires": { - "JSONStream": "^1.0.3", - "assert": "^1.4.0", - "browser-pack": "^6.0.1", - "browser-resolve": "^1.11.0", - "browserify-zlib": "~0.1.2", - "buffer": "^5.0.2", - "cached-path-relative": "^1.0.0", - "concat-stream": "~1.5.1", - "console-browserify": "^1.1.0", - "constants-browserify": "~1.0.0", - "crypto-browserify": "^3.0.0", - "defined": "^1.0.0", - "deps-sort": "^2.0.0", - "domain-browser": "~1.1.0", - "duplexer2": "~0.1.2", - "events": "~1.1.0", - "glob": "^7.1.0", - "has": "^1.0.0", - "htmlescape": "^1.1.0", - "https-browserify": "^1.0.0", - "inherits": "~2.0.1", - "insert-module-globals": "^7.0.0", - "labeled-stream-splicer": "^2.0.0", - "module-deps": "^4.0.8", - "os-browserify": "~0.1.1", - "parents": "^1.0.1", - "path-browserify": "~0.0.0", - "process": "~0.11.0", - "punycode": "^1.3.2", - "querystring-es3": "~0.2.0", - "read-only-stream": "^2.0.0", - "readable-stream": "^2.0.2", - "resolve": "^1.1.4", - "shasum": "^1.0.0", - "shell-quote": "^1.6.1", - "stream-browserify": "^2.0.0", - "stream-http": "^2.0.0", - "string_decoder": "~1.0.0", - "subarg": "^1.0.0", - "syntax-error": "^1.1.1", - "through2": "^2.0.0", - "timers-browserify": "^1.0.1", - "tty-browserify": "~0.0.0", - "url": "~0.11.0", - "util": "~0.10.1", - "vm-browserify": "~0.0.1", - "xtend": "^4.0.0" - }, - "dependencies": { - "glob": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", - "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - } - } - }, - "browserify-aes": { - "version": "1.2.0", - "resolved": "http://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", - "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", - "requires": { - "buffer-xor": "^1.0.3", - "cipher-base": "^1.0.0", - "create-hash": "^1.1.0", - "evp_bytestokey": "^1.0.3", - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" - } - }, - "browserify-cipher": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/browserify-cipher/-/browserify-cipher-1.0.1.tgz", - "integrity": "sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w==", - "requires": { - "browserify-aes": "^1.0.4", - "browserify-des": "^1.0.0", - "evp_bytestokey": "^1.0.0" - } - }, - "browserify-des": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/browserify-des/-/browserify-des-1.0.2.tgz", - "integrity": "sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A==", - "requires": { - "cipher-base": "^1.0.1", - "des.js": "^1.0.0", - "inherits": "^2.0.1", - "safe-buffer": "^5.1.2" - } - }, - "browserify-rsa": { - "version": "4.0.1", - "resolved": "http://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz", - "integrity": "sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ=", - "requires": { - "bn.js": "^4.1.0", - "randombytes": "^2.0.1" - } - }, - "browserify-sign": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.0.4.tgz", - "integrity": "sha1-qk62jl17ZYuqa/alfmMMvXqT0pg=", - "requires": { - "bn.js": "^4.1.1", - "browserify-rsa": "^4.0.0", - "create-hash": "^1.1.0", - "create-hmac": "^1.1.2", - "elliptic": "^6.0.0", - "inherits": "^2.0.1", - "parse-asn1": "^5.0.0" - } - }, - "browserify-transform-tools": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/browserify-transform-tools/-/browserify-transform-tools-1.7.0.tgz", - "integrity": "sha1-g+J3Ih9jJZvtLn6yooOpcKUB9MQ=", - "requires": { - "falafel": "^2.0.0", - "through": "^2.3.7" - } - }, - "browserify-zlib": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.1.4.tgz", - "integrity": "sha1-uzX4pRn2AOD6a4SFJByXnQFB+y0=", - "requires": { - "pako": "~0.2.0" - } - }, - "buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.2.1.tgz", - "integrity": "sha512-c+Ko0loDaFfuPWiL02ls9Xd3GO3cPVmUobQ6t3rXNUk304u6hGq+8N/kFi+QEIKhzK3uwolVhLzszmfLmMLnqg==", - "requires": { - "base64-js": "^1.0.2", - "ieee754": "^1.1.4" - } - }, - "buffer-from": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", - "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==" - }, - "buffer-xor": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", - "integrity": "sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk=" - }, - "builtin-modules": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", - "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=" - }, - "builtin-status-codes": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz", - "integrity": "sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug=" - }, - "builtins": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/builtins/-/builtins-1.0.3.tgz", - "integrity": "sha1-y5T662HIaWRR2zZTThQi+U8K7og=" - }, - "bytes": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", - "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=" - }, - "cached-path-relative": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/cached-path-relative/-/cached-path-relative-1.0.1.tgz", - "integrity": "sha1-0JxLUoAKpMB44t2BqGmqyQ0uVOc=" - }, - "caller-path": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-0.1.0.tgz", - "integrity": "sha1-lAhe9jWB7NPaqSREqP6U6CV3dR8=", - "requires": { - "callsites": "^0.2.0" - } - }, - "callsites": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-0.2.0.tgz", - "integrity": "sha1-r6uWJikQp/M8GaV3WCXGnzTjUMo=" - }, - "camelcase": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", - "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=" - }, - "capture-stack-trace": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/capture-stack-trace/-/capture-stack-trace-1.0.1.tgz", - "integrity": "sha512-mYQLZnx5Qt1JgB1WEiMCf2647plpGeQ2NMR/5L0HNZzGQo4fuSPnK+wjfPnKZV0aiJDgzmWqqkV/g7JD+DW0qw==" - }, - "caseless": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", - "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" - }, - "chalk": { - "version": "1.1.3", - "resolved": "http://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "requires": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - } - }, - "chardet": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.4.2.tgz", - "integrity": "sha1-tUc7M9yXxCTl2Y3IfVXU2KKci/I=" - }, - "ci-info": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-1.6.0.tgz", - "integrity": "sha512-vsGdkwSCDpWmP80ncATX7iea5DWQemg1UgCW5J8tqjU3lYw4FBYuj89J0CTVomA7BEfvSZd84GmHko+MxFQU2A==" - }, - "cipher-base": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", - "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==", - "requires": { - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" - } - }, - "circular-json": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/circular-json/-/circular-json-0.3.3.tgz", - "integrity": "sha512-UZK3NBx2Mca+b5LsG7bY183pHWt5Y1xts4P3Pz7ENTwGVnJOUWbRb3ocjvX7hx9tq/yTAdclXm9sZ38gNuem4A==" - }, - "cli-boxes": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-1.0.0.tgz", - "integrity": "sha1-T6kXw+WclKAEzWH47lCdplFocUM=" - }, - "cli-cursor": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", - "integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=", - "requires": { - "restore-cursor": "^2.0.0" - } - }, - "cli-width": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz", - "integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=" - }, - "co": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=" - }, - "code-point-at": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", - "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" - }, - "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" - }, - "combine-source-map": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/combine-source-map/-/combine-source-map-0.8.0.tgz", - "integrity": "sha1-pY0N8ELBhvz4IqjoAV9UUNLXmos=", - "requires": { - "convert-source-map": "~1.1.0", - "inline-source-map": "~0.6.0", - "lodash.memoize": "~3.0.3", - "source-map": "~0.5.3" - } - }, - "combined-stream": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.7.tgz", - "integrity": "sha512-brWl9y6vOB1xYPZcpZde3N9zDByXTosAeMDo4p1wzo6UMOX4vumB+TP1RZ76sfE6Md68Q0NJSrE/gbezd4Ul+w==", - "requires": { - "delayed-stream": "~1.0.0" - } - }, - "commander": { - "version": "2.17.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.17.1.tgz", - "integrity": "sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg==", - "optional": true - }, - "compressible": { - "version": "2.0.15", - "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.15.tgz", - "integrity": "sha512-4aE67DL33dSW9gw4CI2H/yTxqHLNcxp0yS6jB+4h+wr3e43+1z7vm0HU9qXOH8j+qjKuL8+UtkOxYQSMq60Ylw==", - "requires": { - "mime-db": ">= 1.36.0 < 2" - } - }, - "compression": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.3.tgz", - "integrity": "sha512-HSjyBG5N1Nnz7tF2+O7A9XUhyjru71/fwgNb7oIsEVHR0WShfs2tIS/EySLgiTe98aOK18YDlMXpzjCXY/n9mg==", - "requires": { - "accepts": "~1.3.5", - "bytes": "3.0.0", - "compressible": "~2.0.14", - "debug": "2.6.9", - "on-headers": "~1.0.1", - "safe-buffer": "5.1.2", - "vary": "~1.1.2" - } - }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" - }, - "concat-stream": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.5.2.tgz", - "integrity": "sha1-cIl4Yk2FavQaWnQd790mHadSwmY=", - "requires": { - "inherits": "~2.0.1", - "readable-stream": "~2.0.0", - "typedarray": "~0.0.5" - }, - "dependencies": { - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" - }, - "process-nextick-args": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", - "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=" - }, - "readable-stream": { - "version": "2.0.6", - "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.0.6.tgz", - "integrity": "sha1-j5A0HmilPMySh4jaz80Rs265t44=", - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.1", - "isarray": "~1.0.0", - "process-nextick-args": "~1.0.6", - "string_decoder": "~0.10.x", - "util-deprecate": "~1.0.1" - } - }, - "string_decoder": { - "version": "0.10.31", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", - "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" - } - } - }, - "configstore": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/configstore/-/configstore-3.1.2.tgz", - "integrity": "sha512-vtv5HtGjcYUgFrXc6Kx747B83MRRVS5R1VTEQoXvuP+kMI+if6uywV0nDGoiydJRy4yk7h9od5Og0kxx4zUXmw==", - "requires": { - "dot-prop": "^4.1.0", - "graceful-fs": "^4.1.2", - "make-dir": "^1.0.0", - "unique-string": "^1.0.0", - "write-file-atomic": "^2.0.0", - "xdg-basedir": "^3.0.0" - } - }, - "console-browserify": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.1.0.tgz", - "integrity": "sha1-8CQcRXMKn8YyOyBtvzjtx0HQuxA=", - "requires": { - "date-now": "^0.1.4" - } - }, - "constants-browserify": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-1.0.0.tgz", - "integrity": "sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U=" - }, - "contains-path": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/contains-path/-/contains-path-0.1.0.tgz", - "integrity": "sha1-/ozxhP9mcLa67wGp1IYaXL7EEgo=" - }, - "content-disposition": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", - "integrity": "sha1-DPaLud318r55YcOoUXjLhdunjLQ=" - }, - "content-type": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", - "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" - }, - "convert-source-map": { - "version": "1.1.3", - "resolved": "http://registry.npmjs.org/convert-source-map/-/convert-source-map-1.1.3.tgz", - "integrity": "sha1-SCnId+n+SbMWHzvzZziI4gRpmGA=" - }, - "cookie": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", - "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=" - }, - "cookie-signature": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" - }, - "cordova-app-hello-world": { - "version": "3.12.0", - "resolved": "https://registry.npmjs.org/cordova-app-hello-world/-/cordova-app-hello-world-3.12.0.tgz", - "integrity": "sha1-Jw4Gtnsq6UvP7mWS7TnrQjA9GG8=" - }, - "cordova-common": { - "version": "2.2.5", - "resolved": "https://registry.npmjs.org/cordova-common/-/cordova-common-2.2.5.tgz", - "integrity": "sha1-+TzvKtSUz8v1bEbj1hKqqctfzDI=", - "requires": { - "ansi": "^0.3.1", - "bplist-parser": "^0.1.0", - "cordova-registry-mapper": "^1.1.8", - "elementtree": "0.1.6", - "glob": "^5.0.13", - "minimatch": "^3.0.0", - "plist": "^2.1.0", - "q": "^1.4.1", - "shelljs": "^0.5.3", - "underscore": "^1.8.3", - "unorm": "^1.3.3" - } - }, - "cordova-create": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/cordova-create/-/cordova-create-1.1.2.tgz", - "integrity": "sha1-g7CScbN40cA7x9mnhv7dYEhcPM8=", - "requires": { - "cordova-app-hello-world": "^3.11.0", - "cordova-common": "^2.2.0", - "cordova-fetch": "^1.3.0", - "q": "1.0.1", - "shelljs": "0.3.0", - "valid-identifier": "0.0.1" - }, - "dependencies": { - "q": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/q/-/q-1.0.1.tgz", - "integrity": "sha1-EYcq7t7okmgRCxCnGESP+xARKhQ=" - }, - "shelljs": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.3.0.tgz", - "integrity": "sha1-NZbmMHp4FUT1kfN9phg2DzHbV7E=" - } - } - }, - "cordova-fetch": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/cordova-fetch/-/cordova-fetch-1.3.1.tgz", - "integrity": "sha512-/0PNQUPxHvVcjlzVQcydD5BQtfx1XdCfzQ2KigdtZma5oVVUtR4IxfnYB15RuT/GVb/SGRLvR5AIi2Gd5Gb+mg==", - "requires": { - "cordova-common": "^2.2.5", - "dependency-ls": "^1.1.0", - "hosted-git-info": "^2.5.0", - "is-git-url": "^1.0.0", - "is-url": "^1.2.1", - "q": "^1.4.1", - "shelljs": "^0.7.0" - }, - "dependencies": { - "glob": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", - "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "shelljs": { - "version": "0.7.8", - "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.7.8.tgz", - "integrity": "sha1-3svPh0sNHl+3LhSxZKloMEjprLM=", - "requires": { - "glob": "^7.0.0", - "interpret": "^1.0.0", - "rechoir": "^0.6.2" - } - } - } - }, - "cordova-js": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/cordova-js/-/cordova-js-4.2.4.tgz", - "integrity": "sha512-Qy0O3w/gwbIqIJzlyCy60nPwJlF1c74ELpsfDIGXB92/uST5nQSSUDVDP4UOfb/c6OU7yPqxhCWOGROyTYKPDw==", - "requires": { - "browserify": "14.4.0" - } - }, - "cordova-lib": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/cordova-lib/-/cordova-lib-8.1.1.tgz", - "integrity": "sha512-PcrlEGRGubV2c9ThcSwoVtN/1hKQ0qtwRopl4188rD10gjtt8K+NSKrnRqh6Ia5PouVUUOZBrlhBxDd5BRbfeg==", - "requires": { - "aliasify": "^2.1.0", - "cordova-common": "^2.2.0", - "cordova-create": "^1.1.0", - "cordova-fetch": "^1.3.0", - "cordova-js": "^4.2.2", - "cordova-serve": "^2.0.0", - "dep-graph": "1.1.0", - "dependency-ls": "^1.1.1", - "detect-indent": "^5.0.0", - "elementtree": "^0.1.7", - "glob": "^7.1.2", - "init-package-json": "^1.2.0", - "nopt": "4.0.1", - "opener": "^1.4.3", - "plist": "2.0.1", - "properties-parser": "0.3.1", - "q": "^1.5.1", - "read-chunk": "^2.1.0", - "request": "^2.88.0", - "semver": "^5.3.0", - "shebang-command": "^1.2.0", - "shelljs": "0.3.0", - "tar": "^2.2.1", - "underscore": "^1.9.0", - "unorm": "^1.4.1", - "valid-identifier": "0.0.1", - "which": "^1.3.1", - "xcode": "^1.0.0" - }, - "dependencies": { - "base64-js": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.1.2.tgz", - "integrity": "sha1-1kAMrBxMZgl22Q0HoENR2JOV9eg=" - }, - "elementtree": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/elementtree/-/elementtree-0.1.7.tgz", - "integrity": "sha1-mskb5uUvtuYkTE5UpKw+2K6OKcA=", - "requires": { - "sax": "1.1.4" - } - }, - "glob": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", - "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "plist": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/plist/-/plist-2.0.1.tgz", - "integrity": "sha1-CjLKlIGxw2TpLhjcVch23p0B2os=", - "requires": { - "base64-js": "1.1.2", - "xmlbuilder": "8.2.2", - "xmldom": "0.1.x" - } - }, - "sax": { - "version": "1.1.4", - "resolved": "http://registry.npmjs.org/sax/-/sax-1.1.4.tgz", - "integrity": "sha1-dLbTPJrh4AFRDxeakRaFiPGu2qk=" - }, - "shelljs": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.3.0.tgz", - "integrity": "sha1-NZbmMHp4FUT1kfN9phg2DzHbV7E=" - } - } - }, - "cordova-registry-mapper": { - "version": "1.1.15", - "resolved": "https://registry.npmjs.org/cordova-registry-mapper/-/cordova-registry-mapper-1.1.15.tgz", - "integrity": "sha1-4kS5GFuBdUc7/2B5MkkFEV+D3Hw=" - }, - "cordova-serve": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/cordova-serve/-/cordova-serve-2.0.1.tgz", - "integrity": "sha512-3Xl1D5eyiQlY5ow6Kn/say0us2TqSw/zgQmyTLxbewTngQZ1CIqxmqD7EFGoCNBrB4HsdPmpiSpFCitybKQN9g==", - "requires": { - "chalk": "^1.1.1", - "compression": "^1.6.0", - "express": "^4.13.3", - "opn": "^5.3.0", - "shelljs": "^0.5.3" - } - }, - "core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" - }, - "create-ecdh": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.3.tgz", - "integrity": "sha512-GbEHQPMOswGpKXM9kCWVrremUcBmjteUaQ01T9rkKCPDXfUHX0IoP9LpHYo2NPFampa4e+/pFDc3jQdxrxQLaw==", - "requires": { - "bn.js": "^4.1.0", - "elliptic": "^6.0.0" - } - }, - "create-error-class": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/create-error-class/-/create-error-class-3.0.2.tgz", - "integrity": "sha1-Br56vvlHo/FKMP1hBnHUAbyot7Y=", - "requires": { - "capture-stack-trace": "^1.0.0" - } - }, - "create-hash": { - "version": "1.2.0", - "resolved": "http://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", - "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", - "requires": { - "cipher-base": "^1.0.1", - "inherits": "^2.0.1", - "md5.js": "^1.3.4", - "ripemd160": "^2.0.1", - "sha.js": "^2.4.0" - } - }, - "create-hmac": { - "version": "1.1.7", - "resolved": "http://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", - "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", - "requires": { - "cipher-base": "^1.0.3", - "create-hash": "^1.1.0", - "inherits": "^2.0.1", - "ripemd160": "^2.0.0", - "safe-buffer": "^5.0.1", - "sha.js": "^2.4.8" - } - }, - "cross-spawn": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", - "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", - "requires": { - "lru-cache": "^4.0.1", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - } - }, - "crypto-browserify": { - "version": "3.12.0", - "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.0.tgz", - "integrity": "sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg==", - "requires": { - "browserify-cipher": "^1.0.0", - "browserify-sign": "^4.0.0", - "create-ecdh": "^4.0.0", - "create-hash": "^1.1.0", - "create-hmac": "^1.1.0", - "diffie-hellman": "^5.0.0", - "inherits": "^2.0.1", - "pbkdf2": "^3.0.3", - "public-encrypt": "^4.0.0", - "randombytes": "^2.0.0", - "randomfill": "^1.0.3" - } - }, - "crypto-random-string": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-1.0.0.tgz", - "integrity": "sha1-ojD2T1aDEOFJgAmUB5DsmVRbyn4=" - }, - "currently-unhandled": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz", - "integrity": "sha1-mI3zP+qxke95mmE2nddsF635V+o=", - "requires": { - "array-find-index": "^1.0.1" - } - }, - "dashdash": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", - "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", - "requires": { - "assert-plus": "^1.0.0" - } - }, - "date-now": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/date-now/-/date-now-0.1.4.tgz", - "integrity": "sha1-6vQ5/U1ISK105cx9vvIAZyueNFs=" - }, - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - } - }, - "deep-extend": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", - "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==" - }, - "deep-is": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", - "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=" - }, - "defined": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/defined/-/defined-1.0.0.tgz", - "integrity": "sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM=" - }, - "del": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/del/-/del-2.2.2.tgz", - "integrity": "sha1-wSyYHQZ4RshLyvhiz/kw2Qf/0ag=", - "requires": { - "globby": "^5.0.0", - "is-path-cwd": "^1.0.0", - "is-path-in-cwd": "^1.0.0", - "object-assign": "^4.0.1", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0", - "rimraf": "^2.2.8" - }, - "dependencies": { - "pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=" - } - } - }, - "delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" - }, - "dep-graph": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/dep-graph/-/dep-graph-1.1.0.tgz", - "integrity": "sha1-+t6GqSeZqBPptCURzfPfpsyNvv4=", - "requires": { - "underscore": "1.2.1" - }, - "dependencies": { - "underscore": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.2.1.tgz", - "integrity": "sha1-/FxrB2VnPZKi1KyLTcCqiHAuK9Q=" - } - } - }, - "depd": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", - "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" - }, - "dependency-ls": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/dependency-ls/-/dependency-ls-1.1.1.tgz", - "integrity": "sha1-BIGwfwI9dM4xEZLlxpDRPhhgAFQ=", - "requires": { - "q": "1.4.1" - }, - "dependencies": { - "q": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/q/-/q-1.4.1.tgz", - "integrity": "sha1-VXBbzZPF82c1MMLCy8DCs63cKG4=" - } - } - }, - "deps-sort": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/deps-sort/-/deps-sort-2.0.0.tgz", - "integrity": "sha1-CRckkC6EZYJg65EHSMzNGvbiH7U=", - "requires": { - "JSONStream": "^1.0.3", - "shasum": "^1.0.0", - "subarg": "^1.0.0", - "through2": "^2.0.0" - } - }, - "des.js": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.0.0.tgz", - "integrity": "sha1-wHTS4qpqipoH29YfmhXCzYPsjsw=", - "requires": { - "inherits": "^2.0.1", - "minimalistic-assert": "^1.0.0" - } - }, - "destroy": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", - "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" - }, - "detect-indent": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-5.0.0.tgz", - "integrity": "sha1-OHHMCmoALow+Wzz38zYmRnXwa50=" - }, - "detective": { - "version": "4.7.1", - "resolved": "https://registry.npmjs.org/detective/-/detective-4.7.1.tgz", - "integrity": "sha512-H6PmeeUcZloWtdt4DAkFyzFL94arpHr3NOwwmVILFiy+9Qd4JTxxXrzfyGk/lmct2qVGBwTSwSXagqu2BxmWig==", - "requires": { - "acorn": "^5.2.1", - "defined": "^1.0.0" - } - }, - "diffie-hellman": { - "version": "5.0.3", - "resolved": "http://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", - "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==", - "requires": { - "bn.js": "^4.1.0", - "miller-rabin": "^4.0.0", - "randombytes": "^2.0.0" - } - }, - "doctrine": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", - "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", - "requires": { - "esutils": "^2.0.2" - } - }, - "domain-browser": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.1.7.tgz", - "integrity": "sha1-hnqksJP6oF8d4IwG9NeyH9+GmLw=" - }, - "dot-prop": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-4.2.0.tgz", - "integrity": "sha512-tUMXrxlExSW6U2EXiiKGSBVdYgtV8qlHL+C10TsW4PURY/ic+eaysnSkwB4kA/mBlCyy/IKDJ+Lc3wbWeaXtuQ==", - "requires": { - "is-obj": "^1.0.0" - } - }, - "duplexer2": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", - "integrity": "sha1-ixLauHjA1p4+eJEFFmKjL8a93ME=", - "requires": { - "readable-stream": "^2.0.2" - } - }, - "duplexer3": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz", - "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=" - }, - "ecc-jsbn": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", - "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", - "optional": true, - "requires": { - "jsbn": "~0.1.0", - "safer-buffer": "^2.1.0" - } - }, - "editor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/editor/-/editor-1.0.0.tgz", - "integrity": "sha1-YMf4e9YrzGqJT6jM1q+3gjok90I=" - }, - "ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" - }, - "elementtree": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/elementtree/-/elementtree-0.1.6.tgz", - "integrity": "sha1-KsTEbqMFFsjEy9teOsdBjlkt4gw=", - "requires": { - "sax": "0.3.5" - } - }, - "elliptic": { - "version": "6.4.1", - "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.4.1.tgz", - "integrity": "sha512-BsXLz5sqX8OHcsh7CqBMztyXARmGQ3LWPtGjJi6DiJHq5C/qvi9P3OqgswKSDftbu8+IoI/QDTAm2fFnQ9SZSQ==", - "requires": { - "bn.js": "^4.4.0", - "brorand": "^1.0.1", - "hash.js": "^1.0.0", - "hmac-drbg": "^1.0.0", - "inherits": "^2.0.1", - "minimalistic-assert": "^1.0.0", - "minimalistic-crypto-utils": "^1.0.0" - } - }, - "encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" - }, - "error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "requires": { - "is-arrayish": "^0.2.1" - } - }, - "escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" - }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" - }, - "escodegen": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.8.1.tgz", - "integrity": "sha1-WltTr0aTEQvrsIZ6o0MN07cKEBg=", - "requires": { - "esprima": "^2.7.1", - "estraverse": "^1.9.1", - "esutils": "^2.0.2", - "optionator": "^0.8.1", - "source-map": "~0.2.0" - }, - "dependencies": { - "esprima": { - "version": "2.7.3", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-2.7.3.tgz", - "integrity": "sha1-luO3DVd59q1JzQMmc9HDEnZ7pYE=" - }, - "estraverse": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-1.9.3.tgz", - "integrity": "sha1-r2fy3JIlgkFZUJJgkaQAXSnJu0Q=" - }, - "source-map": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.2.0.tgz", - "integrity": "sha1-2rc/vPwrqBm03gO9b26qSBZLP50=", - "optional": true, - "requires": { - "amdefine": ">=0.0.4" - } - } - } - }, - "eslint": { - "version": "4.19.1", - "resolved": "http://registry.npmjs.org/eslint/-/eslint-4.19.1.tgz", - "integrity": "sha512-bT3/1x1EbZB7phzYu7vCr1v3ONuzDtX8WjuM9c0iYxe+cq+pwcKEoQjl7zd3RpC6YOLgnSy3cTN58M2jcoPDIQ==", - "requires": { - "ajv": "^5.3.0", - "babel-code-frame": "^6.22.0", - "chalk": "^2.1.0", - "concat-stream": "^1.6.0", - "cross-spawn": "^5.1.0", - "debug": "^3.1.0", - "doctrine": "^2.1.0", - "eslint-scope": "^3.7.1", - "eslint-visitor-keys": "^1.0.0", - "espree": "^3.5.4", - "esquery": "^1.0.0", - "esutils": "^2.0.2", - "file-entry-cache": "^2.0.0", - "functional-red-black-tree": "^1.0.1", - "glob": "^7.1.2", - "globals": "^11.0.1", - "ignore": "^3.3.3", - "imurmurhash": "^0.1.4", - "inquirer": "^3.0.6", - "is-resolvable": "^1.0.0", - "js-yaml": "^3.9.1", - "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.3.0", - "lodash": "^4.17.4", - "minimatch": "^3.0.2", - "mkdirp": "^0.5.1", - "natural-compare": "^1.4.0", - "optionator": "^0.8.2", - "path-is-inside": "^1.0.2", - "pluralize": "^7.0.0", - "progress": "^2.0.0", - "regexpp": "^1.0.1", - "require-uncached": "^1.0.3", - "semver": "^5.3.0", - "strip-ansi": "^4.0.0", - "strip-json-comments": "~2.0.1", - "table": "4.0.2", - "text-table": "~0.2.0" - }, - "dependencies": { - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=" - }, - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "requires": { - "color-convert": "^1.9.0" - } - }, - "chalk": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", - "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "concat-stream": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", - "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", - "requires": { - "buffer-from": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^2.2.2", - "typedarray": "^0.0.6" - } - }, - "debug": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.5.tgz", - "integrity": "sha512-D61LaDQPQkxJ5AUM2mbSJRbPkNs/TmdmOeLAi1hgDkpDfIfetSrjmWhccwtuResSwMbACjx/xXQofvM9CE/aeg==", - "requires": { - "ms": "^2.1.1" - } - }, - "glob": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", - "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "ms": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" - }, - "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", - "requires": { - "ansi-regex": "^3.0.0" - } - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "requires": { - "has-flag": "^3.0.0" - } - } - } - }, - "eslint-config-semistandard": { - "version": "12.0.1", - "resolved": "https://registry.npmjs.org/eslint-config-semistandard/-/eslint-config-semistandard-12.0.1.tgz", - "integrity": "sha512-4zaPW5uRFasf2uRZkE19Y+W84KBV3q+oyWYOsgUN+5DQXE5HCsh7ZxeWDXxozk7NPycGm0kXcsJzLe5GZ1jCeg==" - }, - "eslint-config-standard": { - "version": "11.0.0", - "resolved": "http://registry.npmjs.org/eslint-config-standard/-/eslint-config-standard-11.0.0.tgz", - "integrity": "sha512-oDdENzpViEe5fwuRCWla7AXQd++/oyIp8zP+iP9jiUPG6NBj3SHgdgtl/kTn00AjeN+1HNvavTKmYbMo+xMOlw==" - }, - "eslint-import-resolver-node": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.2.tgz", - "integrity": "sha512-sfmTqJfPSizWu4aymbPr4Iidp5yKm8yDkHp+Ir3YiTHiiDfxh69mOUsmiqW6RZ9zRXFaF64GtYmN7e+8GHBv6Q==", - "requires": { - "debug": "^2.6.9", - "resolve": "^1.5.0" - } - }, - "eslint-module-utils": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.2.0.tgz", - "integrity": "sha1-snA2LNiLGkitMIl2zn+lTphBF0Y=", - "requires": { - "debug": "^2.6.8", - "pkg-dir": "^1.0.0" - } - }, - "eslint-plugin-import": { - "version": "2.14.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.14.0.tgz", - "integrity": "sha512-FpuRtniD/AY6sXByma2Wr0TXvXJ4nA/2/04VPlfpmUDPOpOY264x+ILiwnrk/k4RINgDAyFZByxqPUbSQ5YE7g==", - "requires": { - "contains-path": "^0.1.0", - "debug": "^2.6.8", - "doctrine": "1.5.0", - "eslint-import-resolver-node": "^0.3.1", - "eslint-module-utils": "^2.2.0", - "has": "^1.0.1", - "lodash": "^4.17.4", - "minimatch": "^3.0.3", - "read-pkg-up": "^2.0.0", - "resolve": "^1.6.0" - }, - "dependencies": { - "doctrine": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-1.5.0.tgz", - "integrity": "sha1-N53Ocw9hZvds76TmcHoVmwLFpvo=", - "requires": { - "esutils": "^2.0.2", - "isarray": "^1.0.0" - } - }, - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" - } - } - }, - "eslint-plugin-node": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-node/-/eslint-plugin-node-5.2.1.tgz", - "integrity": "sha512-xhPXrh0Vl/b7870uEbaumb2Q+LxaEcOQ3kS1jtIXanBAwpMre1l5q/l2l/hESYJGEFKuI78bp6Uw50hlpr7B+g==", - "requires": { - "ignore": "^3.3.6", - "minimatch": "^3.0.4", - "resolve": "^1.3.3", - "semver": "5.3.0" - }, - "dependencies": { - "semver": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz", - "integrity": "sha1-myzl094C0XxgEq0yaqa00M9U+U8=" - } - } - }, - "eslint-plugin-promise": { - "version": "3.8.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-3.8.0.tgz", - "integrity": "sha512-JiFL9UFR15NKpHyGii1ZcvmtIqa3UTwiDAGb8atSffe43qJ3+1czVGN6UtkklpcJ2DVnqvTMzEKRaJdBkAL2aQ==" - }, - "eslint-plugin-standard": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-standard/-/eslint-plugin-standard-3.1.0.tgz", - "integrity": "sha512-fVcdyuKRr0EZ4fjWl3c+gp1BANFJD1+RaWa2UPYfMZ6jCtp5RG00kSaXnK/dE5sYzt4kaWJ9qdxqUfc0d9kX0w==" - }, - "eslint-scope": { - "version": "3.7.3", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-3.7.3.tgz", - "integrity": "sha512-W+B0SvF4gamyCTmUc+uITPY0989iXVfKvhwtmJocTaYoc/3khEHmEmvfY/Gn9HA9VV75jrQECsHizkNw1b68FA==", - "requires": { - "esrecurse": "^4.1.0", - "estraverse": "^4.1.1" - } - }, - "eslint-visitor-keys": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz", - "integrity": "sha512-qzm/XxIbxm/FHyH341ZrbnMUpe+5Bocte9xkmFMzPMjRaZMcXww+MpBptFvtU+79L362nqiLhekCxCxDPaUMBQ==" - }, - "espree": { - "version": "3.5.4", - "resolved": "https://registry.npmjs.org/espree/-/espree-3.5.4.tgz", - "integrity": "sha512-yAcIQxtmMiB/jL32dzEp2enBeidsB7xWPLNiw3IIkpVds1P+h7qF9YwJq1yUNzp2OKXgAprs4F61ih66UsoD1A==", - "requires": { - "acorn": "^5.5.0", - "acorn-jsx": "^3.0.0" - } - }, - "esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==" - }, - "esquery": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.0.1.tgz", - "integrity": "sha512-SmiyZ5zIWH9VM+SRUReLS5Q8a7GxtRdxEBVZpm98rJM7Sb+A9DVCndXfkeFUd3byderg+EbDkfnevfCwynWaNA==", - "requires": { - "estraverse": "^4.0.0" - } - }, - "esrecurse": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz", - "integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==", - "requires": { - "estraverse": "^4.1.0" - } - }, - "estraverse": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz", - "integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=" - }, - "esutils": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", - "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=" - }, - "etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" - }, - "events": { - "version": "1.1.1", - "resolved": "http://registry.npmjs.org/events/-/events-1.1.1.tgz", - "integrity": "sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ=" - }, - "evp_bytestokey": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz", - "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==", - "requires": { - "md5.js": "^1.3.4", - "safe-buffer": "^5.1.1" - } - }, - "execa": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-0.7.0.tgz", - "integrity": "sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c=", - "requires": { - "cross-spawn": "^5.0.1", - "get-stream": "^3.0.0", - "is-stream": "^1.1.0", - "npm-run-path": "^2.0.0", - "p-finally": "^1.0.0", - "signal-exit": "^3.0.0", - "strip-eof": "^1.0.0" - } - }, - "exit-hook": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/exit-hook/-/exit-hook-1.1.1.tgz", - "integrity": "sha1-8FyiM7SMBdVP/wd2XfhQfpXAL/g=" - }, - "express": { - "version": "4.16.3", - "resolved": "http://registry.npmjs.org/express/-/express-4.16.3.tgz", - "integrity": "sha1-avilAjUNsyRuzEvs9rWjTSL37VM=", - "requires": { - "accepts": "~1.3.5", - "array-flatten": "1.1.1", - "body-parser": "1.18.2", - "content-disposition": "0.5.2", - "content-type": "~1.0.4", - "cookie": "0.3.1", - "cookie-signature": "1.0.6", - "debug": "2.6.9", - "depd": "~1.1.2", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "finalhandler": "1.1.1", - "fresh": "0.5.2", - "merge-descriptors": "1.0.1", - "methods": "~1.1.2", - "on-finished": "~2.3.0", - "parseurl": "~1.3.2", - "path-to-regexp": "0.1.7", - "proxy-addr": "~2.0.3", - "qs": "6.5.1", - "range-parser": "~1.2.0", - "safe-buffer": "5.1.1", - "send": "0.16.2", - "serve-static": "1.13.2", - "setprototypeof": "1.1.0", - "statuses": "~1.4.0", - "type-is": "~1.6.16", - "utils-merge": "1.0.1", - "vary": "~1.1.2" - }, - "dependencies": { - "safe-buffer": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", - "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==" - } - } - }, - "extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" - }, - "external-editor": { - "version": "2.2.0", - "resolved": "http://registry.npmjs.org/external-editor/-/external-editor-2.2.0.tgz", - "integrity": "sha512-bSn6gvGxKt+b7+6TKEv1ZycHleA7aHhRHyAqJyp5pbUFuYYNIzpZnQDk7AsYckyWdEnTeAnay0aCy2aV6iTk9A==", - "requires": { - "chardet": "^0.4.0", - "iconv-lite": "^0.4.17", - "tmp": "^0.0.33" - } - }, - "extsprintf": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", - "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" - }, - "falafel": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/falafel/-/falafel-2.1.0.tgz", - "integrity": "sha1-lrsXdh2rqU9G0AFzizzt86Z/4Gw=", - "requires": { - "acorn": "^5.0.0", - "foreach": "^2.0.5", - "isarray": "0.0.1", - "object-keys": "^1.0.6" - } - }, - "fast-deep-equal": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz", - "integrity": "sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ=" - }, - "fast-json-stable-stringify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", - "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=" - }, - "fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=" - }, - "figures": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", - "integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=", - "requires": { - "escape-string-regexp": "^1.0.5" - } - }, - "file-entry-cache": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-2.0.0.tgz", - "integrity": "sha1-w5KZDD5oR4PYOLjISkXYoEhFg2E=", - "requires": { - "flat-cache": "^1.2.1", - "object-assign": "^4.0.1" - } - }, - "finalhandler": { - "version": "1.1.1", - "resolved": "http://registry.npmjs.org/finalhandler/-/finalhandler-1.1.1.tgz", - "integrity": "sha512-Y1GUDo39ez4aHAw7MysnUD5JzYX+WaIj8I57kO3aEPT1fFRL4sr7mjei97FgnwhAyyzRYmQZaTHb2+9uZ1dPtg==", - "requires": { - "debug": "2.6.9", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "on-finished": "~2.3.0", - "parseurl": "~1.3.2", - "statuses": "~1.4.0", - "unpipe": "~1.0.0" - } - }, - "find-up": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", - "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", - "requires": { - "path-exists": "^2.0.0", - "pinkie-promise": "^2.0.0" - } - }, - "flat-cache": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-1.3.0.tgz", - "integrity": "sha1-0wMLMrOBVPTjt+nHCfSQ9++XxIE=", - "requires": { - "circular-json": "^0.3.1", - "del": "^2.0.2", - "graceful-fs": "^4.1.2", - "write": "^0.2.1" - } - }, - "foreach": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/foreach/-/foreach-2.0.5.tgz", - "integrity": "sha1-C+4AUBiusmDQo6865ljdATbsG5k=" - }, - "forever-agent": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", - "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" - }, - "form-data": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.2.tgz", - "integrity": "sha1-SXBJi+YEwgwAXU9cI67NIda0kJk=", - "requires": { - "asynckit": "^0.4.0", - "combined-stream": "1.0.6", - "mime-types": "^2.1.12" - }, - "dependencies": { - "combined-stream": { - "version": "1.0.6", - "resolved": "http://registry.npmjs.org/combined-stream/-/combined-stream-1.0.6.tgz", - "integrity": "sha1-cj599ugBrFYTETp+RFqbactjKBg=", - "requires": { - "delayed-stream": "~1.0.0" - } - } - } - }, - "forwarded": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", - "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=" - }, - "fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" - }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" - }, - "fstream": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.11.tgz", - "integrity": "sha1-XB+x8RdHcRTwYyoOtLcbPLD9MXE=", - "requires": { - "graceful-fs": "^4.1.2", - "inherits": "~2.0.0", - "mkdirp": ">=0.5 0", - "rimraf": "2" - } - }, - "function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" - }, - "functional-red-black-tree": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", - "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=" - }, - "get-assigned-identifiers": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/get-assigned-identifiers/-/get-assigned-identifiers-1.2.0.tgz", - "integrity": "sha512-mBBwmeGTrxEMO4pMaaf/uUEFHnYtwr8FTe8Y/mer4rcV/bye0qGm6pw1bGZFGStxC5O76c5ZAVBGnqHmOaJpdQ==" - }, - "get-stream": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", - "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=" - }, - "getpass": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", - "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", - "requires": { - "assert-plus": "^1.0.0" - } - }, - "glob": { - "version": "5.0.15", - "resolved": "https://registry.npmjs.org/glob/-/glob-5.0.15.tgz", - "integrity": "sha1-G8k2ueAvSmA/zCIuz3Yz0wuLk7E=", - "requires": { - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "2 || 3", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "global-dirs": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-0.1.1.tgz", - "integrity": "sha1-sxnA3UYH81PzvpzKTHL8FIxJ9EU=", - "requires": { - "ini": "^1.3.4" - } - }, - "globals": { - "version": "11.7.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.7.0.tgz", - "integrity": "sha512-K8BNSPySfeShBQXsahYB/AbbWruVOTyVpgoIDnl8odPpeSfP2J5QO2oLFFdl2j7GfDCtZj2bMKar2T49itTPCg==" - }, - "globby": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-5.0.0.tgz", - "integrity": "sha1-69hGZ8oNuzMLmbz8aOrCvFQ3Dg0=", - "requires": { - "array-union": "^1.0.1", - "arrify": "^1.0.0", - "glob": "^7.0.3", - "object-assign": "^4.0.1", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0" - }, - "dependencies": { - "glob": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", - "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=" - } - } - }, - "got": { - "version": "6.7.1", - "resolved": "http://registry.npmjs.org/got/-/got-6.7.1.tgz", - "integrity": "sha1-JAzQV4WpoY5WHcG0S0HHY+8ejbA=", - "requires": { - "create-error-class": "^3.0.0", - "duplexer3": "^0.1.4", - "get-stream": "^3.0.0", - "is-redirect": "^1.0.0", - "is-retry-allowed": "^1.0.0", - "is-stream": "^1.0.0", - "lowercase-keys": "^1.0.0", - "safe-buffer": "^5.0.1", - "timed-out": "^4.0.0", - "unzip-response": "^2.0.1", - "url-parse-lax": "^1.0.0" - } - }, - "graceful-fs": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", - "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=" - }, - "handlebars": { - "version": "4.0.12", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.0.12.tgz", - "integrity": "sha512-RhmTekP+FZL+XNhwS1Wf+bTTZpdLougwt5pcgA1tuz6Jcx0fpH/7z0qd71RKnZHBCxIRBHfBOnio4gViPemNzA==", - "requires": { - "async": "^2.5.0", - "optimist": "^0.6.1", - "source-map": "^0.6.1", - "uglify-js": "^3.1.4" - }, - "dependencies": { - "async": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.1.tgz", - "integrity": "sha512-fNEiL2+AZt6AlAw/29Cr0UDe4sRAHCpEHh54WMz+Bb7QfNcFw4h3loofyJpLeQs4Yx7yuqu/2dLgM5hKOs6HlQ==", - "requires": { - "lodash": "^4.17.10" - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" - } - } - }, - "har-schema": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", - "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=" - }, - "har-validator": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.0.tgz", - "integrity": "sha512-+qnmNjI4OfH2ipQ9VQOw23bBd/ibtfbVdK2fYbY4acTDqKTW/YDp9McimZdDbG8iV9fZizUqQMD5xvriB146TA==", - "requires": { - "ajv": "^5.3.0", - "har-schema": "^2.0.0" - } - }, - "has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "requires": { - "function-bind": "^1.1.1" - } - }, - "has-ansi": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", - "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", - "requires": { - "ansi-regex": "^2.0.0" - } - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" - }, - "hash-base": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.0.4.tgz", - "integrity": "sha1-X8hoaEfs1zSZQDMZprCj8/auSRg=", - "requires": { - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" - } - }, - "hash.js": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.5.tgz", - "integrity": "sha512-eWI5HG9Np+eHV1KQhisXWwM+4EPPYe5dFX1UZZH7k/E3JzDEazVH+VGlZi6R94ZqImq+A3D1mCEtrFIfg/E7sA==", - "requires": { - "inherits": "^2.0.3", - "minimalistic-assert": "^1.0.1" - } - }, - "hmac-drbg": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", - "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=", - "requires": { - "hash.js": "^1.0.3", - "minimalistic-assert": "^1.0.0", - "minimalistic-crypto-utils": "^1.0.1" - } - }, - "hosted-git-info": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.7.1.tgz", - "integrity": "sha512-7T/BxH19zbcCTa8XkMlbK5lTo1WtgkFi3GvdWEyNuc4Vex7/9Dqbnpsf4JMydcfj9HCg4zUWFTL3Za6lapg5/w==" - }, - "htmlescape": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/htmlescape/-/htmlescape-1.1.1.tgz", - "integrity": "sha1-OgPtwiFLyjtmQko+eVk0lQnLA1E=" - }, - "http-errors": { - "version": "1.6.3", - "resolved": "http://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", - "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", - "requires": { - "depd": "~1.1.2", - "inherits": "2.0.3", - "setprototypeof": "1.1.0", - "statuses": ">= 1.4.0 < 2" - } - }, - "http-signature": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", - "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", - "requires": { - "assert-plus": "^1.0.0", - "jsprim": "^1.2.2", - "sshpk": "^1.7.0" - } - }, - "https-browserify": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz", - "integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=" - }, - "iconv-lite": { - "version": "0.4.19", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.19.tgz", - "integrity": "sha512-oTZqweIP51xaGPI4uPa56/Pri/480R+mo7SeU+YETByQNhDG55ycFyNLIgta9vXhILrxXDmF7ZGhqZIcuN0gJQ==" - }, - "ieee754": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.12.tgz", - "integrity": "sha512-GguP+DRY+pJ3soyIiGPTvdiVXjZ+DbXOxGpXn3eMvNW4x4irjqXm4wHKscC+TfxSJ0yw/S1F24tqdMNsMZTiLA==" - }, - "ignore": { - "version": "3.3.10", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.10.tgz", - "integrity": "sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug==" - }, - "import-lazy": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-2.1.0.tgz", - "integrity": "sha1-BWmOPUXIjo1+nZLLBYTnfwlvPkM=" - }, - "imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=" - }, - "indexof": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/indexof/-/indexof-0.0.1.tgz", - "integrity": "sha1-gtwzbSMrkGIXnQWrMpOmYFn9Q10=" - }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" - }, - "ini": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", - "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==" - }, - "init-package-json": { - "version": "1.10.3", - "resolved": "https://registry.npmjs.org/init-package-json/-/init-package-json-1.10.3.tgz", - "integrity": "sha512-zKSiXKhQveNteyhcj1CoOP8tqp1QuxPIPBl8Bid99DGLFqA1p87M6lNgfjJHSBoWJJlidGOv5rWjyYKEB3g2Jw==", - "requires": { - "glob": "^7.1.1", - "npm-package-arg": "^4.0.0 || ^5.0.0 || ^6.0.0", - "promzard": "^0.3.0", - "read": "~1.0.1", - "read-package-json": "1 || 2", - "semver": "2.x || 3.x || 4 || 5", - "validate-npm-package-license": "^3.0.1", - "validate-npm-package-name": "^3.0.0" - }, - "dependencies": { - "glob": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", - "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - } - } - }, - "inline-source-map": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/inline-source-map/-/inline-source-map-0.6.2.tgz", - "integrity": "sha1-+Tk0ccGKedFyT4Y/o4tYY3Ct4qU=", - "requires": { - "source-map": "~0.5.3" - } - }, - "inquirer": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-3.3.0.tgz", - "integrity": "sha512-h+xtnyk4EwKvFWHrUYsWErEVR+igKtLdchu+o0Z1RL7VU/jVMFbYir2bp6bAj8efFNxWqHX0dIss6fJQ+/+qeQ==", - "requires": { - "ansi-escapes": "^3.0.0", - "chalk": "^2.0.0", - "cli-cursor": "^2.1.0", - "cli-width": "^2.0.0", - "external-editor": "^2.0.4", - "figures": "^2.0.0", - "lodash": "^4.3.0", - "mute-stream": "0.0.7", - "run-async": "^2.2.0", - "rx-lite": "^4.0.8", - "rx-lite-aggregates": "^4.0.8", - "string-width": "^2.1.0", - "strip-ansi": "^4.0.0", - "through": "^2.3.6" - }, - "dependencies": { - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=" - }, - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "requires": { - "color-convert": "^1.9.0" - } - }, - "chalk": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", - "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", - "requires": { - "ansi-regex": "^3.0.0" - } - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "requires": { - "has-flag": "^3.0.0" - } - } - } - }, - "insert-module-globals": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/insert-module-globals/-/insert-module-globals-7.2.0.tgz", - "integrity": "sha512-VE6NlW+WGn2/AeOMd496AHFYmE7eLKkUY6Ty31k4og5vmA3Fjuwe9v6ifH6Xx/Hz27QvdoMoviw1/pqWRB09Sw==", - "requires": { - "JSONStream": "^1.0.3", - "acorn-node": "^1.5.2", - "combine-source-map": "^0.8.0", - "concat-stream": "^1.6.1", - "is-buffer": "^1.1.0", - "path-is-absolute": "^1.0.1", - "process": "~0.11.0", - "through2": "^2.0.0", - "undeclared-identifiers": "^1.1.2", - "xtend": "^4.0.0" - }, - "dependencies": { - "concat-stream": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", - "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", - "requires": { - "buffer-from": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^2.2.2", - "typedarray": "^0.0.6" - } - } - } - }, - "insight": { - "version": "0.8.4", - "resolved": "https://registry.npmjs.org/insight/-/insight-0.8.4.tgz", - "integrity": "sha1-ZxyvZbR8n+jD0bMgbPRbshG3WIQ=", - "requires": { - "async": "^1.4.2", - "chalk": "^1.0.0", - "configstore": "^1.0.0", - "inquirer": "^0.10.0", - "lodash.debounce": "^3.0.1", - "object-assign": "^4.0.1", - "os-name": "^1.0.0", - "request": "^2.74.0", - "tough-cookie": "^2.0.0", - "uuid": "^3.0.0" - }, - "dependencies": { - "ansi-escapes": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-1.4.0.tgz", - "integrity": "sha1-06ioOzGapneTZisT52HHkRQiMG4=" - }, - "cli-cursor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-1.0.2.tgz", - "integrity": "sha1-ZNo/fValRBLll5S9Ytw1KV6PKYc=", - "requires": { - "restore-cursor": "^1.0.1" - } - }, - "cli-width": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-1.1.1.tgz", - "integrity": "sha1-pNKT72frt7iNSk1CwMzwDE0eNm0=" - }, - "configstore": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/configstore/-/configstore-1.4.0.tgz", - "integrity": "sha1-w1eB0FAdJowlxUuLF/YkDopPsCE=", - "requires": { - "graceful-fs": "^4.1.2", - "mkdirp": "^0.5.0", - "object-assign": "^4.0.1", - "os-tmpdir": "^1.0.0", - "osenv": "^0.1.0", - "uuid": "^2.0.1", - "write-file-atomic": "^1.1.2", - "xdg-basedir": "^2.0.0" - }, - "dependencies": { - "uuid": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-2.0.3.tgz", - "integrity": "sha1-Z+LoY3lyFVMN/zGOW/nc6/1Hsho=" - } - } - }, - "figures": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-1.7.0.tgz", - "integrity": "sha1-y+Hjr/zxzUS4DK3+0o3Hk6lwHS4=", - "requires": { - "escape-string-regexp": "^1.0.5", - "object-assign": "^4.1.0" - } - }, - "inquirer": { - "version": "0.10.1", - "resolved": "http://registry.npmjs.org/inquirer/-/inquirer-0.10.1.tgz", - "integrity": "sha1-6iXkzmnKFF4FyZ5G3P7AXkASWUo=", - "requires": { - "ansi-escapes": "^1.1.0", - "ansi-regex": "^2.0.0", - "chalk": "^1.0.0", - "cli-cursor": "^1.0.1", - "cli-width": "^1.0.1", - "figures": "^1.3.5", - "lodash": "^3.3.1", - "readline2": "^1.0.1", - "run-async": "^0.1.0", - "rx-lite": "^3.1.2", - "strip-ansi": "^3.0.0", - "through": "^2.3.6" - } - }, - "lodash": { - "version": "3.10.1", - "resolved": "http://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz", - "integrity": "sha1-W/Rejkm6QYnhfUgnid/RW9FAt7Y=" - }, - "onetime": { - "version": "1.1.0", - "resolved": "http://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz", - "integrity": "sha1-ofeDj4MUxRbwXs78vEzP4EtO14k=" - }, - "restore-cursor": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-1.0.1.tgz", - "integrity": "sha1-NGYfRohjJ/7SmRR5FSJS35LapUE=", - "requires": { - "exit-hook": "^1.0.0", - "onetime": "^1.0.0" - } - }, - "run-async": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/run-async/-/run-async-0.1.0.tgz", - "integrity": "sha1-yK1KXhEGYeQCp9IbUw4AnyX444k=", - "requires": { - "once": "^1.3.0" - } - }, - "rx-lite": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/rx-lite/-/rx-lite-3.1.2.tgz", - "integrity": "sha1-Gc5QLKVyZl87ZHsQk5+X/RYV8QI=" - }, - "write-file-atomic": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-1.3.4.tgz", - "integrity": "sha1-+Aek8LHZ6ROuekgRLmzDrxmRtF8=", - "requires": { - "graceful-fs": "^4.1.11", - "imurmurhash": "^0.1.4", - "slide": "^1.1.5" - } - }, - "xdg-basedir": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-2.0.0.tgz", - "integrity": "sha1-7byQPMOF/ARSPZZqM1UEtVBNG9I=", - "requires": { - "os-homedir": "^1.0.0" - } - } - } - }, - "interpret": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.1.0.tgz", - "integrity": "sha1-ftGxQQxqDg94z5XTuEQMY/eLhhQ=" - }, - "ipaddr.js": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.8.0.tgz", - "integrity": "sha1-6qM9bd16zo9/b+DJygRA5wZzix4=" - }, - "is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=" - }, - "is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" - }, - "is-builtin-module": { - "version": "1.0.0", - "resolved": "http://registry.npmjs.org/is-builtin-module/-/is-builtin-module-1.0.0.tgz", - "integrity": "sha1-VAVy0096wxGfj3bDDLwbHgN6/74=", - "requires": { - "builtin-modules": "^1.0.0" - } - }, - "is-ci": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-1.2.1.tgz", - "integrity": "sha512-s6tfsaQaQi3JNciBH6shVqEDvhGut0SUXr31ag8Pd8BBbVVlcGfWhpPmEOoM6RJ5TFhbypvf5yyRw/VXW1IiWg==", - "requires": { - "ci-info": "^1.5.0" - } - }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=" - }, - "is-git-url": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-git-url/-/is-git-url-1.0.0.tgz", - "integrity": "sha1-U/aEzRQyhbUsMkS05vKCU1J69ms=" - }, - "is-installed-globally": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.1.0.tgz", - "integrity": "sha1-Df2Y9akRFxbdU13aZJL2e/PSWoA=", - "requires": { - "global-dirs": "^0.1.0", - "is-path-inside": "^1.0.0" - } - }, - "is-npm": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-1.0.0.tgz", - "integrity": "sha1-8vtjpl5JBbQGyGBydloaTceTufQ=" - }, - "is-obj": { - "version": "1.0.1", - "resolved": "http://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", - "integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=" - }, - "is-path-cwd": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-1.0.0.tgz", - "integrity": "sha1-0iXsIxMuie3Tj9p2dHLmLmXxEG0=" - }, - "is-path-in-cwd": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-1.0.1.tgz", - "integrity": "sha512-FjV1RTW48E7CWM7eE/J2NJvAEEVektecDBVBE5Hh3nM1Jd0kvhHtX68Pr3xsDf857xt3Y4AkwVULK1Vku62aaQ==", - "requires": { - "is-path-inside": "^1.0.0" - } - }, - "is-path-inside": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.1.tgz", - "integrity": "sha1-jvW33lBDej/cprToZe96pVy0gDY=", - "requires": { - "path-is-inside": "^1.0.1" - } - }, - "is-promise": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz", - "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=" - }, - "is-redirect": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-redirect/-/is-redirect-1.0.0.tgz", - "integrity": "sha1-HQPd7VO9jbDzDCbk+V02/HyH3CQ=" - }, - "is-resolvable": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.1.0.tgz", - "integrity": "sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg==" - }, - "is-retry-allowed": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-1.1.0.tgz", - "integrity": "sha1-EaBgVotnM5REAz0BJaYaINVk+zQ=" - }, - "is-stream": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=" - }, - "is-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" - }, - "is-url": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/is-url/-/is-url-1.2.4.tgz", - "integrity": "sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww==" - }, - "is-wsl": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", - "integrity": "sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=" - }, - "isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" - }, - "isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" - }, - "isstream": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", - "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" - }, - "istanbul": { - "version": "0.4.5", - "resolved": "https://registry.npmjs.org/istanbul/-/istanbul-0.4.5.tgz", - "integrity": "sha1-ZcfXPUxNqE1POsMQuRj7C4Azczs=", - "requires": { - "abbrev": "1.0.x", - "async": "1.x", - "escodegen": "1.8.x", - "esprima": "2.7.x", - "glob": "^5.0.15", - "handlebars": "^4.0.1", - "js-yaml": "3.x", - "mkdirp": "0.5.x", - "nopt": "3.x", - "once": "1.x", - "resolve": "1.1.x", - "supports-color": "^3.1.0", - "which": "^1.1.1", - "wordwrap": "^1.0.0" - }, - "dependencies": { - "abbrev": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.0.9.tgz", - "integrity": "sha1-kbR5JYinc4wl813W9jdSovh3YTU=" - }, - "esprima": { - "version": "2.7.3", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-2.7.3.tgz", - "integrity": "sha1-luO3DVd59q1JzQMmc9HDEnZ7pYE=" - }, - "has-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", - "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=" - }, - "nopt": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz", - "integrity": "sha1-xkZdvwirzU2zWTF/eaxopkayj/k=", - "requires": { - "abbrev": "1" - } - }, - "resolve": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz", - "integrity": "sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=" - }, - "supports-color": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", - "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", - "requires": { - "has-flag": "^1.0.0" - } - } - } - }, - "jasmine": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/jasmine/-/jasmine-3.2.0.tgz", - "integrity": "sha512-qv6TZ32r+slrQz8fbx2EhGbD9zlJo3NwPrpLK1nE8inILtZO9Fap52pyHk7mNTh4tG50a+1+tOiWVT3jO5I0Sg==", - "requires": { - "glob": "^7.0.6", - "jasmine-core": "~3.2.0" - }, - "dependencies": { - "glob": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", - "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - } - } - }, - "jasmine-core": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-3.2.1.tgz", - "integrity": "sha512-pa9tbBWgU0EE4SWgc85T4sa886ufuQdsgruQANhECYjwqgV4z7Vw/499aCaP8ZH79JDS4vhm8doDG9HO4+e4sA==" - }, - "js-tokens": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", - "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=" - }, - "js-yaml": { - "version": "3.12.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.12.0.tgz", - "integrity": "sha512-PIt2cnwmPfL4hKNwqeiuz4bKfnzHTBv6HyVgjahA6mPLwPDzjDWrplJBMjHUFxku/N3FlmrbyPclad+I+4mJ3A==", - "requires": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - } - }, - "jsbn": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", - "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", - "optional": true - }, - "json-parse-better-errors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", - "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==" - }, - "json-schema": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", - "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" - }, - "json-schema-traverse": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz", - "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=" - }, - "json-stable-stringify": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-0.0.1.tgz", - "integrity": "sha1-YRwj6BTbN1Un34URk9tZ3Sryf0U=", - "requires": { - "jsonify": "~0.0.0" - } - }, - "json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=" - }, - "json-stringify-safe": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" - }, - "jsonify": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz", - "integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=" - }, - "jsonparse": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", - "integrity": "sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=" - }, - "jsprim": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", - "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", - "requires": { - "assert-plus": "1.0.0", - "extsprintf": "1.3.0", - "json-schema": "0.2.3", - "verror": "1.10.0" - } - }, - "labeled-stream-splicer": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/labeled-stream-splicer/-/labeled-stream-splicer-2.0.1.tgz", - "integrity": "sha512-MC94mHZRvJ3LfykJlTUipBqenZz1pacOZEMhhQ8dMGcDHs0SBE5GbsavUXV7YtP3icBW17W0Zy1I0lfASmo9Pg==", - "requires": { - "inherits": "^2.0.1", - "isarray": "^2.0.4", - "stream-splicer": "^2.0.0" - }, - "dependencies": { - "isarray": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.4.tgz", - "integrity": "sha512-GMxXOiUirWg1xTKRipM0Ek07rX+ubx4nNVElTJdNLYmNO/2YrDkgJGw9CljXn+r4EWiDQg/8lsRdHyg2PJuUaA==" - } - } - }, - "latest-version": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-3.1.0.tgz", - "integrity": "sha1-ogU4P+oyKzO1rjsYq+4NwvNW7hU=", - "requires": { - "package-json": "^4.0.0" - } - }, - "levn": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", - "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", - "requires": { - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2" - } - }, - "load-json-file": { - "version": "2.0.0", - "resolved": "http://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz", - "integrity": "sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg=", - "requires": { - "graceful-fs": "^4.1.2", - "parse-json": "^2.2.0", - "pify": "^2.0.0", - "strip-bom": "^3.0.0" - }, - "dependencies": { - "pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=" - } - } - }, - "locate-path": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", - "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", - "requires": { - "p-locate": "^2.0.0", - "path-exists": "^3.0.0" - }, - "dependencies": { - "path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=" - } - } - }, - "lodash": { - "version": "4.17.11", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", - "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==" - }, - "lodash._getnative": { - "version": "3.9.1", - "resolved": "https://registry.npmjs.org/lodash._getnative/-/lodash._getnative-3.9.1.tgz", - "integrity": "sha1-VwvH3t5G1hzc3mh9ZdPuy6o6r/U=" - }, - "lodash.debounce": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-3.1.1.tgz", - "integrity": "sha1-gSIRw3ipTMKdWqTjNGzwv846ffU=", - "requires": { - "lodash._getnative": "^3.0.0" - } - }, - "lodash.memoize": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-3.0.4.tgz", - "integrity": "sha1-LcvSwofLwKVcxCMovQxzYVDVPj8=" - }, "loud-rejection": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.6.0.tgz", - "integrity": "sha1-W0b4AUft7leIcPCG0Eghz5mOVR8=", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-2.2.0.tgz", + "integrity": "sha512-S0FayMXku80toa5sZ6Ro4C+s+EtFDCsyJNG/AzFMfX3AxD5Si4dZsgzm/kKnbOxHl5Cv8jBlno8+3XYIh2pNjQ==", "requires": { "currently-unhandled": "^0.4.1", - "signal-exit": "^3.0.0" + "signal-exit": "^3.0.2" } }, - "lowercase-keys": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", - "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==" - }, - "lru-cache": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.3.tgz", - "integrity": "sha512-fFEhvcgzuIoJVUF8fYr5KR0YqxD238zgObTps31YdADwPPAp82a4M8TrckkWyx7ekNlf9aBcVn81cFwwXngrJA==", - "requires": { - "pseudomap": "^1.0.2", - "yallist": "^2.1.2" - }, - "dependencies": { - "yallist": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", - "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=" - } - } - }, - "make-dir": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz", - "integrity": "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==", - "requires": { - "pify": "^3.0.0" - } - }, - "md5.js": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", - "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", - "requires": { - "hash-base": "^3.0.0", - "inherits": "^2.0.1", - "safe-buffer": "^5.1.2" - } - }, - "media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" - }, - "merge-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" - }, - "methods": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" - }, - "miller-rabin": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz", - "integrity": "sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==", - "requires": { - "bn.js": "^4.0.0", - "brorand": "^1.0.1" - } - }, - "mime": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.4.1.tgz", - "integrity": "sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ==" - }, - "mime-db": { - "version": "1.36.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.36.0.tgz", - "integrity": "sha512-L+xvyD9MkoYMXb1jAmzI/lWYAxAMCPvIBSWur0PZ5nOf5euahRLVqH//FKW9mWp2lkqUgYiXPgkzfMUFi4zVDw==" - }, - "mime-types": { - "version": "2.1.20", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.20.tgz", - "integrity": "sha512-HrkrPaP9vGuWbLK1B1FfgAkbqNjIuy4eHlIYnFi7kamZyLLrGlo2mpcx0bBmNpKqBtYtAfGbodDddIgddSJC2A==", - "requires": { - "mime-db": "~1.36.0" - } - }, - "mimic-fn": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", - "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==" - }, - "minimalistic-assert": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", - "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==" - }, - "minimalistic-crypto-utils": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", - "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=" - }, - "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "minimist": { - "version": "1.2.0", - "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" - }, - "mkdirp": { - "version": "0.5.1", - "resolved": "http://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", - "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", - "requires": { - "minimist": "0.0.8" - }, - "dependencies": { - "minimist": { - "version": "0.0.8", - "resolved": "http://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", - "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" - } - } - }, - "module-deps": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/module-deps/-/module-deps-4.1.1.tgz", - "integrity": "sha1-IyFYM/HaE/1gbMuAh7RIUty4If0=", - "requires": { - "JSONStream": "^1.0.3", - "browser-resolve": "^1.7.0", - "cached-path-relative": "^1.0.0", - "concat-stream": "~1.5.0", - "defined": "^1.0.0", - "detective": "^4.0.0", - "duplexer2": "^0.1.2", - "inherits": "^2.0.1", - "parents": "^1.0.0", - "readable-stream": "^2.0.2", - "resolve": "^1.1.3", - "stream-combiner2": "^1.1.1", - "subarg": "^1.0.0", - "through2": "^2.0.0", - "xtend": "^4.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - }, - "mute-stream": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz", - "integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=" - }, - "natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=" - }, - "negotiator": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz", - "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=" - }, "nopt": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.1.tgz", @@ -5646,1827 +2843,31 @@ "abbrev": "1", "osenv": "^0.1.4" } - }, - "normalize-package-data": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.4.0.tgz", - "integrity": "sha512-9jjUFbTPfEy3R/ad/2oNbKtW9Hgovl5O1FvFWKkKblNXoN/Oou6+9+KKohPK13Yc3/TyunyWhJp6gvRNR/PPAw==", - "requires": { - "hosted-git-info": "^2.1.4", - "is-builtin-module": "^1.0.0", - "semver": "2 || 3 || 4 || 5", - "validate-npm-package-license": "^3.0.1" - } - }, - "npm-package-arg": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-6.1.0.tgz", - "integrity": "sha512-zYbhP2k9DbJhA0Z3HKUePUgdB1x7MfIfKssC+WLPFMKTBZKpZh5m13PgexJjCq6KW7j17r0jHWcCpxEqnnncSA==", - "requires": { - "hosted-git-info": "^2.6.0", - "osenv": "^0.1.5", - "semver": "^5.5.0", - "validate-npm-package-name": "^3.0.0" - } - }, - "npm-run-path": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", - "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", - "requires": { - "path-key": "^2.0.0" - } - }, - "number-is-nan": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", - "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" - }, - "oauth-sign": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", - "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==" - }, - "object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" - }, - "object-keys": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.0.12.tgz", - "integrity": "sha512-FTMyFUm2wBcGHnH2eXmz7tC6IwlqQZ6mVZ+6dm6vZ4IQIHjs6FdNsQBuKGPuUUUY6NfJw2PshC08Tn6LzLDOag==" - }, - "on-finished": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", - "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", - "requires": { - "ee-first": "1.1.1" - } - }, - "on-headers": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.1.tgz", - "integrity": "sha1-ko9dD0cNSTQmUepnlLCFfBAGk/c=" - }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "requires": { - "wrappy": "1" - } - }, - "onetime": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", - "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=", - "requires": { - "mimic-fn": "^1.0.0" - } - }, - "opener": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/opener/-/opener-1.5.1.tgz", - "integrity": "sha512-goYSy5c2UXE4Ra1xixabeVh1guIX/ZV/YokJksb6q2lubWu6UbvPQ20p542/sFIll1nl8JnCyK9oBaOcCWXwvA==" - }, - "opn": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/opn/-/opn-5.4.0.tgz", - "integrity": "sha512-YF9MNdVy/0qvJvDtunAOzFw9iasOQHpVthTCvGzxt61Il64AYSGdK+rYwld7NAfk9qJ7dt+hymBNSc9LNYS+Sw==", - "requires": { - "is-wsl": "^1.1.0" - } - }, - "optimist": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz", - "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=", - "requires": { - "minimist": "~0.0.1", - "wordwrap": "~0.0.2" - }, - "dependencies": { - "minimist": { - "version": "0.0.10", - "resolved": "http://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz", - "integrity": "sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8=" - }, - "wordwrap": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", - "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=" - } - } - }, - "optionator": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz", - "integrity": "sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q=", - "requires": { - "deep-is": "~0.1.3", - "fast-levenshtein": "~2.0.4", - "levn": "~0.3.0", - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2", - "wordwrap": "~1.0.0" - } - }, - "os-browserify": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.1.2.tgz", - "integrity": "sha1-ScoCk+CxlZCl9d4Qx/JlphfY/lQ=" - }, - "os-homedir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", - "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=" - }, - "os-name": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/os-name/-/os-name-1.0.3.tgz", - "integrity": "sha1-GzefZINa98Wn9JizV8uVIVwVnt8=", - "requires": { - "osx-release": "^1.0.0", - "win-release": "^1.0.0" - } - }, - "os-tmpdir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=" - }, - "osenv": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", - "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", - "requires": { - "os-homedir": "^1.0.0", - "os-tmpdir": "^1.0.0" - } - }, - "osx-release": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/osx-release/-/osx-release-1.1.0.tgz", - "integrity": "sha1-8heRGigTaUmvG/kwiyQeJzfTzWw=", - "requires": { - "minimist": "^1.1.0" - } - }, - "p-finally": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", - "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=" - }, - "p-limit": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", - "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", - "requires": { - "p-try": "^1.0.0" - } - }, - "p-locate": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", - "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", - "requires": { - "p-limit": "^1.1.0" - } - }, - "p-try": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", - "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=" - }, - "package-json": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/package-json/-/package-json-4.0.1.tgz", - "integrity": "sha1-iGmgQBJTZhxMTKPabCEh7VVfXu0=", - "requires": { - "got": "^6.7.1", - "registry-auth-token": "^3.0.1", - "registry-url": "^3.0.3", - "semver": "^5.1.0" - } - }, - "pako": { - "version": "0.2.9", - "resolved": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz", - "integrity": "sha1-8/dSL073gjSNqBYbrZ7P1Rv4OnU=" - }, - "parents": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parents/-/parents-1.0.1.tgz", - "integrity": "sha1-/t1NK/GTp3dF/nHjcdc8MwfZx1E=", - "requires": { - "path-platform": "~0.11.15" - } - }, - "parse-asn1": { - "version": "5.1.1", - "resolved": "http://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.1.tgz", - "integrity": "sha512-KPx7flKXg775zZpnp9SxJlz00gTd4BmJ2yJufSc44gMCRrRQ7NSzAcSJQfifuOLgW6bEi+ftrALtsgALeB2Adw==", - "requires": { - "asn1.js": "^4.0.0", - "browserify-aes": "^1.0.0", - "create-hash": "^1.1.0", - "evp_bytestokey": "^1.0.0", - "pbkdf2": "^3.0.3" - } - }, - "parse-json": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", - "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", - "requires": { - "error-ex": "^1.2.0" - } - }, - "parseurl": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.2.tgz", - "integrity": "sha1-/CidTtiZMRlGDBViUyYs3I3mW/M=" - }, - "path-browserify": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-0.0.1.tgz", - "integrity": "sha512-BapA40NHICOS+USX9SN4tyhq+A2RrN/Ws5F0Z5aMHDp98Fl86lX8Oti8B7uN93L4Ifv4fHOEA+pQw87gmMO/lQ==" - }, - "path-exists": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", - "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", - "requires": { - "pinkie-promise": "^2.0.0" - } - }, - "path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" - }, - "path-is-inside": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", - "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=" - }, - "path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=" - }, - "path-parse": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", - "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==" - }, - "path-platform": { - "version": "0.11.15", - "resolved": "https://registry.npmjs.org/path-platform/-/path-platform-0.11.15.tgz", - "integrity": "sha1-6GQhf3TDaFDwhSt43Hv31KVyG/I=" - }, - "path-to-regexp": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", - "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" - }, - "path-type": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-2.0.0.tgz", - "integrity": "sha1-8BLMuEFbcJb8LaoQVMPXI4lZTHM=", - "requires": { - "pify": "^2.0.0" - }, - "dependencies": { - "pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=" - } - } - }, - "pbkdf2": { - "version": "3.0.17", - "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.0.17.tgz", - "integrity": "sha512-U/il5MsrZp7mGg3mSQfn742na2T+1/vHDCG5/iTI3X9MKUuYUZVLQhyRsg06mCgDBTd57TxzgZt7P+fYfjRLtA==", - "requires": { - "create-hash": "^1.1.2", - "create-hmac": "^1.1.4", - "ripemd160": "^2.0.1", - "safe-buffer": "^5.0.1", - "sha.js": "^2.4.8" - } - }, - "pegjs": { - "version": "0.10.0", - "resolved": "http://registry.npmjs.org/pegjs/-/pegjs-0.10.0.tgz", - "integrity": "sha1-z4uvrm7d/0tafvsYUmnqr0YQ3b0=" - }, - "performance-now": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", - "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" - }, - "pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=" - }, - "pinkie": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", - "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=" - }, - "pinkie-promise": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", - "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", - "requires": { - "pinkie": "^2.0.0" - } - }, - "pkg-dir": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-1.0.0.tgz", - "integrity": "sha1-ektQio1bstYp1EcFb/TpyTFM89Q=", - "requires": { - "find-up": "^1.0.0" - } - }, - "plist": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/plist/-/plist-2.1.0.tgz", - "integrity": "sha1-V8zbeggh3yGDEhejytVOPhRqECU=", - "requires": { - "base64-js": "1.2.0", - "xmlbuilder": "8.2.2", - "xmldom": "0.1.x" - } - }, - "pluralize": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-7.0.0.tgz", - "integrity": "sha512-ARhBOdzS3e41FbkW/XWrTEtukqqLoK5+Z/4UeDaLuSW+39JPeFgs4gCGqsrJHVZX0fUrx//4OF0K1CUGwlIFow==" - }, - "prelude-ls": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", - "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=" - }, - "prepend-http": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz", - "integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=" - }, - "process": { - "version": "0.11.10", - "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", - "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=" - }, - "process-nextick-args": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", - "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==" - }, - "progress": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.0.tgz", - "integrity": "sha1-ihvjZr+Pwj2yvSPxDG/pILQ4nR8=" - }, - "promzard": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/promzard/-/promzard-0.3.0.tgz", - "integrity": "sha1-JqXW7ox97kyxIggwWs+5O6OCqe4=", - "requires": { - "read": "1" - } - }, - "properties-parser": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/properties-parser/-/properties-parser-0.3.1.tgz", - "integrity": "sha1-ExbpU5/7/ZOEXjabIRAiq9R4dxo=", - "requires": { - "string.prototype.codepointat": "^0.2.0" - } - }, - "proxy-addr": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.4.tgz", - "integrity": "sha512-5erio2h9jp5CHGwcybmxmVqHmnCBZeewlfJ0pex+UW7Qny7OOZXTtH56TGNyBizkgiOwhJtMKrVzDTeKcySZwA==", - "requires": { - "forwarded": "~0.1.2", - "ipaddr.js": "1.8.0" - } - }, - "pseudomap": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", - "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=" - }, - "psl": { - "version": "1.1.29", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.1.29.tgz", - "integrity": "sha512-AeUmQ0oLN02flVHXWh9sSJF7mcdFq0ppid/JkErufc3hGIV/AMa8Fo9VgDo/cT2jFdOWoFvHp90qqBH54W+gjQ==" - }, - "public-encrypt": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.3.tgz", - "integrity": "sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q==", - "requires": { - "bn.js": "^4.1.0", - "browserify-rsa": "^4.0.0", - "create-hash": "^1.1.0", - "parse-asn1": "^5.0.0", - "randombytes": "^2.0.1", - "safe-buffer": "^5.1.2" - } - }, - "punycode": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" - }, - "q": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", - "integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=" - }, - "qs": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz", - "integrity": "sha512-eRzhrN1WSINYCDCbrz796z37LOe3m5tmW7RQf6oBntukAG1nmovJvhnwHHRMAfeoItc1m2Hk02WER2aQ/iqs+A==" - }, - "querystring": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", - "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=" - }, - "querystring-es3": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/querystring-es3/-/querystring-es3-0.2.1.tgz", - "integrity": "sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM=" - }, - "randombytes": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.0.6.tgz", - "integrity": "sha512-CIQ5OFxf4Jou6uOKe9t1AOgqpeU5fd70A8NPdHSGeYXqXsPe6peOwI0cUl88RWZ6sP1vPMV3avd/R6cZ5/sP1A==", - "requires": { - "safe-buffer": "^5.1.0" - } - }, - "randomfill": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/randomfill/-/randomfill-1.0.4.tgz", - "integrity": "sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==", - "requires": { - "randombytes": "^2.0.5", - "safe-buffer": "^5.1.0" - } - }, - "range-parser": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", - "integrity": "sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4=" - }, - "raw-body": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.2.tgz", - "integrity": "sha1-vNYMd9Prk83gBQKVw/N5OJvIj4k=", - "requires": { - "bytes": "3.0.0", - "http-errors": "1.6.2", - "iconv-lite": "0.4.19", - "unpipe": "1.0.0" - }, - "dependencies": { - "depd": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.1.tgz", - "integrity": "sha1-V4O04cRZ8G+lyif5kfPQbnoxA1k=" - }, - "http-errors": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.2.tgz", - "integrity": "sha1-CgAsyFcHGSp+eUbO7cERVfYOxzY=", - "requires": { - "depd": "1.1.1", - "inherits": "2.0.3", - "setprototypeof": "1.0.3", - "statuses": ">= 1.3.1 < 2" - } - }, - "setprototypeof": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.0.3.tgz", - "integrity": "sha1-ZlZ+NwQ+608E2RvWWMDL77VbjgQ=" - } - } - }, - "rc": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", - "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", - "requires": { - "deep-extend": "^0.6.0", - "ini": "~1.3.0", - "minimist": "^1.2.0", - "strip-json-comments": "~2.0.1" - } - }, - "read": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/read/-/read-1.0.7.tgz", - "integrity": "sha1-s9oZvQUkMal2cdRKQmNK33ELQMQ=", - "requires": { - "mute-stream": "~0.0.4" - } - }, - "read-chunk": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/read-chunk/-/read-chunk-2.1.0.tgz", - "integrity": "sha1-agTAkoAF7Z1C4aasVgDhnLx/9lU=", - "requires": { - "pify": "^3.0.0", - "safe-buffer": "^5.1.1" - } - }, - "read-only-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/read-only-stream/-/read-only-stream-2.0.0.tgz", - "integrity": "sha1-JyT9aoET1zdkrCiNQ4YnDB2/F/A=", - "requires": { - "readable-stream": "^2.0.2" - } - }, - "read-package-json": { - "version": "2.0.13", - "resolved": "https://registry.npmjs.org/read-package-json/-/read-package-json-2.0.13.tgz", - "integrity": "sha512-/1dZ7TRZvGrYqE0UAfN6qQb5GYBsNcqS1C0tNK601CFOJmtHI7NIGXwetEPU/OtoFHZL3hDxm4rolFFVE9Bnmg==", - "requires": { - "glob": "^7.1.1", - "graceful-fs": "^4.1.2", - "json-parse-better-errors": "^1.0.1", - "normalize-package-data": "^2.0.0", - "slash": "^1.0.0" - }, - "dependencies": { - "glob": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", - "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - } - } - }, - "read-pkg": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz", - "integrity": "sha1-jvHAYjxqbbDcZxPEv6xGMysjaPg=", - "requires": { - "load-json-file": "^2.0.0", - "normalize-package-data": "^2.3.2", - "path-type": "^2.0.0" - } - }, - "read-pkg-up": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-2.0.0.tgz", - "integrity": "sha1-a3KoBImE4MQeeVEP1en6mbO1Sb4=", - "requires": { - "find-up": "^2.0.0", - "read-pkg": "^2.0.0" - }, - "dependencies": { - "find-up": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", - "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", - "requires": { - "locate-path": "^2.0.0" - } - } - } - }, - "readable-stream": { - "version": "2.3.6", - "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - }, - "dependencies": { - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "requires": { - "safe-buffer": "~5.1.0" - } - } - } - }, - "readline2": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/readline2/-/readline2-1.0.1.tgz", - "integrity": "sha1-QQWWCP/BVHV7cV2ZidGZ/783LjU=", - "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "mute-stream": "0.0.5" - }, - "dependencies": { - "is-fullwidth-code-point": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", - "requires": { - "number-is-nan": "^1.0.0" - } - }, - "mute-stream": { - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.5.tgz", - "integrity": "sha1-j7+rsKmKJT0xhDMfno3rc3L6xsA=" - } - } - }, - "rechoir": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", - "integrity": "sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q=", - "requires": { - "resolve": "^1.1.6" - } - }, - "regexpp": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-1.1.0.tgz", - "integrity": "sha512-LOPw8FpgdQF9etWMaAfG/WRthIdXJGYp4mJ2Jgn/2lpkbod9jPn0t9UqN7AxBOKNfzRbYyVfgc7Vk4t/MpnXgw==" - }, - "registry-auth-token": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-3.3.2.tgz", - "integrity": "sha512-JL39c60XlzCVgNrO+qq68FoNb56w/m7JYvGR2jT5iR1xBrUA3Mfx5Twk5rqTThPmQKMWydGmq8oFtDlxfrmxnQ==", - "requires": { - "rc": "^1.1.6", - "safe-buffer": "^5.0.1" - } - }, - "registry-url": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-3.1.0.tgz", - "integrity": "sha1-PU74cPc93h138M+aOBQyRE4XSUI=", - "requires": { - "rc": "^1.0.1" - } - }, - "request": { - "version": "2.88.0", - "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz", - "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==", - "requires": { - "aws-sign2": "~0.7.0", - "aws4": "^1.8.0", - "caseless": "~0.12.0", - "combined-stream": "~1.0.6", - "extend": "~3.0.2", - "forever-agent": "~0.6.1", - "form-data": "~2.3.2", - "har-validator": "~5.1.0", - "http-signature": "~1.2.0", - "is-typedarray": "~1.0.0", - "isstream": "~0.1.2", - "json-stringify-safe": "~5.0.1", - "mime-types": "~2.1.19", - "oauth-sign": "~0.9.0", - "performance-now": "^2.1.0", - "qs": "~6.5.2", - "safe-buffer": "^5.1.2", - "tough-cookie": "~2.4.3", - "tunnel-agent": "^0.6.0", - "uuid": "^3.3.2" - }, - "dependencies": { - "qs": { - "version": "6.5.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", - "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" - } - } - }, - "require-uncached": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/require-uncached/-/require-uncached-1.0.3.tgz", - "integrity": "sha1-Tg1W1slmL9MeQwEcS5WqSZVUIdM=", - "requires": { - "caller-path": "^0.1.0", - "resolve-from": "^1.0.0" - } - }, - "resolve": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.8.1.tgz", - "integrity": "sha512-AicPrAC7Qu1JxPCZ9ZgCZlY35QgFnNqc+0LtbRNxnVw4TXvjQ72wnuL9JQcEBgXkI9JM8MsT9kaQoHcpCRJOYA==", - "requires": { - "path-parse": "^1.0.5" - } - }, - "resolve-from": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-1.0.1.tgz", - "integrity": "sha1-Jsv+k10a7uq7Kbw/5a6wHpPUQiY=" - }, - "restore-cursor": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", - "integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=", - "requires": { - "onetime": "^2.0.0", - "signal-exit": "^3.0.2" - } - }, - "rewire": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/rewire/-/rewire-4.0.1.tgz", - "integrity": "sha512-+7RQ/BYwTieHVXetpKhT11UbfF6v1kGhKFrtZN7UDL2PybMsSt/rpLWeEUGF5Ndsl1D5BxiCB14VDJyoX+noYw==", - "requires": { - "eslint": "^4.19.1" - } - }, - "rimraf": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz", - "integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==", - "requires": { - "glob": "^7.0.5" - }, - "dependencies": { - "glob": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", - "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - } - } - }, - "ripemd160": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz", - "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==", - "requires": { - "hash-base": "^3.0.0", - "inherits": "^2.0.1" - } - }, - "run-async": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.3.0.tgz", - "integrity": "sha1-A3GrSuC91yDUFm19/aZP96RFpsA=", - "requires": { - "is-promise": "^2.1.0" - } - }, - "rx-lite": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/rx-lite/-/rx-lite-4.0.8.tgz", - "integrity": "sha1-Cx4Rr4vESDbwSmQH6S2kJGe3lEQ=" - }, - "rx-lite-aggregates": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/rx-lite-aggregates/-/rx-lite-aggregates-4.0.8.tgz", - "integrity": "sha1-dTuHqJoRyVRnxKwWJsTvxOBcZ74=", - "requires": { - "rx-lite": "*" - } - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - }, - "safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" - }, - "sax": { - "version": "0.3.5", - "resolved": "http://registry.npmjs.org/sax/-/sax-0.3.5.tgz", - "integrity": "sha1-iPz8H3PAyLvVt8d2ttPzUB7tBz0=" - }, - "semver": { - "version": "5.5.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.1.tgz", - "integrity": "sha512-PqpAxfrEhlSUWge8dwIp4tZnQ25DIOthpiaHNIthsjEFQD6EvqUKUDM7L8O2rShkFccYo1VjJR0coWfNkCubRw==" - }, - "semver-diff": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-2.1.0.tgz", - "integrity": "sha1-S7uEN8jTfksM8aaP1ybsbWRdbTY=", - "requires": { - "semver": "^5.0.3" - } - }, - "send": { - "version": "0.16.2", - "resolved": "https://registry.npmjs.org/send/-/send-0.16.2.tgz", - "integrity": "sha512-E64YFPUssFHEFBvpbbjr44NCLtI1AohxQ8ZSiJjQLskAdKuriYEP6VyGEsRDH8ScozGpkaX1BGvhanqCwkcEZw==", - "requires": { - "debug": "2.6.9", - "depd": "~1.1.2", - "destroy": "~1.0.4", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "~1.6.2", - "mime": "1.4.1", - "ms": "2.0.0", - "on-finished": "~2.3.0", - "range-parser": "~1.2.0", - "statuses": "~1.4.0" - } - }, - "serve-static": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.13.2.tgz", - "integrity": "sha512-p/tdJrO4U387R9oMjb1oj7qSMaMfmOyd4j9hOFoxZe2baQszgHcSWjuya/CiT5kgZZKRudHNOA0pYXOl8rQ5nw==", - "requires": { - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "parseurl": "~1.3.2", - "send": "0.16.2" - } - }, - "setprototypeof": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", - "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==" - }, - "sha.js": { - "version": "2.4.11", - "resolved": "http://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", - "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", - "requires": { - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" - } - }, - "shasum": { - "version": "1.0.2", - "resolved": "http://registry.npmjs.org/shasum/-/shasum-1.0.2.tgz", - "integrity": "sha1-5wEjENj0F/TetXEhUOVni4euVl8=", - "requires": { - "json-stable-stringify": "~0.0.0", - "sha.js": "~2.4.4" - } - }, - "shebang-command": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", - "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", - "requires": { - "shebang-regex": "^1.0.0" - } - }, - "shebang-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=" - }, - "shell-quote": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.6.1.tgz", - "integrity": "sha1-9HgZSczkAmlxJ0MOo7PFR29IF2c=", - "requires": { - "array-filter": "~0.0.0", - "array-map": "~0.0.0", - "array-reduce": "~0.0.0", - "jsonify": "~0.0.0" - } - }, - "shelljs": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.5.3.tgz", - "integrity": "sha1-xUmCuZbHbvDB5rWfvcWCX1txMRM=" - }, - "signal-exit": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", - "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=" - }, - "simple-concat": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.0.tgz", - "integrity": "sha1-c0TLuLbib7J9ZrL8hvn21Zl1IcY=" - }, - "simple-plist": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/simple-plist/-/simple-plist-0.2.1.tgz", - "integrity": "sha1-cXZts1IyaSjPOoByQrp2IyJjZyM=", - "requires": { - "bplist-creator": "0.0.7", - "bplist-parser": "0.1.1", - "plist": "2.0.1" - }, - "dependencies": { - "base64-js": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.1.2.tgz", - "integrity": "sha1-1kAMrBxMZgl22Q0HoENR2JOV9eg=" - }, - "plist": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/plist/-/plist-2.0.1.tgz", - "integrity": "sha1-CjLKlIGxw2TpLhjcVch23p0B2os=", - "requires": { - "base64-js": "1.1.2", - "xmlbuilder": "8.2.2", - "xmldom": "0.1.x" - } - } - } - }, - "slash": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz", - "integrity": "sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU=" - }, - "slice-ansi": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-1.0.0.tgz", - "integrity": "sha512-POqxBK6Lb3q6s047D/XsDVNPnF9Dl8JSaqe9h9lURl0OdNqy/ujDrOiIHtsqXMGbWWTIomRzAMaTyawAU//Reg==", - "requires": { - "is-fullwidth-code-point": "^2.0.0" - } - }, - "slide": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/slide/-/slide-1.1.6.tgz", - "integrity": "sha1-VusCfWW00tzmyy4tMsTUr8nh1wc=" - }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" - }, - "spdx-correct": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.0.0.tgz", - "integrity": "sha512-N19o9z5cEyc8yQQPukRCZ9EUmb4HUpnrmaL/fxS2pBo2jbfcFRVuFZ/oFC+vZz0MNNk0h80iMn5/S6qGZOL5+g==", - "requires": { - "spdx-expression-parse": "^3.0.0", - "spdx-license-ids": "^3.0.0" - } - }, - "spdx-exceptions": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.1.0.tgz", - "integrity": "sha512-4K1NsmrlCU1JJgUrtgEeTVyfx8VaYea9J9LvARxhbHtVtohPs/gFGG5yy49beySjlIMhhXZ4QqujIZEfS4l6Cg==" - }, - "spdx-expression-parse": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz", - "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==", - "requires": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" - } - }, - "spdx-license-ids": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.1.tgz", - "integrity": "sha512-TfOfPcYGBB5sDuPn3deByxPhmfegAhpDYKSOXZQN81Oyrrif8ZCodOLzK3AesELnCx03kikhyDwh0pfvvQvF8w==" - }, - "sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=" - }, - "sshpk": { - "version": "1.14.2", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.14.2.tgz", - "integrity": "sha1-xvxhZIo9nE52T9P8306hBeSSupg=", - "requires": { - "asn1": "~0.2.3", - "assert-plus": "^1.0.0", - "bcrypt-pbkdf": "^1.0.0", - "dashdash": "^1.12.0", - "ecc-jsbn": "~0.1.1", - "getpass": "^0.1.1", - "jsbn": "~0.1.0", - "safer-buffer": "^2.0.2", - "tweetnacl": "~0.14.0" - } - }, - "statuses": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", - "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==" - }, - "stream-browserify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.1.tgz", - "integrity": "sha1-ZiZu5fm9uZQKTkUUyvtDu3Hlyds=", - "requires": { - "inherits": "~2.0.1", - "readable-stream": "^2.0.2" - } - }, - "stream-buffers": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/stream-buffers/-/stream-buffers-2.2.0.tgz", - "integrity": "sha1-kdX1Ew0c75bc+n9yaUUYh0HQnuQ=" - }, - "stream-combiner2": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/stream-combiner2/-/stream-combiner2-1.1.1.tgz", - "integrity": "sha1-+02KFCDqNidk4hrUeAOXvry0HL4=", - "requires": { - "duplexer2": "~0.1.0", - "readable-stream": "^2.0.2" - } - }, - "stream-http": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/stream-http/-/stream-http-2.8.3.tgz", - "integrity": "sha512-+TSkfINHDo4J+ZobQLWiMouQYB+UVYFttRA94FpEzzJ7ZdqcL4uUUQ7WkdkI4DSozGmgBUE/a47L+38PenXhUw==", - "requires": { - "builtin-status-codes": "^3.0.0", - "inherits": "^2.0.1", - "readable-stream": "^2.3.6", - "to-arraybuffer": "^1.0.0", - "xtend": "^4.0.0" - } - }, - "stream-splicer": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/stream-splicer/-/stream-splicer-2.0.0.tgz", - "integrity": "sha1-G2O+Q4oTPktnHMGTUZdgAXWRDYM=", - "requires": { - "inherits": "^2.0.1", - "readable-stream": "^2.0.2" - } - }, - "string-width": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", - "requires": { - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^4.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=" - }, - "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", - "requires": { - "ansi-regex": "^3.0.0" - } - } - } - }, - "string.prototype.codepointat": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/string.prototype.codepointat/-/string.prototype.codepointat-0.2.1.tgz", - "integrity": "sha512-2cBVCj6I4IOvEnjgO/hWqXjqBGsY+zwPmHl12Srk9IXSZ56Jwwmy+66XO5Iut/oQVR7t5ihYdLB0GMa4alEUcg==" - }, - "string_decoder": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", - "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", - "requires": { - "safe-buffer": "~5.1.0" - } - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "requires": { - "ansi-regex": "^2.0.0" - } - }, - "strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=" - }, - "strip-eof": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", - "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=" - }, - "strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=" - }, - "subarg": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/subarg/-/subarg-1.0.0.tgz", - "integrity": "sha1-9izxdYHplrSPyWVpn1TAauJouNI=", - "requires": { - "minimist": "^1.1.0" - } - }, - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=" - }, - "syntax-error": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/syntax-error/-/syntax-error-1.4.0.tgz", - "integrity": "sha512-YPPlu67mdnHGTup2A8ff7BC2Pjq0e0Yp/IyTFN03zWO0RcK07uLcbi7C2KpGR2FvWbaB0+bfE27a+sBKebSo7w==", - "requires": { - "acorn-node": "^1.2.0" - } - }, - "table": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/table/-/table-4.0.2.tgz", - "integrity": "sha512-UUkEAPdSGxtRpiV9ozJ5cMTtYiqz7Ni1OGqLXRCynrvzdtR1p+cfOWe2RJLwvUG8hNanaSRjecIqwOjqeatDsA==", - "requires": { - "ajv": "^5.2.3", - "ajv-keywords": "^2.1.0", - "chalk": "^2.1.0", - "lodash": "^4.17.4", - "slice-ansi": "1.0.0", - "string-width": "^2.1.1" - }, - "dependencies": { - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "requires": { - "color-convert": "^1.9.0" - } - }, - "chalk": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", - "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "requires": { - "has-flag": "^3.0.0" - } - } - } - }, - "tar": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/tar/-/tar-2.2.1.tgz", - "integrity": "sha1-jk0qJWwOIYXGsYrWlK7JaLg8sdE=", - "requires": { - "block-stream": "*", - "fstream": "^1.0.2", - "inherits": "2" - } - }, - "term-size": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/term-size/-/term-size-1.2.0.tgz", - "integrity": "sha1-RYuDiH8oj8Vtb/+/rSYuJmOO+mk=", - "requires": { - "execa": "^0.7.0" - } - }, - "text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=" - }, - "through": { - "version": "2.3.8", - "resolved": "http://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=" - }, - "through2": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.3.tgz", - "integrity": "sha1-AARWmzfHx0ujnEPzzteNGtlBQL4=", - "requires": { - "readable-stream": "^2.1.5", - "xtend": "~4.0.1" - } - }, - "timed-out": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/timed-out/-/timed-out-4.0.1.tgz", - "integrity": "sha1-8y6srFoXW+ol1/q1Zas+2HQe9W8=" - }, - "timers-browserify": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-1.4.2.tgz", - "integrity": "sha1-ycWLV1voQHN1y14kYtrO50NZ9B0=", - "requires": { - "process": "~0.11.0" - } - }, - "tmp": { - "version": "0.0.33", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", - "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", - "requires": { - "os-tmpdir": "~1.0.2" - } - }, - "to-arraybuffer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz", - "integrity": "sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M=" - }, - "tough-cookie": { - "version": "2.4.3", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", - "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", - "requires": { - "psl": "^1.1.24", - "punycode": "^1.4.1" - } - }, - "tty-browserify": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.1.tgz", - "integrity": "sha512-C3TaO7K81YvjCgQH9Q1S3R3P3BtN3RIM8n+OvX4il1K1zgE8ZhI0op7kClgkxtutIE8hQrcrHBXvIheqKUUCxw==" - }, - "tunnel-agent": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", - "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", - "requires": { - "safe-buffer": "^5.0.1" - } - }, - "tweetnacl": { - "version": "0.14.5", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", - "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", - "optional": true - }, - "type-check": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", - "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", - "requires": { - "prelude-ls": "~1.1.2" - } - }, - "type-is": { - "version": "1.6.16", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.16.tgz", - "integrity": "sha512-HRkVv/5qY2G6I8iab9cI7v1bOIdhm94dVjQCPFElW9W+3GeDOSHmy2EBYe4VTApuzolPcmgFTN3ftVJRKR2J9Q==", - "requires": { - "media-typer": "0.3.0", - "mime-types": "~2.1.18" - } - }, - "typedarray": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", - "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=" - }, - "uglify-js": { - "version": "3.4.9", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.4.9.tgz", - "integrity": "sha512-8CJsbKOtEbnJsTyv6LE6m6ZKniqMiFWmm9sRbopbkGs3gMPPfd3Fh8iIA4Ykv5MgaTbqHr4BaoGLJLZNhsrW1Q==", - "optional": true, - "requires": { - "commander": "~2.17.1", - "source-map": "~0.6.1" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "optional": true - } - } - }, - "umd": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/umd/-/umd-3.0.3.tgz", - "integrity": "sha512-4IcGSufhFshvLNcMCV80UnQVlZ5pMOC8mvNPForqwA4+lzYQuetTESLDQkeLmihq8bRcnpbQa48Wb8Lh16/xow==" - }, - "undeclared-identifiers": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/undeclared-identifiers/-/undeclared-identifiers-1.1.2.tgz", - "integrity": "sha512-13EaeocO4edF/3JKime9rD7oB6QI8llAGhgn5fKOPyfkJbRb6NFv9pYV6dFEmpa4uRjKeBqLZP8GpuzqHlKDMQ==", - "requires": { - "acorn-node": "^1.3.0", - "get-assigned-identifiers": "^1.2.0", - "simple-concat": "^1.0.0", - "xtend": "^4.0.1" - } - }, - "underscore": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.9.1.tgz", - "integrity": "sha512-5/4etnCkd9c8gwgowi5/om/mYO5ajCaOgdzj/oW+0eQV9WxKBDZw5+ycmKmeaTXjInS/W0BzpGLo2xR2aBwZdg==" - }, - "unique-string": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-1.0.0.tgz", - "integrity": "sha1-nhBXzKhRq7kzmPizOuGHuZyuwRo=", - "requires": { - "crypto-random-string": "^1.0.0" - } - }, - "unorm": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/unorm/-/unorm-1.4.1.tgz", - "integrity": "sha1-NkIA1fE2RsqLzURJAnEzVhR5IwA=" - }, - "unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" - }, - "unzip-response": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/unzip-response/-/unzip-response-2.0.1.tgz", - "integrity": "sha1-0vD3N9FrBhXnKmk17QQhRXLVb5c=" - }, - "update-notifier": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-2.5.0.tgz", - "integrity": "sha512-gwMdhgJHGuj/+wHJJs9e6PcCszpxR1b236igrOkUofGhqJuG+amlIKwApH1IW1WWl7ovZxsX49lMBWLxSdm5Dw==", - "requires": { - "boxen": "^1.2.1", - "chalk": "^2.0.1", - "configstore": "^3.0.0", - "import-lazy": "^2.1.0", - "is-ci": "^1.0.10", - "is-installed-globally": "^0.1.0", - "is-npm": "^1.0.0", - "latest-version": "^3.0.0", - "semver-diff": "^2.0.0", - "xdg-basedir": "^3.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "requires": { - "color-convert": "^1.9.0" - } - }, - "chalk": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", - "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "requires": { - "has-flag": "^3.0.0" - } - } - } - }, - "url": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/url/-/url-0.11.0.tgz", - "integrity": "sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE=", - "requires": { - "punycode": "1.3.2", - "querystring": "0.2.0" - }, - "dependencies": { - "punycode": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", - "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=" - } - } - }, - "url-parse-lax": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-1.0.0.tgz", - "integrity": "sha1-evjzA2Rem9eaJy56FKxovAYJ2nM=", - "requires": { - "prepend-http": "^1.0.1" - } - }, - "util": { - "version": "0.10.4", - "resolved": "https://registry.npmjs.org/util/-/util-0.10.4.tgz", - "integrity": "sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A==", - "requires": { - "inherits": "2.0.3" - } - }, - "util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" - }, - "utils-merge": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" - }, - "uuid": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", - "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==" - }, - "valid-identifier": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/valid-identifier/-/valid-identifier-0.0.1.tgz", - "integrity": "sha1-7x1wk6nTKH4/zpLfkW+GFrI/kLQ=" - }, - "validate-npm-package-license": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", - "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", - "requires": { - "spdx-correct": "^3.0.0", - "spdx-expression-parse": "^3.0.0" - } - }, - "validate-npm-package-name": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-3.0.0.tgz", - "integrity": "sha1-X6kS2B630MdK/BQN5zF/DKffQ34=", - "requires": { - "builtins": "^1.0.3" - } - }, - "vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" - }, - "verror": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", - "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", - "requires": { - "assert-plus": "^1.0.0", - "core-util-is": "1.0.2", - "extsprintf": "^1.2.0" - } - }, - "vm-browserify": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-0.0.4.tgz", - "integrity": "sha1-XX6kW7755Kb/ZflUOOCofDV9WnM=", - "requires": { - "indexof": "0.0.1" - } - }, - "which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "requires": { - "isexe": "^2.0.0" - } - }, - "widest-line": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-2.0.0.tgz", - "integrity": "sha1-AUKk6KJD+IgsAjOqDgKBqnYVInM=", - "requires": { - "string-width": "^2.1.1" - } - }, - "win-release": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/win-release/-/win-release-1.1.1.tgz", - "integrity": "sha1-X6VeAr58qTTt/BJmVjLoSbcuUgk=", - "requires": { - "semver": "^5.0.1" - } - }, - "wordwrap": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", - "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=" - }, - "wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" - }, - "write": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/write/-/write-0.2.1.tgz", - "integrity": "sha1-X8A4KOJkzqP+kUVUdvejxWbLB1c=", - "requires": { - "mkdirp": "^0.5.1" - } - }, - "write-file-atomic": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.3.0.tgz", - "integrity": "sha512-xuPeK4OdjWqtfi59ylvVL0Yn35SF3zgcAcv7rBPFHVaEapaDr4GdGgm3j7ckTwH9wHL7fGmgfAnb0+THrHb8tA==", - "requires": { - "graceful-fs": "^4.1.11", - "imurmurhash": "^0.1.4", - "signal-exit": "^3.0.2" - } - }, - "xcode": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/xcode/-/xcode-1.0.0.tgz", - "integrity": "sha1-4fWxRDJF3tOMGAeW3xoQ/e2ghOw=", - "requires": { - "pegjs": "^0.10.0", - "simple-plist": "^0.2.1", - "uuid": "3.0.1" - }, - "dependencies": { - "uuid": { - "version": "3.0.1", - "resolved": "http://registry.npmjs.org/uuid/-/uuid-3.0.1.tgz", - "integrity": "sha1-ZUS7ot/ajBzxfmKaOjBeK7H+5sE=" - } - } - }, - "xdg-basedir": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-3.0.0.tgz", - "integrity": "sha1-SWsswQnsqNus/i3HK2A8F8WHCtQ=" - }, - "xmlbuilder": { - "version": "8.2.2", - "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-8.2.2.tgz", - "integrity": "sha1-aSSGc0ELS6QuGmE2VR0pIjNap3M=" - }, - "xmldom": { - "version": "0.1.27", - "resolved": "https://registry.npmjs.org/xmldom/-/xmldom-0.1.27.tgz", - "integrity": "sha1-1QH5ezvbQDr4757MIFcxh6rawOk=" - }, - "xtend": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", - "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=" } } }, "cordova-android": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/cordova-android/-/cordova-android-7.1.2.tgz", - "integrity": "sha512-w28HJGtfAZCT96hVH9BMppWMnmDTZplKu2NRQZN2dCr5e9r7aHpay41MYy9IBkh8+7E7lMo/jZkRwBDNr4VnEg==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/cordova-android/-/cordova-android-8.0.0.tgz", + "integrity": "sha512-Ipv8HbVJpxEyYFSFLTEOaLRp0yxBtJVNbgSuDEB4naa34FzQaRWSNiiMcPJnO+x3hRXNt7pcwa46hARNzhn7+w==", "requires": { - "abbrev": "*", - "android-versions": "1.3.0", - "ansi": "*", - "balanced-match": "*", - "base64-js": "1.2.0", - "big-integer": "*", - "bplist-parser": "*", - "brace-expansion": "*", - "concat-map": "*", - "cordova-common": "2.2.5", - "cordova-registry-mapper": "*", - "elementtree": "0.1.6", - "glob": "5.0.15", - "inflight": "*", - "inherits": "*", - "minimatch": "*", - "nopt": "3.0.1", - "once": "*", - "path-is-absolute": "*", - "plist": "2.1.0", - "properties-parser": "0.2.3", - "q": "1.4.1", - "sax": "0.3.5", - "semver": "*", - "shelljs": "0.5.3", - "underscore": "*", - "unorm": "*", - "wrappy": "*", - "xmlbuilder": "8.2.2", - "xmldom": "*" + "android-versions": "^1.3.0", + "cordova-common": "^3.1.0", + "elementtree": "^0.1.7", + "nopt": "^4.0.1", + "properties-parser": "^0.3.1", + "q": "^1.4.1", + "shelljs": "^0.5.3" }, "dependencies": { - "abbrev": { - "version": "1.1.1", - "bundled": true - }, - "android-versions": { - "version": "1.3.0", - "bundled": true, - "requires": { - "semver": "^5.4.1" - } - }, - "ansi": { - "version": "0.3.1", - "bundled": true - }, - "balanced-match": { - "version": "1.0.0", - "bundled": true - }, - "base64-js": { - "version": "1.2.0", - "bundled": true - }, - "big-integer": { - "version": "1.6.32", - "bundled": true - }, - "bplist-parser": { - "version": "0.1.1", - "bundled": true, - "requires": { - "big-integer": "^1.6.7" - } - }, - "brace-expansion": { - "version": "1.1.11", - "bundled": true, - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "concat-map": { - "version": "0.0.1", - "bundled": true - }, - "cordova-common": { - "version": "2.2.5", - "bundled": true, - "requires": { - "ansi": "^0.3.1", - "bplist-parser": "^0.1.0", - "cordova-registry-mapper": "^1.1.8", - "elementtree": "0.1.6", - "glob": "^5.0.13", - "minimatch": "^3.0.0", - "plist": "^2.1.0", - "q": "^1.4.1", - "shelljs": "^0.5.3", - "underscore": "^1.8.3", - "unorm": "^1.3.3" - } - }, - "cordova-registry-mapper": { - "version": "1.1.15", - "bundled": true - }, - "elementtree": { - "version": "0.1.6", - "bundled": true, - "requires": { - "sax": "0.3.5" - } - }, - "glob": { - "version": "5.0.15", - "bundled": true, - "requires": { - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "2 || 3", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "inflight": { - "version": "1.0.6", - "bundled": true, - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.3", - "bundled": true - }, - "minimatch": { - "version": "3.0.4", - "bundled": true, - "requires": { - "brace-expansion": "^1.1.7" - } - }, "nopt": { - "version": "3.0.1", - "bundled": true, + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.1.tgz", + "integrity": "sha1-0NRoWv1UFRk8jHUFYC0NF81kR00=", "requires": { - "abbrev": "1" + "abbrev": "1", + "osenv": "^0.1.4" } - }, - "once": { - "version": "1.4.0", - "bundled": true, - "requires": { - "wrappy": "1" - } - }, - "path-is-absolute": { - "version": "1.0.1", - "bundled": true - }, - "plist": { - "version": "2.1.0", - "bundled": true, - "requires": { - "base64-js": "1.2.0", - "xmlbuilder": "8.2.2", - "xmldom": "0.1.x" - } - }, - "properties-parser": { - "version": "0.2.3", - "bundled": true - }, - "q": { - "version": "1.4.1", - "bundled": true - }, - "sax": { - "version": "0.3.5", - "bundled": true - }, - "semver": { - "version": "5.5.0", - "bundled": true - }, - "shelljs": { - "version": "0.5.3", - "bundled": true - }, - "underscore": { - "version": "1.9.1", - "bundled": true - }, - "unorm": { - "version": "1.4.1", - "bundled": true - }, - "wrappy": { - "version": "1.0.2", - "bundled": true - }, - "xmlbuilder": { - "version": "8.2.2", - "bundled": true - }, - "xmldom": { - "version": "0.1.27", - "bundled": true } } }, @@ -7486,302 +2887,222 @@ } } }, + "cordova-app-hello-world": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cordova-app-hello-world/-/cordova-app-hello-world-4.0.0.tgz", + "integrity": "sha512-hTNYHUJT5YyMa1cQQE1naGyU6Eh5D5Jl33sMnCh3+q15ZwWTL/TOy3k8+mUvjTp8bwhO5eECGKULYoVO+fp9ZA==" + }, "cordova-clipboard": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/cordova-clipboard/-/cordova-clipboard-1.3.0.tgz", "integrity": "sha512-IGk4LZm/DJ0Xk/jgakHm4wa+A/lrRP3QfzMAHDG7oWLJS4ISOpfI32Wez4ndnENItRslGyBVyJyKD83CxELCAw==" }, - "cordova-ios": { - "version": "4.5.5", - "resolved": "https://registry.npmjs.org/cordova-ios/-/cordova-ios-4.5.5.tgz", - "integrity": "sha512-3+30m2dZ2yii7kg+H7cgpdpkXpMj54zoX5imjGGG4Z7dPXKmalTLc/9rLq+Iaa+Q1BqyOrUFaHopWOODRU6vCg==", + "cordova-common": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/cordova-common/-/cordova-common-3.2.0.tgz", + "integrity": "sha512-EvlQ6PirfR65hGDoQvsluW00uSS2MTVIRKQ3c1Xvsddx7D5T5JgF3fHWkGik/Y/8yNcpI0zI2NcJyie2z/ak2A==", "requires": { - "abbrev": "*", - "ansi": "*", - "balanced-match": "*", - "base64-js": "1.2.0", - "big-integer": "*", - "bplist-creator": "*", - "bplist-parser": "*", - "brace-expansion": "*", - "concat-map": "*", - "cordova-common": "2.2.5", - "cordova-registry-mapper": "*", - "elementtree": "0.1.6", - "glob": "5.0.15", - "inflight": "*", - "inherits": "*", - "ios-sim": "6.1.3", - "minimatch": "*", - "nopt": "3.0.6", - "once": "*", - "path-is-absolute": "*", - "plist": "2.1.0", - "q": "1.5.1", - "sax": "0.3.5", - "shelljs": "0.5.3", - "simctl": "*", - "simple-plist": "0.2.1", - "stream-buffers": "2.2.0", - "tail": "0.4.0", - "underscore": "*", - "unorm": "*", - "uuid": "3.0.1", - "wrappy": "*", - "xcode": "0.9.3", - "xml-escape": "1.1.0", - "xmlbuilder": "8.2.2", - "xmldom": "*" + "ansi": "^0.3.1", + "bplist-parser": "^0.1.0", + "cross-spawn": "^6.0.5", + "elementtree": "0.1.7", + "endent": "^1.1.1", + "fs-extra": "^8.0.0", + "glob": "^7.1.2", + "minimatch": "^3.0.0", + "plist": "^3.0.1", + "q": "^1.4.1", + "strip-bom": "^3.0.0", + "underscore": "^1.8.3", + "which": "^1.3.0" }, "dependencies": { - "abbrev": { - "version": "1.1.1", - "bundled": true - }, - "ansi": { - "version": "0.3.1", - "bundled": true - }, - "balanced-match": { - "version": "1.0.0", - "bundled": true - }, - "base64-js": { - "version": "1.2.0", - "bundled": true - }, - "big-integer": { - "version": "1.6.32", - "bundled": true - }, - "bplist-creator": { - "version": "0.0.7", - "bundled": true, + "cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", "requires": { - "stream-buffers": "~2.2.0" + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" } }, - "bplist-parser": { - "version": "0.1.1", - "bundled": true, + "fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", "requires": { - "big-integer": "^1.6.7" + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" } }, - "brace-expansion": { - "version": "1.1.11", - "bundled": true, + "graceful-fs": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.2.tgz", + "integrity": "sha512-IItsdsea19BoLC7ELy13q1iJFNmd7ofZH5+X/pJr90/nRoPEX0DJo1dHDbgtYWOhJhcCgMDTOw84RZ72q6lB+Q==" + }, + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=" + } + } + }, + "cordova-create": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/cordova-create/-/cordova-create-2.0.0.tgz", + "integrity": "sha512-72CaGg/7x+tiZlzeXKQXLTc8Jh4tbwLdu4Ib97kJ6+R3bcew/Yv/l2cVA2E0CaCuOCtouTqwi+YLcA2I4dPFTQ==", + "requires": { + "cordova-app-hello-world": "^4.0.0", + "cordova-common": "^3.1.0", + "cordova-fetch": "^2.0.0", + "fs-extra": "^7.0.1", + "import-fresh": "^3.0.0", + "is-url": "^1.2.4", + "isobject": "^3.0.1", + "path-is-inside": "^1.0.2", + "tmp": "0.0.33", + "valid-identifier": "0.0.2" + }, + "dependencies": { + "fs-extra": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", + "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "graceful-fs": "^4.1.2", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" } }, - "concat-map": { - "version": "0.0.1", - "bundled": true - }, - "cordova-common": { - "version": "2.2.5", - "bundled": true, - "requires": { - "ansi": "^0.3.1", - "bplist-parser": "^0.1.0", - "cordova-registry-mapper": "^1.1.8", - "elementtree": "0.1.6", - "glob": "^5.0.13", - "minimatch": "^3.0.0", - "plist": "^2.1.0", - "q": "^1.4.1", - "shelljs": "^0.5.3", - "underscore": "^1.8.3", - "unorm": "^1.3.3" - } - }, - "cordova-registry-mapper": { - "version": "1.1.15", - "bundled": true - }, - "elementtree": { - "version": "0.1.6", - "bundled": true, - "requires": { - "sax": "0.3.5" - } - }, - "glob": { - "version": "5.0.15", - "bundled": true, - "requires": { - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "2 || 3", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "inflight": { - "version": "1.0.6", - "bundled": true, - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.3", - "bundled": true - }, - "ios-sim": { - "version": "6.1.3", - "bundled": true, - "requires": { - "bplist-parser": "^0.0.6", - "nopt": "1.0.9", - "plist": "^2.1.0", - "simctl": "^1.1.1" - }, - "dependencies": { - "bplist-parser": { - "version": "0.0.6", - "bundled": true - }, - "nopt": { - "version": "1.0.9", - "bundled": true, - "requires": { - "abbrev": "1" - } - } - } - }, - "minimatch": { - "version": "3.0.4", - "bundled": true, - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "nopt": { - "version": "3.0.6", - "bundled": true, - "requires": { - "abbrev": "1" - } - }, - "once": { - "version": "1.4.0", - "bundled": true, - "requires": { - "wrappy": "1" - } - }, - "path-is-absolute": { - "version": "1.0.1", - "bundled": true - }, - "plist": { - "version": "2.1.0", - "bundled": true, - "requires": { - "base64-js": "1.2.0", - "xmlbuilder": "8.2.2", - "xmldom": "0.1.x" - } - }, - "q": { - "version": "1.5.1", - "bundled": true - }, - "sax": { - "version": "0.3.5", - "bundled": true - }, - "shelljs": { - "version": "0.5.3", - "bundled": true - }, - "simctl": { - "version": "1.1.1", - "bundled": true, - "requires": { - "shelljs": "^0.2.6", - "tail": "^0.4.0" - }, - "dependencies": { - "shelljs": { - "version": "0.2.6", - "bundled": true - } - } - }, - "simple-plist": { - "version": "0.2.1", - "bundled": true, - "requires": { - "bplist-creator": "0.0.7", - "bplist-parser": "0.1.1", - "plist": "2.0.1" - }, - "dependencies": { - "base64-js": { - "version": "1.1.2", - "bundled": true - }, - "plist": { - "version": "2.0.1", - "bundled": true, - "requires": { - "base64-js": "1.1.2", - "xmlbuilder": "8.2.2", - "xmldom": "0.1.x" - } - } - } - }, - "stream-buffers": { - "version": "2.2.0", - "bundled": true - }, - "tail": { - "version": "0.4.0", - "bundled": true - }, - "underscore": { - "version": "1.9.1", - "bundled": true - }, - "unorm": { - "version": "1.4.1", - "bundled": true - }, - "uuid": { + "isobject": { "version": "3.0.1", - "bundled": true - }, - "wrappy": { - "version": "1.0.2", - "bundled": true - }, - "xcode": { - "version": "0.9.3", - "bundled": true, + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=" + } + } + }, + "cordova-fetch": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/cordova-fetch/-/cordova-fetch-2.0.1.tgz", + "integrity": "sha512-q21PeobERzE3Drli5htcl5X9Mtfvodih5VkqIwdRUsjDBCPv+I6ZonRjYGbNnXhYrYx7dm0m0j/7/Smf6Av3hg==", + "requires": { + "cordova-common": "^3.1.0", + "fs-extra": "^7.0.1", + "npm-package-arg": "^6.1.0", + "pify": "^4.0.1", + "resolve": "^1.10.0", + "semver": "^5.6.0", + "which": "^1.3.1" + }, + "dependencies": { + "fs-extra": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", + "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", "requires": { - "pegjs": "^0.10.0", - "simple-plist": "^0.2.1", - "uuid": "3.0.1" + "graceful-fs": "^4.1.2", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" } }, - "xml-escape": { - "version": "1.1.0", - "bundled": true + "path-parse": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", + "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==" }, - "xmlbuilder": { - "version": "8.2.2", - "bundled": true + "pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==" }, - "xmldom": { - "version": "0.1.27", - "bundled": true + "resolve": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.12.0.tgz", + "integrity": "sha512-B/dOmuoAik5bKcD6s6nXDCjzUKnaDvdkRyAk6rsmsKLipWj4797iothd7jmmUhWTfinVMU+wc56rYKsit2Qy4w==", + "requires": { + "path-parse": "^1.0.6" + } + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" + } + } + }, + "cordova-ios": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/cordova-ios/-/cordova-ios-5.0.1.tgz", + "integrity": "sha512-JcFyDmlyzY2OQJo0eHpuFERFqvO4daHl8HL96RhUVjJVtuoqXHsOF0xTuQSAqIbefelMPEWwY3Lc/dvT4ttTwQ==", + "requires": { + "cordova-common": "^3.1.0", + "ios-sim": "^8.0.1", + "nopt": "^4.0.1", + "plist": "^3.0.1", + "q": "^1.5.1", + "shelljs": "^0.5.3", + "unorm": "^1.4.1", + "xcode": "^2.0.0", + "xml-escape": "^1.1.0" + }, + "dependencies": { + "nopt": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.1.tgz", + "integrity": "sha1-0NRoWv1UFRk8jHUFYC0NF81kR00=", + "requires": { + "abbrev": "1", + "osenv": "^0.1.4" + } + } + } + }, + "cordova-lib": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/cordova-lib/-/cordova-lib-9.0.1.tgz", + "integrity": "sha512-P9nQhq91gLOyKZkamvKNzzK89gLDpq8rKue/Vu7NUSgNzhPkiWW0w+6VRTbj/9QGVM9w2uDVhB9c9f6rrTXzCw==", + "requires": { + "cordova-common": "^3.1.0", + "cordova-create": "^2.0.0", + "cordova-fetch": "^2.0.0", + "cordova-serve": "^3.0.0", + "dep-graph": "1.1.0", + "detect-indent": "^5.0.0", + "elementtree": "^0.1.7", + "fs-extra": "^7.0.1", + "globby": "^9.1.0", + "indent-string": "^3.2.0", + "init-package-json": "^1.10.3", + "md5-file": "^4.0.0", + "read-chunk": "^3.1.0", + "semver": "^5.6.0", + "shebang-command": "^1.2.0", + "underscore": "^1.9.1" + }, + "dependencies": { + "fs-extra": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", + "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", + "requires": { + "graceful-fs": "^4.1.2", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + } + }, + "indent-string": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-3.2.0.tgz", + "integrity": "sha1-Sl/W0nzDMvN+VBmlBNu4NxBckok=" + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" } } }, @@ -7811,15 +3132,20 @@ "integrity": "sha512-m7cughw327CjONN/qjzsTpSesLaeybksQh420/gRuSXJX5Zt9NfgsSbqqKDon6jnQ9Mm7h7imgyO2uJ34XMBtA==" }, "cordova-plugin-file-opener2": { - "version": "2.0.19", - "resolved": "https://registry.npmjs.org/cordova-plugin-file-opener2/-/cordova-plugin-file-opener2-2.0.19.tgz", - "integrity": "sha1-yjrhIlOVt3qx/lsgrMv+zGiOJJM=" + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/cordova-plugin-file-opener2/-/cordova-plugin-file-opener2-2.2.1.tgz", + "integrity": "sha512-yeN242U6T+TDlrJ5m00br+lAKsf2fHXn1u1TsDxB5fFUGINZUYLKthEctCMFkQUnURWk+Nh6tc+WtdQjY581Uw==" }, "cordova-plugin-file-transfer": { "version": "1.7.1", "resolved": "https://registry.npmjs.org/cordova-plugin-file-transfer/-/cordova-plugin-file-transfer-1.7.1.tgz", "integrity": "sha1-p12L4uvDu5sjxbG70ZkhTsJnWGs=" }, + "cordova-plugin-geolocation": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/cordova-plugin-geolocation/-/cordova-plugin-geolocation-4.0.2.tgz", + "integrity": "sha512-QGThnPKzPxESHkruZlpE0+5aFBVOet8al0vIJ7laSUOQHIC1dd/JY6peVIbtLboKi5Dap1wCKRubOqPqH8xcQA==" + }, "cordova-plugin-globalization": { "version": "1.11.0", "resolved": "https://registry.npmjs.org/cordova-plugin-globalization/-/cordova-plugin-globalization-1.11.0.tgz", @@ -7836,7 +3162,7 @@ "integrity": "sha512-6ucQ6FdlLdBm8kJfFnzozmBTjru/0xekHP/dAhjoCZggkGRlgs8TsUJFkxa/bV+qi7Dlo50JjmpE4UMWAO+aOQ==" }, "cordova-plugin-local-notification": { - "version": "git+https://github.com/moodlemobile/cordova-plugin-local-notification.git#5b2f3073a1c1fb39cad3566be792445c343db2c6", + "version": "git+https://github.com/moodlemobile/cordova-plugin-local-notification.git#c7430c4f4f8b8c82d051aea49d87da0b4f6a8b1e", "from": "git+https://github.com/moodlemobile/cordova-plugin-local-notification.git#moodle" }, "cordova-plugin-media-capture": { @@ -7874,18 +3200,30 @@ "resolved": "https://registry.npmjs.org/cordova-plugin-zip/-/cordova-plugin-zip-3.1.0.tgz", "integrity": "sha1-F2yCSOog058c+VnvXmFWrMqWshc=" }, - "cordova-sqlite-storage": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/cordova-sqlite-storage/-/cordova-sqlite-storage-2.6.0.tgz", - "integrity": "sha512-m+KylFwNRsQloJ6TZihupfma2muXkmNglh8cFSiJQqriTTm6eq0gyPaAuKxgoPswe679G+0aUai07NFC/f0GGQ==", + "cordova-serve": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cordova-serve/-/cordova-serve-3.0.0.tgz", + "integrity": "sha512-h479g/5a0PXn//yiFuMrD5MDEbB+mtihNkWcE6uD/aCh/6z0FRZ9sWH3NfZbHDB+Bp1yGLYsjbH8LZBL8KOQ0w==", "requires": { - "cordova-sqlite-storage-dependencies": "1.2.1" + "chalk": "^2.4.1", + "compression": "^1.6.0", + "express": "^4.13.3", + "opn": "^5.3.0", + "which": "^1.3.0" + } + }, + "cordova-sqlite-storage": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/cordova-sqlite-storage/-/cordova-sqlite-storage-3.4.0.tgz", + "integrity": "sha512-Uavq3HulVIYXxTFCp5aafiQhYrZF0/cGlyN76RYhIftcD5IRhza9+ghhV5abJYvuGlzY+p9dM5hPcjnYxfAH+g==", + "requires": { + "cordova-sqlite-storage-dependencies": "2.1.0" } }, "cordova-sqlite-storage-dependencies": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/cordova-sqlite-storage-dependencies/-/cordova-sqlite-storage-dependencies-1.2.1.tgz", - "integrity": "sha512-4ihQApBGVKR1QZ4oOSGctKFfthtCfiWMTcIIfxe97vKxlvGr9NyXOvYG9vXU9S7yVR7Ua+Rj47hkE7pQIKvQTg==" + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cordova-sqlite-storage-dependencies/-/cordova-sqlite-storage-dependencies-2.1.0.tgz", + "integrity": "sha512-m0cPOWPzckAqS0/e7v+xtcM+FrHrw63qgh5T91JdkXMKCK8sN9bDoqVNJHZ5E9y7sRO7liMUIDm6Dz439RYqGA==" }, "cordova-support-google-services": { "version": "1.2.1", @@ -7912,6 +3250,14 @@ "elliptic": "^6.0.0" } }, + "create-error-class": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/create-error-class/-/create-error-class-3.0.2.tgz", + "integrity": "sha1-Br56vvlHo/FKMP1hBnHUAbyot7Y=", + "requires": { + "capture-stack-trace": "^1.0.0" + } + }, "create-hash": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", @@ -7943,7 +3289,6 @@ "version": "5.1.0", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", - "dev": true, "requires": { "lru-cache": "^4.0.1", "shebang-command": "^1.2.0", @@ -7969,11 +3314,15 @@ "randomfill": "^1.0.3" } }, + "crypto-random-string": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-1.0.0.tgz", + "integrity": "sha1-ojD2T1aDEOFJgAmUB5DsmVRbyn4=" + }, "currently-unhandled": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz", "integrity": "sha1-mI3zP+qxke95mmE2nddsF635V+o=", - "dev": true, "requires": { "array-find-index": "^1.0.1" } @@ -7991,7 +3340,6 @@ "version": "1.14.1", "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", - "dev": true, "requires": { "assert-plus": "^1.0.0" }, @@ -7999,8 +3347,7 @@ "assert-plus": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", - "dev": true + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" } } }, @@ -8020,7 +3367,6 @@ "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, "requires": { "ms": "2.0.0" } @@ -8034,8 +3380,22 @@ "decode-uri-component": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", - "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=", - "dev": true + "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=" + }, + "dedent": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz", + "integrity": "sha1-JJXduvbrh0q7Dhvp3yLS5aVEMmw=" + }, + "deep-equal": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.0.1.tgz", + "integrity": "sha1-9dJgKStmDghO/0zbyfCK0yR0SLU=" + }, + "deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==" }, "default-compare": { "version": "1.0.0", @@ -8073,7 +3433,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", - "dev": true, "requires": { "is-descriptor": "^1.0.2", "isobject": "^3.0.1" @@ -8083,7 +3442,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, "requires": { "kind-of": "^6.0.0" } @@ -8092,7 +3450,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, "requires": { "kind-of": "^6.0.0" } @@ -8101,7 +3458,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, "requires": { "is-accessor-descriptor": "^1.0.0", "is-data-descriptor": "^1.0.0", @@ -8111,22 +3467,24 @@ "isobject": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=" }, "kind-of": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", - "dev": true + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==" } } }, + "defined": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/defined/-/defined-1.0.0.tgz", + "integrity": "sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM=" + }, "delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", - "dev": true + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" }, "delegates": { "version": "1.0.0", @@ -8134,11 +3492,25 @@ "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=", "dev": true }, + "dep-graph": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/dep-graph/-/dep-graph-1.1.0.tgz", + "integrity": "sha1-+t6GqSeZqBPptCURzfPfpsyNvv4=", + "requires": { + "underscore": "1.2.1" + }, + "dependencies": { + "underscore": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.2.1.tgz", + "integrity": "sha1-/FxrB2VnPZKi1KyLTcCqiHAuK9Q=" + } + } + }, "depd": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", - "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", - "dev": true + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" }, "des.js": { "version": "1.0.0", @@ -8153,8 +3525,7 @@ "destroy": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", - "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=", - "dev": true + "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" }, "detect-file": { "version": "1.0.0", @@ -8162,6 +3533,11 @@ "integrity": "sha1-8NZtA2cqglyxtzvbP+YjEMjlUrc=", "dev": true }, + "detect-indent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-5.0.0.tgz", + "integrity": "sha1-OHHMCmoALow+Wzz38zYmRnXwa50=" + }, "detect-libc": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", @@ -8185,6 +3561,29 @@ "randombytes": "^2.0.0" } }, + "dir-glob": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-2.2.2.tgz", + "integrity": "sha512-f9LBi5QWzIW3I6e//uxZoLBlUt9kcp66qo0sSCxL6YZKc75R1c4MFCoe/LaZiBGmgujvQdxc5Bn3QhfyvK5Hsw==", + "requires": { + "path-type": "^3.0.0" + }, + "dependencies": { + "path-type": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", + "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", + "requires": { + "pify": "^3.0.0" + } + }, + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=" + } + } + }, "doctrine": { "version": "0.7.2", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-0.7.2.tgz", @@ -8215,6 +3614,14 @@ "integrity": "sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA==", "dev": true }, + "dot-prop": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-4.2.0.tgz", + "integrity": "sha512-tUMXrxlExSW6U2EXiiKGSBVdYgtV8qlHL+C10TsW4PURY/ic+eaysnSkwB4kA/mBlCyy/IKDJ+Lc3wbWeaXtuQ==", + "requires": { + "is-obj": "^1.0.0" + } + }, "dotenv": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-6.2.0.tgz", @@ -8280,6 +3687,11 @@ } } }, + "duplexer3": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz", + "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=" + }, "duplexify": { "version": "3.7.1", "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz", @@ -8306,17 +3718,20 @@ "version": "0.1.1", "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz", "integrity": "sha1-D8c6ntXw1Tw4GTOYUj735UN3dQU=", - "dev": true, "optional": true, "requires": { "jsbn": "~0.1.0" } }, + "editor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/editor/-/editor-1.0.0.tgz", + "integrity": "sha1-YMf4e9YrzGqJT6jM1q+3gjok90I=" + }, "ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=", - "dev": true + "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" }, "ejs": { "version": "2.6.1", @@ -8534,6 +3949,21 @@ "integrity": "sha512-De+lPAxEcpxvqPTyZAXELNpRZXABRxf+uL/rSykstQhzj/B0l1150G/ExIIxKc16lI89Hgz81J0BHAcbTqK49g==", "dev": true }, + "elementtree": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/elementtree/-/elementtree-0.1.7.tgz", + "integrity": "sha1-mskb5uUvtuYkTE5UpKw+2K6OKcA=", + "requires": { + "sax": "1.1.4" + }, + "dependencies": { + "sax": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.1.4.tgz", + "integrity": "sha1-dLbTPJrh4AFRDxeakRaFiPGu2qk=" + } + } + }, "elliptic": { "version": "6.4.1", "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.4.1.tgz", @@ -8558,18 +3988,26 @@ "encodeurl": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=", - "dev": true + "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" }, "end-of-stream": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz", "integrity": "sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==", - "dev": true, "requires": { "once": "^1.4.0" } }, + "endent": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/endent/-/endent-1.3.0.tgz", + "integrity": "sha512-C8AryqPPwtydqcpO5AF6k9Bd1EpFkQtvsefJqS3y3n8TG13Jy63MascDxTOULZYqrUde+dK6BjNc6LIMr3iI2A==", + "requires": { + "dedent": "^0.7.0", + "fast-json-parse": "^1.0.3", + "objectorarray": "^1.0.3" + } + }, "enhanced-resolve": { "version": "3.4.1", "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-3.4.1.tgz", @@ -8585,8 +4023,7 @@ "env-paths": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-1.0.0.tgz", - "integrity": "sha1-QWgTO0K7BcOKNbGuQ5fIKYqzaeA=", - "dev": true + "integrity": "sha1-QWgTO0K7BcOKNbGuQ5fIKYqzaeA=" }, "errno": { "version": "0.1.7", @@ -8722,14 +4159,12 @@ "escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=", - "dev": true + "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" }, "escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", - "dev": true + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" }, "escope": { "version": "3.6.0", @@ -8779,8 +4214,7 @@ "etag": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=", - "dev": true + "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" }, "event-emitter": { "version": "0.3.5", @@ -8812,7 +4246,6 @@ "version": "0.7.0", "resolved": "https://registry.npmjs.org/execa/-/execa-0.7.0.tgz", "integrity": "sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c=", - "dev": true, "requires": { "cross-spawn": "^5.0.1", "get-stream": "^3.0.0", @@ -8852,7 +4285,6 @@ "version": "4.16.4", "resolved": "https://registry.npmjs.org/express/-/express-4.16.4.tgz", "integrity": "sha512-j12Uuyb4FMrd/qQAm6uCHAkPtO8FDTRJZBDd5D2KOL2eLaz1yUNdUB/NOIyq0iU4q4cFarsUCrnFDPBcnksuOg==", - "dev": true, "requires": { "accepts": "~1.3.5", "array-flatten": "1.1.1", @@ -8889,8 +4321,7 @@ "path-to-regexp": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", - "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=", - "dev": true + "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" } } }, @@ -8904,7 +4335,6 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", - "dev": true, "requires": { "assign-symbols": "^1.0.0", "is-extendable": "^1.0.1" @@ -8914,13 +4344,32 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dev": true, "requires": { "is-plain-object": "^2.0.4" } } } }, + "external-editor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", + "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", + "requires": { + "chardet": "^0.7.0", + "iconv-lite": "^0.4.24", + "tmp": "^0.0.33" + }, + "dependencies": { + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + } + } + }, "extglob": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/extglob/-/extglob-0.3.2.tgz", @@ -8932,8 +4381,7 @@ "extsprintf": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", - "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", - "dev": true + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" }, "fancy-log": { "version": "1.3.2", @@ -8947,16 +4395,314 @@ } }, "fast-deep-equal": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz", - "integrity": "sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ=", - "dev": true + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", + "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=" + }, + "fast-glob": { + "version": "2.2.7", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-2.2.7.tgz", + "integrity": "sha512-g1KuQwHOZAmOZMuBtHdxDtju+T2RT8jgCC9aANsbpdiDDTSnjgfuVsIBNKbUeJI3oKMRExcfNDtJl4OhbffMsw==", + "requires": { + "@mrmlnc/readdir-enhanced": "^2.2.1", + "@nodelib/fs.stat": "^1.1.2", + "glob-parent": "^3.1.0", + "is-glob": "^4.0.0", + "merge2": "^1.2.3", + "micromatch": "^3.1.10" + }, + "dependencies": { + "arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=" + }, + "array-unique": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", + "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=" + }, + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "requires": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "expand-brackets": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", + "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", + "requires": { + "debug": "^2.3.3", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "posix-character-classes": "^0.1.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "^0.1.0" + } + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "requires": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + } + }, + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==" + } + } + }, + "extglob": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", + "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", + "requires": { + "array-unique": "^0.3.2", + "define-property": "^1.0.0", + "expand-brackets": "^2.1.4", + "extend-shallow": "^2.0.1", + "fragment-cache": "^0.2.1", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "requires": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "glob-parent": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", + "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", + "requires": { + "is-glob": "^3.1.0", + "path-dirname": "^1.0.0" + }, + "dependencies": { + "is-glob": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", + "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", + "requires": { + "is-extglob": "^2.1.0" + } + } + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=" + }, + "is-glob": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", + "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=" + }, + "kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==" + }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + } + } + } + }, + "fast-json-parse": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/fast-json-parse/-/fast-json-parse-1.0.3.tgz", + "integrity": "sha512-FRWsaZRWEJ1ESVNbDWmsAlqDk96gPQezzLghafp5J4GUKjbCz3OkAHuZs5TuPEtkbVQERysLp9xv6c24fBm8Aw==" }, "fast-json-stable-stringify": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", - "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=", - "dev": true + "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=" }, "faye-websocket": { "version": "0.10.0", @@ -8967,6 +4713,14 @@ "websocket-driver": ">=0.5.1" } }, + "figures": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", + "integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=", + "requires": { + "escape-string-regexp": "^1.0.5" + } + }, "filename-regex": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/filename-regex/-/filename-regex-2.0.1.tgz", @@ -8988,7 +4742,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.1.tgz", "integrity": "sha512-Y1GUDo39ez4aHAw7MysnUD5JzYX+WaIj8I57kO3aEPT1fFRL4sr7mjei97FgnwhAyyzRYmQZaTHb2+9uZ1dPtg==", - "dev": true, "requires": { "debug": "2.6.9", "encodeurl": "~1.0.2", @@ -9346,6 +5099,14 @@ "resolved": "https://registry.npmjs.org/font-awesome/-/font-awesome-4.7.0.tgz", "integrity": "sha1-j6jPBBGhoxr9B7BtKQK7n8gVoTM=" }, + "for-each": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", + "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "requires": { + "is-callable": "^1.1.3" + } + }, "for-in": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", @@ -9367,14 +5128,12 @@ "forever-agent": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", - "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", - "dev": true + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" }, "form-data": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", - "dev": true, "requires": { "asynckit": "^0.4.0", "combined-stream": "^1.0.6", @@ -9384,14 +5143,12 @@ "forwarded": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", - "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=", - "dev": true + "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=" }, "fragment-cache": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", - "dev": true, "requires": { "map-cache": "^0.2.2" } @@ -9399,8 +5156,7 @@ "fresh": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=", - "dev": true + "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" }, "fs-extra": { "version": "4.0.3", @@ -9449,8 +5205,7 @@ "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", - "dev": true + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" }, "fsevents": { "version": "1.2.9", @@ -9977,20 +5732,17 @@ "get-stream": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", - "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=", - "dev": true + "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=" }, "get-value": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", - "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=", - "dev": true + "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=" }, "getpass": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", - "dev": true, "requires": { "assert-plus": "^1.0.0" }, @@ -9998,8 +5750,7 @@ "assert-plus": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", - "dev": true + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" } } }, @@ -10007,7 +5758,6 @@ "version": "7.1.2", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", - "dev": true, "requires": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -10079,6 +5829,11 @@ } } }, + "glob-to-regexp": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.3.0.tgz", + "integrity": "sha1-jFoUlNIGbFcMw7/kSWF1rMTVAqs=" + }, "glob-watcher": { "version": "5.0.3", "resolved": "https://registry.npmjs.org/glob-watcher/-/glob-watcher-5.0.3.tgz", @@ -10996,6 +6751,14 @@ } } }, + "global-dirs": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-0.1.1.tgz", + "integrity": "sha1-sxnA3UYH81PzvpzKTHL8FIxJ9EU=", + "requires": { + "ini": "^1.3.4" + } + }, "global-modules": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-1.0.0.tgz", @@ -11020,6 +6783,46 @@ "which": "^1.2.14" } }, + "globby": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-9.2.0.tgz", + "integrity": "sha512-ollPHROa5mcxDEkwg6bPt3QbEf4pDQSNtd6JPL1YvOvAo/7/0VAm9TccUeoTmarjPw4pfUthSCqcyfNB1I3ZSg==", + "requires": { + "@types/glob": "^7.1.1", + "array-union": "^1.0.2", + "dir-glob": "^2.2.2", + "fast-glob": "^2.2.6", + "glob": "^7.1.3", + "ignore": "^4.0.3", + "pify": "^4.0.1", + "slash": "^2.0.0" + }, + "dependencies": { + "glob": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", + "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==" + }, + "slash": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", + "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==" + } + } + }, "globule": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/globule/-/globule-1.2.1.tgz", @@ -11040,6 +6843,24 @@ "sparkles": "^1.0.0" } }, + "got": { + "version": "6.7.1", + "resolved": "https://registry.npmjs.org/got/-/got-6.7.1.tgz", + "integrity": "sha1-JAzQV4WpoY5WHcG0S0HHY+8ejbA=", + "requires": { + "create-error-class": "^3.0.0", + "duplexer3": "^0.1.4", + "get-stream": "^3.0.0", + "is-redirect": "^1.0.0", + "is-retry-allowed": "^1.0.0", + "is-stream": "^1.0.0", + "lowercase-keys": "^1.0.0", + "safe-buffer": "^5.0.1", + "timed-out": "^4.0.0", + "unzip-response": "^2.0.1", + "url-parse-lax": "^1.0.0" + } + }, "graceful-fs": { "version": "4.1.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", @@ -11279,14 +7100,12 @@ "har-schema": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", - "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=", - "dev": true + "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=" }, "har-validator": { "version": "5.1.3", "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", - "dev": true, "requires": { "ajv": "^6.5.5", "har-schema": "^2.0.0" @@ -11296,7 +7115,6 @@ "version": "6.9.1", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.9.1.tgz", "integrity": "sha512-XDN92U311aINL77ieWHmqCcNlwjoP5cHXDxIxbf2MaPYuCXOHS7gHH8jktxeK5omgd52XbSTX6a4Piwd1pQmzA==", - "dev": true, "requires": { "fast-deep-equal": "^2.0.1", "fast-json-stable-stringify": "^2.0.0", @@ -11307,14 +7125,12 @@ "fast-deep-equal": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", - "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=", - "dev": true + "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=" }, "json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" } } }, @@ -11338,8 +7154,7 @@ "has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" }, "has-gulplog": { "version": "0.1.0", @@ -11366,7 +7181,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", - "dev": true, "requires": { "get-value": "^2.0.6", "has-values": "^1.0.0", @@ -11376,8 +7190,7 @@ "isobject": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=" } } }, @@ -11385,7 +7198,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", - "dev": true, "requires": { "is-number": "^3.0.0", "kind-of": "^4.0.0" @@ -11395,7 +7207,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "dev": true, "requires": { "kind-of": "^3.0.2" }, @@ -11404,7 +7215,6 @@ "version": "3.2.2", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, "requires": { "is-buffer": "^1.1.5" } @@ -11415,7 +7225,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", - "dev": true, "requires": { "is-buffer": "^1.1.5" } @@ -11465,14 +7274,12 @@ "hosted-git-info": { "version": "2.6.0", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.6.0.tgz", - "integrity": "sha512-lIbgIIQA3lz5XaB6vxakj6sDHADJiZadYEJB+FgA+C4nubM1NwcuvUr9EJPmnH1skZqpqUzWborWo8EIUi0Sdw==", - "dev": true + "integrity": "sha512-lIbgIIQA3lz5XaB6vxakj6sDHADJiZadYEJB+FgA+C4nubM1NwcuvUr9EJPmnH1skZqpqUzWborWo8EIUi0Sdw==" }, "http-errors": { "version": "1.6.3", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", - "dev": true, "requires": { "depd": "~1.1.2", "inherits": "2.0.3", @@ -11490,7 +7297,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", - "dev": true, "requires": { "assert-plus": "^1.0.0", "jsprim": "^1.2.2", @@ -11507,7 +7313,6 @@ "version": "0.4.23", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz", "integrity": "sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==", - "dev": true, "requires": { "safer-buffer": ">= 2.1.2 < 3" } @@ -11518,11 +7323,35 @@ "integrity": "sha512-GguP+DRY+pJ3soyIiGPTvdiVXjZ+DbXOxGpXn3eMvNW4x4irjqXm4wHKscC+TfxSJ0yw/S1F24tqdMNsMZTiLA==", "dev": true }, + "ignore": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", + "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==" + }, "immediate": { "version": "3.0.6", "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", "integrity": "sha1-nbHb0Pr43m++D13V5Wu2BigN5ps=" }, + "import-fresh": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.1.0.tgz", + "integrity": "sha512-PpuksHKGt8rXfWEr9m9EHIpgyyaltBy8+eF6GJM0QCAxMgxCfucMF3mjecK2QsJr0amJW7gTqh5/wht0z2UhEQ==", + "requires": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + } + }, + "import-lazy": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-2.1.0.tgz", + "integrity": "sha1-BWmOPUXIjo1+nZLLBYTnfwlvPkM=" + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=" + }, "in-publish": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/in-publish/-/in-publish-2.0.0.tgz", @@ -11548,7 +7377,6 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "dev": true, "requires": { "once": "^1.3.0", "wrappy": "1" @@ -11562,8 +7390,168 @@ "ini": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", - "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", - "dev": true + "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==" + }, + "init-package-json": { + "version": "1.10.3", + "resolved": "https://registry.npmjs.org/init-package-json/-/init-package-json-1.10.3.tgz", + "integrity": "sha512-zKSiXKhQveNteyhcj1CoOP8tqp1QuxPIPBl8Bid99DGLFqA1p87M6lNgfjJHSBoWJJlidGOv5rWjyYKEB3g2Jw==", + "requires": { + "glob": "^7.1.1", + "npm-package-arg": "^4.0.0 || ^5.0.0 || ^6.0.0", + "promzard": "^0.3.0", + "read": "~1.0.1", + "read-package-json": "1 || 2", + "semver": "2.x || 3.x || 4 || 5", + "validate-npm-package-license": "^3.0.1", + "validate-npm-package-name": "^3.0.0" + } + }, + "inquirer": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-6.5.2.tgz", + "integrity": "sha512-cntlB5ghuB0iuO65Ovoi8ogLHiWGs/5yNrtUcKjFhSSiVeAIVpD7koaSU9RM8mpXw5YDi9RdYXGQMaOURB7ycQ==", + "requires": { + "ansi-escapes": "^3.2.0", + "chalk": "^2.4.2", + "cli-cursor": "^2.1.0", + "cli-width": "^2.0.0", + "external-editor": "^3.0.3", + "figures": "^2.0.0", + "lodash": "^4.17.12", + "mute-stream": "0.0.7", + "run-async": "^2.2.0", + "rxjs": "^6.4.0", + "string-width": "^2.1.0", + "strip-ansi": "^5.1.0", + "through": "^2.3.6" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=" + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=" + }, + "mute-stream": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz", + "integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=" + }, + "rxjs": { + "version": "6.5.3", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.3.tgz", + "integrity": "sha512-wuYsAYYFdWTAnAaPoKGNhfpWwKZbJW+HgAJ+mImp+Epl7BG8oNWBCTyRM8gba9k4lk8BgWdoYm21Mo/RYhhbgA==", + "requires": { + "tslib": "^1.9.0" + } + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + }, + "dependencies": { + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "requires": { + "ansi-regex": "^3.0.0" + } + } + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "requires": { + "ansi-regex": "^4.1.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==" + } + } + } + } + }, + "insight": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/insight/-/insight-0.10.3.tgz", + "integrity": "sha512-YOncxSN6Omh+1Oqxt+OJAvJVMDKw7l6IEG0wT2cTMGxjsTcroOGW4IR926QDzxg/uZHcFZ2cZbckDWdZhc2pZw==", + "requires": { + "async": "^2.6.2", + "chalk": "^2.4.2", + "conf": "^1.4.0", + "inquirer": "^6.3.1", + "lodash.debounce": "^4.0.8", + "os-name": "^3.1.0", + "request": "^2.88.0", + "tough-cookie": "^3.0.1", + "uuid": "^3.3.2" + }, + "dependencies": { + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "macos-release": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/macos-release/-/macos-release-2.3.0.tgz", + "integrity": "sha512-OHhSbtcviqMPt7yfw5ef5aghS2jzFVKEFyCJndQt2YpSQ9qRVSEv2axSJI1paVThEu+FFGs584h/1YhxjVqajA==" + }, + "os-name": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/os-name/-/os-name-3.1.0.tgz", + "integrity": "sha512-h8L+8aNjNcMpo/mAIBPn5PXCM16iyPGjHNWo6U1YO8sJTMHtEtyczI6QJnLoplswm6goopQkqc7OAnjhWcugVg==", + "requires": { + "macos-release": "^2.2.0", + "windows-release": "^3.1.0" + } + }, + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" + }, + "tough-cookie": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-3.0.1.tgz", + "integrity": "sha512-yQyJ0u4pZsv9D4clxO69OEjLWYw+jbgspjTue4lTQZLfV0c5l1VmK2y1JK8E9ahdpltPOaAThPcp5nKPUgSnsg==", + "requires": { + "ip-regex": "^2.1.0", + "psl": "^1.1.28", + "punycode": "^2.1.1" + } + } + } }, "install": { "version": "0.8.9", @@ -11592,11 +7580,41 @@ "resolved": "https://registry.npmjs.org/ionicons/-/ionicons-3.0.0.tgz", "integrity": "sha1-QLja9P16MRUL0AIWD2ZJbiKpjDw=" }, + "ios-sim": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/ios-sim/-/ios-sim-8.0.2.tgz", + "integrity": "sha512-P7nEG771bfd+JoMRjnis1gpZOkjTUUxu+4Ek1Z+eoaEEoT9byllU9pxfQ8Df7hL3gSkIQxNwTSLhos2I8tWUQA==", + "requires": { + "bplist-parser": "^0.0.6", + "nopt": "1.0.9", + "plist": "^3.0.1", + "simctl": "^2" + }, + "dependencies": { + "bplist-parser": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/bplist-parser/-/bplist-parser-0.0.6.tgz", + "integrity": "sha1-ONo0cYF9+dRKs4kuJ3B7u9daEbk=" + }, + "nopt": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.9.tgz", + "integrity": "sha1-O8DXy6e/sNWmdtvtfA6+SKT9RU4=", + "requires": { + "abbrev": "1" + } + } + } + }, + "ip-regex": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-2.1.0.tgz", + "integrity": "sha1-+ni/XS5pE8kRzp+BnuUUa7bYROk=" + }, "ipaddr.js": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.8.0.tgz", - "integrity": "sha1-6qM9bd16zo9/b+DJygRA5wZzix4=", - "dev": true + "integrity": "sha1-6qM9bd16zo9/b+DJygRA5wZzix4=" }, "is-absolute": { "version": "1.0.0", @@ -11612,7 +7630,6 @@ "version": "0.1.6", "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", - "dev": true, "requires": { "kind-of": "^3.0.2" } @@ -11640,7 +7657,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-1.0.0.tgz", "integrity": "sha1-VAVy0096wxGfj3bDDLwbHgN6/74=", - "dev": true, "requires": { "builtin-modules": "^1.0.0" } @@ -11654,7 +7670,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-1.1.0.tgz", "integrity": "sha512-c7TnwxLePuqIlxHgr7xtxzycJPegNHFuIrBkwbf8hc58//+Op1CqFkyS+xnIMkwn9UsJIwc174BIjkyBmSpjKg==", - "dev": true, "requires": { "ci-info": "^1.0.0" } @@ -11663,7 +7678,6 @@ "version": "0.1.4", "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", - "dev": true, "requires": { "kind-of": "^3.0.2" } @@ -11677,7 +7691,6 @@ "version": "0.1.6", "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "dev": true, "requires": { "is-accessor-descriptor": "^0.1.6", "is-data-descriptor": "^0.1.4", @@ -11687,8 +7700,7 @@ "kind-of": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "dev": true + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==" } } }, @@ -11741,6 +7753,15 @@ "is-extglob": "^1.0.0" } }, + "is-installed-globally": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.1.0.tgz", + "integrity": "sha1-Df2Y9akRFxbdU13aZJL2e/PSWoA=", + "requires": { + "global-dirs": "^0.1.0", + "is-path-inside": "^1.0.0" + } + }, "is-module": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", @@ -11753,6 +7774,11 @@ "integrity": "sha1-aRC8pdqMleeEtXUbl2z1oQ/uNtI=", "dev": true }, + "is-npm": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-1.0.0.tgz", + "integrity": "sha1-8vtjpl5JBbQGyGBydloaTceTufQ=" + }, "is-number": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-2.1.0.tgz", @@ -11761,11 +7787,15 @@ "kind-of": "^3.0.2" } }, + "is-obj": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", + "integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=" + }, "is-odd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-odd/-/is-odd-2.0.0.tgz", "integrity": "sha512-OTiixgpZAT1M4NHgS5IguFp/Vz2VI3U7Goh4/HA1adtwyLtSBrxYlcSYkhpAE07s4fKEcjrFxyvtQBND4vFQyQ==", - "dev": true, "requires": { "is-number": "^4.0.0" }, @@ -11773,16 +7803,22 @@ "is-number": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz", - "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==", - "dev": true + "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==" } } }, + "is-path-inside": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.1.tgz", + "integrity": "sha1-jvW33lBDej/cprToZe96pVy0gDY=", + "requires": { + "path-is-inside": "^1.0.1" + } + }, "is-plain-object": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dev": true, "requires": { "isobject": "^3.0.1" }, @@ -11790,8 +7826,7 @@ "isobject": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=" } } }, @@ -11805,6 +7840,16 @@ "resolved": "https://registry.npmjs.org/is-primitive/-/is-primitive-2.0.0.tgz", "integrity": "sha1-IHurkWOEmcB7Kt8kCkGochADRXU=" }, + "is-promise": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz", + "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=" + }, + "is-redirect": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-redirect/-/is-redirect-1.0.0.tgz", + "integrity": "sha1-HQPd7VO9jbDzDCbk+V02/HyH3CQ=" + }, "is-regex": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.4.tgz", @@ -11822,11 +7867,15 @@ "is-unc-path": "^1.0.0" } }, + "is-retry-allowed": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-1.2.0.tgz", + "integrity": "sha512-RUbUeKwvm3XG2VYamhJL1xFktgjvPzL0Hq8C+6yrWIswDy3BIXGqCxhxkc30N9jqK311gVU137K8Ei55/zVJRg==" + }, "is-stream": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", - "dev": true + "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=" }, "is-symbol": { "version": "1.0.1", @@ -11836,8 +7885,7 @@ "is-typedarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", - "dev": true + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" }, "is-unc-path": { "version": "1.0.0", @@ -11848,6 +7896,11 @@ "unc-path-regex": "^0.1.2" } }, + "is-url": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/is-url/-/is-url-1.2.4.tgz", + "integrity": "sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww==" + }, "is-utf8": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", @@ -11863,8 +7916,12 @@ "is-windows": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", - "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", - "dev": true + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==" + }, + "is-wsl": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", + "integrity": "sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=" }, "isarray": { "version": "1.0.0", @@ -11883,8 +7940,7 @@ "isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", - "dev": true + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" }, "isobject": { "version": "2.1.0", @@ -11897,8 +7953,7 @@ "isstream": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", - "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", - "dev": true + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" }, "js-base64": { "version": "2.5.1", @@ -11926,7 +7981,6 @@ "version": "0.1.1", "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", - "dev": true, "optional": true }, "json-loader": { @@ -11935,17 +7989,20 @@ "integrity": "sha512-QLPs8Dj7lnf3e3QYS1zkCo+4ZwqOiF9d/nZnYozTISxXWCfNs9yuky5rJw4/W34s7POaNlbZmQGaB5NiXCbP4w==", "dev": true }, + "json-parse-better-errors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", + "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==" + }, "json-schema": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", - "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=", - "dev": true + "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" }, "json-schema-traverse": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz", - "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=", - "dev": true + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" }, "json-stable-stringify-without-jsonify": { "version": "1.0.1", @@ -11956,8 +8013,7 @@ "json-stringify-safe": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", - "dev": true + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" }, "json5": { "version": "1.0.1", @@ -11972,7 +8028,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", - "dev": true, "requires": { "graceful-fs": "^4.1.6" } @@ -11981,7 +8036,6 @@ "version": "1.4.1", "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", - "dev": true, "requires": { "assert-plus": "1.0.0", "extsprintf": "1.3.0", @@ -11992,8 +8046,7 @@ "assert-plus": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", - "dev": true + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" } } }, @@ -12058,6 +8111,14 @@ "es6-weak-map": "^2.0.1" } }, + "latest-version": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-3.1.0.tgz", + "integrity": "sha1-ogU4P+oyKzO1rjsYq+4NwvNW7hU=", + "requires": { + "package-json": "^4.0.0" + } + }, "lazy-cache": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz", @@ -12161,7 +8222,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", - "dev": true, "requires": { "p-locate": "^2.0.0", "path-exists": "^3.0.0" @@ -12170,16 +8230,14 @@ "path-exists": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", - "dev": true + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=" } } }, "lodash": { "version": "4.17.15", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", - "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", - "dev": true + "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==" }, "lodash._basecopy": { "version": "3.0.1", @@ -12247,6 +8305,11 @@ "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=", "dev": true }, + "lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168=" + }, "lodash.escape": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/lodash.escape/-/lodash.escape-3.2.0.tgz", @@ -12343,11 +8406,15 @@ "signal-exit": "^3.0.0" } }, + "lowercase-keys": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", + "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==" + }, "lru-cache": { "version": "4.1.5", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", - "dev": true, "requires": { "pseudomap": "^1.0.2", "yallist": "^2.1.2" @@ -12368,6 +8435,21 @@ "vlq": "^0.2.2" } }, + "make-dir": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz", + "integrity": "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==", + "requires": { + "pify": "^3.0.0" + }, + "dependencies": { + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=" + } + } + }, "make-iterator": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/make-iterator/-/make-iterator-1.0.1.tgz", @@ -12388,8 +8470,7 @@ "map-cache": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", - "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=", - "dev": true + "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=" }, "map-obj": { "version": "1.0.1", @@ -12401,7 +8482,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", - "dev": true, "requires": { "object-visit": "^1.0.0" } @@ -12726,6 +8806,16 @@ "resolved": "https://registry.npmjs.org/math-random/-/math-random-1.0.4.tgz", "integrity": "sha512-rUxjysqif/BZQH2yhd5Aaq7vXMSx9NdEsQcyA07uEzIvxgI7zIr33gGsh+RU0/XjmQpCW7RsVof1vlkvQVCK5A==" }, + "mathjax": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/mathjax/-/mathjax-2.7.2.tgz", + "integrity": "sha512-Z9te7r3lsjZibugO1uKsdyqICKXVNr7M1Ll2GtjJu9cUKvOvwDqEp0YIjBD4H58NNuPg5DP5/AQ2Tu8K52Mqng==" + }, + "md5-file": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/md5-file/-/md5-file-4.0.0.tgz", + "integrity": "sha512-UC0qFwyAjn4YdPpKaDNw6gNxRf7Mcx7jC1UGCY4boCzgvU2Aoc1mOGzTtrjjLKhM5ivsnhoKpQVxKPp+1j1qwg==" + }, "md5.js": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", @@ -12740,8 +8830,7 @@ "media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", - "dev": true + "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" }, "mem": { "version": "1.1.0", @@ -12783,14 +8872,17 @@ "merge-descriptors": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=", - "dev": true + "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" + }, + "merge2": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.3.0.tgz", + "integrity": "sha512-2j4DAdlBOkiSZIsaXk4mTE3sRS02yBHAtfy127xRV3bQUFqXkjHCHLW6Scv7DwNRbIWNHH8zpnz9zMaKXIdvYw==" }, "methods": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=", - "dev": true + "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" }, "micromatch": { "version": "2.3.11", @@ -12825,20 +8917,17 @@ "mime": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/mime/-/mime-1.4.1.tgz", - "integrity": "sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ==", - "dev": true + "integrity": "sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ==" }, "mime-db": { "version": "1.38.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.38.0.tgz", - "integrity": "sha512-bqVioMFFzc2awcdJZIzR3HjZFX20QhilVS7hytkKrv7xFAn8bM1gzc/FOX2awLISvWe0PV8ptFKcon+wZ5qYkg==", - "dev": true + "integrity": "sha512-bqVioMFFzc2awcdJZIzR3HjZFX20QhilVS7hytkKrv7xFAn8bM1gzc/FOX2awLISvWe0PV8ptFKcon+wZ5qYkg==" }, "mime-types": { "version": "2.1.22", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.22.tgz", "integrity": "sha512-aGl6TZGnhm/li6F7yx82bJiBZwgiEa4Hf6CNr8YO+r5UHr53tSTYZb102zyU50DOWWKeOv0uQLRL0/9EiKWCog==", - "dev": true, "requires": { "mime-db": "~1.38.0" } @@ -12846,8 +8935,7 @@ "mimic-fn": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", - "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==", - "dev": true + "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==" }, "minimalistic-assert": { "version": "1.0.1", @@ -12878,7 +8966,6 @@ "version": "1.3.2", "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==", - "dev": true, "requires": { "for-in": "^1.0.2", "is-extendable": "^1.0.1" @@ -12888,7 +8975,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dev": true, "requires": { "is-plain-object": "^2.0.4" } @@ -12918,8 +9004,7 @@ "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" }, "multipipe": { "version": "0.1.2", @@ -12936,6 +9021,11 @@ "integrity": "sha512-kDcwXR4PS7caBpuRYYBUz9iVixUk3anO3f5OYFiIPwK/20vCzKCHyKoulbiDY1S53zD2bxUpxN/IJ+TnXjfvxg==", "dev": true }, + "mute-stream": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", + "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==" + }, "nan": { "version": "2.10.0", "resolved": "https://registry.npmjs.org/nan/-/nan-2.10.0.tgz", @@ -12946,7 +9036,6 @@ "version": "1.2.9", "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.9.tgz", "integrity": "sha512-n8R9bS8yQ6eSXaV6jHUpKzD8gLsin02w1HSFiegwrs9E098Ylhw5jdyKPaYqvHknHaSCKTPp7C8dGCQ0q9koXA==", - "dev": true, "requires": { "arr-diff": "^4.0.0", "array-unique": "^0.3.2", @@ -12965,28 +9054,24 @@ "arr-diff": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", - "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", - "dev": true + "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=" }, "array-unique": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", - "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", - "dev": true + "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=" }, "kind-of": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", - "dev": true + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==" } } }, "negotiator": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz", - "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=", - "dev": true + "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=" }, "neo-async": { "version": "2.6.0", @@ -13000,6 +9085,11 @@ "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=", "dev": true }, + "nice-try": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==" + }, "nl.kingsquare.cordova.background-audio": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/nl.kingsquare.cordova.background-audio/-/nl.kingsquare.cordova.background-audio-1.0.1.tgz", @@ -13064,6 +9154,12 @@ "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", "dev": true }, + "fast-deep-equal": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz", + "integrity": "sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ=", + "dev": true + }, "form-data": { "version": "2.3.2", "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.2.tgz", @@ -13083,6 +9179,20 @@ "requires": { "ajv": "^5.3.0", "har-schema": "^2.0.0" + }, + "dependencies": { + "ajv": { + "version": "5.5.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", + "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", + "dev": true, + "requires": { + "co": "^4.6.0", + "fast-deep-equal": "^1.0.0", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.3.0" + } + } } }, "http-signature": { @@ -13096,6 +9206,12 @@ "sshpk": "^1.7.0" } }, + "json-schema-traverse": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz", + "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=", + "dev": true + }, "mime-db": { "version": "1.36.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.36.0.tgz", @@ -13298,7 +9414,6 @@ "version": "2.4.0", "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.4.0.tgz", "integrity": "sha512-9jjUFbTPfEy3R/ad/2oNbKtW9Hgovl5O1FvFWKkKblNXoN/Oou6+9+KKohPK13Yc3/TyunyWhJp6gvRNR/PPAw==", - "dev": true, "requires": { "hosted-git-info": "^2.1.4", "is-builtin-module": "^1.0.0", @@ -13329,11 +9444,33 @@ "once": "^1.3.2" } }, + "npm-package-arg": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-6.1.1.tgz", + "integrity": "sha512-qBpssaL3IOZWi5vEKUKW0cO7kzLeT+EQO9W8RsLOZf76KF9E/K9+wH0C7t06HXPpaH8WH5xF1MExLuCwbTqRUg==", + "requires": { + "hosted-git-info": "^2.7.1", + "osenv": "^0.1.5", + "semver": "^5.6.0", + "validate-npm-package-name": "^3.0.0" + }, + "dependencies": { + "hosted-git-info": { + "version": "2.8.4", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.4.tgz", + "integrity": "sha512-pzXIvANXEFrc5oFFXRMkbLPQ2rXRoDERwDLyrcUxGhaZhgP54BBSl9Oheh7Vv0T090cszWBxPjkQQ5Sq1PbBRQ==" + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" + } + } + }, "npm-run-path": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", - "dev": true, "requires": { "path-key": "^2.0.0" } @@ -13365,8 +9502,7 @@ "oauth-sign": { "version": "0.9.0", "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", - "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", - "dev": true + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==" }, "object-assign": { "version": "4.1.1", @@ -13378,7 +9514,6 @@ "version": "0.1.0", "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", - "dev": true, "requires": { "copy-descriptor": "^0.1.0", "define-property": "^0.2.5", @@ -13389,13 +9524,17 @@ "version": "0.2.5", "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, "requires": { "is-descriptor": "^0.1.0" } } } }, + "object-inspect": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.6.0.tgz", + "integrity": "sha512-GJzfBZ6DgDAmnuaM3104jR4s1Myxr3Y3zfIyN4z3UdqN69oSRacNK8UhnobDdC+7J2AHCjGwxQubNJfE70SXXQ==" + }, "object-keys": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.0.11.tgz", @@ -13405,7 +9544,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", - "dev": true, "requires": { "isobject": "^3.0.0" }, @@ -13413,8 +9551,7 @@ "isobject": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=" } } }, @@ -13493,7 +9630,6 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", - "dev": true, "requires": { "isobject": "^3.0.1" }, @@ -13501,8 +9637,7 @@ "isobject": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=" } } }, @@ -13527,20 +9662,31 @@ } } }, + "objectorarray": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/objectorarray/-/objectorarray-1.0.3.tgz", + "integrity": "sha512-kPoflSYkAf/Onvjr4ZLaq37vDuOXjVzfwLCRuORRzYGdXkHa/vacPT0RgR+KmtkwOYFcxTMM62BRrZk8GGKHjw==", + "requires": { + "tape": "^4.8.0" + } + }, "on-finished": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", - "dev": true, "requires": { "ee-first": "1.1.1" } }, + "on-headers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", + "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==" + }, "once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "dev": true, "requires": { "wrappy": "1" } @@ -13549,11 +9695,18 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=", - "dev": true, "requires": { "mimic-fn": "^1.0.0" } }, + "opn": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/opn/-/opn-5.5.0.tgz", + "integrity": "sha512-PqHpggC9bLV0VeWcdKhkpxY+3JTzetLSqTCWL/z/tFIbI6G8JCjondXklT1JinczLz2Xib62sSp0T/gKT4KksA==", + "requires": { + "is-wsl": "^1.1.0" + } + }, "ora": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/ora/-/ora-1.4.0.tgz", @@ -13584,8 +9737,7 @@ "os-homedir": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", - "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", - "dev": true + "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=" }, "os-locale": { "version": "1.4.0", @@ -13609,14 +9761,12 @@ "os-tmpdir": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", - "dev": true + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=" }, "osenv": { "version": "0.1.5", "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", - "dev": true, "requires": { "os-homedir": "^1.0.0", "os-tmpdir": "^1.0.0" @@ -13625,14 +9775,12 @@ "p-finally": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", - "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=", - "dev": true + "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=" }, "p-limit": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", - "dev": true, "requires": { "p-try": "^1.0.0" } @@ -13641,7 +9789,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", - "dev": true, "requires": { "p-limit": "^1.1.0" } @@ -13649,14 +9796,32 @@ "p-try": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", - "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", - "dev": true + "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=" + }, + "package-json": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/package-json/-/package-json-4.0.1.tgz", + "integrity": "sha1-iGmgQBJTZhxMTKPabCEh7VVfXu0=", + "requires": { + "got": "^6.7.1", + "registry-auth-token": "^3.0.1", + "registry-url": "^3.0.3", + "semver": "^5.1.0" + } }, "pako": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.6.tgz", "integrity": "sha512-lQe48YPsMJAig+yngZ87Lus+NF+3mtu7DVOBu6b/gHO1YpKwIj5AWjZ/TOS7i46HD/UixzWb1zeWDZfGZ3iYcg==" }, + "parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "requires": { + "callsites": "^3.0.0" + } + }, "parse-asn1": { "version": "5.1.4", "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.4.tgz", @@ -13711,14 +9876,12 @@ "parseurl": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.2.tgz", - "integrity": "sha1-/CidTtiZMRlGDBViUyYs3I3mW/M=", - "dev": true + "integrity": "sha1-/CidTtiZMRlGDBViUyYs3I3mW/M=" }, "pascalcase": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", - "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=", - "dev": true + "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=" }, "path-browserify": { "version": "0.0.0", @@ -13729,8 +9892,7 @@ "path-dirname": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz", - "integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=", - "dev": true + "integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=" }, "path-exists": { "version": "2.1.0", @@ -13746,11 +9908,15 @@ "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" }, + "path-is-inside": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", + "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=" + }, "path-key": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", - "dev": true + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=" }, "path-parse": { "version": "1.0.5", @@ -13812,16 +9978,10 @@ "sha.js": "^2.4.8" } }, - "pegjs": { - "version": "0.10.0", - "resolved": "https://registry.npmjs.org/pegjs/-/pegjs-0.10.0.tgz", - "integrity": "sha1-z4uvrm7d/0tafvsYUmnqr0YQ3b0=" - }, "performance-now": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", - "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=", - "dev": true + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" }, "phonegap-plugin-multidex": { "version": "1.0.0", @@ -13829,7 +9989,7 @@ "integrity": "sha512-1wvc3iQOQpEBaQbXgLxA2JUiLSQ2azdF/bF29ghXDiQJWSpQ1BF8gSuqttM8WZoj081Ps8OKL0gYxdDBkFNPqA==" }, "phonegap-plugin-push": { - "version": "git+https://github.com/moodlemobile/phonegap-plugin-push.git#cf8101e86adb774ae1d7ad6b65fb9d8802673f4b", + "version": "git+https://github.com/moodlemobile/phonegap-plugin-push.git#cb332d5ec8a1a0d43916fc6d35455fe6bea7a7f9", "from": "git+https://github.com/moodlemobile/phonegap-plugin-push.git#moodle-v3", "requires": { "babel-plugin-add-header-comment": "^1.0.3", @@ -13857,11 +10017,28 @@ "pinkie": "^2.0.0" } }, + "pkg-up": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pkg-up/-/pkg-up-2.0.0.tgz", + "integrity": "sha1-yBmscoBZpGHKscOImivjxJoATX8=", + "requires": { + "find-up": "^2.1.0" + }, + "dependencies": { + "find-up": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", + "requires": { + "locate-path": "^2.0.0" + } + } + } + }, "plist": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/plist/-/plist-3.0.1.tgz", "integrity": "sha512-GpgvHHocGRyQm74b6FWEZZVRroHKE1I0/BTjAmySaohK+cUn+hZpbqXkc3KWgW3gQYkqcQej35FohcT0FRlkRQ==", - "dev": true, "requires": { "base64-js": "^1.2.3", "xmlbuilder": "^9.0.7", @@ -13923,8 +10100,7 @@ "posix-character-classes": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", - "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=", - "dev": true + "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=" }, "postcss": { "version": "6.0.23", @@ -13943,6 +10119,11 @@ "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", "dev": true }, + "prepend-http": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz", + "integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=" + }, "preserve": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/preserve/-/preserve-0.2.0.tgz", @@ -13975,11 +10156,26 @@ "function-bind": "^1.1.1" } }, + "promzard": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/promzard/-/promzard-0.3.0.tgz", + "integrity": "sha1-JqXW7ox97kyxIggwWs+5O6OCqe4=", + "requires": { + "read": "1" + } + }, + "properties-parser": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/properties-parser/-/properties-parser-0.3.1.tgz", + "integrity": "sha1-ExbpU5/7/ZOEXjabIRAiq9R4dxo=", + "requires": { + "string.prototype.codepointat": "^0.2.0" + } + }, "proxy-addr": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.4.tgz", "integrity": "sha512-5erio2h9jp5CHGwcybmxmVqHmnCBZeewlfJ0pex+UW7Qny7OOZXTtH56TGNyBizkgiOwhJtMKrVzDTeKcySZwA==", - "dev": true, "requires": { "forwarded": "~0.1.2", "ipaddr.js": "1.8.0" @@ -14000,14 +10196,12 @@ "pseudomap": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", - "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=", - "dev": true + "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=" }, "psl": { "version": "1.1.29", "resolved": "https://registry.npmjs.org/psl/-/psl-1.1.29.tgz", - "integrity": "sha512-AeUmQ0oLN02flVHXWh9sSJF7mcdFq0ppid/JkErufc3hGIV/AMa8Fo9VgDo/cT2jFdOWoFvHp90qqBH54W+gjQ==", - "dev": true + "integrity": "sha512-AeUmQ0oLN02flVHXWh9sSJF7mcdFq0ppid/JkErufc3hGIV/AMa8Fo9VgDo/cT2jFdOWoFvHp90qqBH54W+gjQ==" }, "public-encrypt": { "version": "4.0.3", @@ -14047,8 +10241,7 @@ "punycode": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", - "dev": true + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" }, "q": { "version": "1.5.1", @@ -14058,8 +10251,7 @@ "qs": { "version": "6.5.2", "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", - "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", - "dev": true + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" }, "querystring": { "version": "0.2.0", @@ -14117,14 +10309,12 @@ "range-parser": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", - "integrity": "sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4=", - "dev": true + "integrity": "sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4=" }, "raw-body": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.3.tgz", "integrity": "sha512-9esiElv1BrZoI3rCDuOuKCBRbuApGGaDPQfjSflGxdy4oyzqghxu6klEkkVIvBje+FF0BX9coEv8KqW6X/7njw==", - "dev": true, "requires": { "bytes": "3.0.0", "http-errors": "1.6.3", @@ -14132,6 +10322,41 @@ "unpipe": "1.0.0" } }, + "rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "requires": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + } + }, + "read": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/read/-/read-1.0.7.tgz", + "integrity": "sha1-s9oZvQUkMal2cdRKQmNK33ELQMQ=", + "requires": { + "mute-stream": "~0.0.4" + } + }, + "read-chunk": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/read-chunk/-/read-chunk-3.2.0.tgz", + "integrity": "sha512-CEjy9LCzhmD7nUpJ1oVOE6s/hBkejlcJEgLQHVnQznOSilOPb+kpKktlLfFDK3/WP43+F80xkUTM2VOkYoSYvQ==", + "requires": { + "pify": "^4.0.1", + "with-open-file": "^0.1.6" + }, + "dependencies": { + "pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==" + } + } + }, "read-config-file": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/read-config-file/-/read-config-file-3.1.0.tgz", @@ -14190,6 +10415,25 @@ } } }, + "read-package-json": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/read-package-json/-/read-package-json-2.1.0.tgz", + "integrity": "sha512-KLhu8M1ZZNkMcrq1+0UJbR8Dii8KZUqB0Sha4mOx/bknfKI/fyrQVrG/YIt2UOtG667sD8+ee4EXMM91W9dC+A==", + "requires": { + "glob": "^7.1.1", + "graceful-fs": "^4.1.2", + "json-parse-better-errors": "^1.0.1", + "normalize-package-data": "^2.0.0", + "slash": "^1.0.0" + }, + "dependencies": { + "slash": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz", + "integrity": "sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU=" + } + } + }, "read-pkg": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", @@ -14272,12 +10516,28 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", - "dev": true, "requires": { "extend-shallow": "^3.0.2", "safe-regex": "^1.1.0" } }, + "registry-auth-token": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-3.4.0.tgz", + "integrity": "sha512-4LM6Fw8eBQdwMYcES4yTnn2TqIasbXuwDx3um+QRs7S55aMKCBKBxvPXl2RiUjHwuJLTyYfxSpmfSAjQpcuP+A==", + "requires": { + "rc": "^1.1.6", + "safe-buffer": "^5.0.1" + } + }, + "registry-url": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-3.1.0.tgz", + "integrity": "sha1-PU74cPc93h138M+aOBQyRE4XSUI=", + "requires": { + "rc": "^1.0.1" + } + }, "remove-bom-buffer": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/remove-bom-buffer/-/remove-bom-buffer-3.0.0.tgz", @@ -14344,7 +10604,6 @@ "version": "2.88.0", "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz", "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==", - "dev": true, "requires": { "aws-sign2": "~0.7.0", "aws4": "^1.8.0", @@ -14371,8 +10630,7 @@ "extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", - "dev": true + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" } } }, @@ -14407,6 +10665,11 @@ "global-modules": "^1.0.0" } }, + "resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==" + }, "resolve-options": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/resolve-options/-/resolve-options-1.1.0.tgz", @@ -14419,24 +10682,29 @@ "resolve-url": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", - "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=", - "dev": true + "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=" }, "restore-cursor": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", "integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=", - "dev": true, "requires": { "onetime": "^2.0.0", "signal-exit": "^3.0.2" } }, + "resumer": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/resumer/-/resumer-0.0.0.tgz", + "integrity": "sha1-8ej0YeQGS6Oegq883CqMiT0HZ1k=", + "requires": { + "through": "~2.3.4" + } + }, "ret": { "version": "0.1.15", "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", - "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", - "dev": true + "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==" }, "right-align": { "version": "0.1.3", @@ -14789,6 +11057,14 @@ } } }, + "run-async": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.3.0.tgz", + "integrity": "sha1-A3GrSuC91yDUFm19/aZP96RFpsA=", + "requires": { + "is-promise": "^2.1.0" + } + }, "rxjs": { "version": "5.5.11", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-5.5.11.tgz", @@ -14812,7 +11088,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", - "dev": true, "requires": { "ret": "~0.1.10" } @@ -14820,8 +11095,7 @@ "safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, "sanitize-filename": { "version": "1.6.1", @@ -14874,8 +11148,15 @@ "semver": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz", - "integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==", - "dev": true + "integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==" + }, + "semver-diff": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-2.1.0.tgz", + "integrity": "sha1-S7uEN8jTfksM8aaP1ybsbWRdbTY=", + "requires": { + "semver": "^5.0.3" + } }, "semver-greatest-satisfied-range": { "version": "1.1.0", @@ -14890,7 +11171,6 @@ "version": "0.16.2", "resolved": "https://registry.npmjs.org/send/-/send-0.16.2.tgz", "integrity": "sha512-E64YFPUssFHEFBvpbbjr44NCLtI1AohxQ8ZSiJjQLskAdKuriYEP6VyGEsRDH8ScozGpkaX1BGvhanqCwkcEZw==", - "dev": true, "requires": { "debug": "2.6.9", "depd": "~1.1.2", @@ -14911,7 +11191,6 @@ "version": "1.13.2", "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.13.2.tgz", "integrity": "sha512-p/tdJrO4U387R9oMjb1oj7qSMaMfmOyd4j9hOFoxZe2baQszgHcSWjuya/CiT5kgZZKRudHNOA0pYXOl8rQ5nw==", - "dev": true, "requires": { "encodeurl": "~1.0.2", "escape-html": "~1.0.3", @@ -14939,7 +11218,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==", - "dev": true, "requires": { "extend-shallow": "^2.0.1", "is-extendable": "^0.1.1", @@ -14951,7 +11229,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, "requires": { "is-extendable": "^0.1.0" } @@ -14967,8 +11244,7 @@ "setprototypeof": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", - "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", - "dev": true + "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==" }, "sha.js": { "version": "2.4.11", @@ -14984,7 +11260,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", - "dev": true, "requires": { "shebang-regex": "^1.0.0" } @@ -14992,14 +11267,43 @@ "shebang-regex": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", - "dev": true + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=" + }, + "shelljs": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.5.3.tgz", + "integrity": "sha1-xUmCuZbHbvDB5rWfvcWCX1txMRM=" }, "signal-exit": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", - "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", - "dev": true + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=" + }, + "simctl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/simctl/-/simctl-2.0.0.tgz", + "integrity": "sha512-5rB7rN4N3b0z0nFdy9eczVssXqrv2aAgdVRksPVqVoiDtvXmfzNvebp3EMdId2sAUzXIflarQlx4P0hjVQEzKQ==", + "requires": { + "shelljs": "^0.2.6", + "tail": "^0.4.0" + }, + "dependencies": { + "shelljs": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.2.6.tgz", + "integrity": "sha1-kEktcv/MgVmXa6umL7D2iE8MM3g=" + } + } + }, + "simple-plist": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/simple-plist/-/simple-plist-1.0.0.tgz", + "integrity": "sha512-043L2rO80LVF7zfZ+fqhsEkoJFvW8o59rt/l4ctx1TJWoTx7/jkiS1R5TatD15Z1oYnuLJytzE7gcnnBuIPL2g==", + "requires": { + "bplist-creator": "0.0.7", + "bplist-parser": "0.1.1", + "plist": "^3.0.1" + } }, "slash": { "version": "0.1.3", @@ -15011,7 +11315,6 @@ "version": "0.8.2", "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", - "dev": true, "requires": { "base": "^0.11.1", "debug": "^2.2.0", @@ -15027,7 +11330,6 @@ "version": "0.2.5", "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, "requires": { "is-descriptor": "^0.1.0" } @@ -15036,7 +11338,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, "requires": { "is-extendable": "^0.1.0" } @@ -15044,8 +11345,7 @@ "source-map": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" } } }, @@ -15053,7 +11353,6 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", - "dev": true, "requires": { "define-property": "^1.0.0", "isobject": "^3.0.0", @@ -15064,7 +11363,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, "requires": { "is-descriptor": "^1.0.0" } @@ -15073,7 +11371,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, "requires": { "kind-of": "^6.0.0" } @@ -15082,7 +11379,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, "requires": { "kind-of": "^6.0.0" } @@ -15091,7 +11387,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, "requires": { "is-accessor-descriptor": "^1.0.0", "is-data-descriptor": "^1.0.0", @@ -15101,14 +11396,12 @@ "isobject": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=" }, "kind-of": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", - "dev": true + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==" } } }, @@ -15116,7 +11409,6 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", - "dev": true, "requires": { "kind-of": "^3.2.0" } @@ -15136,7 +11428,6 @@ "version": "0.5.2", "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.2.tgz", "integrity": "sha512-MjqsvNwyz1s0k81Goz/9vRBe9SZdB09Bdw+/zYyO+3CuPk6fouTaxscHkgtE8jKvf01kVfl8riHzERQ/kefaSA==", - "dev": true, "requires": { "atob": "^2.1.1", "decode-uri-component": "^0.2.0", @@ -15157,8 +11448,7 @@ "source-map-url": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz", - "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=", - "dev": true + "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=" }, "sparkles": { "version": "1.0.1", @@ -15181,7 +11471,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.0.0.tgz", "integrity": "sha512-N19o9z5cEyc8yQQPukRCZ9EUmb4HUpnrmaL/fxS2pBo2jbfcFRVuFZ/oFC+vZz0MNNk0h80iMn5/S6qGZOL5+g==", - "dev": true, "requires": { "spdx-expression-parse": "^3.0.0", "spdx-license-ids": "^3.0.0" @@ -15190,14 +11479,12 @@ "spdx-exceptions": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.1.0.tgz", - "integrity": "sha512-4K1NsmrlCU1JJgUrtgEeTVyfx8VaYea9J9LvARxhbHtVtohPs/gFGG5yy49beySjlIMhhXZ4QqujIZEfS4l6Cg==", - "dev": true + "integrity": "sha512-4K1NsmrlCU1JJgUrtgEeTVyfx8VaYea9J9LvARxhbHtVtohPs/gFGG5yy49beySjlIMhhXZ4QqujIZEfS4l6Cg==" }, "spdx-expression-parse": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz", "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==", - "dev": true, "requires": { "spdx-exceptions": "^2.1.0", "spdx-license-ids": "^3.0.0" @@ -15206,14 +11493,12 @@ "spdx-license-ids": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.0.tgz", - "integrity": "sha512-2+EPwgbnmOIl8HjGBXXMd9NAu02vLjOO1nWw4kmeRDFyHn+M/ETfHxQUK0oXg8ctgVnl9t3rosNVsZ1jG61nDA==", - "dev": true + "integrity": "sha512-2+EPwgbnmOIl8HjGBXXMd9NAu02vLjOO1nWw4kmeRDFyHn+M/ETfHxQUK0oXg8ctgVnl9t3rosNVsZ1jG61nDA==" }, "split-string": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", - "dev": true, "requires": { "extend-shallow": "^3.0.0" } @@ -15228,7 +11513,6 @@ "version": "1.14.2", "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.14.2.tgz", "integrity": "sha1-xvxhZIo9nE52T9P8306hBeSSupg=", - "dev": true, "requires": { "asn1": "~0.2.3", "assert-plus": "^1.0.0", @@ -15244,8 +11528,7 @@ "assert-plus": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", - "dev": true + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" } } }, @@ -15265,7 +11548,6 @@ "version": "0.1.2", "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", - "dev": true, "requires": { "define-property": "^0.2.5", "object-copy": "^0.1.0" @@ -15275,7 +11557,6 @@ "version": "0.2.5", "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, "requires": { "is-descriptor": "^0.1.0" } @@ -15285,8 +11566,7 @@ "statuses": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", - "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==", - "dev": true + "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==" }, "stdout-stream": { "version": "1.4.1", @@ -15307,6 +11587,11 @@ "readable-stream": "^2.0.2" } }, + "stream-buffers": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/stream-buffers/-/stream-buffers-2.2.0.tgz", + "integrity": "sha1-kdX1Ew0c75bc+n9yaUUYh0HQnuQ=" + }, "stream-exhaust": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/stream-exhaust/-/stream-exhaust-1.0.2.tgz", @@ -15349,6 +11634,21 @@ "strip-ansi": "^3.0.0" } }, + "string.prototype.codepointat": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/string.prototype.codepointat/-/string.prototype.codepointat-0.2.1.tgz", + "integrity": "sha512-2cBVCj6I4IOvEnjgO/hWqXjqBGsY+zwPmHl12Srk9IXSZ56Jwwmy+66XO5Iut/oQVR7t5ihYdLB0GMa4alEUcg==" + }, + "string.prototype.trim": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.1.2.tgz", + "integrity": "sha1-0E3iyJ4Tf019IG8Ia17S+ua+jOo=", + "requires": { + "define-properties": "^1.1.2", + "es-abstract": "^1.5.0", + "function-bind": "^1.0.2" + } + }, "string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", @@ -15378,8 +11678,7 @@ "strip-eof": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", - "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=", - "dev": true + "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=" }, "strip-indent": { "version": "1.0.1", @@ -15390,6 +11689,11 @@ "get-stdin": "^4.0.1" } }, + "strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=" + }, "sumchecker": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/sumchecker/-/sumchecker-2.0.2.tgz", @@ -15403,7 +11707,6 @@ "version": "5.4.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", - "dev": true, "requires": { "has-flag": "^3.0.0" } @@ -15432,12 +11735,70 @@ "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.0.1.tgz", "integrity": "sha1-g0D8RwLDEi310iKI+IKD9RPT/dQ=" }, + "tail": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/tail/-/tail-0.4.0.tgz", + "integrity": "sha1-0p3nJ1DMmdseBTr/E8NZ7PtxMAI=" + }, "tapable": { "version": "0.2.9", "resolved": "https://registry.npmjs.org/tapable/-/tapable-0.2.9.tgz", "integrity": "sha512-2wsvQ+4GwBvLPLWsNfLCDYGsW6xb7aeC6utq2Qh0PFwgEy7K7dsma9Jsmb2zSQj7GvYAyUGSntLtsv++GmgL1A==", "dev": true }, + "tape": { + "version": "4.11.0", + "resolved": "https://registry.npmjs.org/tape/-/tape-4.11.0.tgz", + "integrity": "sha512-yixvDMX7q7JIs/omJSzSZrqulOV51EC9dK8dM0TzImTIkHWfe2/kFyL5v+d9C+SrCMaICk59ujsqFAVidDqDaA==", + "requires": { + "deep-equal": "~1.0.1", + "defined": "~1.0.0", + "for-each": "~0.3.3", + "function-bind": "~1.1.1", + "glob": "~7.1.4", + "has": "~1.0.3", + "inherits": "~2.0.4", + "minimist": "~1.2.0", + "object-inspect": "~1.6.0", + "resolve": "~1.11.1", + "resumer": "~0.0.0", + "string.prototype.trim": "~1.1.2", + "through": "~2.3.8" + }, + "dependencies": { + "glob": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", + "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "path-parse": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", + "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==" + }, + "resolve": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.11.1.tgz", + "integrity": "sha512-vIpgF6wfuJOZI7KKKSP+HmiKggadPQAdsp5HiC1mvqnfp0gF1vdwgBWZIdrVft9pgqoMFQN+R7BSWZiBxx+BBw==", + "requires": { + "path-parse": "^1.0.6" + } + } + } + }, "tar": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/tar/-/tar-2.2.2.tgz", @@ -15475,11 +11836,18 @@ "lazy-val": "^1.0.3" } }, + "term-size": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/term-size/-/term-size-1.2.0.tgz", + "integrity": "sha1-RYuDiH8oj8Vtb/+/rSYuJmOO+mk=", + "requires": { + "execa": "^0.7.0" + } + }, "through": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", - "dev": true + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=" }, "through2": { "version": "2.0.3", @@ -15507,6 +11875,11 @@ "integrity": "sha1-dkpaEa9QVhkhsTPztE5hhofg9cM=", "dev": true }, + "timed-out": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/timed-out/-/timed-out-4.0.1.tgz", + "integrity": "sha1-8y6srFoXW+ol1/q1Zas+2HQe9W8=" + }, "timers-browserify": { "version": "2.0.10", "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.10.tgz", @@ -15547,6 +11920,14 @@ } } }, + "tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "requires": { + "os-tmpdir": "~1.0.2" + } + }, "to-absolute-glob": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/to-absolute-glob/-/to-absolute-glob-2.0.2.tgz", @@ -15567,7 +11948,6 @@ "version": "0.3.0", "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", - "dev": true, "requires": { "kind-of": "^3.0.2" } @@ -15576,7 +11956,6 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", - "dev": true, "requires": { "define-property": "^2.0.2", "extend-shallow": "^3.0.2", @@ -15588,7 +11967,6 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", - "dev": true, "requires": { "is-number": "^3.0.0", "repeat-string": "^1.6.1" @@ -15598,7 +11976,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "dev": true, "requires": { "kind-of": "^3.0.2" } @@ -15618,7 +11995,6 @@ "version": "2.4.3", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", - "dev": true, "requires": { "psl": "^1.1.24", "punycode": "^1.4.1" @@ -15727,7 +12103,6 @@ "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", - "dev": true, "requires": { "safe-buffer": "^5.0.1" } @@ -15736,14 +12111,12 @@ "version": "0.14.5", "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", - "dev": true, "optional": true }, "type-is": { "version": "1.6.16", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.16.tgz", "integrity": "sha512-HRkVv/5qY2G6I8iab9cI7v1bOIdhm94dVjQCPFElW9W+3GeDOSHmy2EBYe4VTApuzolPcmgFTN3ftVJRKR2J9Q==", - "dev": true, "requires": { "media-typer": "0.3.0", "mime-types": "~2.1.18" @@ -15857,6 +12230,11 @@ "integrity": "sha1-5z3T17DXxe2G+6xrCufYxqadUPo=", "dev": true }, + "underscore": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.9.1.tgz", + "integrity": "sha512-5/4etnCkd9c8gwgowi5/om/mYO5ajCaOgdzj/oW+0eQV9WxKBDZw5+ycmKmeaTXjInS/W0BzpGLo2xR2aBwZdg==" + }, "undertaker": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/undertaker/-/undertaker-1.2.1.tgz", @@ -15884,7 +12262,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==", - "dev": true, "requires": { "arr-union": "^3.1.0", "get-value": "^2.0.6", @@ -15902,23 +12279,33 @@ "through2-filter": "^3.0.0" } }, + "unique-string": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-1.0.0.tgz", + "integrity": "sha1-nhBXzKhRq7kzmPizOuGHuZyuwRo=", + "requires": { + "crypto-random-string": "^1.0.0" + } + }, "universalify": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.1.tgz", - "integrity": "sha1-+nG63UQ3r0wUiEHjs7Fl+enlkLc=", - "dev": true + "integrity": "sha1-+nG63UQ3r0wUiEHjs7Fl+enlkLc=" + }, + "unorm": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/unorm/-/unorm-1.6.0.tgz", + "integrity": "sha512-b2/KCUlYZUeA7JFUuRJZPUtr4gZvBh7tavtv4fvk4+KV9pfGiR6CQAQAWl49ZpR3ts2dk4FYkP7EIgDJoiOLDA==" }, "unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=", - "dev": true + "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" }, "unset-value": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=", - "dev": true, "requires": { "has-value": "^0.3.1", "isobject": "^3.0.0" @@ -15928,7 +12315,6 @@ "version": "0.3.1", "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=", - "dev": true, "requires": { "get-value": "^2.0.3", "has-values": "^0.1.4", @@ -15939,7 +12325,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", - "dev": true, "requires": { "isarray": "1.0.0" } @@ -15949,28 +12334,62 @@ "has-values": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", - "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=", - "dev": true + "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=" }, "isobject": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=" } } }, + "unzip-response": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/unzip-response/-/unzip-response-2.0.1.tgz", + "integrity": "sha1-0vD3N9FrBhXnKmk17QQhRXLVb5c=" + }, "upath": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/upath/-/upath-1.1.0.tgz", "integrity": "sha512-bzpH/oBhoS/QI/YtbkqCg6VEiPYjSZtrHQM6/QnJS6OL9pKUFLqb3aFh4Scvwm45+7iAgiMkLhSbaZxUqmrprw==", "dev": true }, + "update-notifier": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-2.5.0.tgz", + "integrity": "sha512-gwMdhgJHGuj/+wHJJs9e6PcCszpxR1b236igrOkUofGhqJuG+amlIKwApH1IW1WWl7ovZxsX49lMBWLxSdm5Dw==", + "requires": { + "boxen": "^1.2.1", + "chalk": "^2.0.1", + "configstore": "^3.0.0", + "import-lazy": "^2.1.0", + "is-ci": "^1.0.10", + "is-installed-globally": "^0.1.0", + "is-npm": "^1.0.0", + "latest-version": "^3.0.0", + "semver-diff": "^2.0.0", + "xdg-basedir": "^3.0.0" + }, + "dependencies": { + "configstore": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/configstore/-/configstore-3.1.2.tgz", + "integrity": "sha512-vtv5HtGjcYUgFrXc6Kx747B83MRRVS5R1VTEQoXvuP+kMI+if6uywV0nDGoiydJRy4yk7h9od5Og0kxx4zUXmw==", + "requires": { + "dot-prop": "^4.1.0", + "graceful-fs": "^4.1.2", + "make-dir": "^1.0.0", + "unique-string": "^1.0.0", + "write-file-atomic": "^2.0.0", + "xdg-basedir": "^3.0.0" + } + } + } + }, "uri-js": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", - "dev": true, "requires": { "punycode": "^2.1.0" }, @@ -15978,16 +12397,14 @@ "punycode": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "dev": true + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" } } }, "urix": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", - "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=", - "dev": true + "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=" }, "url": { "version": "0.11.0", @@ -16007,11 +12424,18 @@ } } }, + "url-parse-lax": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-1.0.0.tgz", + "integrity": "sha1-evjzA2Rem9eaJy56FKxovAYJ2nM=", + "requires": { + "prepend-http": "^1.0.1" + } + }, "use": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/use/-/use-3.1.0.tgz", "integrity": "sha512-6UJEQM/L+mzC3ZJNM56Q4DFGLX/evKGRg15UJHGB9X5j5Z3AFbgZvjUh2yq/UJUY4U5dh7Fal++XbNg1uzpRAw==", - "dev": true, "requires": { "kind-of": "^6.0.2" }, @@ -16019,8 +12443,7 @@ "kind-of": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", - "dev": true + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==" } } }, @@ -16047,14 +12470,12 @@ "utils-merge": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=", - "dev": true + "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" }, "uuid": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", - "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==", - "dev": true + "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==" }, "v8flags": { "version": "3.1.3", @@ -16065,16 +12486,28 @@ "homedir-polyfill": "^1.0.1" } }, + "valid-identifier": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/valid-identifier/-/valid-identifier-0.0.2.tgz", + "integrity": "sha512-zaSmOW6ykXwrkX0YTuFUSoALNEKGaQHpxBJQLb3TXspRNDpBwbfrIQCZqAQ0LKBlKuyn2YOq7NNd6415hvZ33g==" + }, "validate-npm-package-license": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.3.tgz", "integrity": "sha512-63ZOUnL4SIXj4L0NixR3L1lcjO38crAbgrTpl28t8jjrfuiOBL5Iygm+60qPs/KsZGzPNg6Smnc/oY16QTjF0g==", - "dev": true, "requires": { "spdx-correct": "^3.0.0", "spdx-expression-parse": "^3.0.0" } }, + "validate-npm-package-name": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-3.0.0.tgz", + "integrity": "sha1-X6kS2B630MdK/BQN5zF/DKffQ34=", + "requires": { + "builtins": "^1.0.3" + } + }, "value-or-function": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/value-or-function/-/value-or-function-3.0.0.tgz", @@ -16084,14 +12517,12 @@ "vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=", - "dev": true + "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" }, "verror": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", - "dev": true, "requires": { "assert-plus": "^1.0.0", "core-util-is": "1.0.2", @@ -16101,8 +12532,7 @@ "assert-plus": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", - "dev": true + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" } } }, @@ -17353,7 +13783,6 @@ "version": "1.3.1", "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "dev": true, "requires": { "isexe": "^2.0.0" } @@ -17373,6 +13802,43 @@ "string-width": "^1.0.2 || 2" } }, + "widest-line": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-2.0.1.tgz", + "integrity": "sha512-Ba5m9/Fa4Xt9eb2ELXt77JxVDV8w7qQrH0zS/TWSJdLyAwQjWoOzpzj5lwVftDz6n/EOu3tNACS84v509qwnJA==", + "requires": { + "string-width": "^2.1.1" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=" + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=" + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + } + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "requires": { + "ansi-regex": "^3.0.0" + } + } + } + }, "win-release": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/win-release/-/win-release-1.1.1.tgz", @@ -17388,6 +13854,81 @@ "integrity": "sha1-VDjNLqk7IC76Ohn+iIeu58lPnJ0=", "dev": true }, + "windows-release": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/windows-release/-/windows-release-3.2.0.tgz", + "integrity": "sha512-QTlz2hKLrdqukrsapKsINzqMgOUpQW268eJ0OaOpJN32h272waxR9fkB9VoWRtK7uKHG5EHJcTXQBD8XZVJkFA==", + "requires": { + "execa": "^1.0.0" + }, + "dependencies": { + "cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "requires": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "execa": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", + "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", + "requires": { + "cross-spawn": "^6.0.0", + "get-stream": "^4.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + } + }, + "get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "requires": { + "pump": "^3.0.0" + } + }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + } + } + }, + "with-open-file": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/with-open-file/-/with-open-file-0.1.6.tgz", + "integrity": "sha512-SQS05JekbtwQSgCYlBsZn/+m2gpn4zWsqpCYIrCHva0+ojXcnmUEPsBN6Ipoz3vmY/81k5PvYEWSxER2g4BTqA==", + "requires": { + "p-finally": "^1.0.0", + "p-try": "^2.1.0", + "pify": "^4.0.1" + }, + "dependencies": { + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==" + }, + "pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==" + } + } + }, "wordwrap": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.2.tgz", @@ -17407,8 +13948,17 @@ "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + }, + "write-file-atomic": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.4.3.tgz", + "integrity": "sha512-GaETH5wwsX+GcnzhPgKcKjJ6M2Cq3/iZp1WyY/X1CSqrW+jVNM9Y7D8EC2sM4ZG/V8wZlSniJnCKWPmBYAucRQ==", + "requires": { + "graceful-fs": "^4.1.11", + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.2" + } }, "ws": { "version": "3.3.2", @@ -17421,6 +13971,25 @@ "ultron": "~1.1.0" } }, + "xcode": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/xcode/-/xcode-2.0.0.tgz", + "integrity": "sha512-5xF6RCjAdDEiEsbbZaS/gBRt3jZ/177otZcpoLCjGN/u1LrfgH7/Sgeeavpr/jELpyDqN2im3AKosl2G2W8hfw==", + "requires": { + "simple-plist": "^1.0.0", + "uuid": "^3.3.2" + } + }, + "xdg-basedir": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-3.0.0.tgz", + "integrity": "sha1-SWsswQnsqNus/i3HK2A8F8WHCtQ=" + }, + "xml-escape": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/xml-escape/-/xml-escape-1.1.0.tgz", + "integrity": "sha1-OQTBQ/qOs6ADDsZG0pAqLxtwbEQ=" + }, "xml2js": { "version": "0.4.19", "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.19.tgz", @@ -17434,14 +14003,12 @@ "xmlbuilder": { "version": "9.0.7", "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz", - "integrity": "sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0=", - "dev": true + "integrity": "sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0=" }, "xmldom": { "version": "0.1.27", "resolved": "https://registry.npmjs.org/xmldom/-/xmldom-0.1.27.tgz", - "integrity": "sha1-1QH5ezvbQDr4757MIFcxh6rawOk=", - "dev": true + "integrity": "sha1-1QH5ezvbQDr4757MIFcxh6rawOk=" }, "xtend": { "version": "4.0.1", @@ -17458,8 +14025,7 @@ "yallist": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", - "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=", - "dev": true + "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=" }, "yargs": { "version": "7.1.0", diff --git a/package.json b/package.json index 6eae16161..28d6cc054 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "moodlemobile", - "version": "3.7.2", + "version": "3.8.0", "description": "The official app for Moodle.", "author": { "name": "Moodle Pty Ltd.", @@ -57,6 +57,7 @@ "@ionic-native/file": "4.17.0", "@ionic-native/file-opener": "4.17.0", "@ionic-native/file-transfer": "4.17.0", + "@ionic-native/geolocation": "4.17.0", "@ionic-native/globalization": "4.17.0", "@ionic-native/in-app-browser": "4.17.0", "@ionic-native/keyboard": "4.17.0", @@ -78,20 +79,22 @@ "@types/cordova-plugin-network-information": "0.0.3", "@types/node": "8.10.19", "@types/promise.prototype.finally": "2.0.2", + "ajv": "6.10.2", "chart.js": "2.7.2", - "com-darryncampbell-cordova-plugin-intent": "1.1.7", - "cordova": "8.1.2", - "cordova-android": "7.1.2", + "com-darryncampbell-cordova-plugin-intent": "1.3.0", + "cordova": "9.0.0", + "cordova-android": "8.0.0", "cordova-android-support-gradle-release": "3.0.1", "cordova-clipboard": "1.3.0", - "cordova-ios": "4.5.5", + "cordova-ios": "5.0.1", "cordova-plugin-badge": "0.8.8", "cordova-plugin-camera": "4.1.0", "cordova-plugin-customurlscheme": "4.4.0", "cordova-plugin-device": "2.0.3", "cordova-plugin-file": "6.0.2", - "cordova-plugin-file-opener2": "2.0.19", + "cordova-plugin-file-opener2": "2.2.1", "cordova-plugin-file-transfer": "1.7.1", + "cordova-plugin-geolocation": "4.0.2", "cordova-plugin-globalization": "1.11.0", "cordova-plugin-inappbrowser": "3.1.0", "cordova-plugin-ionic-keyboard": "2.1.3", @@ -103,13 +106,14 @@ "cordova-plugin-statusbar": "2.4.3", "cordova-plugin-whitelist": "1.3.4", "cordova-plugin-zip": "3.1.0", - "cordova-sqlite-storage": "2.6.0", + "cordova-sqlite-storage": "3.4.0", "cordova-support-google-services": "1.2.1", "es6-promise-plugin": "4.2.2", "font-awesome": "4.7.0", "ionic-angular": "3.9.3", "ionicons": "3.0.0", "jszip": "3.1.5", + "mathjax": "2.7.2", "moment": "2.22.2", "nl.kingsquare.cordova.background-audio": "1.0.1", "phonegap-plugin-multidex": "1.0.0", @@ -176,6 +180,9 @@ "phonegap-plugin-push": { "ANDROID_SUPPORT_V13_VERSION": "27.+", "FCM_VERSION": "17.0.+" + }, + "cordova-plugin-geolocation": { + "GEOLOCATION_USAGE_DESCRIPTION": "To locate you" } } }, @@ -216,7 +223,7 @@ "category": "public.app-category.education", "icon": "resources/desktop/icon.icns", "target": "mas", - "bundleVersion": "3.7.2", + "bundleVersion": "3.8.0", "extendInfo": { "ElectronTeamID": "2NU57U5PAW" } diff --git a/resources/android/icon-foreground.png b/resources/android/icon-foreground.png new file mode 100644 index 000000000..cdadeb07f Binary files /dev/null and b/resources/android/icon-foreground.png differ diff --git a/resources/android/icon-foreground.svg b/resources/android/icon-foreground.svg new file mode 100644 index 000000000..add166f8b --- /dev/null +++ b/resources/android/icon-foreground.svg @@ -0,0 +1,98 @@ + + + + + + image/svg+xml + + MoodleApp_Icon_White_RGB + + + + + + + + MoodleApp_Icon_White_RGB + + + + + + + + diff --git a/resources/android/icon/drawable-hdpi-icon.png b/resources/android/icon/drawable-hdpi-icon.png index b155f74e0..e96fcfe45 100644 Binary files a/resources/android/icon/drawable-hdpi-icon.png and b/resources/android/icon/drawable-hdpi-icon.png differ diff --git a/resources/android/icon/drawable-ldpi-icon.png b/resources/android/icon/drawable-ldpi-icon.png index 4d607a119..3becf6217 100644 Binary files a/resources/android/icon/drawable-ldpi-icon.png and b/resources/android/icon/drawable-ldpi-icon.png differ diff --git a/resources/android/icon/drawable-mdpi-icon.png b/resources/android/icon/drawable-mdpi-icon.png index 33e7a52b1..16bb102e8 100644 Binary files a/resources/android/icon/drawable-mdpi-icon.png and b/resources/android/icon/drawable-mdpi-icon.png differ diff --git a/resources/android/icon/drawable-xhdpi-icon.png b/resources/android/icon/drawable-xhdpi-icon.png index 1503f3377..a43251c56 100644 Binary files a/resources/android/icon/drawable-xhdpi-icon.png and b/resources/android/icon/drawable-xhdpi-icon.png differ diff --git a/resources/android/icon/drawable-xxhdpi-icon.png b/resources/android/icon/drawable-xxhdpi-icon.png index 2c5af19f9..48ce4f3c9 100644 Binary files a/resources/android/icon/drawable-xxhdpi-icon.png and b/resources/android/icon/drawable-xxhdpi-icon.png differ diff --git a/resources/android/icon/drawable-xxxhdpi-icon.png b/resources/android/icon/drawable-xxxhdpi-icon.png index b19a28d9f..53e0e9d67 100644 Binary files a/resources/android/icon/drawable-xxxhdpi-icon.png and b/resources/android/icon/drawable-xxxhdpi-icon.png differ diff --git a/resources/android/icon/hdpi-foreground.png b/resources/android/icon/hdpi-foreground.png new file mode 100644 index 000000000..1616ddedf Binary files /dev/null and b/resources/android/icon/hdpi-foreground.png differ diff --git a/resources/android/icon/ldpi-foreground.png b/resources/android/icon/ldpi-foreground.png new file mode 100644 index 000000000..d24c14f7d Binary files /dev/null and b/resources/android/icon/ldpi-foreground.png differ diff --git a/resources/android/icon/mdpi-foreground.png b/resources/android/icon/mdpi-foreground.png new file mode 100644 index 000000000..41ba86c39 Binary files /dev/null and b/resources/android/icon/mdpi-foreground.png differ diff --git a/resources/android/icon/xhdpi-foreground.png b/resources/android/icon/xhdpi-foreground.png new file mode 100644 index 000000000..dcb852e00 Binary files /dev/null and b/resources/android/icon/xhdpi-foreground.png differ diff --git a/resources/android/icon/xxhdpi-foreground.png b/resources/android/icon/xxhdpi-foreground.png new file mode 100644 index 000000000..fa39ca199 Binary files /dev/null and b/resources/android/icon/xxhdpi-foreground.png differ diff --git a/resources/android/icon/xxxhdpi-foreground.png b/resources/android/icon/xxxhdpi-foreground.png new file mode 100644 index 000000000..8ba201678 Binary files /dev/null and b/resources/android/icon/xxxhdpi-foreground.png differ diff --git a/resources/android/splash/drawable-land-hdpi-screen.png b/resources/android/splash/drawable-land-hdpi-screen.png index 2bbe24c29..bf0f39e5a 100644 Binary files a/resources/android/splash/drawable-land-hdpi-screen.png and b/resources/android/splash/drawable-land-hdpi-screen.png differ diff --git a/resources/android/splash/drawable-land-ldpi-screen.png b/resources/android/splash/drawable-land-ldpi-screen.png index c043d3597..38728e3eb 100644 Binary files a/resources/android/splash/drawable-land-ldpi-screen.png and b/resources/android/splash/drawable-land-ldpi-screen.png differ diff --git a/resources/android/splash/drawable-land-mdpi-screen.png b/resources/android/splash/drawable-land-mdpi-screen.png index d5eab5c1d..6c94d5721 100644 Binary files a/resources/android/splash/drawable-land-mdpi-screen.png and b/resources/android/splash/drawable-land-mdpi-screen.png differ diff --git a/resources/android/splash/drawable-land-xhdpi-screen.png b/resources/android/splash/drawable-land-xhdpi-screen.png index 0398df2bd..34d11f804 100644 Binary files a/resources/android/splash/drawable-land-xhdpi-screen.png and b/resources/android/splash/drawable-land-xhdpi-screen.png differ diff --git a/resources/android/splash/drawable-land-xxhdpi-screen.png b/resources/android/splash/drawable-land-xxhdpi-screen.png index bcb36cc12..8043bf1a7 100644 Binary files a/resources/android/splash/drawable-land-xxhdpi-screen.png and b/resources/android/splash/drawable-land-xxhdpi-screen.png differ diff --git a/resources/android/splash/drawable-land-xxxhdpi-screen.png b/resources/android/splash/drawable-land-xxxhdpi-screen.png index 686f13132..3688f5732 100644 Binary files a/resources/android/splash/drawable-land-xxxhdpi-screen.png and b/resources/android/splash/drawable-land-xxxhdpi-screen.png differ diff --git a/resources/android/splash/drawable-port-hdpi-screen.png b/resources/android/splash/drawable-port-hdpi-screen.png index 8256b58de..8c7353e9d 100644 Binary files a/resources/android/splash/drawable-port-hdpi-screen.png and b/resources/android/splash/drawable-port-hdpi-screen.png differ diff --git a/resources/android/splash/drawable-port-ldpi-screen.png b/resources/android/splash/drawable-port-ldpi-screen.png index d41cda737..b5605fe72 100644 Binary files a/resources/android/splash/drawable-port-ldpi-screen.png and b/resources/android/splash/drawable-port-ldpi-screen.png differ diff --git a/resources/android/splash/drawable-port-mdpi-screen.png b/resources/android/splash/drawable-port-mdpi-screen.png index ac8009538..a3f133a16 100644 Binary files a/resources/android/splash/drawable-port-mdpi-screen.png and b/resources/android/splash/drawable-port-mdpi-screen.png differ diff --git a/resources/android/splash/drawable-port-xhdpi-screen.png b/resources/android/splash/drawable-port-xhdpi-screen.png index 3defde516..777b8c86f 100644 Binary files a/resources/android/splash/drawable-port-xhdpi-screen.png and b/resources/android/splash/drawable-port-xhdpi-screen.png differ diff --git a/resources/android/splash/drawable-port-xxhdpi-screen.png b/resources/android/splash/drawable-port-xxhdpi-screen.png index 00c8a8055..58f7d76b1 100644 Binary files a/resources/android/splash/drawable-port-xxhdpi-screen.png and b/resources/android/splash/drawable-port-xxhdpi-screen.png differ diff --git a/resources/android/splash/drawable-port-xxxhdpi-screen.png b/resources/android/splash/drawable-port-xxxhdpi-screen.png index 53b5b8661..460d49a84 100644 Binary files a/resources/android/splash/drawable-port-xxxhdpi-screen.png and b/resources/android/splash/drawable-port-xxxhdpi-screen.png differ diff --git a/resources/ios/icon/icon-1024.png b/resources/ios/icon/icon-1024.png index 377e167bf..9df9c6fe2 100644 Binary files a/resources/ios/icon/icon-1024.png and b/resources/ios/icon/icon-1024.png differ diff --git a/resources/ios/icon/icon-108@2x.png b/resources/ios/icon/icon-108@2x.png new file mode 100644 index 000000000..b1ee7709a Binary files /dev/null and b/resources/ios/icon/icon-108@2x.png differ diff --git a/resources/ios/icon/icon-20.png b/resources/ios/icon/icon-20.png new file mode 100644 index 000000000..eab5d3e35 Binary files /dev/null and b/resources/ios/icon/icon-20.png differ diff --git a/resources/ios/icon/icon-20@2x.png b/resources/ios/icon/icon-20@2x.png new file mode 100644 index 000000000..125cfc725 Binary files /dev/null and b/resources/ios/icon/icon-20@2x.png differ diff --git a/resources/ios/icon/icon-20@3x.png b/resources/ios/icon/icon-20@3x.png new file mode 100644 index 000000000..bfa0ecdba Binary files /dev/null and b/resources/ios/icon/icon-20@3x.png differ diff --git a/resources/ios/icon/icon-24@2x.png b/resources/ios/icon/icon-24@2x.png new file mode 100644 index 000000000..e3b06bfc6 Binary files /dev/null and b/resources/ios/icon/icon-24@2x.png differ diff --git a/resources/ios/icon/icon-27.5@2x.png b/resources/ios/icon/icon-27.5@2x.png new file mode 100644 index 000000000..07b03695d Binary files /dev/null and b/resources/ios/icon/icon-27.5@2x.png differ diff --git a/resources/ios/icon/icon-29.png b/resources/ios/icon/icon-29.png new file mode 100644 index 000000000..fe94321bb Binary files /dev/null and b/resources/ios/icon/icon-29.png differ diff --git a/resources/ios/icon/icon-29@2x.png b/resources/ios/icon/icon-29@2x.png new file mode 100644 index 000000000..a0b6b0438 Binary files /dev/null and b/resources/ios/icon/icon-29@2x.png differ diff --git a/resources/ios/icon/icon-29@3x.png b/resources/ios/icon/icon-29@3x.png new file mode 100644 index 000000000..0756d9fed Binary files /dev/null and b/resources/ios/icon/icon-29@3x.png differ diff --git a/resources/ios/icon/icon-40.png b/resources/ios/icon/icon-40.png index d257a5303..125cfc725 100644 Binary files a/resources/ios/icon/icon-40.png and b/resources/ios/icon/icon-40.png differ diff --git a/resources/ios/icon/icon-40@2x.png b/resources/ios/icon/icon-40@2x.png index 64ab98ba1..25fb10e3a 100644 Binary files a/resources/ios/icon/icon-40@2x.png and b/resources/ios/icon/icon-40@2x.png differ diff --git a/resources/ios/icon/icon-40@3x.png b/resources/ios/icon/icon-40@3x.png index f9c0c0490..9ec2a7f12 100644 Binary files a/resources/ios/icon/icon-40@3x.png and b/resources/ios/icon/icon-40@3x.png differ diff --git a/resources/ios/icon/icon-44@2x.png b/resources/ios/icon/icon-44@2x.png new file mode 100644 index 000000000..e1f2e56bf Binary files /dev/null and b/resources/ios/icon/icon-44@2x.png differ diff --git a/resources/ios/icon/icon-50.png b/resources/ios/icon/icon-50.png index 0fefa6608..462c1ddda 100644 Binary files a/resources/ios/icon/icon-50.png and b/resources/ios/icon/icon-50.png differ diff --git a/resources/ios/icon/icon-50@2x.png b/resources/ios/icon/icon-50@2x.png index 503c6de51..9b85f2603 100644 Binary files a/resources/ios/icon/icon-50@2x.png and b/resources/ios/icon/icon-50@2x.png differ diff --git a/resources/ios/icon/icon-60.png b/resources/ios/icon/icon-60.png index f3ae32330..bfa0ecdba 100644 Binary files a/resources/ios/icon/icon-60.png and b/resources/ios/icon/icon-60.png differ diff --git a/resources/ios/icon/icon-60@2x.png b/resources/ios/icon/icon-60@2x.png index 83acef985..9ec2a7f12 100644 Binary files a/resources/ios/icon/icon-60@2x.png and b/resources/ios/icon/icon-60@2x.png differ diff --git a/resources/ios/icon/icon-60@3x.png b/resources/ios/icon/icon-60@3x.png index 2be3dc5ca..781bfc7eb 100644 Binary files a/resources/ios/icon/icon-60@3x.png and b/resources/ios/icon/icon-60@3x.png differ diff --git a/resources/ios/icon/icon-72.png b/resources/ios/icon/icon-72.png index 8ebd26a51..948cb58bf 100644 Binary files a/resources/ios/icon/icon-72.png and b/resources/ios/icon/icon-72.png differ diff --git a/resources/ios/icon/icon-72@2x.png b/resources/ios/icon/icon-72@2x.png index 42c444fa8..57192935c 100644 Binary files a/resources/ios/icon/icon-72@2x.png and b/resources/ios/icon/icon-72@2x.png differ diff --git a/resources/ios/icon/icon-76.png b/resources/ios/icon/icon-76.png index 5b4184494..a646758f9 100644 Binary files a/resources/ios/icon/icon-76.png and b/resources/ios/icon/icon-76.png differ diff --git a/resources/ios/icon/icon-76@2x.png b/resources/ios/icon/icon-76@2x.png index d50c660f4..b52383bf9 100644 Binary files a/resources/ios/icon/icon-76@2x.png and b/resources/ios/icon/icon-76@2x.png differ diff --git a/resources/ios/icon/icon-83.5@2x.png b/resources/ios/icon/icon-83.5@2x.png index ccc01673f..9b2542500 100644 Binary files a/resources/ios/icon/icon-83.5@2x.png and b/resources/ios/icon/icon-83.5@2x.png differ diff --git a/resources/ios/icon/icon-86@2x.png b/resources/ios/icon/icon-86@2x.png new file mode 100644 index 000000000..c791d4cab Binary files /dev/null and b/resources/ios/icon/icon-86@2x.png differ diff --git a/resources/ios/icon/icon-98@2x.png b/resources/ios/icon/icon-98@2x.png new file mode 100644 index 000000000..01f600e0c Binary files /dev/null and b/resources/ios/icon/icon-98@2x.png differ diff --git a/resources/ios/icon/icon-small.png b/resources/ios/icon/icon-small.png index f7978170f..fe94321bb 100644 Binary files a/resources/ios/icon/icon-small.png and b/resources/ios/icon/icon-small.png differ diff --git a/resources/ios/icon/icon-small@2x.png b/resources/ios/icon/icon-small@2x.png index 1ab363c66..a0b6b0438 100644 Binary files a/resources/ios/icon/icon-small@2x.png and b/resources/ios/icon/icon-small@2x.png differ diff --git a/resources/ios/icon/icon-small@3x.png b/resources/ios/icon/icon-small@3x.png index 8ded4e1ea..0756d9fed 100644 Binary files a/resources/ios/icon/icon-small@3x.png and b/resources/ios/icon/icon-small@3x.png differ diff --git a/resources/ios/icon/icon.png b/resources/ios/icon/icon.png index 9c018d104..23aec9534 100644 Binary files a/resources/ios/icon/icon.png and b/resources/ios/icon/icon.png differ diff --git a/resources/ios/icon/icon@2x.png b/resources/ios/icon/icon@2x.png index e3bc95e6a..e18bedc51 100644 Binary files a/resources/ios/icon/icon@2x.png and b/resources/ios/icon/icon@2x.png differ diff --git a/resources/ios/splash/Default-1792h~iphone.png b/resources/ios/splash/Default-1792h~iphone.png new file mode 100644 index 000000000..e725ae101 Binary files /dev/null and b/resources/ios/splash/Default-1792h~iphone.png differ diff --git a/resources/ios/splash/Default-2436h.png b/resources/ios/splash/Default-2436h.png new file mode 100644 index 000000000..4e7fb644c Binary files /dev/null and b/resources/ios/splash/Default-2436h.png differ diff --git a/resources/ios/splash/Default-2688h~iphone.png b/resources/ios/splash/Default-2688h~iphone.png new file mode 100644 index 000000000..af1b72ec8 Binary files /dev/null and b/resources/ios/splash/Default-2688h~iphone.png differ diff --git a/resources/ios/splash/Default-568h@2x~iphone.png b/resources/ios/splash/Default-568h@2x~iphone.png index 9e7f3dde5..6e5005418 100644 Binary files a/resources/ios/splash/Default-568h@2x~iphone.png and b/resources/ios/splash/Default-568h@2x~iphone.png differ diff --git a/resources/ios/splash/Default-667h.png b/resources/ios/splash/Default-667h.png index 9b6ee14a5..6c2e7b17e 100644 Binary files a/resources/ios/splash/Default-667h.png and b/resources/ios/splash/Default-667h.png differ diff --git a/resources/ios/splash/Default-736h.png b/resources/ios/splash/Default-736h.png index 1773d666e..51e8cffc6 100644 Binary files a/resources/ios/splash/Default-736h.png and b/resources/ios/splash/Default-736h.png differ diff --git a/resources/ios/splash/Default-Landscape-1792h~iphone.png b/resources/ios/splash/Default-Landscape-1792h~iphone.png new file mode 100644 index 000000000..ed0dcc13c Binary files /dev/null and b/resources/ios/splash/Default-Landscape-1792h~iphone.png differ diff --git a/resources/ios/splash/Default-Landscape-2436h.png b/resources/ios/splash/Default-Landscape-2436h.png new file mode 100644 index 000000000..8933029ae Binary files /dev/null and b/resources/ios/splash/Default-Landscape-2436h.png differ diff --git a/resources/ios/splash/Default-Landscape-2688h~iphone.png b/resources/ios/splash/Default-Landscape-2688h~iphone.png new file mode 100644 index 000000000..02a152e5e Binary files /dev/null and b/resources/ios/splash/Default-Landscape-2688h~iphone.png differ diff --git a/resources/ios/splash/Default-Landscape-736h.png b/resources/ios/splash/Default-Landscape-736h.png index 7cfc62946..8123091af 100644 Binary files a/resources/ios/splash/Default-Landscape-736h.png and b/resources/ios/splash/Default-Landscape-736h.png differ diff --git a/resources/ios/splash/Default-Landscape@2x~ipad.png b/resources/ios/splash/Default-Landscape@2x~ipad.png index c7a2b2494..63398a5cd 100644 Binary files a/resources/ios/splash/Default-Landscape@2x~ipad.png and b/resources/ios/splash/Default-Landscape@2x~ipad.png differ diff --git a/resources/ios/splash/Default-Landscape@~ipadpro.png b/resources/ios/splash/Default-Landscape@~ipadpro.png index ab00ad820..add5c8016 100644 Binary files a/resources/ios/splash/Default-Landscape@~ipadpro.png and b/resources/ios/splash/Default-Landscape@~ipadpro.png differ diff --git a/resources/ios/splash/Default-Landscape~ipad.png b/resources/ios/splash/Default-Landscape~ipad.png index 995decfa1..6e5fab597 100644 Binary files a/resources/ios/splash/Default-Landscape~ipad.png and b/resources/ios/splash/Default-Landscape~ipad.png differ diff --git a/resources/ios/splash/Default-Portrait@2x~ipad.png b/resources/ios/splash/Default-Portrait@2x~ipad.png index 5a9b09090..1ebad1ba7 100644 Binary files a/resources/ios/splash/Default-Portrait@2x~ipad.png and b/resources/ios/splash/Default-Portrait@2x~ipad.png differ diff --git a/resources/ios/splash/Default-Portrait@~ipadpro.png b/resources/ios/splash/Default-Portrait@~ipadpro.png index dd8141a79..c158dafff 100644 Binary files a/resources/ios/splash/Default-Portrait@~ipadpro.png and b/resources/ios/splash/Default-Portrait@~ipadpro.png differ diff --git a/resources/ios/splash/Default-Portrait~ipad.png b/resources/ios/splash/Default-Portrait~ipad.png index a430930f2..f7a7b564b 100644 Binary files a/resources/ios/splash/Default-Portrait~ipad.png and b/resources/ios/splash/Default-Portrait~ipad.png differ diff --git a/resources/ios/splash/Default@2x~iphone.png b/resources/ios/splash/Default@2x~iphone.png index dfd140f53..7149bb0d4 100644 Binary files a/resources/ios/splash/Default@2x~iphone.png and b/resources/ios/splash/Default@2x~iphone.png differ diff --git a/resources/ios/splash/Default@2x~universal~anyany.png b/resources/ios/splash/Default@2x~universal~anyany.png index 5d21a832c..1bb650ee0 100644 Binary files a/resources/ios/splash/Default@2x~universal~anyany.png and b/resources/ios/splash/Default@2x~universal~anyany.png differ diff --git a/resources/ios/splash/Default~iphone.png b/resources/ios/splash/Default~iphone.png index ff0484e50..a3f133a16 100644 Binary files a/resources/ios/splash/Default~iphone.png and b/resources/ios/splash/Default~iphone.png differ diff --git a/resources/values/colors.xml b/resources/values/colors.xml new file mode 100644 index 000000000..841ad9a24 --- /dev/null +++ b/resources/values/colors.xml @@ -0,0 +1,4 @@ + + + #FFFFFF + diff --git a/scripts/aot.sh b/scripts/aot.sh index 541375435..81c4d4ea8 100755 --- a/scripts/aot.sh +++ b/scripts/aot.sh @@ -51,7 +51,7 @@ if [ ! -z $GIT_ORG ] && [ ! -z $GIT_TOKEN ] ; then git checkout $TRAVIS_BRANCH - rm -Rf assets build index.html templates www destkop + rm -Rf assets build index.html templates www destkop lib if [ $TRAVIS_BRANCH == 'desktop' ] ; then cp -Rf ../$gitfolder/desktop ./ diff --git a/scripts/functions.sh b/scripts/functions.sh index ed1472357..55af8536b 100644 --- a/scripts/functions.sh +++ b/scripts/functions.sh @@ -20,22 +20,22 @@ function print_success { } function print_error { - tput setaf 1; echo " ERROR: $1" + tput setaf 1; echo " ERROR: $1"; tput sgr0 } function print_ok { - tput setaf 2; echo " OK: $1" + tput setaf 2; echo " OK: $1"; tput sgr0 echo } function print_message { - tput setaf 3; echo "-------- $1" + tput setaf 3; echo "-------- $1"; tput sgr0 echo } function print_title { stepnumber=$(($stepnumber + 1)) echo - tput setaf 5; echo "$stepnumber $1" - tput setaf 5; echo '==================' + tput setaf 5; echo "$stepnumber $1"; tput sgr0 + tput setaf 5; echo '=================='; tput sgr0 } \ No newline at end of file diff --git a/scripts/get_ws_changes.php b/scripts/get_ws_changes.php new file mode 100644 index 000000000..d5b91f9f0 --- /dev/null +++ b/scripts/get_ws_changes.php @@ -0,0 +1,99 @@ +. + +/** + * Script for converting a PHP WS structure to a TS type. + * + * The first parameter (required) is the path to the Moodle installation to use. + * The second parameter (required) is the name to the WS to convert. + * The third parameter (optional) is a number: 1 to convert the params structure, + * 0 to convert the returns structure. Defaults to 0. + */ + +if (!isset($argv[1])) { + echo "ERROR: Please pass the path to the folder containing the Moodle installations as the first parameter.\n"; + die(); +} + + +if (!isset($argv[2])) { + echo "ERROR: Please pass the WS name as the second parameter.\n"; + die(); +} + +define('CLI_SCRIPT', true); +require_once('ws_to_ts_functions.php'); + +$versions = array('master', '37', '36', '35', '34', '33', '32', '31'); + +$moodlespath = $argv[1]; +$wsname = $argv[2]; +$useparams = !!(isset($argv[3]) && $argv[3]); +$pathseparator = '/'; + +// Get the path to the script. +$index = strrpos(__FILE__, $pathseparator); +if ($index === false) { + $pathseparator = '\\'; + $index = strrpos(__FILE__, $pathseparator); +} +$scriptfolder = substr(__FILE__, 0, $index); +$scriptpath = concatenate_paths($scriptfolder, 'get_ws_structure.php', $pathseparator); + +$previousstructure = null; +$previousversion = null; +$libsloaded = false; + +foreach ($versions as $version) { + $moodlepath = concatenate_paths($moodlespath, 'stable_' . $version, $pathseparator); + + if (!$libsloaded) { + $libsloaded = true; + + require($moodlepath . '/config.php'); + require($CFG->dirroot . '/webservice/lib.php'); + } + + // Get the structure in this Moodle version. + $structure = shell_exec("php $scriptpath $moodlepath $wsname " . ($useparams ? 'true' : '')); + + if (strpos($structure, 'ERROR:') === 0) { + echo "WS not found in version $version. Stop.\n"; + break; + } + + $structure = unserialize($structure); + + if ($previousstructure != null) { + echo "*** Check changes from version $version to $previousversion ***\n"; + + $messages = detect_ws_changes($previousstructure, $structure); + + if (count($messages) > 0) { + $haschanged = true; + + foreach($messages as $message) { + echo "$message\n"; + } + } else { + echo "No changes found.\n"; + } + echo "\n"; + } + + $previousstructure = $structure; + $previousversion = $version; +} diff --git a/scripts/get_ws_structure.php b/scripts/get_ws_structure.php new file mode 100644 index 000000000..bfa975ee9 --- /dev/null +++ b/scripts/get_ws_structure.php @@ -0,0 +1,55 @@ +. + +/** + * Script for getting the PHP structure of a WS returns or params. + * + * The first parameter (required) is the path to the Moodle installation to use. + * The second parameter (required) is the name to the WS to convert. + * The third parameter (optional) is a number: 1 to convert the params structure, + * 0 to convert the returns structure. Defaults to 0. + */ + +if (!isset($argv[1])) { + echo "ERROR: Please pass the Moodle path as the first parameter.\n"; + die(); +} + + +if (!isset($argv[2])) { + echo "ERROR: Please pass the WS name as the second parameter.\n"; + die(); +} + +$moodlepath = $argv[1]; +$wsname = $argv[2]; +$useparams = !!(isset($argv[3]) && $argv[3]); + +define('CLI_SCRIPT', true); + +require($moodlepath . '/config.php'); +require($CFG->dirroot . '/webservice/lib.php'); +require_once('ws_to_ts_functions.php'); + +$structure = get_ws_structure($wsname, $useparams); + +if ($structure === false) { + echo "ERROR: The WS wasn't found in this Moodle installation.\n"; + die(); +} + +remove_default_closures($structure); +echo serialize($structure); diff --git a/scripts/get_ws_ts.php b/scripts/get_ws_ts.php new file mode 100644 index 000000000..900a153b8 --- /dev/null +++ b/scripts/get_ws_ts.php @@ -0,0 +1,62 @@ +. + +/** + * Script for converting a PHP WS structure to a TS type. + * + * The first parameter (required) is the path to the Moodle installation to use. + * The second parameter (required) is the name to the WS to convert. + * The third parameter (optional) is the name to put to the TS type. Defaults to "TypeName". + * The fourth parameter (optional) is a number: 1 to convert the params structure, + * 0 to convert the returns structure. Defaults to 0. + */ + +if (!isset($argv[1])) { + echo "ERROR: Please pass the Moodle path as the first parameter.\n"; + die(); +} + + +if (!isset($argv[2])) { + echo "ERROR: Please pass the WS name as the second parameter.\n"; + die(); +} + +$moodlepath = $argv[1]; +$wsname = $argv[2]; +$typename = isset($argv[3]) ? $argv[3] : 'TypeName'; +$useparams = !!(isset($argv[4]) && $argv[4]); + +define('CLI_SCRIPT', true); + +require($moodlepath . '/config.php'); +require($CFG->dirroot . '/webservice/lib.php'); +require_once('ws_to_ts_functions.php'); + +$structure = get_ws_structure($wsname, $useparams); + +if ($structure === false) { + echo "ERROR: The WS wasn't found in this Moodle installation.\n"; + die(); +} + +if ($useparams) { + $description = "Params of WS $wsname."; +} else { + $description = "Result of WS $wsname."; +} + +echo get_ts_doc(null, $description, '') . "export type $typename = " . convert_to_ts(null, $structure, $useparams) . ";\n"; diff --git a/scripts/lang_functions.php b/scripts/lang_functions.php index af1e2ab3b..7765841ca 100644 --- a/scripts/lang_functions.php +++ b/scripts/lang_functions.php @@ -58,6 +58,7 @@ function build_languages($languages, $keys, $added_langs = []) { } function get_langindex_keys() { + $local = 0; // Process the index file, just once. $keys = file_get_contents('langindex.json'); $keys = (array) json_decode($keys); @@ -67,6 +68,7 @@ function get_langindex_keys() { if ($value == 'local_moodlemobileapp') { $map->file = $value; $map->string = $key; + $local++; } else { $exp = explode('/', $value, 2); $map->file = $exp[0]; @@ -87,7 +89,7 @@ function get_langindex_keys() { } $total = count($keys); - echo "Total strings to translate $total\n"; + echo "Total strings to translate $total ($local local)\n"; return $keys; } @@ -232,6 +234,10 @@ function build_lang($lang, $keys) { // Prevent double. $text = str_replace(array('{{{', '}}}'), array('{{', '}}'), $text); } else { + // @TODO: Remove that line when core.cannotconnect and core.login.invalidmoodleversion are completelly changed to use $a + if (($key == 'core.cannotconnect' || $key == 'core.login.invalidmoodleversion') && strpos($text, '2.4') != false) { + $text = str_replace('2.4', '{{$a}}', $text); + } $local++; } @@ -244,7 +250,11 @@ function build_lang($lang, $keys) { $success = count($translations); $percentage = floor($success/$total * 100); - echo "\t\t$success of $total -> $percentage% ($local local)\n"; + $bar = progressbar($percentage); + if (strlen($lang) <= 2 && !$parent) { + echo "\t"; + } + echo "\t\t$success of $total -> $percentage% $bar ($local local)\n"; if ($lang == 'en') { generate_local_moodlemobileapp($keys, $translations); @@ -254,6 +264,11 @@ function build_lang($lang, $keys) { return true; } +function progressbar($percentage) { + $done = $percentage/10; + return "\t".str_repeat('=', $done) . str_repeat('-', 10-$done); +} + function detect_lang($lang, $keys) { $langfoldername = get_langfolder($lang); if (!$langfoldername) { @@ -271,12 +286,14 @@ function detect_lang($lang, $keys) { return false; } - echo "Checking $lang"; + $title = $lang; if ($parent != "" && $parent != $lang) { - echo " ($parent)"; + $title .= " ($parent)"; } $langname = $string['thislanguage']; - echo " ".$langname." -D"; + $title .= " ".$langname." -D"; + + // Add the translation to the array. foreach ($keys as $key => $value) { @@ -300,7 +317,10 @@ function detect_lang($lang, $keys) { } $percentage = floor($success/$total * 100); - echo "\t\t$success of $total -> $percentage% ($local local)"; + $bar = progressbar($percentage); + + echo "Checking ".$title.str_repeat("\t", 7 - floor(mb_strlen($title, 'UTF-8')/8)); + echo "\t$success of $total -> $percentage% $bar ($local local)"; if (($percentage > 75 && $local > 50) || ($percentage > 50 && $local > 75)) { echo " \t DETECTED\n"; return true; diff --git a/scripts/langindex.json b/scripts/langindex.json index fe54142bd..ed6b18340 100644 --- a/scripts/langindex.json +++ b/scripts/langindex.json @@ -39,6 +39,7 @@ "addon.block_glossaryrandom.pluginname": "block_glossary_random", "addon.block_learningplans.pluginname": "block_lp", "addon.block_myoverview.all": "block_myoverview", + "addon.block_myoverview.allincludinghidden": "block_myoverview", "addon.block_myoverview.favourites": "block_myoverview", "addon.block_myoverview.future": "block_myoverview", "addon.block_myoverview.hiddencourses": "block_myoverview", @@ -89,8 +90,10 @@ "addon.calendar.calendarevent": "local_moodlemobileapp", "addon.calendar.calendarevents": "local_moodlemobileapp", "addon.calendar.calendarreminders": "local_moodlemobileapp", + "addon.calendar.categoryevents": "calendar", "addon.calendar.confirmeventdelete": "calendar", "addon.calendar.confirmeventseriesdelete": "calendar", + "addon.calendar.courseevents": "calendar", "addon.calendar.currentmonth": "local_moodlemobileapp", "addon.calendar.daynext": "calendar", "addon.calendar.dayprev": "calendar", @@ -114,6 +117,7 @@ "addon.calendar.fri": "calendar", "addon.calendar.friday": "calendar", "addon.calendar.gotoactivity": "calendar", + "addon.calendar.groupevents": "calendar", "addon.calendar.invalidtimedurationminutes": "calendar", "addon.calendar.invalidtimedurationuntil": "calendar", "addon.calendar.mon": "calendar", @@ -131,6 +135,7 @@ "addon.calendar.sat": "calendar", "addon.calendar.saturday": "calendar", "addon.calendar.setnewreminder": "local_moodlemobileapp", + "addon.calendar.siteevents": "calendar", "addon.calendar.sun": "calendar", "addon.calendar.sunday": "calendar", "addon.calendar.thu": "calendar", @@ -149,6 +154,7 @@ "addon.calendar.typesite": "calendar", "addon.calendar.typeuser": "calendar", "addon.calendar.upcomingevents": "calendar", + "addon.calendar.userevents": "calendar", "addon.calendar.wed": "calendar", "addon.calendar.wednesday": "calendar", "addon.calendar.when": "calendar", @@ -383,7 +389,6 @@ "addon.mod_assign.numwords": "moodle", "addon.mod_assign.outof": "assign", "addon.mod_assign.overdue": "assign", - "addon.mod_assign.savechanges": "assign", "addon.mod_assign.submission": "assign", "addon.mod_assign.submissioneditable": "assign", "addon.mod_assign.submissionnoteditable": "assign", @@ -405,6 +410,7 @@ "addon.mod_assign.timemodified": "assign", "addon.mod_assign.timeremaining": "assign", "addon.mod_assign.ungroupedusers": "assign", + "addon.mod_assign.ungroupedusersoptional": "assign", "addon.mod_assign.unlimitedattempts": "assign", "addon.mod_assign.userswhoneedtosubmit": "assign", "addon.mod_assign.userwithid": "local_moodlemobileapp", @@ -433,14 +439,17 @@ "addon.mod_chat.errorwhilegettingchatusers": "local_moodlemobileapp", "addon.mod_chat.errorwhileretrievingmessages": "local_moodlemobileapp", "addon.mod_chat.errorwhilesendingmessage": "local_moodlemobileapp", + "addon.mod_chat.messagebeepseveryone": "chat", "addon.mod_chat.messagebeepsyou": "chat", "addon.mod_chat.messageenter": "chat", "addon.mod_chat.messageexit": "chat", "addon.mod_chat.messages": "chat", + "addon.mod_chat.messageyoubeep": "chat", "addon.mod_chat.modulenameplural": "chat", "addon.mod_chat.mustbeonlinetosendmessages": "local_moodlemobileapp", "addon.mod_chat.nomessages": "chat", "addon.mod_chat.nosessionsfound": "local_moodlemobileapp", + "addon.mod_chat.saidto": "chat", "addon.mod_chat.send": "chat", "addon.mod_chat.sessionstart": "chat", "addon.mod_chat.showincompletesessions": "local_moodlemobileapp", @@ -491,10 +500,13 @@ "addon.mod_data.expired": "data", "addon.mod_data.fields": "data", "addon.mod_data.foundrecords": "data", + "addon.mod_data.gettinglocation": "local_moodlemobileapp", "addon.mod_data.latlongboth": "data", + "addon.mod_data.locationpermissiondenied": "local_moodlemobileapp", "addon.mod_data.menuchoose": "data", "addon.mod_data.modulenameplural": "data", "addon.mod_data.more": "data", + "addon.mod_data.mylocation": "local_moodlemobileapp", "addon.mod_data.nomatch": "data", "addon.mod_data.norecords": "data", "addon.mod_data.notapproved": "data", @@ -560,7 +572,11 @@ "addon.mod_forum.cannotadddiscussionall": "forum", "addon.mod_forum.cannotcreatediscussion": "forum", "addon.mod_forum.couldnotadd": "forum", + "addon.mod_forum.couldnotupdate": "forum", "addon.mod_forum.cutoffdatereached": "forum", + "addon.mod_forum.delete": "forum", + "addon.mod_forum.deletedpost": "forum", + "addon.mod_forum.deletesure": "forum", "addon.mod_forum.discussion": "forum", "addon.mod_forum.discussionlistsortbycreatedasc": "forum", "addon.mod_forum.discussionlistsortbycreateddesc": "forum", @@ -580,6 +596,7 @@ "addon.mod_forum.favouriteupdated": "forum", "addon.mod_forum.forumnodiscussionsyet": "local_moodlemobileapp", "addon.mod_forum.group": "local_moodlemobileapp", + "addon.mod_forum.lastpost": "forum", "addon.mod_forum.lockdiscussion": "forum", "addon.mod_forum.lockupdated": "forum", "addon.mod_forum.message": "forum", @@ -609,6 +626,7 @@ "addon.mod_forum.unpindiscussion": "forum", "addon.mod_forum.unread": "forum", "addon.mod_forum.unreadpostsnumber": "forum", + "addon.mod_forum.yourreply": "forum", "addon.mod_glossary.addentry": "glossary", "addon.mod_glossary.aliases": "glossary", "addon.mod_glossary.attachment": "glossary", @@ -863,6 +881,7 @@ "addon.mod_scorm.organizations": "scorm", "addon.mod_scorm.passed": "scorm", "addon.mod_scorm.reviewmode": "scorm", + "addon.mod_scorm.score": "scorm", "addon.mod_scorm.scormstatusnotdownloaded": "local_moodlemobileapp", "addon.mod_scorm.scormstatusoutdated": "local_moodlemobileapp", "addon.mod_scorm.suspended": "scorm", @@ -1374,6 +1393,7 @@ "core.course.confirmdeletemodulefiles": "local_moodlemobileapp", "core.course.confirmdownload": "local_moodlemobileapp", "core.course.confirmdownloadunknownsize": "local_moodlemobileapp", + "core.course.confirmdownloadzerosize": "local_moodlemobileapp", "core.course.confirmlimiteddownload": "local_moodlemobileapp", "core.course.confirmpartialdownloadsize": "local_moodlemobileapp", "core.course.contents": "local_moodlemobileapp", @@ -1512,6 +1532,7 @@ "core.fileuploader.uploading": "local_moodlemobileapp", "core.fileuploader.uploadingperc": "local_moodlemobileapp", "core.fileuploader.video": "local_moodlemobileapp", + "core.filter": "moodle", "core.folder": "moodle", "core.forcepasswordchangenotice": "moodle", "core.fulllistofcourses": "moodle", @@ -1533,6 +1554,95 @@ "core.group": "moodle", "core.groupsseparate": "moodle", "core.groupsvisible": "moodle", + "core.h5p.additionallicenseinfo": "h5p", + "core.h5p.author": "h5p", + "core.h5p.authorcomments": "h5p", + "core.h5p.authorcommentsdescription": "h5p", + "core.h5p.authorname": "h5p", + "core.h5p.authorrole": "h5p", + "core.h5p.by": "h5p", + "core.h5p.cancellabel": "h5p", + "core.h5p.ccattribution": "h5p", + "core.h5p.ccattributionnc": "h5p", + "core.h5p.ccattributionncnd": "h5p", + "core.h5p.ccattributionncsa": "h5p", + "core.h5p.ccattributionnd": "h5p", + "core.h5p.ccattributionsa": "h5p", + "core.h5p.ccpdd": "h5p", + "core.h5p.changedby": "h5p", + "core.h5p.changedescription": "h5p", + "core.h5p.changelog": "h5p", + "core.h5p.changeplaceholder": "h5p", + "core.h5p.close": "h5p", + "core.h5p.confirmdialogbody": "h5p", + "core.h5p.confirmdialogheader": "h5p", + "core.h5p.confirmlabel": "h5p", + "core.h5p.connectionLost": "h5p", + "core.h5p.connectionReestablished": "h5p", + "core.h5p.contentCopied": "h5p", + "core.h5p.contentchanged": "h5p", + "core.h5p.contenttype": "h5p", + "core.h5p.copyright": "h5p", + "core.h5p.copyrightinfo": "h5p", + "core.h5p.copyrightstring": "h5p", + "core.h5p.copyrighttitle": "h5p", + "core.h5p.creativecommons": "h5p", + "core.h5p.date": "h5p", + "core.h5p.disablefullscreen": "h5p", + "core.h5p.download": "h5p", + "core.h5p.downloadtitle": "h5p", + "core.h5p.editor": "h5p", + "core.h5p.embed": "h5p", + "core.h5p.embedtitle": "h5p", + "core.h5p.fullscreen": "h5p", + "core.h5p.gpl": "h5p", + "core.h5p.h5ptitle": "h5p", + "core.h5p.hideadvanced": "h5p", + "core.h5p.license": "h5p", + "core.h5p.licenseCC010": "h5p", + "core.h5p.licenseCC010U": "h5p", + "core.h5p.licenseCC10": "h5p", + "core.h5p.licenseCC20": "h5p", + "core.h5p.licenseCC25": "h5p", + "core.h5p.licenseCC30": "h5p", + "core.h5p.licenseCC40": "h5p", + "core.h5p.licenseGPL": "h5p", + "core.h5p.licenseV1": "h5p", + "core.h5p.licenseV2": "h5p", + "core.h5p.licenseV3": "h5p", + "core.h5p.licensee": "h5p", + "core.h5p.licenseextras": "h5p", + "core.h5p.licenseversion": "h5p", + "core.h5p.nocopyright": "h5p", + "core.h5p.offlineDialogBody": "h5p", + "core.h5p.offlineDialogHeader": "h5p", + "core.h5p.offlineDialogRetryButtonLabel": "h5p", + "core.h5p.offlineDialogRetryMessage": "h5p", + "core.h5p.offlineSuccessfulSubmit": "h5p", + "core.h5p.originator": "h5p", + "core.h5p.pd": "h5p", + "core.h5p.pddl": "h5p", + "core.h5p.pdm": "h5p", + "core.h5p.play": "local_moodlemobileapp", + "core.h5p.resizescript": "h5p", + "core.h5p.resubmitScores": "h5p", + "core.h5p.reuse": "h5p", + "core.h5p.reuseContent": "h5p", + "core.h5p.reuseDescription": "h5p", + "core.h5p.showadvanced": "h5p", + "core.h5p.showless": "h5p", + "core.h5p.showmore": "h5p", + "core.h5p.size": "h5p", + "core.h5p.source": "h5p", + "core.h5p.startingover": "h5p", + "core.h5p.sublevel": "h5p", + "core.h5p.thumbnail": "h5p", + "core.h5p.title": "h5p", + "core.h5p.undisclosed": "h5p", + "core.h5p.year": "h5p", + "core.h5p.years": "h5p", + "core.h5p.yearsfrom": "h5p", + "core.h5p.yearsto": "h5p", "core.hasdatatosync": "local_moodlemobileapp", "core.help": "moodle", "core.hide": "moodle", @@ -1558,6 +1668,12 @@ "core.login.auth_email": "auth_email/pluginname", "core.login.authenticating": "local_moodlemobileapp", "core.login.cancel": "moodle", + "core.login.changepassword": "moodle", + "core.login.changepasswordbutton": "local_moodlemobileapp", + "core.login.changepasswordhelp": "local_moodlemobileapp", + "core.login.changepasswordinstructions": "local_moodlemobileapp", + "core.login.changepasswordlogoutinstructions": "local_moodlemobileapp", + "core.login.changepasswordreconnectinstructions": "local_moodlemobileapp", "core.login.checksiteversion": "local_moodlemobileapp", "core.login.confirmdeletesite": "local_moodlemobileapp", "core.login.connect": "local_moodlemobileapp", @@ -1579,6 +1695,7 @@ "core.login.errorupdatesite": "local_moodlemobileapp", "core.login.findyoursite": "local_moodlemobileapp", "core.login.firsttime": "moodle", + "core.login.forcepasswordchangenotice": "moodle", "core.login.forgotten": "moodle", "core.login.getanothercaptcha": "auth", "core.login.help": "moodle", @@ -1697,6 +1814,7 @@ "core.nocomments": "moodle", "core.nograde": "moodle", "core.none": "moodle", + "core.nooptionavailable": "local_moodlemobileapp", "core.nopasswordchangeforced": "local_moodlemobileapp", "core.nopermissionerror": "local_moodlemobileapp", "core.nopermissions": "error", @@ -1757,6 +1875,7 @@ "core.redirectingtosite": "local_moodlemobileapp", "core.refresh": "moodle", "core.remove": "moodle", + "core.removefiles": "local_moodlemobileapp", "core.required": "moodle", "core.requireduserdatamissing": "local_moodlemobileapp", "core.resourcedisplayopen": "moodle", @@ -1765,6 +1884,7 @@ "core.restricted": "moodle", "core.retry": "local_moodlemobileapp", "core.save": "moodle", + "core.savechanges": "assign", "core.search": "moodle", "core.searching": "local_moodlemobileapp", "core.searchresults": "moodle", @@ -1781,6 +1901,10 @@ "core.settings.appready": "local_moodlemobileapp", "core.settings.cannotsyncoffline": "local_moodlemobileapp", "core.settings.cannotsyncwithoutwifi": "local_moodlemobileapp", + "core.settings.colorscheme": "local_moodlemobileapp", + "core.settings.colorscheme-auto": "local_moodlemobileapp", + "core.settings.colorscheme-dark": "local_moodlemobileapp", + "core.settings.colorscheme-light": "local_moodlemobileapp", "core.settings.compilationinfo": "local_moodlemobileapp", "core.settings.cordovadevicemodel": "local_moodlemobileapp", "core.settings.cordovadeviceosversion": "local_moodlemobileapp", @@ -1908,6 +2032,8 @@ "core.unknown": "local_moodlemobileapp", "core.unlimited": "moodle", "core.unzipping": "local_moodlemobileapp", + "core.updaterequired": "local_moodlemobileapp", + "core.updaterequireddesc": "local_moodlemobileapp", "core.upgraderunning": "error", "core.user": "moodle", "core.user.address": "moodle", @@ -1954,5 +2080,7 @@ "core.wsfunctionnotavailable": "local_moodlemobileapp", "core.year": "moodle", "core.years": "moodle", - "core.yes": "moodle" + "core.yes": "moodle", + "core.youreoffline": "local_moodlemobileapp", + "core.youreonline": "local_moodlemobileapp" } diff --git a/scripts/remove-jsdocs-types.js b/scripts/remove-jsdocs-types.js new file mode 100644 index 000000000..0392c4d1c --- /dev/null +++ b/scripts/remove-jsdocs-types.js @@ -0,0 +1,71 @@ +const fs = require('fs') +const path = require('path') + +const srcPath = path.join(__dirname, '..', 'src') + +function findFiles(dirPath) { + const result = [] + const entries = fs.readdirSync(dirPath, {withFileTypes: true}) + + entries.forEach((entry) => { + const entryPath = path.join(dirPath, entry.name) + if (entry.isFile() && entry.name.endsWith('.ts')) { + result.push(entryPath) + } else if (entry.isDirectory()) { + result.push(...findFiles(entryPath)) + } + }) + + return result +} + +findFiles(srcPath).forEach((file) => { + let src = fs.readFileSync(file, 'utf-8') + + // Fix wrong use of @type instead of @return. + src = src.replace(/(\n +\* \@param [^\n]*\n +\* )\@type /g, (match, p1) => p1 + '@return ') + + // Remove square brackets and default values from @param lines. + src = src.replace(/(\n +\* @param +\{[^\n]+\} +)\[(\w+)[^\]\n]*\]/g, (match, p1, p2) => p1 + p2) + + // Remove types from @param and @return lines. + src = src.replace(/(\n +\* \@(?:param|returns?) *)([a-zA-Z0-9_]+ *)?(\{[^\n]*)/g, (match, p1, p2, p3) => { + p2 = p2 || '' + let brackets = 1; + let end = 0; + for (let i = 1; brackets > 0 && i < p3.length; i++) { + if (p3[i] == '{') { + brackets++ + } else if (p3[i] == '}') { + brackets-- + end = i + 1 + } + } + p1 = p1.trimEnd().replace('@returns', '@return') + p2 = p2.trim() + p3 = p3.slice(end).trim().replace(/^([a-zA-Z0-9_]+) +/, '$1 ') + if (!p2 && !p3) return '' + return p1 + ' ' + p2 + (p2 && p3 ? ' ' + p3 : p3) + }) + + // Remove @type lines. + src = src.replace(/\n +\* \@type .*\n/g, '\n') + + // Remove consecutive empty doc lines. + src = src.replace(/\n *\* *(\n *\*\/? *\n)/g, (match, p1) => p1) + + // Remove empty docs. + src = src.replace(/\n *\/\*\*[ \n\*]+\*\/ *\n/g, '\n') + + // Fix indentation. + src = src.replace(/(\n +\* +(?:@param \w+|@return) )([^\n]*)((?:\n +\* +[^@\n][^\n]+)+)/g, (match, p1, p2, p3) => { + const indent = p1.length + p3 = p3.replace(/(\n +\*)( +)([^\n]+)/g, (match, p1, p2, p3) => { + p2 = new Array(Math.max(0, indent - p1.length + 1)).join(' ') + return p1 + p2 + p3 + }) + return p1 + p2 + p3 + }) + + fs.writeFileSync(file, src) +}) diff --git a/scripts/ws_to_ts_functions.php b/scripts/ws_to_ts_functions.php new file mode 100644 index 000000000..ca05d2082 --- /dev/null +++ b/scripts/ws_to_ts_functions.php @@ -0,0 +1,244 @@ +. + +/** + * Helper functions for converting a Moodle WS structure to a TS type. + */ + +/** + * Get the structure of a WS params or returns. + */ +function get_ws_structure($wsname, $useparams) { + global $DB; + + // get all the function descriptions + $functions = $DB->get_records('external_functions', array(), 'name'); + $functiondescs = array(); + foreach ($functions as $function) { + $functiondescs[$function->name] = external_api::external_function_info($function); + } + + if (!isset($functiondescs[$wsname])) { + return false; + } else if ($useparams) { + return $functiondescs[$wsname]->parameters_desc; + } else { + return $functiondescs[$wsname]->returns_desc; + } +} + +/** + * Fix a comment: make sure first letter is uppercase and add a dot at the end if needed. + */ +function fix_comment($desc) { + $desc = trim($desc); + $desc = ucfirst($desc); + + if (substr($desc, -1) !== '.') { + $desc .= '.'; + } + + return $desc; +} + +/** + * Get an inline comment based on a certain text. + */ +function get_inline_comment($desc) { + if (empty($desc)) { + return ''; + } + + return ' // ' . fix_comment($desc); +} + +/** + * Add the TS documentation of a certain element. + */ +function get_ts_doc($type, $desc, $indentation) { + if (empty($desc)) { + // If no key, it's probably in an array. We only document object properties. + return ''; + } + + return $indentation . "/**\n" . + $indentation . " * " . fix_comment($desc) . "\n" . + (!empty($type) ? ($indentation . " * @type {" . $type . "}\n") : '') . + $indentation . " */\n"; +} + +/** + * Specify a certain type, with or without a key. + */ +function convert_key_type($key, $type, $required, $indentation) { + if ($key) { + // It has a key, it's inside an object. + return $indentation . "$key" . ($required == VALUE_OPTIONAL ? '?' : '') . ": $type"; + } else { + // No key, it's probably in an array. Just include the type. + return $type; + } +} + +/** + * Convert a certain element into a TS structure. + */ +function convert_to_ts($key, $value, $boolisnumber = false, $indentation = '', $arraydesc = '') { + if ($value instanceof external_value || $value instanceof external_warnings || $value instanceof external_files) { + // It's a basic field or a pre-defined type like warnings. + $type = 'string'; + + if ($value instanceof external_warnings) { + $type = 'CoreWSExternalWarning[]'; + } else if ($value instanceof external_files) { + $type = 'CoreWSExternalFile[]'; + } else if ($value->type == PARAM_BOOL && !$boolisnumber) { + $type = 'boolean'; + } else if (($value->type == PARAM_BOOL && $boolisnumber) || $value->type == PARAM_INT || $value->type == PARAM_FLOAT || + $value->type == PARAM_LOCALISEDFLOAT || $value->type == PARAM_PERMISSION || $value->type == PARAM_INTEGER || + $value->type == PARAM_NUMBER) { + $type = 'number'; + } + + $result = convert_key_type($key, $type, $value->required, $indentation); + + return $result; + + } else if ($value instanceof external_single_structure) { + // It's an object. + $result = convert_key_type($key, '{', $value->required, $indentation); + + if ($arraydesc) { + // It's an array of objects. Print the array description now. + $result .= get_inline_comment($arraydesc); + } + + $result .= "\n"; + + foreach ($value->keys as $key => $value) { + $result .= convert_to_ts($key, $value, $boolisnumber, $indentation . ' ') . ';'; + + if (!$value instanceof external_multiple_structure || !$value->content instanceof external_single_structure) { + // Add inline comments after the field, except for arrays of objects where it's added at the start. + $result .= get_inline_comment($value->desc); + } + + $result .= "\n"; + } + + $result .= "$indentation}"; + + return $result; + + } else if ($value instanceof external_multiple_structure) { + // It's an array. + $result = convert_key_type($key, '', $value->required, $indentation); + + $result .= convert_to_ts(null, $value->content, $boolisnumber, $indentation, $value->desc); + + $result .= "[]"; + + return $result; + } else { + echo "WARNING: Unknown structure: $key " . get_class($value) . " \n"; + + return ""; + } +} + +/** + * Concatenate two paths. + */ +function concatenate_paths($left, $right, $separator = '/') { + if (!is_string($left) || $left == '') { + return $right; + } else if (!is_string($right) || $right == '') { + return $left; + } + + $lastCharLeft = substr($left, -1); + $firstCharRight = $right[0]; + + if ($lastCharLeft === $separator && $firstCharRight === $separator) { + return $left . substr($right, 1); + } else if ($lastCharLeft !== $separator && $firstCharRight !== '/') { + return $left . '/' . $right; + } else { + return $left . $right; + } +} + +/** + * Detect changes between 2 WS structures. We only detect fields that have been added or modified, not removed fields. + */ +function detect_ws_changes($new, $old, $key = '', $path = '') { + $messages = []; + + if (gettype($new) != gettype($old)) { + // The type has changed. + $messages[] = "Property '$key' has changed type, from '" . gettype($old) . "' to '" . gettype($new) . + ($path != '' ? "' inside $path." : "'."); + + } else if ($new instanceof external_value && $new->type != $old->type) { + // The type has changed. + $messages[] = "Property '$key' has changed type, from '" . $old->type . "' to '" . $new->type . + ($path != '' ? "' inside $path." : "'."); + + } else if ($new instanceof external_warnings || $new instanceof external_files) { + // Ignore these types. + + } else if ($new instanceof external_single_structure) { + // Check each subproperty. + $newpath = ($path != '' ? "$path." : '') . $key; + + foreach ($new->keys as $subkey => $value) { + if (!isset($old->keys[$subkey])) { + // New property. + $messages[] = "New property '$subkey' found" . ($newpath != '' ? " inside '$newpath'." : '.'); + } else { + $messages = array_merge($messages, detect_ws_changes($value, $old->keys[$subkey], $subkey, $newpath)); + } + } + } else if ($new instanceof external_multiple_structure) { + // Recursive call with the content. + $messages = array_merge($messages, detect_ws_changes($new->content, $old->content, $key, $path)); + } + + return $messages; +} + +/** + * Remove all closures (anonymous functions) in the default values so the object can be serialized. + */ +function remove_default_closures($value) { + if ($value instanceof external_warnings || $value instanceof external_files) { + // Ignore these types. + + } else if ($value instanceof external_value) { + if ($value->default instanceof Closure) { + $value->default = null; + } + + } else if ($value instanceof external_single_structure) { + + foreach ($value->keys as $key => $subvalue) { + remove_default_closures($subvalue); + } + + } else if ($value instanceof external_multiple_structure) { + remove_default_closures($value->content); + } +} diff --git a/src/addon/badges/badges.module.ts b/src/addon/badges/badges.module.ts index 20163a208..a85fb49f5 100644 --- a/src/addon/badges/badges.module.ts +++ b/src/addon/badges/badges.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/addon/badges/pages/issued-badge/issued-badge.html b/src/addon/badges/pages/issued-badge/issued-badge.html index 111cc8a8f..ec4dc0e4f 100644 --- a/src/addon/badges/pages/issued-badge/issued-badge.html +++ b/src/addon/badges/pages/issued-badge/issued-badge.html @@ -1,6 +1,6 @@ - {{badge.name}} + {{badge && badge.name}} @@ -9,7 +9,7 @@ - + @@ -24,31 +24,27 @@

{{ 'core.name' | translate}}

-

- -

+

{{ user.fullname }}

- +

{{ 'addon.badges.issuerdetails' | translate}}

{{ 'addon.badges.issuername' | translate}}

-

- -

+

{{ badge.issuername }}

{{ 'addon.badges.contact' | translate}}

- + {{ badge.issuercontact }}

- +

{{ 'addon.badges.badgedetails' | translate}}

@@ -66,9 +62,7 @@

{{ 'core.description' | translate}}

-

- -

+

{{ badge.description }}

{{ 'addon.badges.imageauthorname' | translate}}

@@ -77,29 +71,29 @@

{{ 'addon.badges.imageauthoremail' | translate}}

- + {{ badge.imageauthoremail }}

{{ 'addon.badges.imageauthorurl' | translate}}

- + {{ badge.imageauthorurl }}

{{ 'addon.badges.imagecaption' | translate}}

-

+

{{ badge.imagecaption }}

{{ 'core.course' | translate}}

- +

- +

{{ 'addon.badges.issuancedetails' | translate}}

@@ -120,7 +114,7 @@
- +

{{ 'addon.badges.bendorsement' | translate}}

@@ -131,13 +125,13 @@

{{ 'addon.badges.issueremail' | translate}}

- + {{ badge.endorsement.issueremail }}

{{ 'addon.badges.issuerurl' | translate}}

- + {{ badge.endorsement.issuerurl }}

@@ -147,24 +141,22 @@

{{ 'addon.badges.claimid' | translate}}

- + {{ badge.endorsement.claimid }}

{{ 'addon.badges.claimcomment' | translate}}

-

- -

+

{{ badge.endorsement.claimcomment }}

- +

{{ 'addon.badges.relatedbages' | translate}}

-

+

{{ relatedBadge.name }}

{{ 'addon.badges.norelated' | translate}}

@@ -172,14 +164,14 @@
- +

{{ 'addon.badges.alignment' | translate}}

- -

+
+

{{ alignment.targetname }}

- +

{{ 'addon.badges.noalignment' | translate}}

diff --git a/src/addon/badges/pages/issued-badge/issued-badge.module.ts b/src/addon/badges/pages/issued-badge/issued-badge.module.ts index cdc621998..a44bd34ef 100644 --- a/src/addon/badges/pages/issued-badge/issued-badge.module.ts +++ b/src/addon/badges/pages/issued-badge/issued-badge.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/addon/badges/pages/issued-badge/issued-badge.ts b/src/addon/badges/pages/issued-badge/issued-badge.ts index ff79f7841..46977fd45 100644 --- a/src/addon/badges/pages/issued-badge/issued-badge.ts +++ b/src/addon/badges/pages/issued-badge/issued-badge.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -19,7 +19,7 @@ import { CoreDomUtilsProvider } from '@providers/utils/dom'; import { CoreSitesProvider } from '@providers/sites'; import { CoreUserProvider } from '@core/user/providers/user'; import { CoreCoursesProvider } from '@core/courses/providers/courses'; -import { AddonBadgesProvider } from '../../providers/badges'; +import { AddonBadgesProvider, AddonBadgesUserBadge } from '../../providers/badges'; /** * Page that displays the list of calendar events. @@ -38,7 +38,7 @@ export class AddonBadgesIssuedBadgePage { user: any = {}; course: any = {}; - badge: any = {}; + badge: AddonBadgesUserBadge; badgeLoaded = false; currentTime = 0; @@ -65,7 +65,7 @@ export class AddonBadgesIssuedBadgePage { /** * Fetch the issued badge required for the view. * - * @return {Promise} Promise resolved when done. + * @return Promise resolved when done. */ fetchIssuedBadge(): Promise { const promises = []; @@ -101,7 +101,7 @@ export class AddonBadgesIssuedBadgePage { /** * Refresh the badges. * - * @param {any} refresher Refresher. + * @param refresher Refresher. */ refreshBadges(refresher: any): void { this.badgesProvider.invalidateUserBadges(this.courseId, this.userId).finally(() => { diff --git a/src/addon/badges/pages/user-badges/user-badges.html b/src/addon/badges/pages/user-badges/user-badges.html index 6a2a33a24..315e9af08 100644 --- a/src/addon/badges/pages/user-badges/user-badges.html +++ b/src/addon/badges/pages/user-badges/user-badges.html @@ -17,7 +17,7 @@ -

+

{{ badge.name }}

{{ badge.dateissued * 1000 | coreFormatDate :'strftimedatetimeshort' }}

{{ 'addon.badges.expired' | translate }} diff --git a/src/addon/badges/pages/user-badges/user-badges.module.ts b/src/addon/badges/pages/user-badges/user-badges.module.ts index 2609cdf00..03c8ecbe8 100644 --- a/src/addon/badges/pages/user-badges/user-badges.module.ts +++ b/src/addon/badges/pages/user-badges/user-badges.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/addon/badges/pages/user-badges/user-badges.ts b/src/addon/badges/pages/user-badges/user-badges.ts index cde1db77a..70c7f04ed 100644 --- a/src/addon/badges/pages/user-badges/user-badges.ts +++ b/src/addon/badges/pages/user-badges/user-badges.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -14,7 +14,7 @@ import { Component, ViewChild } from '@angular/core'; import { IonicPage, Content, NavParams } from 'ionic-angular'; -import { AddonBadgesProvider } from '../../providers/badges'; +import { AddonBadgesProvider, AddonBadgesUserBadge } from '../../providers/badges'; import { CoreTimeUtilsProvider } from '@providers/utils/time'; import { CoreDomUtilsProvider } from '@providers/utils/dom'; import { CoreSitesProvider } from '@providers/sites'; @@ -36,7 +36,7 @@ export class AddonBadgesUserBadgesPage { userId: number; badgesLoaded = false; - badges = []; + badges: AddonBadgesUserBadge[] = []; currentTime = 0; badgeHash: string; @@ -64,7 +64,7 @@ export class AddonBadgesUserBadgesPage { /** * Fetch all the badges required for the view. * - * @return {Promise} Promise resolved when done. + * @return Promise resolved when done. */ fetchBadges(): Promise { this.currentTime = this.timeUtils.timestamp(); @@ -79,7 +79,7 @@ export class AddonBadgesUserBadgesPage { /** * Refresh the badges. * - * @param {any} refresher Refresher. + * @param refresher Refresher. */ refreshBadges(refresher: any): void { this.badgesProvider.invalidateUserBadges(this.courseId, this.userId).finally(() => { @@ -92,7 +92,7 @@ export class AddonBadgesUserBadgesPage { /** * Navigate to a particular badge. * - * @param {string} badgeHash Badge to load. + * @param badgeHash Badge to load. */ loadIssuedBadge(badgeHash: string): void { this.badgeHash = badgeHash; diff --git a/src/addon/badges/providers/badge-link-handler.ts b/src/addon/badges/providers/badge-link-handler.ts index 144acad5c..21fd59ab7 100644 --- a/src/addon/badges/providers/badge-link-handler.ts +++ b/src/addon/badges/providers/badge-link-handler.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -33,11 +33,11 @@ export class AddonBadgesBadgeLinkHandler extends CoreContentLinksHandlerBase { /** * Get the list of actions for a link (url). * - * @param {string[]} siteIds List of sites the URL belongs to. - * @param {string} url The URL to treat. - * @param {any} params The params of the URL. E.g. 'mysite.com?id=1' -> {id: 1} - * @param {number} [courseId] Course ID related to the URL. Optional but recommended. - * @return {CoreContentLinksAction[]|Promise} List of (or promise resolved with list of) actions. + * @param siteIds List of sites the URL belongs to. + * @param url The URL to treat. + * @param params The params of the URL. E.g. 'mysite.com?id=1' -> {id: 1} + * @param courseId Course ID related to the URL. Optional but recommended. + * @return List of (or promise resolved with list of) actions. */ getActions(siteIds: string[], url: string, params: any, courseId?: number): CoreContentLinksAction[] | Promise { @@ -53,11 +53,11 @@ export class AddonBadgesBadgeLinkHandler extends CoreContentLinksHandlerBase { * Check if the handler is enabled for a certain site (site + user) and a URL. * If not defined, defaults to true. * - * @param {string} siteId The site ID. - * @param {string} url The URL to treat. - * @param {any} params The params of the URL. E.g. 'mysite.com?id=1' -> {id: 1} - * @param {number} [courseId] Course ID related to the URL. Optional but recommended. - * @return {boolean|Promise} Whether the handler is enabled for the URL and site. + * @param siteId The site ID. + * @param url The URL to treat. + * @param params The params of the URL. E.g. 'mysite.com?id=1' -> {id: 1} + * @param courseId Course ID related to the URL. Optional but recommended. + * @return Whether the handler is enabled for the URL and site. */ isEnabled(siteId: string, url: string, params: any, courseId?: number): boolean | Promise { diff --git a/src/addon/badges/providers/badges.ts b/src/addon/badges/providers/badges.ts index 019341e39..bce298dc4 100644 --- a/src/addon/badges/providers/badges.ts +++ b/src/addon/badges/providers/badges.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -15,6 +15,7 @@ import { Injectable } from '@angular/core'; import { CoreLoggerProvider } from '@providers/logger'; import { CoreSitesProvider } from '@providers/sites'; +import { CoreWSExternalWarning } from '@providers/ws'; import { CoreSite } from '@classes/site'; /** @@ -35,8 +36,8 @@ export class AddonBadgesProvider { * This method is called quite often and thus should only perform a quick * check, we should not be calling WS from here. * - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with true if enabled, false otherwise. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with true if enabled, false otherwise. */ isPluginEnabled(siteId?: string): Promise { @@ -54,9 +55,9 @@ export class AddonBadgesProvider { /** * Get the cache key for the get badges call. * - * @param {number} courseId ID of the course to get the badges from. - * @param {number} userId ID of the user to get the badges from. - * @return {string} Cache key. + * @param courseId ID of the course to get the badges from. + * @param userId ID of the user to get the badges from. + * @return Cache key. */ protected getBadgesCacheKey(courseId: number, userId: number): string { return this.ROOT_CACHE_KEY + 'badges:' + courseId + ':' + userId; @@ -65,12 +66,12 @@ export class AddonBadgesProvider { /** * Get issued badges for a certain user in a course. * - * @param {number} courseId ID of the course to get the badges from. - * @param {number} userId ID of the user to get the badges from. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise}Promise to be resolved when the badges are retrieved. + * @param courseId ID of the course to get the badges from. + * @param userId ID of the user to get the badges from. + * @param siteId Site ID. If not defined, current site. + * @return Promise to be resolved when the badges are retrieved. */ - getUserBadges(courseId: number, userId: number, siteId?: string): Promise { + getUserBadges(courseId: number, userId: number, siteId?: string): Promise { this.logger.debug('Get badges for course ' + courseId); @@ -85,8 +86,19 @@ export class AddonBadgesProvider { updateFrequency: CoreSite.FREQUENCY_RARELY }; - return site.read('core_badges_get_user_badges', data, preSets).then((response) => { + return site.read('core_badges_get_user_badges', data, preSets).then((response: AddonBadgesGetUserBadgesResult): any => { if (response && response.badges) { + // In 3.7, competencies was renamed to alignment. Rename the property in 3.6 too. + response.badges.forEach((badge) => { + badge.alignment = badge.alignment || badge.competencies; + + // Check that the alignment is valid, they were broken in 3.7. + if (badge.alignment && badge.alignment[0] && typeof badge.alignment[0].targetname == 'undefined') { + // If any badge lacks targetname it means they are affected by the Moodle bug, don't display them. + delete badge.alignment; + } + }); + return response.badges; } else { return Promise.reject(null); @@ -98,10 +110,10 @@ export class AddonBadgesProvider { /** * Invalidate get badges WS call. * - * @param {number} courseId Course ID. - * @param {number} userId ID of the user to get the badges from. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when data is invalidated. + * @param courseId Course ID. + * @param userId ID of the user to get the badges from. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when data is invalidated. */ invalidateUserBadges(courseId: number, userId: number, siteId?: string): Promise { @@ -110,3 +122,85 @@ export class AddonBadgesProvider { }); } } + +/** + * Result of WS core_badges_get_user_badges. + */ +export type AddonBadgesGetUserBadgesResult = { + badges: AddonBadgesUserBadge[]; // List of badges. + warnings?: CoreWSExternalWarning[]; // List of warnings. +}; + +/** + * Badge data returned by WS core_badges_get_user_badges. + */ +export type AddonBadgesUserBadge = { + id?: number; // Badge id. + name: string; // Badge name. + description: string; // Badge description. + timecreated?: number; // Time created. + timemodified?: number; // Time modified. + usercreated?: number; // User created. + usermodified?: number; // User modified. + issuername: string; // Issuer name. + issuerurl: string; // Issuer URL. + issuercontact: string; // Issuer contact. + expiredate?: number; // Expire date. + expireperiod?: number; // Expire period. + type?: number; // Type. + courseid?: number; // Course id. + message?: string; // Message. + messagesubject?: string; // Message subject. + attachment?: number; // Attachment. + notification?: number; // @since 3.6. Whether to notify when badge is awarded. + nextcron?: number; // @since 3.6. Next cron. + status?: number; // Status. + issuedid?: number; // Issued id. + uniquehash: string; // Unique hash. + dateissued: number; // Date issued. + dateexpire: number; // Date expire. + visible?: number; // Visible. + email?: string; // @since 3.6. User email. + version?: string; // @since 3.6. Version. + language?: string; // @since 3.6. Language. + imageauthorname?: string; // @since 3.6. Name of the image author. + imageauthoremail?: string; // @since 3.6. Email of the image author. + imageauthorurl?: string; // @since 3.6. URL of the image author. + imagecaption?: string; // @since 3.6. Caption of the image. + badgeurl: string; // Badge URL. + endorsement?: { // @since 3.6. + id: number; // Endorsement id. + badgeid: number; // Badge id. + issuername: string; // Endorsement issuer name. + issuerurl: string; // Endorsement issuer URL. + issueremail: string; // Endorsement issuer email. + claimid: string; // Claim URL. + claimcomment: string; // Claim comment. + dateissued: number; // Date issued. + }; + alignment?: { // @since 3.7. Calculated by the app for 3.6 sites. Badge alignments. + id?: number; // Alignment id. + badgeid?: number; // Badge id. + targetname?: string; // Target name. + targeturl?: string; // Target URL. + targetdescription?: string; // Target description. + targetframework?: string; // Target framework. + targetcode?: string; // Target code. + }[]; + competencies?: { // @deprecated from 3.7. @since 3.6. In 3.7 it was renamed to alignment. + id?: number; // Alignment id. + badgeid?: number; // Badge id. + targetname?: string; // Target name. + targeturl?: string; // Target URL. + targetdescription?: string; // Target description. + targetframework?: string; // Target framework. + targetcode?: string; // Target code. + }[]; + relatedbadges?: { // @since 3.6. Related badges. + id: number; // Badge id. + name: string; // Badge name. + version?: string; // Version. + language?: string; // Language. + type?: number; // Type. + }[]; +}; diff --git a/src/addon/badges/providers/mybadges-link-handler.ts b/src/addon/badges/providers/mybadges-link-handler.ts index 190e575ac..7a3db5562 100644 --- a/src/addon/badges/providers/mybadges-link-handler.ts +++ b/src/addon/badges/providers/mybadges-link-handler.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -34,11 +34,11 @@ export class AddonBadgesMyBadgesLinkHandler extends CoreContentLinksHandlerBase /** * Get the list of actions for a link (url). * - * @param {string[]} siteIds List of sites the URL belongs to. - * @param {string} url The URL to treat. - * @param {any} params The params of the URL. E.g. 'mysite.com?id=1' -> {id: 1} - * @param {number} [courseId] Course ID related to the URL. Optional but recommended. - * @return {CoreContentLinksAction[]|Promise} List of (or promise resolved with list of) actions. + * @param siteIds List of sites the URL belongs to. + * @param url The URL to treat. + * @param params The params of the URL. E.g. 'mysite.com?id=1' -> {id: 1} + * @param courseId Course ID related to the URL. Optional but recommended. + * @return List of (or promise resolved with list of) actions. */ getActions(siteIds: string[], url: string, params: any, courseId?: number): CoreContentLinksAction[] | Promise { @@ -54,11 +54,11 @@ export class AddonBadgesMyBadgesLinkHandler extends CoreContentLinksHandlerBase * Check if the handler is enabled for a certain site (site + user) and a URL. * If not defined, defaults to true. * - * @param {string} siteId The site ID. - * @param {string} url The URL to treat. - * @param {any} params The params of the URL. E.g. 'mysite.com?id=1' -> {id: 1} - * @param {number} [courseId] Course ID related to the URL. Optional but recommended. - * @return {boolean|Promise} Whether the handler is enabled for the URL and site. + * @param siteId The site ID. + * @param url The URL to treat. + * @param params The params of the URL. E.g. 'mysite.com?id=1' -> {id: 1} + * @param courseId Course ID related to the URL. Optional but recommended. + * @return Whether the handler is enabled for the URL and site. */ isEnabled(siteId: string, url: string, params: any, courseId?: number): boolean | Promise { diff --git a/src/addon/badges/providers/push-click-handler.ts b/src/addon/badges/providers/push-click-handler.ts index 1aabf4f28..816a5cc47 100644 --- a/src/addon/badges/providers/push-click-handler.ts +++ b/src/addon/badges/providers/push-click-handler.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -33,8 +33,8 @@ export class AddonBadgesPushClickHandler implements CorePushNotificationsClickHa /** * Check if a notification click is handled by this handler. * - * @param {any} notification The notification to check. - * @return {boolean} Whether the notification click is handled by this handler + * @param notification The notification to check. + * @return Whether the notification click is handled by this handler */ handles(notification: any): boolean | Promise { const data = notification.customdata || {}; @@ -50,8 +50,8 @@ export class AddonBadgesPushClickHandler implements CorePushNotificationsClickHa /** * Handle the notification click. * - * @param {any} notification The notification to check. - * @return {Promise} Promise resolved when done. + * @param notification The notification to check. + * @return Promise resolved when done. */ handleClick(notification: any): Promise { const data = notification.customdata || {}; diff --git a/src/addon/badges/providers/user-handler.ts b/src/addon/badges/providers/user-handler.ts index fb34cd633..22c7daa58 100644 --- a/src/addon/badges/providers/user-handler.ts +++ b/src/addon/badges/providers/user-handler.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -30,7 +30,7 @@ export class AddonBadgesUserHandler implements CoreUserProfileHandler { /** * Check if handler is enabled. * - * @return {Promise} Always enabled. + * @return Always enabled. */ isEnabled(): Promise { return this.badgesProvider.isPluginEnabled(); @@ -39,11 +39,11 @@ export class AddonBadgesUserHandler implements CoreUserProfileHandler { /** * Check if handler is enabled for this user in this context. * - * @param {any} user User to check. - * @param {number} courseId Course ID. - * @param {any} [navOptions] Course navigation options for current user. See CoreCoursesProvider.getUserNavigationOptions. - * @param {any} [admOptions] Course admin options for current user. See CoreCoursesProvider.getUserAdministrationOptions. - * @return {boolean} True if enabled, false otherwise. + * @param user User to check. + * @param courseId Course ID. + * @param navOptions Course navigation options for current user. See CoreCoursesProvider.getUserNavigationOptions. + * @param admOptions Course admin options for current user. See CoreCoursesProvider.getUserAdministrationOptions. + * @return True if enabled, false otherwise. */ isEnabledForUser(user: any, courseId: number, navOptions?: any, admOptions?: any): boolean { @@ -58,7 +58,7 @@ export class AddonBadgesUserHandler implements CoreUserProfileHandler { /** * Returns the data needed to render the handler. * - * @return {CoreUserProfileHandlerData} Data needed to render the handler. + * @return Data needed to render the handler. */ getDisplayData(user: any, courseId: number): CoreUserProfileHandlerData { return { diff --git a/src/addon/block/activitymodules/activitymodules.module.ts b/src/addon/block/activitymodules/activitymodules.module.ts index 88fbb6e06..0a05e1cdc 100644 --- a/src/addon/block/activitymodules/activitymodules.module.ts +++ b/src/addon/block/activitymodules/activitymodules.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/addon/block/activitymodules/components/activitymodules/activitymodules.ts b/src/addon/block/activitymodules/components/activitymodules/activitymodules.ts index 69a0122bb..b46a9b366 100644 --- a/src/addon/block/activitymodules/components/activitymodules/activitymodules.ts +++ b/src/addon/block/activitymodules/components/activitymodules/activitymodules.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -13,12 +13,12 @@ // limitations under the License. import { Component, OnInit, Injector, Input } from '@angular/core'; -import { CoreUtilsProvider } from '@providers/utils/utils'; import { CoreCourseProvider } from '@core/course/providers/course'; import { CoreCourseModuleDelegate } from '@core/course/providers/module-delegate'; import { CoreBlockBaseComponent } from '@core/block/classes/base-block-component'; -import { CoreConstants } from '@core/constants'; +import { CoreConstants, ContextLevel } from '@core/constants'; import { TranslateService } from '@ngx-translate/core'; +import { CoreSitesProvider } from '@providers/sites'; /** * Component to render an "activity modules" block. @@ -29,15 +29,16 @@ import { TranslateService } from '@ngx-translate/core'; }) export class AddonBlockActivityModulesComponent extends CoreBlockBaseComponent implements OnInit { @Input() block: any; // The block to render. - @Input() contextLevel: string; // The context where the block will be used. + @Input() contextLevel: ContextLevel; // The context where the block will be used. @Input() instanceId: number; // The instance ID associated with the context level. entries: any[] = []; protected fetchContentDefaultError = 'Error getting activity modules data.'; - constructor(injector: Injector, protected utils: CoreUtilsProvider, protected courseProvider: CoreCourseProvider, - protected translate: TranslateService, protected moduleDelegate: CoreCourseModuleDelegate) { + constructor(injector: Injector, protected courseProvider: CoreCourseProvider, + protected translate: TranslateService, protected moduleDelegate: CoreCourseModuleDelegate, + protected sitesProvider: CoreSitesProvider) { super(injector, 'AddonBlockActivityModulesComponent'); } @@ -52,7 +53,7 @@ export class AddonBlockActivityModulesComponent extends CoreBlockBaseComponent i /** * Perform the invalidate content function. * - * @return {Promise} Resolved when done. + * @return Resolved when done. */ protected invalidateContent(): Promise { return this.courseProvider.invalidateSections(this.instanceId); @@ -61,11 +62,10 @@ export class AddonBlockActivityModulesComponent extends CoreBlockBaseComponent i /** * Fetch the data to render the block. * - * @return {Promise} Promise resolved when done. + * @return Promise resolved when done. */ protected fetchContent(): Promise { - return this.courseProvider.getSections(this.instanceId, false, true).then((sections) => { - + return this.courseProvider.getSections(this.getCourseId(), false, true).then((sections) => { this.entries = []; const archetypes = {}, @@ -123,4 +123,18 @@ export class AddonBlockActivityModulesComponent extends CoreBlockBaseComponent i } }); } + + /** + * Obtain the appropiate course id for the block. + * + * @return Course id. + */ + protected getCourseId(): number { + switch (this.contextLevel) { + case ContextLevel.COURSE: + return this.instanceId; + default: + return this.sitesProvider.getCurrentSiteHomeId(); + } + } } diff --git a/src/addon/block/activitymodules/components/activitymodules/addon-block-activitymodules.html b/src/addon/block/activitymodules/components/activitymodules/addon-block-activitymodules.html index 7711b65f6..342e38924 100644 --- a/src/addon/block/activitymodules/components/activitymodules/addon-block-activitymodules.html +++ b/src/addon/block/activitymodules/components/activitymodules/addon-block-activitymodules.html @@ -4,6 +4,6 @@ - + {{ entry.name }} diff --git a/src/addon/block/activitymodules/components/components.module.ts b/src/addon/block/activitymodules/components/components.module.ts index 5cbc4cfd4..a5d1651b3 100644 --- a/src/addon/block/activitymodules/components/components.module.ts +++ b/src/addon/block/activitymodules/components/components.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/addon/block/activitymodules/providers/block-handler.ts b/src/addon/block/activitymodules/providers/block-handler.ts index 752d23a47..53310d351 100644 --- a/src/addon/block/activitymodules/providers/block-handler.ts +++ b/src/addon/block/activitymodules/providers/block-handler.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -32,11 +32,11 @@ export class AddonBlockActivityModulesHandler extends CoreBlockBaseHandler { /** * Returns the data needed to render the block. * - * @param {Injector} injector Injector. - * @param {any} block The block to render. - * @param {string} contextLevel The context where the block will be used. - * @param {number} instanceId The instance ID associated with the context level. - * @return {CoreBlockHandlerData|Promise} Data or promise resolved with the data. + * @param injector Injector. + * @param block The block to render. + * @param contextLevel The context where the block will be used. + * @param instanceId The instance ID associated with the context level. + * @return Data or promise resolved with the data. */ getDisplayData(injector: Injector, block: any, contextLevel: string, instanceId: number) : CoreBlockHandlerData | Promise { diff --git a/src/addon/block/badges/badges.module.ts b/src/addon/block/badges/badges.module.ts index ff9c450d2..7b4a5ad38 100644 --- a/src/addon/block/badges/badges.module.ts +++ b/src/addon/block/badges/badges.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/addon/block/badges/providers/block-handler.ts b/src/addon/block/badges/providers/block-handler.ts index d6cfb677a..defc559c6 100644 --- a/src/addon/block/badges/providers/block-handler.ts +++ b/src/addon/block/badges/providers/block-handler.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -33,11 +33,11 @@ export class AddonBlockBadgesHandler extends CoreBlockBaseHandler { /** * Returns the data needed to render the block. * - * @param {Injector} injector Injector. - * @param {any} block The block to render. - * @param {string} contextLevel The context where the block will be used. - * @param {number} instanceId The instance ID associated with the context level. - * @return {CoreBlockHandlerData|Promise} Data or promise resolved with the data. + * @param injector Injector. + * @param block The block to render. + * @param contextLevel The context where the block will be used. + * @param instanceId The instance ID associated with the context level. + * @return Data or promise resolved with the data. */ getDisplayData(injector: Injector, block: any, contextLevel: string, instanceId: number) : CoreBlockHandlerData | Promise { diff --git a/src/addon/block/blogmenu/blogmenu.module.ts b/src/addon/block/blogmenu/blogmenu.module.ts index cbca10200..6c3b51dc8 100644 --- a/src/addon/block/blogmenu/blogmenu.module.ts +++ b/src/addon/block/blogmenu/blogmenu.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/addon/block/blogmenu/providers/block-handler.ts b/src/addon/block/blogmenu/providers/block-handler.ts index 231137b8e..b8d445b0f 100644 --- a/src/addon/block/blogmenu/providers/block-handler.ts +++ b/src/addon/block/blogmenu/providers/block-handler.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -33,11 +33,11 @@ export class AddonBlockBlogMenuHandler extends CoreBlockBaseHandler { /** * Returns the data needed to render the block. * - * @param {Injector} injector Injector. - * @param {any} block The block to render. - * @param {string} contextLevel The context where the block will be used. - * @param {number} instanceId The instance ID associated with the context level. - * @return {CoreBlockHandlerData|Promise} Data or promise resolved with the data. + * @param injector Injector. + * @param block The block to render. + * @param contextLevel The context where the block will be used. + * @param instanceId The instance ID associated with the context level. + * @return Data or promise resolved with the data. */ getDisplayData(injector: Injector, block: any, contextLevel: string, instanceId: number) : CoreBlockHandlerData | Promise { diff --git a/src/addon/block/blogrecent/blogrecent.module.ts b/src/addon/block/blogrecent/blogrecent.module.ts index 1ecdf6f7b..0f09c7e4b 100644 --- a/src/addon/block/blogrecent/blogrecent.module.ts +++ b/src/addon/block/blogrecent/blogrecent.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/addon/block/blogrecent/providers/block-handler.ts b/src/addon/block/blogrecent/providers/block-handler.ts index 55f03cb8e..7c3887c5b 100644 --- a/src/addon/block/blogrecent/providers/block-handler.ts +++ b/src/addon/block/blogrecent/providers/block-handler.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -33,11 +33,11 @@ export class AddonBlockBlogRecentHandler extends CoreBlockBaseHandler { /** * Returns the data needed to render the block. * - * @param {Injector} injector Injector. - * @param {any} block The block to render. - * @param {string} contextLevel The context where the block will be used. - * @param {number} instanceId The instance ID associated with the context level. - * @return {CoreBlockHandlerData|Promise} Data or promise resolved with the data. + * @param injector Injector. + * @param block The block to render. + * @param contextLevel The context where the block will be used. + * @param instanceId The instance ID associated with the context level. + * @return Data or promise resolved with the data. */ getDisplayData(injector: Injector, block: any, contextLevel: string, instanceId: number) : CoreBlockHandlerData | Promise { diff --git a/src/addon/block/blogtags/blogtags.module.ts b/src/addon/block/blogtags/blogtags.module.ts index a9ed3a090..7a36e6f4e 100644 --- a/src/addon/block/blogtags/blogtags.module.ts +++ b/src/addon/block/blogtags/blogtags.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/addon/block/blogtags/blogtags.scss b/src/addon/block/blogtags/blogtags.scss index a974b45cb..ef3a6a4c8 100644 --- a/src/addon/block/blogtags/blogtags.scss +++ b/src/addon/block/blogtags/blogtags.scss @@ -1,6 +1,7 @@ .addon-block-blog-tags core-block-pre-rendered { .core-block-content { ul.inline-list { + font-size: 80%; list-style: none; @include margin-horizontal(0); -webkit-padding-start: 0; diff --git a/src/addon/block/blogtags/providers/block-handler.ts b/src/addon/block/blogtags/providers/block-handler.ts index aa2a17495..0c2becf40 100644 --- a/src/addon/block/blogtags/providers/block-handler.ts +++ b/src/addon/block/blogtags/providers/block-handler.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -33,11 +33,11 @@ export class AddonBlockBlogTagsHandler extends CoreBlockBaseHandler { /** * Returns the data needed to render the block. * - * @param {Injector} injector Injector. - * @param {any} block The block to render. - * @param {string} contextLevel The context where the block will be used. - * @param {number} instanceId The instance ID associated with the context level. - * @return {CoreBlockHandlerData|Promise} Data or promise resolved with the data. + * @param injector Injector. + * @param block The block to render. + * @param contextLevel The context where the block will be used. + * @param instanceId The instance ID associated with the context level. + * @return Data or promise resolved with the data. */ getDisplayData(injector: Injector, block: any, contextLevel: string, instanceId: number) : CoreBlockHandlerData | Promise { diff --git a/src/addon/block/calendarmonth/calendarmonth.module.ts b/src/addon/block/calendarmonth/calendarmonth.module.ts index e3204ae8f..8388cc464 100644 --- a/src/addon/block/calendarmonth/calendarmonth.module.ts +++ b/src/addon/block/calendarmonth/calendarmonth.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/addon/block/calendarmonth/providers/block-handler.ts b/src/addon/block/calendarmonth/providers/block-handler.ts index 80e85edf8..1f4c46084 100644 --- a/src/addon/block/calendarmonth/providers/block-handler.ts +++ b/src/addon/block/calendarmonth/providers/block-handler.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -33,11 +33,11 @@ export class AddonBlockCalendarMonthHandler extends CoreBlockBaseHandler { /** * Returns the data needed to render the block. * - * @param {Injector} injector Injector. - * @param {any} block The block to render. - * @param {string} contextLevel The context where the block will be used. - * @param {number} instanceId The instance ID associated with the context level. - * @return {CoreBlockHandlerData|Promise} Data or promise resolved with the data. + * @param injector Injector. + * @param block The block to render. + * @param contextLevel The context where the block will be used. + * @param instanceId The instance ID associated with the context level. + * @return Data or promise resolved with the data. */ getDisplayData(injector: Injector, block: any, contextLevel: string, instanceId: number) : CoreBlockHandlerData | Promise { diff --git a/src/addon/block/calendarupcoming/calendarupcoming.module.ts b/src/addon/block/calendarupcoming/calendarupcoming.module.ts index 345d17672..cc9d804f7 100644 --- a/src/addon/block/calendarupcoming/calendarupcoming.module.ts +++ b/src/addon/block/calendarupcoming/calendarupcoming.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/addon/block/calendarupcoming/providers/block-handler.ts b/src/addon/block/calendarupcoming/providers/block-handler.ts index c58a0d080..4adc5dfb5 100644 --- a/src/addon/block/calendarupcoming/providers/block-handler.ts +++ b/src/addon/block/calendarupcoming/providers/block-handler.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -33,11 +33,11 @@ export class AddonBlockCalendarUpcomingHandler extends CoreBlockBaseHandler { /** * Returns the data needed to render the block. * - * @param {Injector} injector Injector. - * @param {any} block The block to render. - * @param {string} contextLevel The context where the block will be used. - * @param {number} instanceId The instance ID associated with the context level. - * @return {CoreBlockHandlerData|Promise} Data or promise resolved with the data. + * @param injector Injector. + * @param block The block to render. + * @param contextLevel The context where the block will be used. + * @param instanceId The instance ID associated with the context level. + * @return Data or promise resolved with the data. */ getDisplayData(injector: Injector, block: any, contextLevel: string, instanceId: number) : CoreBlockHandlerData | Promise { diff --git a/src/addon/block/comments/comments.module.ts b/src/addon/block/comments/comments.module.ts index dce565e7d..ab09d9ac1 100644 --- a/src/addon/block/comments/comments.module.ts +++ b/src/addon/block/comments/comments.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/addon/block/comments/providers/block-handler.ts b/src/addon/block/comments/providers/block-handler.ts index ada6e1654..526cc9d79 100644 --- a/src/addon/block/comments/providers/block-handler.ts +++ b/src/addon/block/comments/providers/block-handler.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -32,11 +32,11 @@ export class AddonBlockCommentsHandler extends CoreBlockBaseHandler { /** * Returns the data needed to render the block. * - * @param {Injector} injector Injector. - * @param {any} block The block to render. - * @param {string} contextLevel The context where the block will be used. - * @param {number} instanceId The instance ID associated with the context level. - * @return {CoreBlockHandlerData|Promise} Data or promise resolved with the data. + * @param injector Injector. + * @param block The block to render. + * @param contextLevel The context where the block will be used. + * @param instanceId The instance ID associated with the context level. + * @return Data or promise resolved with the data. */ getDisplayData(injector: Injector, block: any, contextLevel: string, instanceId: number) : CoreBlockHandlerData | Promise { diff --git a/src/addon/block/completionstatus/completionstatus.module.ts b/src/addon/block/completionstatus/completionstatus.module.ts index 37d448a93..c84115d9d 100644 --- a/src/addon/block/completionstatus/completionstatus.module.ts +++ b/src/addon/block/completionstatus/completionstatus.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/addon/block/completionstatus/providers/block-handler.ts b/src/addon/block/completionstatus/providers/block-handler.ts index 88fca4ec8..d64fee217 100644 --- a/src/addon/block/completionstatus/providers/block-handler.ts +++ b/src/addon/block/completionstatus/providers/block-handler.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -32,11 +32,11 @@ export class AddonBlockCompletionStatusHandler extends CoreBlockBaseHandler { /** * Returns the data needed to render the block. * - * @param {Injector} injector Injector. - * @param {any} block The block to render. - * @param {string} contextLevel The context where the block will be used. - * @param {number} instanceId The instance ID associated with the context level. - * @return {CoreBlockHandlerData|Promise} Data or promise resolved with the data. + * @param injector Injector. + * @param block The block to render. + * @param contextLevel The context where the block will be used. + * @param instanceId The instance ID associated with the context level. + * @return Data or promise resolved with the data. */ getDisplayData(injector: Injector, block: any, contextLevel: string, instanceId: number) : CoreBlockHandlerData | Promise { diff --git a/src/addon/block/glossaryrandom/glossaryrandom.module.ts b/src/addon/block/glossaryrandom/glossaryrandom.module.ts index e6d238957..df316f008 100644 --- a/src/addon/block/glossaryrandom/glossaryrandom.module.ts +++ b/src/addon/block/glossaryrandom/glossaryrandom.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/addon/block/glossaryrandom/providers/block-handler.ts b/src/addon/block/glossaryrandom/providers/block-handler.ts index d639ccb16..259b4616b 100644 --- a/src/addon/block/glossaryrandom/providers/block-handler.ts +++ b/src/addon/block/glossaryrandom/providers/block-handler.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -32,11 +32,11 @@ export class AddonBlockGlossaryRandomHandler extends CoreBlockBaseHandler { /** * Returns the data needed to render the block. * - * @param {Injector} injector Injector. - * @param {any} block The block to render. - * @param {string} contextLevel The context where the block will be used. - * @param {number} instanceId The instance ID associated with the context level. - * @return {CoreBlockHandlerData|Promise} Data or promise resolved with the data. + * @param injector Injector. + * @param block The block to render. + * @param contextLevel The context where the block will be used. + * @param instanceId The instance ID associated with the context level. + * @return Data or promise resolved with the data. */ getDisplayData(injector: Injector, block: any, contextLevel: string, instanceId: number) : CoreBlockHandlerData | Promise { diff --git a/src/addon/block/html/html.module.ts b/src/addon/block/html/html.module.ts index 1b81cf4ad..18cd3925d 100644 --- a/src/addon/block/html/html.module.ts +++ b/src/addon/block/html/html.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/addon/block/html/providers/block-handler.ts b/src/addon/block/html/providers/block-handler.ts index a5603fb53..5b5dda4f0 100644 --- a/src/addon/block/html/providers/block-handler.ts +++ b/src/addon/block/html/providers/block-handler.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -32,11 +32,11 @@ export class AddonBlockHtmlHandler extends CoreBlockBaseHandler { /** * Returns the data needed to render the block. * - * @param {Injector} injector Injector. - * @param {any} block The block to render. - * @param {string} contextLevel The context where the block will be used. - * @param {number} instanceId The instance ID associated with the context level. - * @return {CoreBlockHandlerData|Promise} Data or promise resolved with the data. + * @param injector Injector. + * @param block The block to render. + * @param contextLevel The context where the block will be used. + * @param instanceId The instance ID associated with the context level. + * @return Data or promise resolved with the data. */ getDisplayData(injector: Injector, block: any, contextLevel: string, instanceId: number) : CoreBlockHandlerData | Promise { diff --git a/src/addon/block/learningplans/learningplans.module.ts b/src/addon/block/learningplans/learningplans.module.ts index 167178d70..936aa8edb 100644 --- a/src/addon/block/learningplans/learningplans.module.ts +++ b/src/addon/block/learningplans/learningplans.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/addon/block/learningplans/providers/block-handler.ts b/src/addon/block/learningplans/providers/block-handler.ts index fa98be638..cc1396a78 100644 --- a/src/addon/block/learningplans/providers/block-handler.ts +++ b/src/addon/block/learningplans/providers/block-handler.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -32,11 +32,11 @@ export class AddonBlockLearningPlansHandler extends CoreBlockBaseHandler { /** * Returns the data needed to render the block. * - * @param {Injector} injector Injector. - * @param {any} block The block to render. - * @param {string} contextLevel The context where the block will be used. - * @param {number} instanceId The instance ID associated with the context level. - * @return {CoreBlockHandlerData|Promise} Data or promise resolved with the data. + * @param injector Injector. + * @param block The block to render. + * @param contextLevel The context where the block will be used. + * @param instanceId The instance ID associated with the context level. + * @return Data or promise resolved with the data. */ getDisplayData(injector: Injector, block: any, contextLevel: string, instanceId: number) : CoreBlockHandlerData | Promise { diff --git a/src/addon/block/myoverview/components/components.module.ts b/src/addon/block/myoverview/components/components.module.ts index 45930cb01..d345b508c 100644 --- a/src/addon/block/myoverview/components/components.module.ts +++ b/src/addon/block/myoverview/components/components.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/addon/block/myoverview/components/myoverview/addon-block-myoverview.html b/src/addon/block/myoverview/components/myoverview/addon-block-myoverview.html index 294a499c4..e4016144a 100644 --- a/src/addon/block/myoverview/components/myoverview/addon-block-myoverview.html +++ b/src/addon/block/myoverview/components/myoverview/addon-block-myoverview.html @@ -1,15 +1,15 @@

{{ 'addon.block_myoverview.pluginname' | translate }}

-
- {{prefetchCoursesData[selectedFilter].badge}}
- + @@ -18,15 +18,21 @@
- {{ 'addon.block_myoverview.all' | translate }}∫ - {{ 'addon.block_myoverview.inprogress' | translate }} - {{ 'addon.block_myoverview.future' | translate }} - {{ 'addon.block_myoverview.past' | translate }} - {{ 'addon.block_myoverview.favourites' | translate }} - {{ 'addon.block_myoverview.hiddencourses' | translate }} + {{ 'addon.block_myoverview.allincludinghidden' | translate }} + {{ 'addon.block_myoverview.all' | translate }} + {{ 'addon.block_myoverview.inprogress' | translate }} + {{ 'addon.block_myoverview.future' | translate }} + {{ 'addon.block_myoverview.past' | translate }} + + + {{ customOption.name }} + + + {{ 'addon.block_myoverview.favourites' | translate }} + {{ 'addon.block_myoverview.hiddencourses' | translate }}
- + diff --git a/src/addon/block/myoverview/components/myoverview/myoverview.ts b/src/addon/block/myoverview/components/myoverview/myoverview.ts index ed68b9205..b6f062542 100644 --- a/src/addon/block/myoverview/components/myoverview/myoverview.ts +++ b/src/addon/block/myoverview/components/myoverview/myoverview.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -15,7 +15,6 @@ import { Component, OnInit, Input, OnDestroy, ViewChild, Injector, OnChanges, SimpleChange } from '@angular/core'; import { Searchbar } from 'ionic-angular'; import { CoreEventsProvider } from '@providers/events'; -import { CoreUtilsProvider } from '@providers/utils/utils'; import { CoreTimeUtilsProvider } from '@providers/utils/time'; import { CoreSitesProvider } from '@providers/sites'; import { CoreCoursesProvider } from '@core/courses/providers/courses'; @@ -39,36 +38,44 @@ export class AddonBlockMyOverviewComponent extends CoreBlockBaseComponent implem courses = { filter: '', all: [], + allincludinghidden: [], past: [], inprogress: [], future: [], favourite: [], - hidden: [] + hidden: [], + custom: [], // Leave it empty to avoid download all those courses. }; + customFilter: any[] = []; selectedFilter = 'inprogress'; sort = 'fullname'; currentSite: any; - filteredCourses: any[]; + filteredCourses: any[] = []; prefetchCoursesData = { all: {}, + allincludinghidden: {}, inprogress: {}, past: {}, future: {}, favourite: {}, - hidden: {} + hidden: {}, + custom: {}, // Leave it empty to avoid download all those courses. + }; + showFilters = { // Options are show, disabled, hidden. + all: 'show', + allincludinghidden: 'show', + past: 'show', + inprogress: 'show', + future: 'show', + favourite: 'show', + hidden: 'show', + custom: 'hidden', }; showFilter = false; - showFavourite = false; - showHidden = false; showSelectorFilter = false; showSortFilter = false; downloadCourseEnabled: boolean; downloadCoursesEnabled: boolean; - disableInProgress = false; - disablePast = false; - disableFuture = false; - disableFavourite = false; - disableHidden = false; protected prefetchIconsInitialized = false; protected isDestroyed; @@ -77,11 +84,15 @@ export class AddonBlockMyOverviewComponent extends CoreBlockBaseComponent implem protected courseIds = []; protected fetchContentDefaultError = 'Error getting my overview data.'; - constructor(injector: Injector, private coursesProvider: CoreCoursesProvider, - private courseCompletionProvider: AddonCourseCompletionProvider, private eventsProvider: CoreEventsProvider, - private courseHelper: CoreCourseHelperProvider, private utils: CoreUtilsProvider, - private courseOptionsDelegate: CoreCourseOptionsDelegate, private coursesHelper: CoreCoursesHelperProvider, - private sitesProvider: CoreSitesProvider, private timeUtils: CoreTimeUtilsProvider) { + constructor(injector: Injector, + protected coursesProvider: CoreCoursesProvider, + protected courseCompletionProvider: AddonCourseCompletionProvider, + protected eventsProvider: CoreEventsProvider, + protected courseHelper: CoreCourseHelperProvider, + protected courseOptionsDelegate: CoreCourseOptionsDelegate, + protected coursesHelper: CoreCoursesHelperProvider, + protected sitesProvider: CoreSitesProvider, + protected timeUtils: CoreTimeUtilsProvider) { super(injector, 'AddonBlockMyOverviewComponent'); } @@ -112,7 +123,7 @@ export class AddonBlockMyOverviewComponent extends CoreBlockBaseComponent implem this.sort = value; })); promises.push(this.currentSite.getLocalSiteConfig('AddonBlockMyOverviewFilter', this.selectedFilter).then((value) => { - this.selectedFilter = typeof this.courses[value] == 'undefined' ? 'inprogress' : value; + this.selectedFilter = value; })); Promise.all(promises).finally(() => { @@ -133,7 +144,7 @@ export class AddonBlockMyOverviewComponent extends CoreBlockBaseComponent implem /** * Perform the invalidate content function. * - * @return {Promise} Resolved when done. + * @return Resolved when done. */ protected invalidateContent(): Promise { const promises = []; @@ -161,10 +172,14 @@ export class AddonBlockMyOverviewComponent extends CoreBlockBaseComponent implem /** * Fetch the courses for my overview. * - * @return {Promise} Promise resolved when done. + * @return Promise resolved when done. */ protected fetchContent(): Promise { - return this.coursesHelper.getUserCoursesWithOptions(this.sort).then((courses) => { + const config = this.block.configs; + + const showCategories = config && config.displaycategories && config.displaycategories.value == '1'; + + return this.coursesHelper.getUserCoursesWithOptions(this.sort, null, null, showCategories).then((courses) => { this.courseIds = courses.map((course) => { return course.id; }); @@ -173,45 +188,94 @@ export class AddonBlockMyOverviewComponent extends CoreBlockBaseComponent implem this.initCourseFilters(courses); - this.courses.filter = ''; - this.showFilter = false; - this.disableInProgress = this.courses.inprogress.length === 0; - this.disablePast = this.courses.past.length === 0; - this.disableFuture = this.courses.future.length === 0; this.showSelectorFilter = courses.length > 0 && (this.courses.past.length > 0 || this.courses.future.length > 0 || typeof courses[0].enddate != 'undefined'); - this.showHidden = this.showSelectorFilter && typeof courses[0].hidden != 'undefined'; - this.disableHidden = this.courses.hidden.length === 0; - this.showFavourite = this.showSelectorFilter && typeof courses[0].isfavourite != 'undefined'; - this.disableFavourite = this.courses.favourite.length === 0; - if (!this.showSelectorFilter || (this.selectedFilter === 'inprogress' && this.disableInProgress)) { + + this.courses.filter = ''; + this.showFilter = false; + + this.showFilters.all = this.getShowFilterValue(!config || config.displaygroupingall.value == '1', + this.courses.all.length === 0); + // Do not show allincludinghiddenif config it's not present (before 3.8). + this.showFilters.allincludinghidden = + this.getShowFilterValue(config && config.displaygroupingallincludinghidden.value == '1', + this.courses.allincludinghidden.length === 0); + + this.showFilters.inprogress = this.getShowFilterValue(!config || config.displaygroupinginprogress.value == '1', + this.courses.inprogress.length === 0); + this.showFilters.past = this.getShowFilterValue(!config || config.displaygroupingpast.value == '1', + this.courses.past.length === 0); + this.showFilters.future = this.getShowFilterValue(!config || config.displaygroupingfuture.value == '1', + this.courses.future.length === 0); + + this.showSelectorFilter = courses.length > 0 && (this.courses.past.length > 0 || this.courses.future.length > 0 || + typeof courses[0].enddate != 'undefined'); + + this.showFilters.hidden = this.getShowFilterValue( + this.showSelectorFilter && typeof courses[0].hidden != 'undefined' && + (!config || config.displaygroupinghidden.value == '1'), + this.courses.hidden.length === 0); + + this.showFilters.favourite = this.getShowFilterValue( + this.showSelectorFilter && typeof courses[0].isfavourite != 'undefined' && + (!config || config.displaygroupingstarred.value == '1'), + this.courses.favourite.length === 0); + + this.showFilters.custom = this.getShowFilterValue(this.showSelectorFilter && config && + config.displaygroupingcustomfield.value == '1' && config.customfieldsexport && config.customfieldsexport.value, + false); + if (this.showFilters.custom == 'show') { + this.customFilter = this.textUtils.parseJSON(config.customfieldsexport.value); + } else { + this.customFilter = []; + } + + if (this.showSelectorFilter) { + // Check if any selector is shown and not disabled. + this.showSelectorFilter = Object.keys(this.showFilters).some((key) => { + return this.showFilters[key] == 'show'; + }); + } + + if (!this.showSelectorFilter || (this.selectedFilter === 'inprogress' && this.showFilters.inprogress == 'disabled')) { // No selector, or the default option is disabled, show all. this.selectedFilter = 'all'; } - this.filteredCourses = this.courses[this.selectedFilter]; + this.setCourseFilter(this.selectedFilter); this.initPrefetchCoursesIcons(); }); } + /** + * Helper function to help with filter values. + * + * @param showCondition If true, filter will be shown. + * @param disabledCondition If true, and showCondition is also met, it will be shown as disabled. + * @return show / disabled / hidden value. + */ + protected getShowFilterValue(showCondition: boolean, disabledCondition: boolean): string { + return showCondition ? (disabledCondition ? 'disabled' : 'show') : 'hidden'; + } + /** * The filter has changed. * - * @param {any} Received Event. + * @param Received Event. */ filterChanged(event: any): void { const newValue = event.target.value && event.target.value.trim().toLowerCase(); - if (!newValue || !this.courses['all']) { - this.filteredCourses = this.courses['all']; + if (!newValue || this.courses.allincludinghidden.length <= 0) { + this.filteredCourses = this.courses.allincludinghidden; } else { // Use displayname if avalaible, or fullname if not. - if (this.courses['all'].length > 0 && - typeof this.courses['all'][0].displayname != 'undefined') { - this.filteredCourses = this.courses['all'].filter((course) => { + if (this.courses.allincludinghidden.length > 0 && + typeof this.courses.allincludinghidden[0].displayname != 'undefined') { + this.filteredCourses = this.courses.allincludinghidden.filter((course) => { return course.displayname.toLowerCase().indexOf(newValue) > -1; }); } else { - this.filteredCourses = this.courses['all'].filter((course) => { + this.filteredCourses = this.courses.allincludinghidden.filter((course) => { return course.fullname.toLowerCase().indexOf(newValue) > -1; }); } @@ -239,7 +303,7 @@ export class AddonBlockMyOverviewComponent extends CoreBlockBaseComponent implem /** * Prefetch all the shown courses. * - * @return {Promise} Promise resolved when done. + * @return Promise resolved when done. */ prefetchCourses(): Promise { const selected = this.selectedFilter, @@ -257,16 +321,60 @@ export class AddonBlockMyOverviewComponent extends CoreBlockBaseComponent implem * The selected courses filter have changed. */ selectedChanged(): void { - this.currentSite.setLocalSiteConfig('AddonBlockMyOverviewFilter', this.selectedFilter); - this.filteredCourses = this.courses[this.selectedFilter]; + this.setCourseFilter(this.selectedFilter); + } + + /** + * Set selected courses filter. + * + * @param filter Filter name to set. + */ + protected setCourseFilter(filter: string): void { + this.selectedFilter = filter; + + if (this.showFilters.custom == 'show' && filter.startsWith('custom-') && + typeof this.customFilter[filter.substr(7)] != 'undefined') { + const filterName = this.block.configs.customfiltergrouping.value, + filterValue = this.customFilter[filter.substr(7)].value; + + this.loaded = false; + + this.coursesProvider.getEnrolledCoursesByCustomField(filterName, filterValue).then((courses) => { + // Get the courses information from allincludinghidden to get the max info about the course. + const courseIds = courses.map((course) => course.id); + this.filteredCourses = this.courses.allincludinghidden.filter((allCourse) => + courseIds.indexOf(allCourse.id) !== -1); + }).catch((error) => { + this.domUtils.showErrorModalDefault(error, this.fetchContentDefaultError); + }).finally(() => { + this.loaded = true; + }); + } else { + // Only save the filter if not a custom one. + this.currentSite.setLocalSiteConfig('AddonBlockMyOverviewFilter', filter); + + if (this.showFilters[filter] == 'show') { + this.filteredCourses = this.courses[filter]; + } else { + const activeFilter = Object.keys(this.showFilters).find((name) => { + return this.showFilters[name] == 'show'; + }); + + if (activeFilter) { + this.setCourseFilter(activeFilter); + } + } + } } /** * Init courses filters. * - * @param {any[]} courses Courses to filter. + * @param courses Courses to filter. */ initCourseFilters(courses: any[]): void { + this.courses.allincludinghidden = courses; + if (this.showSortFilter) { if (this.sort == 'lastaccess') { courses.sort((a, b) => { @@ -313,18 +421,18 @@ export class AddonBlockMyOverviewComponent extends CoreBlockBaseComponent implem } }); - this.filteredCourses = this.courses[this.selectedFilter]; + this.setCourseFilter(this.selectedFilter); } /** * The selected courses sort filter have changed. * - * @param {string} sort New sorting. + * @param sort New sorting. */ switchSort(sort: string): void { this.sort = sort; this.currentSite.setLocalSiteConfig('AddonBlockMyOverviewSort', this.sort); - this.initCourseFilters(this.courses.all.concat(this.courses.hidden)); + this.initCourseFilters(this.courses.allincludinghidden); } /** @@ -333,21 +441,32 @@ export class AddonBlockMyOverviewComponent extends CoreBlockBaseComponent implem switchFilter(): void { this.showFilter = !this.showFilter; this.courses.filter = ''; - this.filteredCourses = this.courses[this.showFilter ? 'all' : this.selectedFilter]; + + if (this.showFilter) { + this.filteredCourses = this.courses.allincludinghidden; + } else { + this.setCourseFilter(this.selectedFilter); + } + } + + /** + * Popover closed after clicking switch filter. + */ + switchFilterClosed(): void { if (this.showFilter) { setTimeout(() => { this.searchbar.setFocus(); - }, 500); + }); } } /** * If switch button that enables the filter input is shown or not. * - * @return {boolean} If switch button that enables the filter input is shown or not. + * @return If switch button that enables the filter input is shown or not. */ showFilterSwitchButton(): boolean { - return this.loaded && this.courses['all'] && this.courses['all'].length > 5; + return this.loaded && this.courses.allincludinghidden && this.courses.allincludinghidden.length > 5; } /** diff --git a/src/addon/block/myoverview/lang/en.json b/src/addon/block/myoverview/lang/en.json index 29ff9d51c..a8180b4a8 100644 --- a/src/addon/block/myoverview/lang/en.json +++ b/src/addon/block/myoverview/lang/en.json @@ -1,5 +1,6 @@ { - "all": "All (except removed from view)", + "allincludinghidden": "All", + "all": "All (except hidden)", "favourites": "Starred", "future": "Future", "hiddencourses": "Removed from view", diff --git a/src/addon/block/myoverview/myoverview.module.ts b/src/addon/block/myoverview/myoverview.module.ts index 91e854546..6b26273e2 100644 --- a/src/addon/block/myoverview/myoverview.module.ts +++ b/src/addon/block/myoverview/myoverview.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/addon/block/myoverview/providers/block-handler.ts b/src/addon/block/myoverview/providers/block-handler.ts index e0a593b9a..976c3c046 100644 --- a/src/addon/block/myoverview/providers/block-handler.ts +++ b/src/addon/block/myoverview/providers/block-handler.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -34,7 +34,7 @@ export class AddonBlockMyOverviewHandler extends CoreBlockBaseHandler { /** * Check if the handler is enabled on a site level. * - * @return {boolean} Whether or not the handler is enabled on a site level. + * @return Whether or not the handler is enabled on a site level. */ isEnabled(): boolean | Promise { return this.sitesProvider.getCurrentSite().isVersionGreaterEqualThan('3.6') || @@ -44,11 +44,11 @@ export class AddonBlockMyOverviewHandler extends CoreBlockBaseHandler { /** * Returns the data needed to render the block. * - * @param {Injector} injector Injector. - * @param {any} block The block to render. - * @param {string} contextLevel The context where the block will be used. - * @param {number} instanceId The instance ID associated with the context level. - * @return {CoreBlockHandlerData|Promise} Data or promise resolved with the data. + * @param injector Injector. + * @param block The block to render. + * @param contextLevel The context where the block will be used. + * @param instanceId The instance ID associated with the context level. + * @return Data or promise resolved with the data. */ getDisplayData(injector: Injector, block: any, contextLevel: string, instanceId: number) : CoreBlockHandlerData | Promise { diff --git a/src/addon/block/newsitems/newsitems.module.ts b/src/addon/block/newsitems/newsitems.module.ts index a3364ea5e..11af484ba 100644 --- a/src/addon/block/newsitems/newsitems.module.ts +++ b/src/addon/block/newsitems/newsitems.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/addon/block/newsitems/providers/block-handler.ts b/src/addon/block/newsitems/providers/block-handler.ts index c077474d8..48e1692c3 100644 --- a/src/addon/block/newsitems/providers/block-handler.ts +++ b/src/addon/block/newsitems/providers/block-handler.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -32,11 +32,11 @@ export class AddonBlockNewsItemsHandler extends CoreBlockBaseHandler { /** * Returns the data needed to render the block. * - * @param {Injector} injector Injector. - * @param {any} block The block to render. - * @param {string} contextLevel The context where the block will be used. - * @param {number} instanceId The instance ID associated with the context level. - * @return {CoreBlockHandlerData|Promise} Data or promise resolved with the data. + * @param injector Injector. + * @param block The block to render. + * @param contextLevel The context where the block will be used. + * @param instanceId The instance ID associated with the context level. + * @return Data or promise resolved with the data. */ getDisplayData(injector: Injector, block: any, contextLevel: string, instanceId: number) : CoreBlockHandlerData | Promise { diff --git a/src/addon/block/onlineusers/onlineusers.module.ts b/src/addon/block/onlineusers/onlineusers.module.ts index 0df61ad7a..b243c7e8a 100644 --- a/src/addon/block/onlineusers/onlineusers.module.ts +++ b/src/addon/block/onlineusers/onlineusers.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/addon/block/onlineusers/providers/block-handler.ts b/src/addon/block/onlineusers/providers/block-handler.ts index 353835c83..a1bfc4e61 100644 --- a/src/addon/block/onlineusers/providers/block-handler.ts +++ b/src/addon/block/onlineusers/providers/block-handler.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -32,11 +32,11 @@ export class AddonBlockOnlineUsersHandler extends CoreBlockBaseHandler { /** * Returns the data needed to render the block. * - * @param {Injector} injector Injector. - * @param {any} block The block to render. - * @param {string} contextLevel The context where the block will be used. - * @param {number} instanceId The instance ID associated with the context level. - * @return {CoreBlockHandlerData|Promise} Data or promise resolved with the data. + * @param injector Injector. + * @param block The block to render. + * @param contextLevel The context where the block will be used. + * @param instanceId The instance ID associated with the context level. + * @return Data or promise resolved with the data. */ getDisplayData(injector: Injector, block: any, contextLevel: string, instanceId: number) : CoreBlockHandlerData | Promise { diff --git a/src/addon/block/privatefiles/privatefiles.module.ts b/src/addon/block/privatefiles/privatefiles.module.ts index c9617c372..81c2a7f5f 100644 --- a/src/addon/block/privatefiles/privatefiles.module.ts +++ b/src/addon/block/privatefiles/privatefiles.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/addon/block/privatefiles/providers/block-handler.ts b/src/addon/block/privatefiles/providers/block-handler.ts index f0284df97..7632f770b 100644 --- a/src/addon/block/privatefiles/providers/block-handler.ts +++ b/src/addon/block/privatefiles/providers/block-handler.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -32,11 +32,11 @@ export class AddonBlockPrivateFilesHandler extends CoreBlockBaseHandler { /** * Returns the data needed to render the block. * - * @param {Injector} injector Injector. - * @param {any} block The block to render. - * @param {string} contextLevel The context where the block will be used. - * @param {number} instanceId The instance ID associated with the context level. - * @return {CoreBlockHandlerData|Promise} Data or promise resolved with the data. + * @param injector Injector. + * @param block The block to render. + * @param contextLevel The context where the block will be used. + * @param instanceId The instance ID associated with the context level. + * @return Data or promise resolved with the data. */ getDisplayData(injector: Injector, block: any, contextLevel: string, instanceId: number) : CoreBlockHandlerData | Promise { diff --git a/src/addon/block/recentactivity/providers/block-handler.ts b/src/addon/block/recentactivity/providers/block-handler.ts index ac69af02b..832fdbcbc 100644 --- a/src/addon/block/recentactivity/providers/block-handler.ts +++ b/src/addon/block/recentactivity/providers/block-handler.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -33,11 +33,11 @@ export class AddonBlockRecentActivityHandler extends CoreBlockBaseHandler { /** * Returns the data needed to render the block. * - * @param {Injector} injector Injector. - * @param {any} block The block to render. - * @param {string} contextLevel The context where the block will be used. - * @param {number} instanceId The instance ID associated with the context level. - * @return {CoreBlockHandlerData|Promise} Data or promise resolved with the data. + * @param injector Injector. + * @param block The block to render. + * @param contextLevel The context where the block will be used. + * @param instanceId The instance ID associated with the context level. + * @return Data or promise resolved with the data. */ getDisplayData(injector: Injector, block: any, contextLevel: string, instanceId: number) : CoreBlockHandlerData | Promise { diff --git a/src/addon/block/recentactivity/recentactivity.module.ts b/src/addon/block/recentactivity/recentactivity.module.ts index cd0136763..4d20d2114 100644 --- a/src/addon/block/recentactivity/recentactivity.module.ts +++ b/src/addon/block/recentactivity/recentactivity.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/addon/block/recentlyaccessedcourses/components/components.module.ts b/src/addon/block/recentlyaccessedcourses/components/components.module.ts index dde98101c..39e3c11a1 100644 --- a/src/addon/block/recentlyaccessedcourses/components/components.module.ts +++ b/src/addon/block/recentlyaccessedcourses/components/components.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/addon/block/recentlyaccessedcourses/components/recentlyaccessedcourses/addon-block-recentlyaccessedcourses.html b/src/addon/block/recentlyaccessedcourses/components/recentlyaccessedcourses/addon-block-recentlyaccessedcourses.html index 0d83e5a7e..aede44831 100644 --- a/src/addon/block/recentlyaccessedcourses/components/recentlyaccessedcourses/addon-block-recentlyaccessedcourses.html +++ b/src/addon/block/recentlyaccessedcourses/components/recentlyaccessedcourses/addon-block-recentlyaccessedcourses.html @@ -1,6 +1,6 @@

{{ 'addon.block_recentlyaccessedcourses.pluginname' | translate }}

-
+
@@ -8,14 +8,12 @@
- + -
-
- - - -
+
+ + +
diff --git a/src/addon/block/recentlyaccessedcourses/components/recentlyaccessedcourses/recentlyaccessedcourses.ts b/src/addon/block/recentlyaccessedcourses/components/recentlyaccessedcourses/recentlyaccessedcourses.ts index 92697e4f0..4b61bfeb1 100644 --- a/src/addon/block/recentlyaccessedcourses/components/recentlyaccessedcourses/recentlyaccessedcourses.ts +++ b/src/addon/block/recentlyaccessedcourses/components/recentlyaccessedcourses/recentlyaccessedcourses.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -14,7 +14,6 @@ import { Component, OnInit, OnDestroy, Injector, Input, OnChanges, SimpleChange } from '@angular/core'; import { CoreEventsProvider } from '@providers/events'; -import { CoreUtilsProvider } from '@providers/utils/utils'; import { CoreSitesProvider } from '@providers/sites'; import { CoreCoursesProvider } from '@core/courses/providers/courses'; import { CoreCoursesHelperProvider } from '@core/courses/providers/helper'; @@ -38,16 +37,19 @@ export class AddonBlockRecentlyAccessedCoursesComponent extends CoreBlockBaseCom icon: '', badge: '' }; + downloadCourseEnabled: boolean; + downloadCoursesEnabled: boolean; protected prefetchIconsInitialized = false; protected isDestroyed; protected coursesObserver; + protected updateSiteObserver; protected courseIds = []; protected fetchContentDefaultError = 'Error getting recent courses data.'; constructor(injector: Injector, private coursesProvider: CoreCoursesProvider, private courseCompletionProvider: AddonCourseCompletionProvider, private eventsProvider: CoreEventsProvider, - private courseHelper: CoreCourseHelperProvider, private utils: CoreUtilsProvider, + private courseHelper: CoreCourseHelperProvider, private courseOptionsDelegate: CoreCourseOptionsDelegate, private coursesHelper: CoreCoursesHelperProvider, private sitesProvider: CoreSitesProvider) { @@ -59,6 +61,17 @@ export class AddonBlockRecentlyAccessedCoursesComponent extends CoreBlockBaseCom */ ngOnInit(): void { + // Refresh the enabled flags if enabled. + this.downloadCourseEnabled = !this.coursesProvider.isDownloadCourseDisabledInSite(); + this.downloadCoursesEnabled = !this.coursesProvider.isDownloadCoursesDisabledInSite(); + + // Refresh the enabled flags if site is updated. + this.updateSiteObserver = this.eventsProvider.on(CoreEventsProvider.SITE_UPDATED, () => { + this.downloadCourseEnabled = !this.coursesProvider.isDownloadCourseDisabledInSite(); + this.downloadCoursesEnabled = !this.coursesProvider.isDownloadCoursesDisabledInSite(); + + }, this.sitesProvider.getCurrentSiteId()); + this.coursesObserver = this.eventsProvider.on(CoreCoursesProvider.EVENT_MY_COURSES_UPDATED, () => { this.refreshContent(); }, this.sitesProvider.getCurrentSiteId()); @@ -79,7 +92,7 @@ export class AddonBlockRecentlyAccessedCoursesComponent extends CoreBlockBaseCom /** * Perform the invalidate content function. * - * @return {Promise} Resolved when done. + * @return Resolved when done. */ protected invalidateContent(): Promise { const promises = []; @@ -104,10 +117,13 @@ export class AddonBlockRecentlyAccessedCoursesComponent extends CoreBlockBaseCom /** * Fetch the courses for recent courses. * - * @return {Promise} Promise resolved when done. + * @return Promise resolved when done. */ protected fetchContent(): Promise { - return this.coursesHelper.getUserCoursesWithOptions('lastaccess', 10).then((courses) => { + const showCategories = this.block.configs && this.block.configs.displaycategories && + this.block.configs.displaycategories.value == '1'; + + return this.coursesHelper.getUserCoursesWithOptions('lastaccess', 10, null, showCategories).then((courses) => { this.courses = courses; this.initPrefetchCoursesIcons(); @@ -133,7 +149,7 @@ export class AddonBlockRecentlyAccessedCoursesComponent extends CoreBlockBaseCom /** * Prefetch all the shown courses. * - * @return {Promise} Promise resolved when done. + * @return Promise resolved when done. */ prefetchCourses(): Promise { const initialIcon = this.prefetchCoursesData.icon; @@ -152,5 +168,6 @@ export class AddonBlockRecentlyAccessedCoursesComponent extends CoreBlockBaseCom ngOnDestroy(): void { this.isDestroyed = true; this.coursesObserver && this.coursesObserver.off(); + this.updateSiteObserver && this.updateSiteObserver.off(); } } diff --git a/src/addon/block/recentlyaccessedcourses/providers/block-handler.ts b/src/addon/block/recentlyaccessedcourses/providers/block-handler.ts index 3af9cf0dd..d562effe8 100644 --- a/src/addon/block/recentlyaccessedcourses/providers/block-handler.ts +++ b/src/addon/block/recentlyaccessedcourses/providers/block-handler.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -32,11 +32,11 @@ export class AddonBlockRecentlyAccessedCoursesHandler extends CoreBlockBaseHandl /** * Returns the data needed to render the block. * - * @param {Injector} injector Injector. - * @param {any} block The block to render. - * @param {string} contextLevel The context where the block will be used. - * @param {number} instanceId The instance ID associated with the context level. - * @return {CoreBlockHandlerData|Promise} Data or promise resolved with the data. + * @param injector Injector. + * @param block The block to render. + * @param contextLevel The context where the block will be used. + * @param instanceId The instance ID associated with the context level. + * @return Data or promise resolved with the data. */ getDisplayData(injector: Injector, block: any, contextLevel: string, instanceId: number) : CoreBlockHandlerData | Promise { diff --git a/src/addon/block/recentlyaccessedcourses/recentlyaccessedcourses.module.ts b/src/addon/block/recentlyaccessedcourses/recentlyaccessedcourses.module.ts index 99a9ad3fd..53ade7657 100644 --- a/src/addon/block/recentlyaccessedcourses/recentlyaccessedcourses.module.ts +++ b/src/addon/block/recentlyaccessedcourses/recentlyaccessedcourses.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/addon/block/recentlyaccesseditems/components/components.module.ts b/src/addon/block/recentlyaccesseditems/components/components.module.ts index 1ce6d5557..bede51164 100644 --- a/src/addon/block/recentlyaccesseditems/components/components.module.ts +++ b/src/addon/block/recentlyaccesseditems/components/components.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/addon/block/recentlyaccesseditems/components/recentlyaccesseditems/addon-block-recentlyaccesseditems.html b/src/addon/block/recentlyaccesseditems/components/recentlyaccesseditems/addon-block-recentlyaccesseditems.html index f3538163d..cfa2892d6 100644 --- a/src/addon/block/recentlyaccesseditems/components/recentlyaccesseditems/addon-block-recentlyaccesseditems.html +++ b/src/addon/block/recentlyaccesseditems/components/recentlyaccesseditems/addon-block-recentlyaccesseditems.html @@ -1,14 +1,14 @@

{{ 'addon.block_recentlyaccesseditems.pluginname' | translate }}

- +
diff --git a/src/addon/block/recentlyaccesseditems/components/recentlyaccesseditems/recentlyaccesseditems.ts b/src/addon/block/recentlyaccesseditems/components/recentlyaccesseditems/recentlyaccesseditems.ts index e1c9bf372..60041df84 100644 --- a/src/addon/block/recentlyaccesseditems/components/recentlyaccesseditems/recentlyaccesseditems.ts +++ b/src/addon/block/recentlyaccesseditems/components/recentlyaccesseditems/recentlyaccesseditems.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -16,8 +16,9 @@ import { Component, OnInit, Injector, Optional } from '@angular/core'; import { NavController } from 'ionic-angular'; import { CoreSitesProvider } from '@providers/sites'; import { CoreBlockBaseComponent } from '@core/block/classes/base-block-component'; -import { AddonBlockRecentlyAccessedItemsProvider } from '../../providers/recentlyaccesseditems'; -import { CoreTextUtilsProvider } from '@providers/utils/text'; +import { + AddonBlockRecentlyAccessedItemsProvider, AddonBlockRecentlyAccessedItemsItem +} from '../../providers/recentlyaccesseditems'; import { CoreContentLinksHelperProvider } from '@core/contentlinks/providers/helper'; /** @@ -28,12 +29,12 @@ import { CoreContentLinksHelperProvider } from '@core/contentlinks/providers/hel templateUrl: 'addon-block-recentlyaccesseditems.html' }) export class AddonBlockRecentlyAccessedItemsComponent extends CoreBlockBaseComponent implements OnInit { - items = []; + items: AddonBlockRecentlyAccessedItemsItem[] = []; protected fetchContentDefaultError = 'Error getting recently accessed items data.'; constructor(injector: Injector, @Optional() private navCtrl: NavController, - private sitesProvider: CoreSitesProvider, private textUtils: CoreTextUtilsProvider, + private sitesProvider: CoreSitesProvider, private recentItemsProvider: AddonBlockRecentlyAccessedItemsProvider, private contentLinksHelper: CoreContentLinksHelperProvider) { @@ -50,7 +51,7 @@ export class AddonBlockRecentlyAccessedItemsComponent extends CoreBlockBaseCompo /** * Perform the invalidate content function. * - * @return {Promise} Resolved when done. + * @return Resolved when done. */ protected invalidateContent(): Promise { return this.recentItemsProvider.invalidateRecentItems(); @@ -59,7 +60,7 @@ export class AddonBlockRecentlyAccessedItemsComponent extends CoreBlockBaseCompo /** * Fetch the data to render the block. * - * @return {Promise} Promise resolved when done. + * @return Promise resolved when done. */ protected fetchContent(): Promise { return this.recentItemsProvider.getRecentItems().then((items) => { @@ -70,8 +71,8 @@ export class AddonBlockRecentlyAccessedItemsComponent extends CoreBlockBaseCompo /** * Event clicked. * - * @param {Event} e Click event. - * @param {any} item Activity item info. + * @param e Click event. + * @param item Activity item info. */ action(e: Event, item: any): void { e.preventDefault(); diff --git a/src/addon/block/recentlyaccesseditems/providers/block-handler.ts b/src/addon/block/recentlyaccesseditems/providers/block-handler.ts index dfadcc542..5ba0425c9 100644 --- a/src/addon/block/recentlyaccesseditems/providers/block-handler.ts +++ b/src/addon/block/recentlyaccesseditems/providers/block-handler.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -32,11 +32,11 @@ export class AddonBlockRecentlyAccessedItemsHandler extends CoreBlockBaseHandler /** * Returns the data needed to render the block. * - * @param {Injector} injector Injector. - * @param {any} block The block to render. - * @param {string} contextLevel The context where the block will be used. - * @param {number} instanceId The instance ID associated with the context level. - * @return {CoreBlockHandlerData|Promise} Data or promise resolved with the data. + * @param injector Injector. + * @param block The block to render. + * @param contextLevel The context where the block will be used. + * @param instanceId The instance ID associated with the context level. + * @return Data or promise resolved with the data. */ getDisplayData(injector: Injector, block: any, contextLevel: string, instanceId: number) : CoreBlockHandlerData | Promise { diff --git a/src/addon/block/recentlyaccesseditems/providers/recentlyaccesseditems.ts b/src/addon/block/recentlyaccesseditems/providers/recentlyaccesseditems.ts index f07f386d5..3eb38e75b 100644 --- a/src/addon/block/recentlyaccesseditems/providers/recentlyaccesseditems.ts +++ b/src/addon/block/recentlyaccesseditems/providers/recentlyaccesseditems.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -30,7 +30,7 @@ export class AddonBlockRecentlyAccessedItemsProvider { /** * Get cache key for get last accessed items value WS call. * - * @return {string} Cache key. + * @return Cache key. */ protected getRecentItemsCacheKey(): string { return this.ROOT_CACHE_KEY + ':recentitems'; @@ -39,17 +39,19 @@ export class AddonBlockRecentlyAccessedItemsProvider { /** * Get last accessed items. * - * @param {string} [siteId] Site ID. If not defined, use current site. - * @return {Promise} Promise resolved when the info is retrieved. + * @param siteId Site ID. If not defined, use current site. + * @return Promise resolved when the info is retrieved. */ - getRecentItems(siteId?: string): Promise { + getRecentItems(siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { const preSets = { cacheKey: this.getRecentItemsCacheKey() }; - return site.read('block_recentlyaccesseditems_get_recent_items', undefined, preSets).then((items) => { + return site.read('block_recentlyaccesseditems_get_recent_items', undefined, preSets) + .then((items: AddonBlockRecentlyAccessedItemsItem[]) => { + return items.map((item) => { const modicon = item.icon && this.domUtils.getHTMLElementAttribute(item.icon, 'src'); item.iconUrl = this.courseProvider.getModuleIconSrc(item.modname, modicon); @@ -63,8 +65,8 @@ export class AddonBlockRecentlyAccessedItemsProvider { /** * Invalidates get last accessed items WS call. * - * @param {string} [siteId] Site ID to invalidate. If not defined, use current site. - * @return {Promise} Promise resolved when the data is invalidated. + * @param siteId Site ID to invalidate. If not defined, use current site. + * @return Promise resolved when the data is invalidated. */ invalidateRecentItems(siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -72,3 +74,27 @@ export class AddonBlockRecentlyAccessedItemsProvider { }); } } + +/** + * Result of WS block_recentlyaccesseditems_get_recent_items. + */ +export type AddonBlockRecentlyAccessedItemsItem = { + id: number; // Id. + courseid: number; // Courseid. + cmid: number; // Cmid. + userid: number; // Userid. + modname: string; // Modname. + name: string; // Name. + coursename: string; // Coursename. + timeaccess: number; // Timeaccess. + viewurl: string; // Viewurl. + courseviewurl: string; // Courseviewurl. + icon: string; // Icon. +} & AddonBlockRecentlyAccessedItemsItemCalculatedData; + +/** + * Calculated data for recently accessed item. + */ +export type AddonBlockRecentlyAccessedItemsItemCalculatedData = { + iconUrl: string; // Icon URL. Calculated by the app. +}; diff --git a/src/addon/block/recentlyaccesseditems/recentlyaccesseditems.module.ts b/src/addon/block/recentlyaccesseditems/recentlyaccesseditems.module.ts index 5df77fe5f..3a9229176 100644 --- a/src/addon/block/recentlyaccesseditems/recentlyaccesseditems.module.ts +++ b/src/addon/block/recentlyaccesseditems/recentlyaccesseditems.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/addon/block/rssclient/providers/block-handler.ts b/src/addon/block/rssclient/providers/block-handler.ts index 8976f2182..e7ad835e3 100644 --- a/src/addon/block/rssclient/providers/block-handler.ts +++ b/src/addon/block/rssclient/providers/block-handler.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -33,11 +33,11 @@ export class AddonBlockRssClientHandler extends CoreBlockBaseHandler { /** * Returns the data needed to render the block. * - * @param {Injector} injector Injector. - * @param {any} block The block to render. - * @param {string} contextLevel The context where the block will be used. - * @param {number} instanceId The instance ID associated with the context level. - * @return {CoreBlockHandlerData|Promise} Data or promise resolved with the data. + * @param injector Injector. + * @param block The block to render. + * @param contextLevel The context where the block will be used. + * @param instanceId The instance ID associated with the context level. + * @return Data or promise resolved with the data. */ getDisplayData(injector: Injector, block: any, contextLevel: string, instanceId: number) : CoreBlockHandlerData | Promise { diff --git a/src/addon/block/rssclient/rssclient.module.ts b/src/addon/block/rssclient/rssclient.module.ts index b98c39ae5..402bd00ac 100644 --- a/src/addon/block/rssclient/rssclient.module.ts +++ b/src/addon/block/rssclient/rssclient.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/addon/block/selfcompletion/providers/block-handler.ts b/src/addon/block/selfcompletion/providers/block-handler.ts index 57d716e5a..b9309609d 100644 --- a/src/addon/block/selfcompletion/providers/block-handler.ts +++ b/src/addon/block/selfcompletion/providers/block-handler.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -32,11 +32,11 @@ export class AddonBlockSelfCompletionHandler extends CoreBlockBaseHandler { /** * Returns the data needed to render the block. * - * @param {Injector} injector Injector. - * @param {any} block The block to render. - * @param {string} contextLevel The context where the block will be used. - * @param {number} instanceId The instance ID associated with the context level. - * @return {CoreBlockHandlerData|Promise} Data or promise resolved with the data. + * @param injector Injector. + * @param block The block to render. + * @param contextLevel The context where the block will be used. + * @param instanceId The instance ID associated with the context level. + * @return Data or promise resolved with the data. */ getDisplayData(injector: Injector, block: any, contextLevel: string, instanceId: number) : CoreBlockHandlerData | Promise { diff --git a/src/addon/block/selfcompletion/selfcompletion.module.ts b/src/addon/block/selfcompletion/selfcompletion.module.ts index b101210b2..8330faa9d 100644 --- a/src/addon/block/selfcompletion/selfcompletion.module.ts +++ b/src/addon/block/selfcompletion/selfcompletion.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/addon/block/sitemainmenu/components/components.module.ts b/src/addon/block/sitemainmenu/components/components.module.ts index a121183a5..9ca18bf78 100644 --- a/src/addon/block/sitemainmenu/components/components.module.ts +++ b/src/addon/block/sitemainmenu/components/components.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/addon/block/sitemainmenu/components/sitemainmenu/addon-block-sitemainmenu.html b/src/addon/block/sitemainmenu/components/sitemainmenu/addon-block-sitemainmenu.html index f9a5b7f82..49dfb8748 100644 --- a/src/addon/block/sitemainmenu/components/sitemainmenu/addon-block-sitemainmenu.html +++ b/src/addon/block/sitemainmenu/components/sitemainmenu/addon-block-sitemainmenu.html @@ -4,7 +4,7 @@ - + diff --git a/src/addon/block/sitemainmenu/components/sitemainmenu/sitemainmenu.ts b/src/addon/block/sitemainmenu/components/sitemainmenu/sitemainmenu.ts index 23bfb74ca..968af16b8 100644 --- a/src/addon/block/sitemainmenu/components/sitemainmenu/sitemainmenu.ts +++ b/src/addon/block/sitemainmenu/components/sitemainmenu/sitemainmenu.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -30,6 +30,7 @@ import { CoreBlockBaseComponent } from '@core/block/classes/base-block-component export class AddonBlockSiteMainMenuComponent extends CoreBlockBaseComponent implements OnInit { @Input() downloadEnabled: boolean; + component = 'AddonBlockSiteMainMenu'; mainMenuBlock: any; siteHomeId: number; @@ -54,7 +55,7 @@ export class AddonBlockSiteMainMenuComponent extends CoreBlockBaseComponent impl /** * Perform the invalidate content function. * - * @return {Promise} Resolved when done. + * @return Resolved when done. */ protected invalidateContent(): Promise { const promises = []; @@ -73,7 +74,7 @@ export class AddonBlockSiteMainMenuComponent extends CoreBlockBaseComponent impl /** * Fetch the data to render the block. * - * @return {Promise} Promise resolved when done. + * @return Promise resolved when done. */ protected fetchContent(): Promise { return this.courseProvider.getSections(this.siteHomeId, false, true).then((sections) => { @@ -81,7 +82,7 @@ export class AddonBlockSiteMainMenuComponent extends CoreBlockBaseComponent impl if (this.mainMenuBlock) { this.mainMenuBlock.hasContent = this.courseHelper.sectionHasContent(this.mainMenuBlock); - this.courseHelper.addHandlerDataForModules([this.mainMenuBlock], this.siteHomeId); + this.courseHelper.addHandlerDataForModules([this.mainMenuBlock], this.siteHomeId, undefined, undefined, true); // Check if Site Home displays announcements. If so, remove it from the main menu block. const currentSite = this.sitesProvider.getCurrentSite(), diff --git a/src/addon/block/sitemainmenu/providers/block-handler.ts b/src/addon/block/sitemainmenu/providers/block-handler.ts index dcaf4fe01..305a46eb0 100644 --- a/src/addon/block/sitemainmenu/providers/block-handler.ts +++ b/src/addon/block/sitemainmenu/providers/block-handler.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -32,11 +32,11 @@ export class AddonBlockSiteMainMenuHandler extends CoreBlockBaseHandler { /** * Returns the data needed to render the block. * - * @param {Injector} injector Injector. - * @param {any} block The block to render. - * @param {string} contextLevel The context where the block will be used. - * @param {number} instanceId The instance ID associated with the context level. - * @return {CoreBlockHandlerData|Promise} Data or promise resolved with the data. + * @param injector Injector. + * @param block The block to render. + * @param contextLevel The context where the block will be used. + * @param instanceId The instance ID associated with the context level. + * @return Data or promise resolved with the data. */ getDisplayData(injector: Injector, block: any, contextLevel: string, instanceId: number) : CoreBlockHandlerData | Promise { diff --git a/src/addon/block/sitemainmenu/sitemainmenu.module.ts b/src/addon/block/sitemainmenu/sitemainmenu.module.ts index ace9f339e..5c475da1d 100644 --- a/src/addon/block/sitemainmenu/sitemainmenu.module.ts +++ b/src/addon/block/sitemainmenu/sitemainmenu.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/addon/block/starredcourses/components/components.module.ts b/src/addon/block/starredcourses/components/components.module.ts index f24aff329..5cd67b4b6 100644 --- a/src/addon/block/starredcourses/components/components.module.ts +++ b/src/addon/block/starredcourses/components/components.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/addon/block/starredcourses/components/starredcourses/addon-block-starredcourses.html b/src/addon/block/starredcourses/components/starredcourses/addon-block-starredcourses.html index a900b1f0f..af38133a9 100644 --- a/src/addon/block/starredcourses/components/starredcourses/addon-block-starredcourses.html +++ b/src/addon/block/starredcourses/components/starredcourses/addon-block-starredcourses.html @@ -1,6 +1,6 @@

{{ 'addon.block_starredcourses.pluginname' | translate }}

-
+
@@ -8,14 +8,12 @@
- + -
-
- - - -
+
+ + +
diff --git a/src/addon/block/starredcourses/components/starredcourses/starredcourses.ts b/src/addon/block/starredcourses/components/starredcourses/starredcourses.ts index ca25f3354..8acddb2a2 100644 --- a/src/addon/block/starredcourses/components/starredcourses/starredcourses.ts +++ b/src/addon/block/starredcourses/components/starredcourses/starredcourses.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -14,7 +14,6 @@ import { Component, OnInit, OnDestroy, Injector, Input, OnChanges, SimpleChange } from '@angular/core'; import { CoreEventsProvider } from '@providers/events'; -import { CoreUtilsProvider } from '@providers/utils/utils'; import { CoreSitesProvider } from '@providers/sites'; import { CoreCoursesProvider } from '@core/courses/providers/courses'; import { CoreCoursesHelperProvider } from '@core/courses/providers/helper'; @@ -38,16 +37,19 @@ export class AddonBlockStarredCoursesComponent extends CoreBlockBaseComponent im icon: '', badge: '' }; + downloadCourseEnabled: boolean; + downloadCoursesEnabled: boolean; protected prefetchIconsInitialized = false; protected isDestroyed; protected coursesObserver; + protected updateSiteObserver; protected courseIds = []; protected fetchContentDefaultError = 'Error getting starred courses data.'; constructor(injector: Injector, private coursesProvider: CoreCoursesProvider, private courseCompletionProvider: AddonCourseCompletionProvider, private eventsProvider: CoreEventsProvider, - private courseHelper: CoreCourseHelperProvider, private utils: CoreUtilsProvider, + private courseHelper: CoreCourseHelperProvider, private courseOptionsDelegate: CoreCourseOptionsDelegate, private coursesHelper: CoreCoursesHelperProvider, private sitesProvider: CoreSitesProvider) { @@ -59,6 +61,17 @@ export class AddonBlockStarredCoursesComponent extends CoreBlockBaseComponent im */ ngOnInit(): void { + // Refresh the enabled flags if enabled. + this.downloadCourseEnabled = !this.coursesProvider.isDownloadCourseDisabledInSite(); + this.downloadCoursesEnabled = !this.coursesProvider.isDownloadCoursesDisabledInSite(); + + // Refresh the enabled flags if site is updated. + this.updateSiteObserver = this.eventsProvider.on(CoreEventsProvider.SITE_UPDATED, () => { + this.downloadCourseEnabled = !this.coursesProvider.isDownloadCourseDisabledInSite(); + this.downloadCoursesEnabled = !this.coursesProvider.isDownloadCoursesDisabledInSite(); + + }, this.sitesProvider.getCurrentSiteId()); + this.coursesObserver = this.eventsProvider.on(CoreCoursesProvider.EVENT_MY_COURSES_UPDATED, () => { this.refreshContent(); }, this.sitesProvider.getCurrentSiteId()); @@ -79,7 +92,7 @@ export class AddonBlockStarredCoursesComponent extends CoreBlockBaseComponent im /** * Perform the invalidate content function. * - * @return {Promise} Resolved when done. + * @return Resolved when done. */ protected invalidateContent(): Promise { const promises = []; @@ -104,10 +117,13 @@ export class AddonBlockStarredCoursesComponent extends CoreBlockBaseComponent im /** * Fetch the courses. * - * @return {Promise} Promise resolved when done. + * @return Promise resolved when done. */ protected fetchContent(): Promise { - return this.coursesHelper.getUserCoursesWithOptions('timemodified', 0, 'isfavourite').then((courses) => { + const showCategories = this.block.configs && this.block.configs.displaycategories && + this.block.configs.displaycategories.value == '1'; + + return this.coursesHelper.getUserCoursesWithOptions('timemodified', 0, 'isfavourite', showCategories).then((courses) => { this.courses = courses; this.initPrefetchCoursesIcons(); @@ -133,7 +149,7 @@ export class AddonBlockStarredCoursesComponent extends CoreBlockBaseComponent im /** * Prefetch all the shown courses. * - * @return {Promise} Promise resolved when done. + * @return Promise resolved when done. */ prefetchCourses(): Promise { const initialIcon = this.prefetchCoursesData.icon; @@ -152,5 +168,6 @@ export class AddonBlockStarredCoursesComponent extends CoreBlockBaseComponent im ngOnDestroy(): void { this.isDestroyed = true; this.coursesObserver && this.coursesObserver.off(); + this.updateSiteObserver && this.updateSiteObserver.off(); } } diff --git a/src/addon/block/starredcourses/providers/block-handler.ts b/src/addon/block/starredcourses/providers/block-handler.ts index c7abc3485..a9bbc579e 100644 --- a/src/addon/block/starredcourses/providers/block-handler.ts +++ b/src/addon/block/starredcourses/providers/block-handler.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -32,11 +32,11 @@ export class AddonBlockStarredCoursesHandler extends CoreBlockBaseHandler { /** * Returns the data needed to render the block. * - * @param {Injector} injector Injector. - * @param {any} block The block to render. - * @param {string} contextLevel The context where the block will be used. - * @param {number} instanceId The instance ID associated with the context level. - * @return {CoreBlockHandlerData|Promise} Data or promise resolved with the data. + * @param injector Injector. + * @param block The block to render. + * @param contextLevel The context where the block will be used. + * @param instanceId The instance ID associated with the context level. + * @return Data or promise resolved with the data. */ getDisplayData(injector: Injector, block: any, contextLevel: string, instanceId: number) : CoreBlockHandlerData | Promise { diff --git a/src/addon/block/starredcourses/starredcourses.module.ts b/src/addon/block/starredcourses/starredcourses.module.ts index 3d363ec79..96b973ef7 100644 --- a/src/addon/block/starredcourses/starredcourses.module.ts +++ b/src/addon/block/starredcourses/starredcourses.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/addon/block/tags/providers/block-handler.ts b/src/addon/block/tags/providers/block-handler.ts index f1c7d4cd8..566bd6bfa 100644 --- a/src/addon/block/tags/providers/block-handler.ts +++ b/src/addon/block/tags/providers/block-handler.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -33,11 +33,11 @@ export class AddonBlockTagsHandler extends CoreBlockBaseHandler { /** * Returns the data needed to render the block. * - * @param {Injector} injector Injector. - * @param {any} block The block to render. - * @param {string} contextLevel The context where the block will be used. - * @param {number} instanceId The instance ID associated with the context level. - * @return {CoreBlockHandlerData|Promise} Data or promise resolved with the data. + * @param injector Injector. + * @param block The block to render. + * @param contextLevel The context where the block will be used. + * @param instanceId The instance ID associated with the context level. + * @return Data or promise resolved with the data. */ getDisplayData(injector: Injector, block: any, contextLevel: string, instanceId: number) : CoreBlockHandlerData | Promise { diff --git a/src/addon/block/tags/tags.module.ts b/src/addon/block/tags/tags.module.ts index 4b57911c0..7367edbdc 100644 --- a/src/addon/block/tags/tags.module.ts +++ b/src/addon/block/tags/tags.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/addon/block/tags/tags.scss b/src/addon/block/tags/tags.scss index f4c54d167..c6704e962 100644 --- a/src/addon/block/tags/tags.scss +++ b/src/addon/block/tags/tags.scss @@ -1,6 +1,7 @@ .addon-block-tags core-block-pre-rendered { .core-block-content { .tag_cloud { + font-size: 80%; text-align: center; ul.inline-list { list-style: none; diff --git a/src/addon/block/timeline/components/components.module.ts b/src/addon/block/timeline/components/components.module.ts index c408138ef..465a66858 100644 --- a/src/addon/block/timeline/components/components.module.ts +++ b/src/addon/block/timeline/components/components.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/addon/block/timeline/components/events/addon-block-timeline-events.html b/src/addon/block/timeline/components/events/addon-block-timeline-events.html index 1cebcecc1..832c4902d 100644 --- a/src/addon/block/timeline/components/events/addon-block-timeline-events.html +++ b/src/addon/block/timeline/components/events/addon-block-timeline-events.html @@ -4,10 +4,10 @@ - -

+ +

- +

@@ -19,7 +19,7 @@ - + @@ -37,7 +37,7 @@ - + {{ 'core.hasdatatosync' | translate:{$a: 'core.day' | translate} }} @@ -50,9 +50,9 @@ - -

-

+ +

+

{{ 'core.notsent' | translate }} diff --git a/src/addon/calendar/pages/day/day.module.ts b/src/addon/calendar/pages/day/day.module.ts index c4e33dbb9..7a672b6c5 100644 --- a/src/addon/calendar/pages/day/day.module.ts +++ b/src/addon/calendar/pages/day/day.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/addon/calendar/pages/day/day.ts b/src/addon/calendar/pages/day/day.ts index c3b566f88..92abebece 100644 --- a/src/addon/calendar/pages/day/day.ts +++ b/src/addon/calendar/pages/day/day.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -13,19 +13,20 @@ // limitations under the License. import { Component, OnInit, OnDestroy, NgZone } from '@angular/core'; -import { IonicPage, NavParams, NavController } from 'ionic-angular'; +import { IonicPage, NavParams, NavController, PopoverController } from 'ionic-angular'; import { CoreAppProvider } from '@providers/app'; import { CoreEventsProvider } from '@providers/events'; import { CoreLocalNotificationsProvider } from '@providers/local-notifications'; import { CoreSitesProvider } from '@providers/sites'; import { CoreDomUtilsProvider } from '@providers/utils/dom'; import { CoreTimeUtilsProvider } from '@providers/utils/time'; -import { AddonCalendarProvider } from '../../providers/calendar'; +import { AddonCalendarProvider, AddonCalendarCalendarEvent } from '../../providers/calendar'; import { AddonCalendarOfflineProvider } from '../../providers/calendar-offline'; -import { AddonCalendarHelperProvider } from '../../providers/helper'; +import { AddonCalendarHelperProvider, AddonCalendarFilter } from '../../providers/helper'; import { AddonCalendarSyncProvider } from '../../providers/calendar-sync'; import { CoreCoursesProvider } from '@core/courses/providers/courses'; import { CoreCoursesHelperProvider } from '@core/courses/providers/helper'; +import { AddonCalendarFilterPopoverComponent } from '../../components/filter/filter'; import { Network } from '@ionic-native/network'; import * as moment from 'moment'; @@ -45,7 +46,7 @@ export class AddonCalendarDayPage implements OnInit, OnDestroy { protected day: number; protected categories = {}; protected events = []; // Events (both online and offline). - protected onlineEvents = []; + protected onlineEvents: AddonCalendarCalendarEvent[] = []; protected offlineEvents = {}; // Offline events. protected offlineEditedEventsIds = []; // IDs of events edited in offline. protected deletedEvents = []; // Events deleted in offline. @@ -63,11 +64,10 @@ export class AddonCalendarDayPage implements OnInit, OnDestroy { protected manualSyncObserver: any; protected onlineObserver: any; protected obsDefaultTimeChange: any; + protected filterChangedObserver: any; periodName: string; filteredEvents = []; - courseId: number; - categoryId: number; canCreate = false; courses: any[]; loaded = false; @@ -76,6 +76,16 @@ export class AddonCalendarDayPage implements OnInit, OnDestroy { syncIcon: string; isCurrentDay: boolean; isPastDay: boolean; + filter: AddonCalendarFilter = { + filtered: false, + courseId: null, + categoryId: null, + course: true, + group: true, + site: true, + user: true, + category: true + }; constructor(localNotificationsProvider: CoreLocalNotificationsProvider, navParams: NavParams, @@ -92,14 +102,23 @@ export class AddonCalendarDayPage implements OnInit, OnDestroy { private eventsProvider: CoreEventsProvider, private coursesProvider: CoreCoursesProvider, private coursesHelper: CoreCoursesHelperProvider, - private appProvider: CoreAppProvider) { + private appProvider: CoreAppProvider, + private popoverCtrl: PopoverController) { const now = new Date(); + AddonCalendarProvider.ALL_TYPES.forEach((name) => { + this.filter[name] = navParams.get(name); + this.filter[name] = typeof this.filter[name] == 'undefined' ? true : this.filter[name]; + }); + this.filter.courseId = navParams.get('courseId'); + this.filter.categoryId = navParams.get('categoryId'); + + this.filter.filtered = !!this.filter.courseId || AddonCalendarProvider.ALL_TYPES.some((name) => !this.filter[name]); + this.year = navParams.get('year') || now.getFullYear(); this.month = navParams.get('month') || (now.getMonth() + 1); this.day = navParams.get('day') || now.getDate(); - this.courseId = navParams.get('courseId'); this.currentSiteId = sitesProvider.getCurrentSiteId(); if (localNotificationsProvider.isAvailable()) { @@ -186,6 +205,17 @@ export class AddonCalendarDayPage implements OnInit, OnDestroy { } }, this.currentSiteId); + this.filterChangedObserver = this.eventsProvider.on(AddonCalendarProvider.FILTER_CHANGED_EVENT, (data) => { + this.filter = data; + + // Course viewed has changed, check if the user can create events for this course calendar. + this.calendarHelper.canEditEvents(this.filter['courseId']).then((canEdit) => { + this.canCreate = canEdit; + }); + + this.filterEvents(); + }); + // Refresh online status when changes. this.onlineObserver = network.onchange().subscribe(() => { // Execute the callback in the Angular zone, so change detection doesn't stop working. @@ -208,9 +238,9 @@ export class AddonCalendarDayPage implements OnInit, OnDestroy { /** * Fetch all the data required for the view. * - * @param {boolean} [sync] Whether it should try to synchronize offline events. - * @param {boolean} [showErrors] Whether to show sync errors to the user. - * @return {Promise} Promise resolved when done. + * @param sync Whether it should try to synchronize offline events. + * @param showErrors Whether to show sync errors to the user. + * @return Promise resolved when done. */ fetchData(sync?: boolean, showErrors?: boolean): Promise { @@ -223,9 +253,8 @@ export class AddonCalendarDayPage implements OnInit, OnDestroy { const promises = []; // Load courses for the popover. - promises.push(this.coursesHelper.getCoursesForPopover(this.courseId).then((data) => { + promises.push(this.coursesHelper.getCoursesForPopover(this.filter['courseId']).then((data) => { this.courses = data.courses; - this.categoryId = data.categoryId; })); // Get categories. @@ -257,7 +286,7 @@ export class AddonCalendarDayPage implements OnInit, OnDestroy { })); // Check if user can create events. - promises.push(this.calendarHelper.canEditEvents(this.courseId).then((canEdit) => { + promises.push(this.calendarHelper.canEditEvents(this.filter['courseId']).then((canEdit) => { this.canCreate = canEdit; })); @@ -280,14 +309,14 @@ export class AddonCalendarDayPage implements OnInit, OnDestroy { /** * Fetch the events for current day. * - * @return {Promise} Promise resolved when done. + * @return Promise resolved when done. */ fetchEvents(): Promise { // Don't pass courseId and categoryId, we'll filter them locally. return this.calendarProvider.getDayEvents(this.year, this.month, this.day).catch((error) => { if (!this.appProvider.isOnline()) { // Allow navigating to non-cached days in offline (behave as if using emergency cache). - return Promise.resolve({ events: [] }); + return Promise.resolve({ events: [] }); } else { return Promise.reject(error); } @@ -328,7 +357,7 @@ export class AddonCalendarDayPage implements OnInit, OnDestroy { /** * Merge online events with the offline events of that period. * - * @return {any[]} Merged events. + * @return Merged events. */ protected mergeEvents(): any[] { this.hasOffline = false; @@ -374,22 +403,16 @@ export class AddonCalendarDayPage implements OnInit, OnDestroy { } /** - * Filter events to only display events belonging to a certain course. + * Filter events based on the filter popover. */ protected filterEvents(): void { - if (!this.courseId || this.courseId < 0) { - this.filteredEvents = this.events; - } else { - this.filteredEvents = this.events.filter((event) => { - return this.calendarHelper.shouldDisplayEvent(event, this.courseId, this.categoryId, this.categories); - }); - } + this.filteredEvents = this.calendarHelper.getFilteredEvents(this.events, this.filter, this.categories); } /** * Sort events by timestart. * - * @param {any[]} events List to sort. + * @param events List to sort. */ protected sortEvents(events: any[]): any[] { return events.sort((a, b) => { @@ -404,10 +427,10 @@ export class AddonCalendarDayPage implements OnInit, OnDestroy { /** * Refresh the data. * - * @param {any} [refresher] Refresher. - * @param {Function} [done] Function to call when done. - * @param {boolean} [showErrors] Whether to show sync errors to the user. - * @return {Promise} Promise resolved when done. + * @param refresher Refresher. + * @param done Function to call when done. + * @param showErrors Whether to show sync errors to the user. + * @return Promise resolved when done. */ doRefresh(refresher?: any, done?: () => void, showErrors?: boolean): Promise { if (this.loaded) { @@ -423,10 +446,10 @@ export class AddonCalendarDayPage implements OnInit, OnDestroy { /** * Refresh the data. * - * @param {boolean} [sync] Whether it should try to synchronize offline events. - * @param {boolean} [showErrors] Whether to show sync errors to the user. - * @param {boolean} [afterChange] Whether the refresh is done after an event has changed or has been synced. - * @return {Promise} Promise resolved when done. + * @param sync Whether it should try to synchronize offline events. + * @param showErrors Whether to show sync errors to the user. + * @param afterChange Whether the refresh is done after an event has changed or has been synced. + * @return Promise resolved when done. */ refreshData(sync?: boolean, showErrors?: boolean, afterChange?: boolean): Promise { this.syncIcon = 'spinner'; @@ -449,7 +472,7 @@ export class AddonCalendarDayPage implements OnInit, OnDestroy { /** * Load categories to be able to filter events. * - * @return {Promise} Promise resolved when done. + * @return Promise resolved when done. */ protected loadCategories(): Promise { return this.coursesProvider.getCategories(0, true).then((cats) => { @@ -467,8 +490,8 @@ export class AddonCalendarDayPage implements OnInit, OnDestroy { /** * Try to synchronize offline events. * - * @param {boolean} [showErrors] Whether to show sync errors to the user. - * @return {Promise} Promise resolved when done. + * @param showErrors Whether to show sync errors to the user. + * @return Promise resolved when done. */ protected sync(showErrors?: boolean): Promise { return this.calendarSync.syncEvents().then((result) => { @@ -495,7 +518,7 @@ export class AddonCalendarDayPage implements OnInit, OnDestroy { /** * Navigate to a particular event. * - * @param {number} eventId Event to load. + * @param eventId Event to load. */ gotoEvent(eventId: number): void { if (eventId < 0) { @@ -511,28 +534,23 @@ export class AddonCalendarDayPage implements OnInit, OnDestroy { /** * Show the context menu. * - * @param {MouseEvent} event Event. + * @param event Event. */ - openCourseFilter(event: MouseEvent): void { - this.coursesHelper.selectCourse(event, this.courses, this.courseId).then((result) => { - if (typeof result.courseId != 'undefined') { - this.courseId = result.courseId > 0 ? result.courseId : undefined; - this.categoryId = result.courseId > 0 ? result.categoryId : undefined; + openFilter(event: MouseEvent): void { + const popover = this.popoverCtrl.create(AddonCalendarFilterPopoverComponent, { + courses: this.courses, + filter: this.filter + }); - // Course viewed has changed, check if the user can create events for this course calendar. - this.calendarHelper.canEditEvents(this.courseId).then((canEdit) => { - this.canCreate = canEdit; - }); - - this.filterEvents(); - } + popover.present({ + ev: event }); } /** * Open page to create/edit an event. * - * @param {number} [eventId] Event ID to edit. + * @param eventId Event ID to edit. */ openEdit(eventId?: number): void { const params: any = {}; @@ -544,8 +562,8 @@ export class AddonCalendarDayPage implements OnInit, OnDestroy { params.timestamp = moment().year(this.year).month(this.month - 1).date(this.day).unix() * 1000; } - if (this.courseId) { - params.courseId = this.courseId; + if (this.filter['courseId']) { + params.courseId = this.filter['courseId']; } this.navCtrl.push('AddonCalendarEditEventPage', params); @@ -658,9 +676,9 @@ export class AddonCalendarDayPage implements OnInit, OnDestroy { /** * Find an event and mark it as deleted. * - * @param {number} eventId Event ID. - * @param {boolean} deleted Whether to mark it as deleted or not. - * @return {boolean} Whether the event was found. + * @param eventId Event ID. + * @param deleted Whether to mark it as deleted or not. + * @return Whether the event was found. */ protected markAsDeleted(eventId: number, deleted: boolean): boolean { const event = this.onlineEvents.find((event) => { @@ -678,8 +696,8 @@ export class AddonCalendarDayPage implements OnInit, OnDestroy { /** * Returns if the event is in the past or not. - * @param {any} event Event object. - * @return {boolean} True if it's in the past. + * @param event Event object. + * @return True if it's in the past. */ isEventPast(event: any): boolean { return (event.timestart + event.timeduration) < this.currentTime; @@ -697,6 +715,7 @@ export class AddonCalendarDayPage implements OnInit, OnDestroy { this.syncObserver && this.syncObserver.off(); this.manualSyncObserver && this.manualSyncObserver.off(); this.onlineObserver && this.onlineObserver.unsubscribe(); + this.filterChangedObserver && this.filterChangedObserver.off(); this.obsDefaultTimeChange && this.obsDefaultTimeChange.off(); } } diff --git a/src/addon/calendar/pages/edit-event/edit-event.html b/src/addon/calendar/pages/edit-event/edit-event.html index 9d79a7e5d..9eae210f8 100644 --- a/src/addon/calendar/pages/edit-event/edit-event.html +++ b/src/addon/calendar/pages/edit-event/edit-event.html @@ -134,7 +134,7 @@

{{ 'addon.calendar.repeatedevents' | translate }}

- {{ 'addon.calendar.repeateditall' | translate:{$a: event.othereventscount} }} + {{ 'addon.calendar.repeateditall' | translate:{$a: otherEventsCount} }} diff --git a/src/addon/calendar/pages/edit-event/edit-event.module.ts b/src/addon/calendar/pages/edit-event/edit-event.module.ts index c5b20e969..32a1ac65a 100644 --- a/src/addon/calendar/pages/edit-event/edit-event.module.ts +++ b/src/addon/calendar/pages/edit-event/edit-event.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/addon/calendar/pages/edit-event/edit-event.ts b/src/addon/calendar/pages/edit-event/edit-event.ts index b8d8ef9f4..293d1fd62 100644 --- a/src/addon/calendar/pages/edit-event/edit-event.ts +++ b/src/addon/calendar/pages/edit-event/edit-event.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -21,17 +21,17 @@ import { CoreGroupsProvider } from '@providers/groups'; import { CoreSitesProvider } from '@providers/sites'; import { CoreSyncProvider } from '@providers/sync'; import { CoreDomUtilsProvider } from '@providers/utils/dom'; -import { CoreTextUtilsProvider } from '@providers/utils/text'; import { CoreTimeUtilsProvider } from '@providers/utils/time'; import { CoreUtilsProvider } from '@providers/utils/utils'; import { CoreCoursesProvider } from '@core/courses/providers/courses'; import { CoreSplitViewComponent } from '@components/split-view/split-view'; import { CoreRichTextEditorComponent } from '@components/rich-text-editor/rich-text-editor.ts'; -import { AddonCalendarProvider } from '../../providers/calendar'; +import { AddonCalendarProvider, AddonCalendarGetAccessInfoResult, AddonCalendarEvent } from '../../providers/calendar'; import { AddonCalendarOfflineProvider } from '../../providers/calendar-offline'; import { AddonCalendarHelperProvider } from '../../providers/helper'; import { AddonCalendarSyncProvider } from '../../providers/calendar-sync'; import { CoreSite } from '@classes/site'; +import { CoreFilterHelperProvider } from '@core/filter/providers/helper'; /** * Page that displays a form to create/edit an event. @@ -58,7 +58,8 @@ export class AddonCalendarEditEventPage implements OnInit, OnDestroy { courseGroupSet = false; advanced = false; errors: any; - event: any; // The event object (when editing an event). + event: AddonCalendarEvent; // The event object (when editing an event). + otherEventsCount: number; // Form variables. eventForm: FormGroup; @@ -70,7 +71,7 @@ export class AddonCalendarEditEventPage implements OnInit, OnDestroy { protected courseId: number; protected originalData: any; protected currentSite: CoreSite; - protected types: any; // Object with the supported types. + protected types: {[name: string]: boolean}; // Object with the supported types. protected showAll: boolean; protected isDestroyed = false; protected error = false; @@ -80,7 +81,6 @@ export class AddonCalendarEditEventPage implements OnInit, OnDestroy { private navCtrl: NavController, private translate: TranslateService, private domUtils: CoreDomUtilsProvider, - private textUtils: CoreTextUtilsProvider, private timeUtils: CoreTimeUtilsProvider, private eventsProvider: CoreEventsProvider, private groupsProvider: CoreGroupsProvider, @@ -93,6 +93,7 @@ export class AddonCalendarEditEventPage implements OnInit, OnDestroy { private calendarSync: AddonCalendarSyncProvider, private fb: FormBuilder, private syncProvider: CoreSyncProvider, + private filterHelper: CoreFilterHelperProvider, @Optional() private svComponent: CoreSplitViewComponent) { this.eventId = navParams.get('eventId'); @@ -148,11 +149,11 @@ export class AddonCalendarEditEventPage implements OnInit, OnDestroy { /** * Fetch the data needed to render the form. * - * @param {boolean} [refresh] Whether it's refreshing data. - * @return {Promise} Promise resolved when done. + * @param refresh Whether it's refreshing data. + * @return Promise resolved when done. */ protected fetchData(refresh?: boolean): Promise { - let accessInfo; + let accessInfo: AddonCalendarGetAccessInfoResult; this.error = false; @@ -197,7 +198,7 @@ export class AddonCalendarEditEventPage implements OnInit, OnDestroy { promises.push(this.calendarProvider.getEventById(this.eventId).then((event) => { this.event = event; if (event && event.repeatid) { - event.othereventscount = event.eventcount ? event.eventcount - 1 : ''; + this.otherEventsCount = event.eventcount ? event.eventcount - 1 : 0; } return event; @@ -243,8 +244,9 @@ export class AddonCalendarEditEventPage implements OnInit, OnDestroy { // Format the name of the courses. const subPromises = []; courses.forEach((course) => { - subPromises.push(this.textUtils.formatText(course.fullname).then((text) => { - course.fullname = text; + subPromises.push(this.filterHelper.getFiltersAndFormatText(course.fullname, 'course', course.id) + .then((result) => { + course.fullname = result.text; }).catch(() => { // Ignore errors. })); @@ -289,9 +291,9 @@ export class AddonCalendarEditEventPage implements OnInit, OnDestroy { /** * Load an event data into the form. * - * @param {any} event Event data. - * @param {boolean} isOffline Whether the data is from offline or not. - * @return {Promise} Promise resolved when done. + * @param event Event data. + * @param isOffline Whether the data is from offline or not. + * @return Promise resolved when done. */ protected loadEventData(event: any, isOffline: boolean): Promise { const courseId = event.course ? event.course.id : event.courseid; @@ -344,7 +346,7 @@ export class AddonCalendarEditEventPage implements OnInit, OnDestroy { /** * Pull to refresh. * - * @param {any} refresher Refresher. + * @param refresher Refresher. */ refreshData(refresher: any): void { const promises = [ @@ -375,7 +377,7 @@ export class AddonCalendarEditEventPage implements OnInit, OnDestroy { /** * A course was selected, get its groups. * - * @param {number} courseId Course ID. + * @param courseId Course ID. */ groupCourseSelected(courseId: number): void { if (!courseId) { @@ -396,8 +398,8 @@ export class AddonCalendarEditEventPage implements OnInit, OnDestroy { /** * Load groups of a certain course. * - * @param {number} courseId Course ID. - * @return {Promise} Promise resolved when done. + * @param courseId Course ID. + * @return Promise resolved when done. */ protected loadGroups(courseId: number): Promise { this.loadingGroups = true; @@ -489,7 +491,7 @@ export class AddonCalendarEditEventPage implements OnInit, OnDestroy { // Send the data. const modal = this.domUtils.showModalLoading('core.sending', true); - let event; + let event: AddonCalendarEvent; this.calendarProvider.submitEvent(this.eventId, data).then((result) => { event = result.event; @@ -497,7 +499,7 @@ export class AddonCalendarEditEventPage implements OnInit, OnDestroy { if (result.sent) { // Event created or edited, invalidate right days & months. const numberOfRepetitions = formData.repeat ? formData.repeats : - (data.repeateditall && this.event.othereventscount ? this.event.othereventscount + 1 : 1); + (data.repeateditall && this.otherEventsCount ? this.otherEventsCount + 1 : 1); return this.calendarHelper.refreshAfterChangeEvent(result.event, numberOfRepetitions).catch(() => { // Ignore errors. @@ -515,7 +517,7 @@ export class AddonCalendarEditEventPage implements OnInit, OnDestroy { /** * Convenience function to update or return to event list depending on device. * - * @param {number} [event] Event. + * @param event Event. */ protected returnToList(event?: any): void { // Unblock the sync because the view will be destroyed and the sync process could be triggered before ngOnDestroy. @@ -568,7 +570,7 @@ export class AddonCalendarEditEventPage implements OnInit, OnDestroy { /** * Check if we can leave the page or not. * - * @return {boolean|Promise} Resolved if we can leave it, rejected if not. + * @return Resolved if we can leave it, rejected if not. */ ionViewCanLeave(): boolean | Promise { diff --git a/src/addon/calendar/pages/event/event.html b/src/addon/calendar/pages/event/event.html index 366327f66..63f53e1e6 100644 --- a/src/addon/calendar/pages/event/event.html +++ b/src/addon/calendar/pages/event/event.html @@ -2,8 +2,8 @@ - - + + @@ -32,16 +32,16 @@ - +

{{ 'addon.calendar.eventname' | translate }}

-

+

{{ 'core.deletedoffline' | translate }}

{{ 'addon.calendar.when' | translate }}

-

+

{{ 'core.deletedoffline' | translate }} @@ -52,7 +52,7 @@

{{ 'core.course' | translate}}

-

+

{{ 'core.group' | translate}}

@@ -60,19 +60,19 @@

{{ 'core.category' | translate}}

-

+

{{ 'core.description' | translate}}

- +

{{ 'core.location' | translate}}

- +

@@ -98,12 +98,11 @@ -

{{ 'addon.calendar.setnewreminder' | translate }}

- -
- - + +
diff --git a/src/addon/calendar/pages/event/event.module.ts b/src/addon/calendar/pages/event/event.module.ts index fe5029958..54d4d5e31 100644 --- a/src/addon/calendar/pages/event/event.module.ts +++ b/src/addon/calendar/pages/event/event.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/addon/calendar/pages/event/event.ts b/src/addon/calendar/pages/event/event.ts index 8edc9b96e..4eae82b39 100644 --- a/src/addon/calendar/pages/event/event.ts +++ b/src/addon/calendar/pages/event/event.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -57,6 +57,7 @@ export class AddonCalendarEventPage implements OnDestroy { notificationMax: string; notificationTimeText: string; event: any = {}; + courseId: number; courseName: string; groupName: string; courseUrl = ''; @@ -144,9 +145,9 @@ export class AddonCalendarEventPage implements OnDestroy { /** * Fetches the event and updates the view. * - * @param {boolean} [sync] Whether it should try to synchronize offline events. - * @param {boolean} [showErrors] Whether to show sync errors to the user. - * @return {Promise} Promise resolved when done. + * @param sync Whether it should try to synchronize offline events. + * @param showErrors Whether to show sync errors to the user. + * @return Promise resolved when done. */ fetchEvent(sync?: boolean, showErrors?: boolean): Promise { const currentSite = this.sitesProvider.getCurrentSite(), @@ -250,11 +251,13 @@ export class AddonCalendarEventPage implements OnDestroy { // If the event belongs to a course, get the course name and the URL to view it. if (canGetById && event.course && event.course.id != this.siteHomeId) { + this.courseId = event.course.id; this.courseName = event.course.fullname; this.courseUrl = event.course.viewurl; } else if (event.courseid && event.courseid != this.siteHomeId) { // Retrieve the course. promises.push(this.coursesProvider.getUserCourse(event.courseid, true).then((course) => { + this.courseId = course.id; this.courseName = course.fullname; this.courseUrl = currentSite ? this.textUtils.concatenatePaths(currentSite.siteUrl, '/course/view.php?id=' + event.courseid) : ''; @@ -311,13 +314,8 @@ export class AddonCalendarEventPage implements OnDestroy { /** * Add a reminder for this event. - * - * @param {Event} e Click event. */ - addNotificationTime(e: Event): void { - e.preventDefault(); - e.stopPropagation(); - + addNotificationTime(): void { if (this.notificationTimeText && this.event && this.event.id) { let notificationTime = this.timeUtils.convertToTimestamp(this.notificationTimeText); @@ -342,27 +340,36 @@ export class AddonCalendarEventPage implements OnDestroy { /** * Cancel the selected notification. * - * @param {number} id Reminder ID. - * @param {Event} e Click event. + * @param id Reminder ID. + * @param e Click event. */ cancelNotification(id: number, e: Event): void { e.preventDefault(); e.stopPropagation(); - this.calendarProvider.deleteEventReminder(id).then(() => { - this.calendarProvider.getEventReminders(this.eventId).then((reminders) => { - this.reminders = reminders; + this.domUtils.showDeleteConfirm().then(() => { + const modal = this.domUtils.showModalLoading('core.deleting', true); + this.calendarProvider.deleteEventReminder(id).then(() => { + this.calendarProvider.getEventReminders(this.eventId).then((reminders) => { + this.reminders = reminders; + }); + }).catch((error) => { + this.domUtils.showErrorModalDefault(error, 'Error deleting reminder'); + }).finally(() => { + modal.dismiss(); }); + }).catch(() => { + // Cancelled. }); } /** * Refresh the data. * - * @param {any} [refresher] Refresher. - * @param {Function} [done] Function to call when done. - * @param {boolean} [showErrors] Whether to show sync errors to the user. - * @return {Promise} Promise resolved when done. + * @param refresher Refresher. + * @param done Function to call when done. + * @param showErrors Whether to show sync errors to the user. + * @return Promise resolved when done. */ doRefresh(refresher?: any, done?: () => void, showErrors?: boolean): Promise { if (this.eventLoaded) { @@ -378,9 +385,9 @@ export class AddonCalendarEventPage implements OnDestroy { /** * Refresh the event. * - * @param {boolean} [sync] Whether it should try to synchronize offline events. - * @param {boolean} [showErrors] Whether to show sync errors to the user. - * @return {Promise} Promise resolved when done. + * @param sync Whether it should try to synchronize offline events. + * @param showErrors Whether to show sync errors to the user. + * @return Promise resolved when done. */ refreshEvent(sync?: boolean, showErrors?: boolean): Promise { this.syncIcon = 'spinner'; @@ -511,8 +518,8 @@ export class AddonCalendarEventPage implements OnDestroy { /** * Check the result of an automatic sync or a manual sync not done by this page. * - * @param {boolean} isManual Whether it's a manual sync. - * @param {any} data Sync result. + * @param isManual Whether it's a manual sync. + * @param data Sync result. */ protected checkSyncResult(isManual: boolean, data: any): void { if (!data) { diff --git a/src/addon/calendar/pages/index/index.html b/src/addon/calendar/pages/index/index.html index d2d6d38b0..005d68f25 100644 --- a/src/addon/calendar/pages/index/index.html +++ b/src/addon/calendar/pages/index/index.html @@ -2,9 +2,9 @@ {{ (showCalendar ? 'addon.calendar.calendarevents' : 'addon.calendar.upcomingevents') | translate }} - @@ -25,9 +25,9 @@ {{ 'core.hasdatatosync' | translate:{$a: 'addon.calendar.calendar' | translate} }} - + - + diff --git a/src/addon/calendar/pages/index/index.module.ts b/src/addon/calendar/pages/index/index.module.ts index bf925e799..91a657304 100644 --- a/src/addon/calendar/pages/index/index.module.ts +++ b/src/addon/calendar/pages/index/index.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/addon/calendar/pages/index/index.ts b/src/addon/calendar/pages/index/index.ts index 8f3d5a129..6cdf432b2 100644 --- a/src/addon/calendar/pages/index/index.ts +++ b/src/addon/calendar/pages/index/index.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -13,7 +13,7 @@ // limitations under the License. import { Component, OnInit, OnDestroy, ViewChild, NgZone } from '@angular/core'; -import { IonicPage, NavParams, NavController } from 'ionic-angular'; +import { IonicPage, NavParams, NavController, PopoverController } from 'ionic-angular'; import { CoreAppProvider } from '@providers/app'; import { CoreEventsProvider } from '@providers/events'; import { CoreLocalNotificationsProvider } from '@providers/local-notifications'; @@ -21,9 +21,10 @@ import { CoreSitesProvider } from '@providers/sites'; import { CoreDomUtilsProvider } from '@providers/utils/dom'; import { AddonCalendarProvider } from '../../providers/calendar'; import { AddonCalendarOfflineProvider } from '../../providers/calendar-offline'; -import { AddonCalendarHelperProvider } from '../../providers/helper'; +import { AddonCalendarHelperProvider, AddonCalendarFilter } from '../../providers/helper'; import { AddonCalendarCalendarComponent } from '../../components/calendar/calendar'; import { AddonCalendarUpcomingEventsComponent } from '../../components/upcoming-events/upcoming-events'; +import { AddonCalendarFilterPopoverComponent } from '../../components/filter/filter'; import { AddonCalendarSyncProvider } from '../../providers/calendar-sync'; import { CoreCoursesHelperProvider } from '@core/courses/providers/helper'; import { Network } from '@ionic-native/network'; @@ -52,11 +53,10 @@ export class AddonCalendarIndexPage implements OnInit, OnDestroy { protected syncObserver: any; protected manualSyncObserver: any; protected onlineObserver: any; + protected filterChangedObserver: any; year: number; month: number; - courseId: number; - categoryId: number; canCreate = false; courses: any[]; notificationsEnabled = false; @@ -66,6 +66,16 @@ export class AddonCalendarIndexPage implements OnInit, OnDestroy { syncIcon: string; showCalendar = true; loadUpcoming = false; + filter: AddonCalendarFilter = { + filtered: false, + courseId: null, + categoryId: null, + course: true, + group: true, + site: true, + user: true, + category: true + }; constructor(localNotificationsProvider: CoreLocalNotificationsProvider, navParams: NavParams, @@ -80,9 +90,9 @@ export class AddonCalendarIndexPage implements OnInit, OnDestroy { private calendarSync: AddonCalendarSyncProvider, private eventsProvider: CoreEventsProvider, private coursesHelper: CoreCoursesHelperProvider, - private appProvider: CoreAppProvider) { + private appProvider: CoreAppProvider, + private popoverCtrl: PopoverController) { - this.courseId = navParams.get('courseId'); this.eventId = navParams.get('eventId') || false; this.year = navParams.get('year'); this.month = navParams.get('month'); @@ -91,6 +101,11 @@ export class AddonCalendarIndexPage implements OnInit, OnDestroy { this.loadUpcoming = !!navParams.get('upcoming'); this.showCalendar = !this.loadUpcoming; + AddonCalendarProvider.ALL_TYPES.forEach((name) => { + this.filter[name] = true; + }); + this.filter.courseId = navParams.get('courseId'); + // Listen for events added. When an event is added, reload the data. this.newEventObserver = eventsProvider.on(AddonCalendarProvider.NEW_EVENT_EVENT, (data) => { if (data && data.event) { @@ -140,6 +155,15 @@ export class AddonCalendarIndexPage implements OnInit, OnDestroy { }); }, this.currentSiteId); + this.filterChangedObserver = this.eventsProvider.on(AddonCalendarProvider.FILTER_CHANGED_EVENT, (data) => { + this.filter = data; + + // Course viewed has changed, check if the user can create events for this course calendar. + this.calendarHelper.canEditEvents(this.filter['courseId']).then((canEdit) => { + this.canCreate = canEdit; + }); + }); + // Refresh online status when changes. this.onlineObserver = network.onchange().subscribe(() => { // Execute the callback in the Angular zone, so change detection doesn't stop working. @@ -164,9 +188,9 @@ export class AddonCalendarIndexPage implements OnInit, OnDestroy { /** * Fetch all the data required for the view. * - * @param {boolean} [sync] Whether it should try to synchronize offline events. - * @param {boolean} [showErrors] Whether to show sync errors to the user. - * @return {Promise} Promise resolved when done. + * @param sync Whether it should try to synchronize offline events. + * @param showErrors Whether to show sync errors to the user. + * @return Promise resolved when done. */ fetchData(sync?: boolean, showErrors?: boolean): Promise { @@ -203,13 +227,12 @@ export class AddonCalendarIndexPage implements OnInit, OnDestroy { this.hasOffline = false; // Load courses for the popover. - promises.push(this.coursesHelper.getCoursesForPopover(this.courseId).then((data) => { + promises.push(this.coursesHelper.getCoursesForPopover(this.filter.courseId).then((data) => { this.courses = data.courses; - this.categoryId = data.categoryId; })); // Check if user can create events. - promises.push(this.calendarHelper.canEditEvents(this.courseId).then((canEdit) => { + promises.push(this.calendarHelper.canEditEvents(this.filter.courseId).then((canEdit) => { this.canCreate = canEdit; })); @@ -230,10 +253,10 @@ export class AddonCalendarIndexPage implements OnInit, OnDestroy { /** * Refresh the data. * - * @param {any} [refresher] Refresher. - * @param {Function} [done] Function to call when done. - * @param {boolean} [showErrors] Whether to show sync errors to the user. - * @return {Promise} Promise resolved when done. + * @param refresher Refresher. + * @param done Function to call when done. + * @param showErrors Whether to show sync errors to the user. + * @return Promise resolved when done. */ doRefresh(refresher?: any, done?: () => void, showErrors?: boolean): Promise { if (this.loaded) { @@ -249,10 +272,10 @@ export class AddonCalendarIndexPage implements OnInit, OnDestroy { /** * Refresh the data. * - * @param {boolean} [sync] Whether it should try to synchronize offline events. - * @param {boolean} [showErrors] Whether to show sync errors to the user. - * @param {boolean} [afterChange] Whether the refresh is done after an event has changed or has been synced. - * @return {Promise} Promise resolved when done. + * @param sync Whether it should try to synchronize offline events. + * @param showErrors Whether to show sync errors to the user. + * @param afterChange Whether the refresh is done after an event has changed or has been synced. + * @return Promise resolved when done. */ refreshData(sync?: boolean, showErrors?: boolean, afterChange?: boolean): Promise { this.syncIcon = 'spinner'; @@ -276,7 +299,7 @@ export class AddonCalendarIndexPage implements OnInit, OnDestroy { /** * Navigate to a particular event. * - * @param {number} eventId Event to load. + * @param eventId Event to load. */ gotoEvent(eventId: number): void { if (eventId < 0) { @@ -292,7 +315,7 @@ export class AddonCalendarIndexPage implements OnInit, OnDestroy { /** * View a certain day. * - * @param {any} data Data with the year, month and day. + * @param data Data with the year, month and day. */ gotoDay(data: any): void { const params: any = { @@ -301,9 +324,9 @@ export class AddonCalendarIndexPage implements OnInit, OnDestroy { year: data.year }; - if (this.courseId) { - params.courseId = this.courseId; - } + Object.keys(this.filter).forEach((key) => { + params[key] = this.filter[key]; + }); this.navCtrl.push('AddonCalendarDayPage', params); } @@ -311,26 +334,23 @@ export class AddonCalendarIndexPage implements OnInit, OnDestroy { /** * Show the context menu. * - * @param {MouseEvent} event Event. + * @param event Event. */ - openCourseFilter(event: MouseEvent): void { - this.coursesHelper.selectCourse(event, this.courses, this.courseId).then((result) => { - if (typeof result.courseId != 'undefined') { - this.courseId = result.courseId > 0 ? result.courseId : undefined; - this.categoryId = result.courseId > 0 ? result.categoryId : undefined; + openFilter(event: MouseEvent): void { + const popover = this.popoverCtrl.create(AddonCalendarFilterPopoverComponent, { + courses: this.courses, + filter: this.filter + }); - // Course viewed has changed, check if the user can create events for this course calendar. - this.calendarHelper.canEditEvents(this.courseId).then((canEdit) => { - this.canCreate = canEdit; - }); - } + popover.present({ + ev: event }); } /** * Open page to create/edit an event. * - * @param {number} [eventId] Event ID to edit. + * @param eventId Event ID to edit. */ openEdit(eventId?: number): void { const params: any = {}; @@ -338,8 +358,8 @@ export class AddonCalendarIndexPage implements OnInit, OnDestroy { if (eventId) { params.eventId = eventId; } - if (this.courseId) { - params.courseId = this.courseId; + if (this.filter.courseId) { + params.courseId = this.filter.courseId; } this.navCtrl.push('AddonCalendarEditEventPage', params); @@ -374,6 +394,7 @@ export class AddonCalendarIndexPage implements OnInit, OnDestroy { this.undeleteEventObserver && this.undeleteEventObserver.off(); this.syncObserver && this.syncObserver.off(); this.manualSyncObserver && this.manualSyncObserver.off(); + this.filterChangedObserver && this.filterChangedObserver.off(); this.onlineObserver && this.onlineObserver.unsubscribe(); } } diff --git a/src/addon/calendar/pages/list/list.html b/src/addon/calendar/pages/list/list.html index 3ae4f4c0d..9f5e4178f 100644 --- a/src/addon/calendar/pages/list/list.html +++ b/src/addon/calendar/pages/list/list.html @@ -2,9 +2,9 @@ {{ 'addon.calendar.calendarevents' | translate }} - @@ -34,8 +34,8 @@ - -

+ +

{{ event.timestart * 1000 | coreFormatDate: "strftimetime" }} - {{ (event.timestart + event.timeduration) * 1000 | coreFormatDate: "strftimetime" }} diff --git a/src/addon/calendar/pages/list/list.module.ts b/src/addon/calendar/pages/list/list.module.ts index bfe0f7ecd..b5e7d1f99 100644 --- a/src/addon/calendar/pages/list/list.module.ts +++ b/src/addon/calendar/pages/list/list.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/addon/calendar/pages/list/list.ts b/src/addon/calendar/pages/list/list.ts index 0b864a78a..250c9054b 100644 --- a/src/addon/calendar/pages/list/list.ts +++ b/src/addon/calendar/pages/list/list.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -13,10 +13,10 @@ // limitations under the License. import { Component, ViewChild, OnDestroy, NgZone } from '@angular/core'; -import { IonicPage, Content, NavParams, NavController } from 'ionic-angular'; -import { AddonCalendarProvider } from '../../providers/calendar'; +import { IonicPage, Content, NavParams, NavController, PopoverController } from 'ionic-angular'; +import { AddonCalendarProvider, AddonCalendarGetEventsEvent } from '../../providers/calendar'; import { AddonCalendarOfflineProvider } from '../../providers/calendar-offline'; -import { AddonCalendarHelperProvider } from '../../providers/helper'; +import { AddonCalendarHelperProvider, AddonCalendarFilter } from '../../providers/helper'; import { AddonCalendarSyncProvider } from '../../providers/calendar-sync'; import { CoreCoursesProvider } from '@core/courses/providers/courses'; import { CoreCoursesHelperProvider } from '@core/courses/providers/helper'; @@ -31,6 +31,7 @@ import { CoreSplitViewComponent } from '@components/split-view/split-view'; import * as moment from 'moment'; import { Network } from '@ionic-native/network'; import { CoreConstants } from '@core/constants'; +import { AddonCalendarFilterPopoverComponent } from '../../components/filter/filter'; /** * Page that displays the list of calendar events. @@ -51,8 +52,14 @@ export class AddonCalendarListPage implements OnDestroy { protected getCategories = false; protected categories = {}; protected siteHomeId: number; - protected obsDefaultTimeChange: any; protected eventId: number; + protected currentSiteId: string; + protected onlineEvents: AddonCalendarGetEventsEvent[] = []; + protected offlineEvents = []; + protected deletedEvents = []; + + // Observers. + protected obsDefaultTimeChange: any; protected newEventObserver: any; protected discardedObserver: any; protected editEventObserver: any; @@ -61,32 +68,49 @@ export class AddonCalendarListPage implements OnDestroy { protected syncObserver: any; protected manualSyncObserver: any; protected onlineObserver: any; - protected currentSiteId: string; - protected onlineEvents = []; - protected offlineEvents = []; - protected deletedEvents = []; + protected filterChangedObserver: any; courses: any[]; eventsLoaded = false; events = []; // Events (both online and offline). notificationsEnabled = false; - filteredEvents = []; + filteredEvents: AddonCalendarGetEventsEvent[] = []; canLoadMore = false; loadMoreError = false; - courseId: number; - categoryId: number; canCreate = false; hasOffline = false; isOnline = false; syncIcon: string; // Sync icon. + filter: AddonCalendarFilter = { + filtered: false, + courseId: null, + categoryId: null, + course: false, + group: false, + site: false, + user: false, + category: false + }; - constructor(private calendarProvider: AddonCalendarProvider, navParams: NavParams, - private domUtils: CoreDomUtilsProvider, private coursesProvider: CoreCoursesProvider, private utils: CoreUtilsProvider, - private calendarHelper: AddonCalendarHelperProvider, sitesProvider: CoreSitesProvider, zone: NgZone, - localNotificationsProvider: CoreLocalNotificationsProvider, private coursesHelper: CoreCoursesHelperProvider, - private eventsProvider: CoreEventsProvider, private navCtrl: NavController, private appProvider: CoreAppProvider, - private calendarOffline: AddonCalendarOfflineProvider, private calendarSync: AddonCalendarSyncProvider, - network: Network, private timeUtils: CoreTimeUtilsProvider) { + constructor( + navParams: NavParams, + sitesProvider: CoreSitesProvider, + network: Network, + zone: NgZone, + localNotificationsProvider: CoreLocalNotificationsProvider, + private calendarProvider: AddonCalendarProvider, + private domUtils: CoreDomUtilsProvider, + private coursesProvider: CoreCoursesProvider, + private utils: CoreUtilsProvider, + private calendarHelper: AddonCalendarHelperProvider, + private coursesHelper: CoreCoursesHelperProvider, + private eventsProvider: CoreEventsProvider, + private navCtrl: NavController, + private appProvider: CoreAppProvider, + private calendarOffline: AddonCalendarOfflineProvider, + private calendarSync: AddonCalendarSyncProvider, + private timeUtils: CoreTimeUtilsProvider, + private popoverCtrl: PopoverController) { this.siteHomeId = sitesProvider.getCurrentSite().getSiteHomeId(); this.notificationsEnabled = localNotificationsProvider.isAvailable(); @@ -100,7 +124,11 @@ export class AddonCalendarListPage implements OnDestroy { } this.eventId = navParams.get('eventId') || false; - this.courseId = navParams.get('courseId'); + + AddonCalendarProvider.ALL_TYPES.forEach((name) => { + this.filter[name] = true; + }); + this.filter['courseId'] = navParams.get('courseId'); // Listen for events added. When an event is added, reload the data. this.newEventObserver = eventsProvider.on(AddonCalendarProvider.NEW_EVENT_EVENT, (data) => { @@ -198,6 +226,19 @@ export class AddonCalendarListPage implements OnDestroy { } }, this.currentSiteId); + this.filterChangedObserver = this.eventsProvider.on(AddonCalendarProvider.FILTER_CHANGED_EVENT, (data) => { + this.filter = data; + + // Course viewed has changed, check if the user can create events for this course calendar. + this.calendarHelper.canEditEvents(this.filter['courseId']).then((canEdit) => { + this.canCreate = canEdit; + }); + + this.filterEvents(); + + this.domUtils.scrollToTop(this.content); + }); + // Refresh online status when changes. this.onlineObserver = network.onchange().subscribe(() => { // Execute the callback in the Angular zone, so change detection doesn't stop working. @@ -233,10 +274,10 @@ export class AddonCalendarListPage implements OnDestroy { /** * Fetch all the data required for the view. * - * @param {boolean} [refresh] Empty events array first. - * @param {boolean} [sync] Whether it should try to synchronize offline events. - * @param {boolean} [showErrors] Whether to show sync errors to the user. - * @return {Promise} Promise resolved when done. + * @param refresh Empty events array first. + * @param sync Whether it should try to synchronize offline events. + * @param showErrors Whether to show sync errors to the user. + * @return Promise resolved when done. */ fetchData(refresh?: boolean, sync?: boolean, showErrors?: boolean): Promise { this.initialTime = this.timeUtils.timestamp(); @@ -274,14 +315,13 @@ export class AddonCalendarListPage implements OnDestroy { this.hasOffline = false; - promises.push(this.calendarHelper.canEditEvents(this.courseId).then((canEdit) => { + promises.push(this.calendarHelper.canEditEvents(this.filter['courseId']).then((canEdit) => { this.canCreate = canEdit; })); // Load courses for the popover. - promises.push(this.coursesHelper.getCoursesForPopover(this.courseId).then((result) => { + promises.push(this.coursesHelper.getCoursesForPopover(this.filter['courseId']).then((result) => { this.courses = result.courses; - this.categoryId = result.categoryId; return this.fetchEvents(refresh); })); @@ -314,8 +354,8 @@ export class AddonCalendarListPage implements OnDestroy { /** * Fetches the events and updates the view. * - * @param {boolean} [refresh] Empty events array first. - * @return {Promise} Promise resolved when done. + * @param refresh Empty events array first. + * @return Promise resolved when done. */ fetchEvents(refresh?: boolean): Promise { this.loadMoreError = false; @@ -354,7 +394,7 @@ export class AddonCalendarListPage implements OnDestroy { this.onlineEvents = this.utils.mergeArraysWithoutDuplicates(this.onlineEvents, onlineEvents, 'id'); this.events = this.utils.mergeArraysWithoutDuplicates(this.events, events, 'id'); } - this.filteredEvents = this.getFilteredEvents(); + this.filterEvents(); // Calculate which evemts need to display the date. this.filteredEvents.forEach((event, index): any => { @@ -388,8 +428,8 @@ export class AddonCalendarListPage implements OnDestroy { /** * Function to load more events. * - * @param {any} [infiniteComplete] Infinite scroll complete function. Only used from core-infinite-loading. - * @return {Promise} Resolved when done. + * @param infiniteComplete Infinite scroll complete function. Only used from core-infinite-loading. + * @return Resolved when done. */ loadMoreEvents(infiniteComplete?: any): Promise { return this.fetchEvents().finally(() => { @@ -397,26 +437,14 @@ export class AddonCalendarListPage implements OnDestroy { }); } - /** - * Get filtered events. - * - * @return {any[]} Filtered events. - */ - protected getFilteredEvents(): any[] { - if (!this.courseId) { - // No filter, display everything. - return this.events; - } - - return this.events.filter((event) => { - return this.calendarHelper.shouldDisplayEvent(event, this.courseId, this.categoryId, this.categories); - }); + protected filterEvents(): void { + this.filteredEvents = this.calendarHelper.getFilteredEvents(this.events, this.filter, this.categories); } /** * Returns if the current state should load categories or not. - * @param {any[]} events Events to parse. - * @return {boolean} True if categories should be loaded. + * @param events Events to parse. + * @return True if categories should be loaded. */ protected shouldLoadCategories(events: any[]): boolean { if (this.categoriesRetrieved || this.getCategories) { @@ -433,7 +461,7 @@ export class AddonCalendarListPage implements OnDestroy { /** * Load categories to be able to filter events. * - * @return {Promise} Promise resolved when done. + * @return Promise resolved when done. */ protected loadCategories(): Promise { return this.coursesProvider.getCategories(0, true).then((cats) => { @@ -451,8 +479,8 @@ export class AddonCalendarListPage implements OnDestroy { /** * Merge a period of online events with the offline events of that period. * - * @param {any[]} onlineEvents Online events. - * @return {any[]} Merged events. + * @param onlineEvents Online events. + * @return Merged events. */ protected mergeEvents(onlineEvents: any[]): any[] { if (!this.offlineEvents.length && !this.deletedEvents.length) { @@ -501,7 +529,7 @@ export class AddonCalendarListPage implements OnDestroy { /** * Sort events by timestart. * - * @param {any[]} events List to sort. + * @param events List to sort. */ protected sortEvents(events: any[]): any[] { return events.sort((a, b) => { @@ -516,10 +544,10 @@ export class AddonCalendarListPage implements OnDestroy { /** * Refresh the data. * - * @param {any} [refresher] Refresher. - * @param {Function} [done] Function to call when done. - * @param {boolean} [showErrors] Whether to show sync errors to the user. - * @return {Promise} Promise resolved when done. + * @param refresher Refresher. + * @param done Function to call when done. + * @param showErrors Whether to show sync errors to the user. + * @return Promise resolved when done. */ doRefresh(refresher?: any, done?: () => void, showErrors?: boolean): Promise { if (this.eventsLoaded) { @@ -535,9 +563,9 @@ export class AddonCalendarListPage implements OnDestroy { /** * Refresh the events. * - * @param {boolean} [sync] Whether it should try to synchronize offline events. - * @param {boolean} [showErrors] Whether to show sync errors to the user. - * @return {Promise} Promise resolved when done. + * @param sync Whether it should try to synchronize offline events. + * @param showErrors Whether to show sync errors to the user. + * @return Promise resolved when done. */ refreshEvents(sync?: boolean, showErrors?: boolean): Promise { this.syncIcon = 'spinner'; @@ -561,9 +589,9 @@ export class AddonCalendarListPage implements OnDestroy { * Check date should be shown on event list for the current event. * If date has changed from previous to current event it should be shown. * - * @param {any} event Current event where to show the date. - * @param {any} [prevEvent] Previous event where to compare the date with. - * @return {boolean} If date has changed and should be shown. + * @param event Current event where to show the date. + * @param prevEvent Previous event where to compare the date with. + * @return If date has changed and should be shown. */ protected showDate(event: any, prevEvent?: any): boolean { if (!prevEvent) { @@ -578,10 +606,10 @@ export class AddonCalendarListPage implements OnDestroy { /** * Check if event ends the same date or not. * - * @param {any} event Event info. - * @return {boolean} If date has changed and should be shown. + * @param event Event info. + * @return If date has changed and should be shown. */ - protected endsSameDay(event: any): boolean { + protected endsSameDay(event: AddonCalendarGetEventsEvent): boolean { if (!event.timeduration) { // No duration. return true; @@ -594,30 +622,23 @@ export class AddonCalendarListPage implements OnDestroy { /** * Show the context menu. * - * @param {MouseEvent} event Event. + * @param event Event. */ - openCourseFilter(event: MouseEvent): void { - this.coursesHelper.selectCourse(event, this.courses, this.courseId).then((result) => { - if (typeof result.courseId != 'undefined') { - this.courseId = result.courseId > 0 ? result.courseId : undefined; - this.categoryId = result.courseId > 0 ? result.categoryId : undefined; + openFilter(event: MouseEvent): void { + const popover = this.popoverCtrl.create(AddonCalendarFilterPopoverComponent, { + courses: this.courses, + filter: this.filter + }); - // Course viewed has changed, check if the user can create events for this course calendar. - this.calendarHelper.canEditEvents(this.courseId).then((canEdit) => { - this.canCreate = canEdit; - }); - - this.filteredEvents = this.getFilteredEvents(); - - this.domUtils.scrollToTop(this.content); - } + popover.present({ + ev: event }); } /** * Open page to create/edit an event. * - * @param {number} [eventId] Event ID to edit. + * @param eventId Event ID to edit. */ openEdit(eventId?: number): void { this.eventId = undefined; @@ -627,8 +648,8 @@ export class AddonCalendarListPage implements OnDestroy { if (eventId) { params.eventId = eventId; } - if (this.courseId) { - params.courseId = this.courseId; + if (this.filter['courseId']) { + params.courseId = this.filter['courseId']; } this.splitviewCtrl.push('AddonCalendarEditEventPage', params); @@ -644,7 +665,7 @@ export class AddonCalendarListPage implements OnDestroy { /** * Navigate to a particular event. * - * @param {number} eventId Event to load. + * @param eventId Event to load. */ gotoEvent(eventId: number): void { this.eventId = eventId; @@ -662,8 +683,8 @@ export class AddonCalendarListPage implements OnDestroy { /** * Find an event and mark it as deleted. * - * @param {number} eventId Event ID. - * @param {boolean} deleted Whether to mark it as deleted or not. + * @param eventId Event ID. + * @param deleted Whether to mark it as deleted or not. */ protected markAsDeleted(eventId: number, deleted: boolean): void { const event = this.onlineEvents.find((event) => { @@ -687,6 +708,7 @@ export class AddonCalendarListPage implements OnDestroy { this.undeleteEventObserver && this.undeleteEventObserver.off(); this.syncObserver && this.syncObserver.off(); this.manualSyncObserver && this.manualSyncObserver.off(); + this.filterChangedObserver && this.filterChangedObserver.off(); this.onlineObserver && this.onlineObserver.unsubscribe(); } } diff --git a/src/addon/calendar/pages/settings/settings.module.ts b/src/addon/calendar/pages/settings/settings.module.ts index f325d5f7a..d6abe8a15 100644 --- a/src/addon/calendar/pages/settings/settings.module.ts +++ b/src/addon/calendar/pages/settings/settings.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/addon/calendar/pages/settings/settings.ts b/src/addon/calendar/pages/settings/settings.ts index bebc4f1e9..cfd83d501 100644 --- a/src/addon/calendar/pages/settings/settings.ts +++ b/src/addon/calendar/pages/settings/settings.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -45,7 +45,7 @@ export class AddonCalendarSettingsPage { /** * Update default time. * - * @param {number} newTime New time. + * @param newTime New time. */ updateDefaultTime(newTime: number): void { this.calendarProvider.setDefaultNotificationTime(newTime); diff --git a/src/addon/calendar/providers/calendar-offline.ts b/src/addon/calendar/providers/calendar-offline.ts index 5b0f02436..072f579dc 100644 --- a/src/addon/calendar/providers/calendar-offline.ts +++ b/src/addon/calendar/providers/calendar-offline.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -148,9 +148,9 @@ export class AddonCalendarOfflineProvider { /** * Delete an offline event. * - * @param {number} eventId Event ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved if deleted, rejected if failure. + * @param eventId Event ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved if deleted, rejected if failure. */ deleteEvent(eventId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -165,8 +165,8 @@ export class AddonCalendarOfflineProvider { /** * Get the IDs of all the events created/edited/deleted in offline. * - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with the IDs. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the IDs. */ getAllEventsIds(siteId?: string): Promise { const promises = []; @@ -182,8 +182,8 @@ export class AddonCalendarOfflineProvider { /** * Get all the events deleted in offline. * - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with all the events deleted in offline. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with all the events deleted in offline. */ getAllDeletedEvents(siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -194,8 +194,8 @@ export class AddonCalendarOfflineProvider { /** * Get the IDs of all the events deleted in offline. * - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with the IDs of all the events deleted in offline. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the IDs of all the events deleted in offline. */ getAllDeletedEventsIds(siteId?: string): Promise { return this.getAllDeletedEvents(siteId).then((events) => { @@ -208,8 +208,8 @@ export class AddonCalendarOfflineProvider { /** * Get all the events created/edited in offline. * - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with events. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with events. */ getAllEditedEvents(siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -220,8 +220,8 @@ export class AddonCalendarOfflineProvider { /** * Get the IDs of all the events created/edited in offline. * - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with events IDs. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with events IDs. */ getAllEditedEventsIds(siteId?: string): Promise { return this.getAllEditedEvents(siteId).then((events) => { @@ -234,9 +234,9 @@ export class AddonCalendarOfflineProvider { /** * Get an event deleted in offline. * - * @param {number} eventId Event ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with the deleted event. + * @param eventId Event ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the deleted event. */ getDeletedEvent(eventId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -251,9 +251,9 @@ export class AddonCalendarOfflineProvider { /** * Get an offline event. * - * @param {number} eventId Event ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with the event. + * @param eventId Event ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the event. */ getEvent(eventId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -268,8 +268,8 @@ export class AddonCalendarOfflineProvider { /** * Check if there are offline events to send. * - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with boolean: true if has offline events, false otherwise. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with boolean: true if has offline events, false otherwise. */ hasEditedEvents(siteId?: string): Promise { return this.getAllEditedEvents(siteId).then((events) => { @@ -283,8 +283,8 @@ export class AddonCalendarOfflineProvider { /** * Check whether there's offline data for a site. * - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with boolean: true if has offline data, false otherwise. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with boolean: true if has offline data, false otherwise. */ hasOfflineData(siteId?: string): Promise { return this.getAllEventsIds(siteId).then((ids) => { @@ -295,9 +295,9 @@ export class AddonCalendarOfflineProvider { /** * Check if an event is deleted. * - * @param {number} eventId Event ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with boolean: whether the event is deleted. + * @param eventId Event ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with boolean: whether the event is deleted. */ isEventDeleted(eventId: number, siteId?: string): Promise { return this.getDeletedEvent(eventId, siteId).then((event) => { @@ -310,11 +310,11 @@ export class AddonCalendarOfflineProvider { /** * Mark an event as deleted. * - * @param {number} eventId Event ID to delete. - * @param {number} name Name of the event to delete. - * @param {boolean} [deleteAll] If it's a repeated event. whether to delete all events of the series. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when done. + * @param eventId Event ID to delete. + * @param name Name of the event to delete. + * @param deleteAll If it's a repeated event. whether to delete all events of the series. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when done. */ markDeleted(eventId: number, name: string, deleteAll?: boolean, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -332,11 +332,11 @@ export class AddonCalendarOfflineProvider { /** * Offline version for adding a new discussion to a forum. * - * @param {number} eventId Event ID. If it's a new event, set it to undefined/null. - * @param {any} data Event data. - * @param {number} [timeCreated] The time the event was created. If not defined, current time. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with the stored event. + * @param eventId Event ID. If it's a new event, set it to undefined/null. + * @param data Event data. + * @param timeCreated The time the event was created. If not defined, current time. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the stored event. */ saveEvent(eventId: number, data: any, timeCreated?: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -373,9 +373,9 @@ export class AddonCalendarOfflineProvider { /** * Unmark an event as deleted. * - * @param {number} eventId Event ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved if deleted, rejected if failure. + * @param eventId Event ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved if deleted, rejected if failure. */ unmarkDeleted(eventId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { diff --git a/src/addon/calendar/providers/calendar-sync.ts b/src/addon/calendar/providers/calendar-sync.ts index bc9d96f33..3dd0e6f16 100644 --- a/src/addon/calendar/providers/calendar-sync.ts +++ b/src/addon/calendar/providers/calendar-sync.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -59,9 +59,9 @@ export class AddonCalendarSyncProvider extends CoreSyncBaseProvider { /** * Try to synchronize all events in a certain site or in all sites. * - * @param {string} [siteId] Site ID to sync. If not defined, sync all sites. - * @param {boolean} [force] Wether to force sync not depending on last execution. - * @return {Promise} Promise resolved if sync is successful, rejected if sync fails. + * @param siteId Site ID to sync. If not defined, sync all sites. + * @param force Wether to force sync not depending on last execution. + * @return Promise resolved if sync is successful, rejected if sync fails. */ syncAllEvents(siteId?: string, force?: boolean): Promise { return this.syncOnSites('all calendar events', this.syncAllEventsFunc.bind(this), [force], siteId); @@ -70,9 +70,9 @@ export class AddonCalendarSyncProvider extends CoreSyncBaseProvider { /** * Sync all events on a site. * - * @param {string} siteId Site ID to sync. - * @param {boolean} [force] Wether to force sync not depending on last execution. - * @return {Promise} Promise resolved if sync is successful, rejected if sync fails. + * @param siteId Site ID to sync. + * @param force Wether to force sync not depending on last execution. + * @return Promise resolved if sync is successful, rejected if sync fails. */ protected syncAllEventsFunc(siteId: string, force?: boolean): Promise { @@ -93,8 +93,8 @@ export class AddonCalendarSyncProvider extends CoreSyncBaseProvider { /** * Sync a site events only if a certain time has passed since the last time. * - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the events are synced or if it doesn't need to be synced. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the events are synced or if it doesn't need to be synced. */ syncEventsIfNeeded(siteId?: string): Promise { siteId = siteId || this.sitesProvider.getCurrentSiteId(); @@ -109,8 +109,8 @@ export class AddonCalendarSyncProvider extends CoreSyncBaseProvider { /** * Synchronize all offline events of a certain site. * - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved if sync is successful, rejected otherwise. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved if sync is successful, rejected otherwise. */ syncEvents(siteId?: string): Promise { siteId = siteId || this.sitesProvider.getCurrentSiteId(); @@ -182,10 +182,10 @@ export class AddonCalendarSyncProvider extends CoreSyncBaseProvider { /** * Synchronize an offline event. * - * @param {number} eventId The event ID to sync. - * @param {any} result Object where to store the result of the sync. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved if sync is successful, rejected otherwise. + * @param eventId The event ID to sync. + * @param result Object where to store the result of the sync. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved if sync is successful, rejected otherwise. */ protected syncOfflineEvent(eventId: number, result: any, siteId?: string): Promise { diff --git a/src/addon/calendar/providers/calendar.ts b/src/addon/calendar/providers/calendar.ts index 892cb35cd..ed3891a00 100644 --- a/src/addon/calendar/providers/calendar.ts +++ b/src/addon/calendar/providers/calendar.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -31,6 +31,7 @@ import { SQLiteDB } from '@classes/sqlitedb'; import { AddonCalendarOfflineProvider } from './calendar-offline'; import { CoreUserProvider } from '@core/user/providers/user'; import { TranslateService } from '@ngx-translate/core'; +import { CoreWSExternalWarning, CoreWSDate } from '@providers/ws'; import * as moment from 'moment'; /** @@ -49,11 +50,17 @@ export class AddonCalendarProvider { static EDIT_EVENT_EVENT = 'addon_calendar_edit_event'; static DELETED_EVENT_EVENT = 'addon_calendar_deleted_event'; static UNDELETED_EVENT_EVENT = 'addon_calendar_undeleted_event'; + static FILTER_CHANGED_EVENT = 'addon_calendar_filter_changed_event'; static TYPE_CATEGORY = 'category'; static TYPE_COURSE = 'course'; static TYPE_GROUP = 'group'; static TYPE_SITE = 'site'; static TYPE_USER = 'user'; + static ALL_TYPES = [ AddonCalendarProvider.TYPE_SITE, + AddonCalendarProvider.TYPE_CATEGORY, + AddonCalendarProvider.TYPE_COURSE, + AddonCalendarProvider.TYPE_GROUP, + AddonCalendarProvider.TYPE_USER ]; static CALENDAR_TF_24 = '%H:%M'; // Calendar time in 24 hours format. static CALENDAR_TF_12 = '%I:%M %p'; // Calendar time in 12 hours format. @@ -335,8 +342,8 @@ export class AddonCalendarProvider { /** * Check if a certain site allows deleting events. * - * @param {string} [siteId] Site Id. If not defined, use current site. - * @return {Promise} Promise resolved with true if can delete. + * @param siteId Site Id. If not defined, use current site. + * @return Promise resolved with true if can delete. * @since 3.3 */ canDeleteEvents(siteId?: string): Promise { @@ -350,8 +357,8 @@ export class AddonCalendarProvider { /** * Check if a certain site allows deleting events. * - * @param {CoreSite} [site] Site. If not defined, use current site. - * @return {boolean} Whether events can be deleted. + * @param site Site. If not defined, use current site. + * @return Whether events can be deleted. * @since 3.3 */ canDeleteEventsInSite(site?: CoreSite): boolean { @@ -363,8 +370,8 @@ export class AddonCalendarProvider { /** * Check if a certain site allows creating and editing events. * - * @param {string} [siteId] Site Id. If not defined, use current site. - * @return {Promise} Promise resolved with true if can create/edit. + * @param siteId Site Id. If not defined, use current site. + * @return Promise resolved with true if can create/edit. * @since 3.7.1 */ canEditEvents(siteId?: string): Promise { @@ -378,8 +385,8 @@ export class AddonCalendarProvider { /** * Check if a certain site allows creating and editing events. * - * @param {CoreSite} [site] Site. If not defined, use current site. - * @return {boolean} Whether events can be created and edited. + * @param site Site. If not defined, use current site. + * @return Whether events can be created and edited. * @since 3.7.1 */ canEditEventsInSite(site?: CoreSite): boolean { @@ -392,8 +399,8 @@ export class AddonCalendarProvider { /** * Check if a certain site allows viewing events in monthly view. * - * @param {string} [siteId] Site Id. If not defined, use current site. - * @return {Promise} Promise resolved with true if monthly view is supported. + * @param siteId Site Id. If not defined, use current site. + * @return Promise resolved with true if monthly view is supported. * @since 3.4 */ canViewMonth(siteId?: string): Promise { @@ -407,8 +414,8 @@ export class AddonCalendarProvider { /** * Check if a certain site allows viewing events in monthly view. * - * @param {CoreSite} [site] Site. If not defined, use current site. - * @return {boolean} Whether monthly view is supported. + * @param site Site. If not defined, use current site. + * @return Whether monthly view is supported. * @since 3.4 */ canViewMonthInSite(site?: CoreSite): boolean { @@ -420,8 +427,8 @@ export class AddonCalendarProvider { /** * Removes expired events from local DB. * - * @param {string} [siteId] ID of the site the event belongs to. If not defined, use current site. - * @return {Promise} Promise resolved when done. + * @param siteId ID of the site the event belongs to. If not defined, use current site. + * @return Promise resolved when done. */ cleanExpiredEvents(siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -442,12 +449,12 @@ export class AddonCalendarProvider { /** * Delete an event. * - * @param {number} eventId Event ID to delete. - * @param {string} name Name of the event to delete. - * @param {boolean} [deleteAll] If it's a repeated event. whether to delete all events of the series. - * @param {boolean} [forceOffline] True to always save it in offline. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when done. + * @param eventId Event ID to delete. + * @param name Name of the event to delete. + * @param deleteAll If it's a repeated event. whether to delete all events of the series. + * @param forceOffline True to always save it in offline. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when done. */ deleteEvent(eventId: number, name: string, deleteAll?: boolean, forceOffline?: boolean, siteId?: string): Promise { @@ -484,12 +491,12 @@ export class AddonCalendarProvider { /** * Delete an event. It will fail if offline or cannot connect. * - * @param {number} eventId Event ID to delete. - * @param {boolean} [deleteAll] If it's a repeated event. whether to delete all events of the series. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when done. + * @param eventId Event ID to delete. + * @param deleteAll If it's a repeated event. whether to delete all events of the series. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when done. */ - deleteEventOnline(eventId: number, deleteAll?: boolean, siteId?: string): Promise { + deleteEventOnline(eventId: number, deleteAll?: boolean, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { const params = { @@ -511,9 +518,9 @@ export class AddonCalendarProvider { /** * Delete a locally stored event cancelling all the reminders and notifications. * - * @param {number} eventId Event ID. - * @param {string} [siteId] ID of the site the event belongs to. If not defined, use current site. - * @return {Promise} Resolved when done. + * @param eventId Event ID. + * @param siteId ID of the site the event belongs to. If not defined, use current site. + * @return Resolved when done. */ protected deleteLocalEvent(eventId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -535,35 +542,19 @@ export class AddonCalendarProvider { }); } - /** - * Check if event ends the same day or not. - * - * @param {any} event Event info. - * @return {boolean} If the . - */ - endsSameDay(event: any): boolean { - if (!event.timeduration) { - // No duration. - return true; - } - - // Check if day has changed. - return moment(event.timestart * 1000).isSame((event.timestart + event.timeduration) * 1000, 'day'); - } - /** * Format event time. Similar to calendar_format_event_time. * - * @param {any} event Event to format. - * @param {string} format Calendar time format (from getCalendarTimeFormat). - * @param {boolean} [useCommonWords=true] Whether to use common words like "Today", "Yesterday", etc. - * @param {number} [seenDay] Timestamp of day currently seen. If set, the function will not add links to this day. - * @param {number} [showTime=0] Determine the show time GMT timestamp. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with the formatted event time. + * @param event Event to format. + * @param format Calendar time format (from getCalendarTimeFormat). + * @param useCommonWords Whether to use common words like "Today", "Yesterday", etc. + * @param seenDay Timestamp of day currently seen. If set, the function will not add links to this day. + * @param showTime Determine the show time GMT timestamp. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the formatted event time. */ - formatEventTime(event: any, format: string, useCommonWords: boolean = true, seenDay?: number, showTime: number = 0, - siteId?: string): Promise { + formatEventTime(event: AddonCalendarAnyEvent, format: string, useCommonWords: boolean = true, seenDay?: number, + showTime: number = 0, siteId?: string): Promise { const start = event.timestart * 1000, end = (event.timestart + event.timeduration) * 1000; @@ -630,12 +621,12 @@ export class AddonCalendarProvider { /** * Get access information for a calendar (either course calendar or site calendar). * - * @param {number} [courseId] Course ID. If not defined, site calendar. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with object with access information. + * @param courseId Course ID. If not defined, site calendar. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with object with access information. * @since 3.7 */ - getAccessInformation(courseId?: number, siteId?: string): Promise { + getAccessInformation(courseId?: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { const params: any = {}, preSets = { @@ -653,8 +644,8 @@ export class AddonCalendarProvider { /** * Get cache key for calendar access information WS calls. * - * @param {number} [courseId] Course ID. - * @return {string} Cache key. + * @param courseId Course ID. + * @return Cache key. */ protected getAccessInformationCacheKey(courseId?: number): string { return this.ROOT_CACHE_KEY + 'accessInformation:' + (courseId || 0); @@ -663,8 +654,8 @@ export class AddonCalendarProvider { /** * Get all calendar events from local Db. * - * @param {string} [siteId] ID of the site the event belongs to. If not defined, use current site. - * @return {Promise} Promise resolved with all the events. + * @param siteId ID of the site the event belongs to. If not defined, use current site. + * @return Promise resolved with all the events. */ getAllEventsFromLocalDb(siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -675,12 +666,12 @@ export class AddonCalendarProvider { /** * Get the type of events a user can create (either course calendar or site calendar). * - * @param {number} [courseId] Course ID. If not defined, site calendar. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with an object indicating the types. + * @param courseId Course ID. If not defined, site calendar. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with an object indicating the types. * @since 3.7 */ - getAllowedEventTypes(courseId?: number, siteId?: string): Promise { + getAllowedEventTypes(courseId?: number, siteId?: string): Promise<{[name: string]: boolean}> { return this.sitesProvider.getSite(siteId).then((site) => { const params: any = {}, preSets = { @@ -691,7 +682,8 @@ export class AddonCalendarProvider { params.courseid = courseId; } - return site.read('core_calendar_get_allowed_event_types', params, preSets).then((response) => { + return site.read('core_calendar_get_allowed_event_types', params, preSets) + .then((response: AddonCalendarGetAllowedEventTypesResult) => { // Convert the array to an object. const result = {}; @@ -709,8 +701,8 @@ export class AddonCalendarProvider { /** * Get cache key for calendar allowed event types WS calls. * - * @param {number} [courseId] Course ID. - * @return {string} Cache key. + * @param courseId Course ID. + * @return Cache key. */ protected getAllowedEventTypesCacheKey(courseId?: number): string { return this.ROOT_CACHE_KEY + 'allowedEventTypes:' + (courseId || 0); @@ -719,8 +711,8 @@ export class AddonCalendarProvider { /** * Get the "look ahead" for a certain user. * - * @param {string} [siteId] ID of the site. If not defined, use current site. - * @return {Promise} Promise resolved with the look ahead (number of days). + * @param siteId ID of the site. If not defined, use current site. + * @return Promise resolved with the look ahead (number of days). */ getCalendarLookAhead(siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -739,8 +731,8 @@ export class AddonCalendarProvider { /** * Get the time format to use in calendar. * - * @param {string} [siteId] ID of the site. If not defined, use current site. - * @return {Promise} Promise resolved with the format. + * @param siteId ID of the site. If not defined, use current site. + * @return Promise resolved with the format. */ getCalendarTimeFormat(siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -766,9 +758,9 @@ export class AddonCalendarProvider { /** * Return the representation day. Equivalent to Moodle's calendar_day_representation. * - * @param {number} time Timestamp to get the day from. - * @param {boolean} [useCommonWords=true] Whether to use common words like "Today", "Yesterday", etc. - * @return {string} The formatted date/time. + * @param time Timestamp to get the day from. + * @param useCommonWords Whether to use common words like "Today", "Yesterday", etc. + * @return The formatted date/time. */ getDayRepresentation(time: number, useCommonWords: boolean = true): string { @@ -797,8 +789,8 @@ export class AddonCalendarProvider { /** * Get the configured default notification time. * - * @param {string} [siteId] ID of the site. If not defined, use current site. - * @return {Promise} Promise resolved with the default time. + * @param siteId ID of the site. If not defined, use current site. + * @return Promise resolved with the default time. */ getDefaultNotificationTime(siteId?: string): Promise { siteId = siteId || this.sitesProvider.getCurrentSiteId(); @@ -811,12 +803,11 @@ export class AddonCalendarProvider { /** * Get a calendar event. If the server request fails and data is not cached, try to get it from local DB. * - * @param {number} id Event ID. - * @param {boolean} [refresh] True when we should update the event data. - * @param {string} [siteId] ID of the site. If not defined, use current site. - * @return {Promise} Promise resolved when the event data is retrieved. + * @param id Event ID. + * @param siteId ID of the site. If not defined, use current site. + * @return Promise resolved when the event data is retrieved. */ - getEvent(id: number, siteId?: string): Promise { + getEvent(id: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { const preSets = { cacheKey: this.getEventCacheKey(id), @@ -834,7 +825,8 @@ export class AddonCalendarProvider { } }; - return site.read('core_calendar_get_calendar_events', data, preSets).then((response) => { + return site.read('core_calendar_get_calendar_events', data, preSets) + .then((response: AddonCalendarGetEventsResult) => { // The WebService returns all category events. Check the response to search for the event we want. const event = response.events.find((e) => { return e.id == id; }); @@ -848,13 +840,12 @@ export class AddonCalendarProvider { /** * Get a calendar event by ID. This function returns more data than getEvent, but it isn't available in all Moodles. * - * @param {number} id Event ID. - * @param {boolean} [refresh] True when we should update the event data. - * @param {string} [siteId] ID of the site. If not defined, use current site. - * @return {Promise} Promise resolved when the event data is retrieved. + * @param id Event ID. + * @param siteId ID of the site. If not defined, use current site. + * @return Promise resolved when the event data is retrieved. * @since 3.4 */ - getEventById(id: number, siteId?: string): Promise { + getEventById(id: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { const preSets = { cacheKey: this.getEventCacheKey(id), @@ -864,7 +855,8 @@ export class AddonCalendarProvider { eventid: id }; - return site.read('core_calendar_get_calendar_event_by_id', data, preSets).then((response) => { + return site.read('core_calendar_get_calendar_event_by_id', data, preSets) + .then((response: AddonCalendarGetEventByIdResult) => { return response.event; }).catch((error) => { return this.getEventFromLocalDb(id).catch(() => { @@ -877,8 +869,8 @@ export class AddonCalendarProvider { /** * Get cache key for a single event WS call. * - * @param {number} id Event ID. - * @return {string} Cache key. + * @param id Event ID. + * @return Cache key. */ protected getEventCacheKey(id: number): string { return this.ROOT_CACHE_KEY + 'events:' + id; @@ -887,9 +879,9 @@ export class AddonCalendarProvider { /** * Get a calendar event from local Db. * - * @param {number} id Event ID. - * @param {string} [siteId] ID of the site the event belongs to. If not defined, use current site. - * @return {Promise} Promise resolved when the event data is retrieved. + * @param id Event ID. + * @param siteId ID of the site the event belongs to. If not defined, use current site. + * @return Promise resolved when the event data is retrieved. */ getEventFromLocalDb(id: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -913,12 +905,12 @@ export class AddonCalendarProvider { /** * Adds an event reminder and schedule a new notification. * - * @param {any} event Event to update its notification time. - * @param {number} time New notification setting timestamp. - * @param {string} [siteId] ID of the site the event belongs to. If not defined, use current site. - * @return {Promise} Promise resolved when the notification is updated. + * @param event Event to update its notification time. + * @param time New notification setting timestamp. + * @param siteId ID of the site the event belongs to. If not defined, use current site. + * @return Promise resolved when the notification is updated. */ - addEventReminder(event: any, time: number, siteId?: string): Promise { + addEventReminder(event: AddonCalendarAnyEvent, time: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { const reminder = { eventid: event.id, @@ -935,8 +927,8 @@ export class AddonCalendarProvider { * Return the normalised event type. * Activity events are normalised to be course events. * - * @param {any} event The event to get its type. - * @return {string} Event type. + * @param event The event to get its type. + * @return Event type. */ getEventType(event: any): string { if (event.modulename) { @@ -949,9 +941,9 @@ export class AddonCalendarProvider { /** * Remove an event reminder and cancel the notification. * - * @param {number} id Reminder ID. - * @param {string} [siteId] ID of the site the event belongs to. If not defined, use current site. - * @return {Promise} Promise resolved when the notification is updated. + * @param id Reminder ID. + * @param siteId ID of the site the event belongs to. If not defined, use current site. + * @return Promise resolved when the notification is updated. */ deleteEventReminder(id: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -966,17 +958,17 @@ export class AddonCalendarProvider { /** * Get calendar events for a certain day. * - * @param {number} year Year to get. - * @param {number} month Month to get. - * @param {number} day Day to get. - * @param {number} [courseId] Course to get. - * @param {number} [categoryId] Category to get. - * @param {boolean} [ignoreCache] True if it should ignore cached data (it will always fail in offline or server down). - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with the response. + * @param year Year to get. + * @param month Month to get. + * @param day Day to get. + * @param courseId Course to get. + * @param categoryId Category to get. + * @param ignoreCache True if it should ignore cached data (it will always fail in offline or server down). + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the response. */ getDayEvents(year: number, month: number, day: number, courseId?: number, categoryId?: number, ignoreCache?: boolean, - siteId?: string): Promise { + siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -1003,7 +995,7 @@ export class AddonCalendarProvider { preSets.emergencyCache = false; } - return site.read('core_calendar_get_calendar_day_view', data, preSets).then((response) => { + return site.read('core_calendar_get_calendar_day_view', data, preSets).then((response: AddonCalendarCalendarDay) => { this.storeEventsInLocalDB(response.events, siteId); return response; @@ -1014,7 +1006,7 @@ export class AddonCalendarProvider { /** * Get prefix cache key for day events WS calls. * - * @return {string} Prefix Cache key. + * @return Prefix Cache key. */ protected getDayEventsPrefixCacheKey(): string { return this.ROOT_CACHE_KEY + 'day:'; @@ -1023,10 +1015,10 @@ export class AddonCalendarProvider { /** * Get prefix cache key for a certain day for day events WS calls. * - * @param {number} year Year to get. - * @param {number} month Month to get. - * @param {number} day Day to get. - * @return {string} Prefix Cache key. + * @param year Year to get. + * @param month Month to get. + * @param day Day to get. + * @return Prefix Cache key. */ protected getDayEventsDayPrefixCacheKey(year: number, month: number, day: number): string { return this.getDayEventsPrefixCacheKey() + year + ':' + month + ':' + day + ':'; @@ -1035,12 +1027,12 @@ export class AddonCalendarProvider { /** * Get cache key for day events WS calls. * - * @param {number} year Year to get. - * @param {number} month Month to get. - * @param {number} day Day to get. - * @param {number} [courseId] Course to get. - * @param {number} [categoryId] Category to get. - * @return {string} Cache key. + * @param year Year to get. + * @param month Month to get. + * @param day Day to get. + * @param courseId Course to get. + * @param categoryId Category to get. + * @return Cache key. */ protected getDayEventsCacheKey(year: number, month: number, day: number, courseId?: number, categoryId?: number): string { return this.getDayEventsDayPrefixCacheKey(year, month, day) + (courseId ? courseId : '') + ':' + @@ -1050,9 +1042,9 @@ export class AddonCalendarProvider { /** * Get a calendar reminders from local Db. * - * @param {number} id Event ID. - * @param {string} [siteId] ID of the site the event belongs to. If not defined, use current site. - * @return {Promise} Promise resolved when the event data is retrieved. + * @param id Event ID. + * @param siteId ID of the site the event belongs to. If not defined, use current site. + * @return Promise resolved when the event data is retrieved. */ getEventReminders(id: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -1067,14 +1059,14 @@ export class AddonCalendarProvider { * E.g. using provider.getEventsList(undefined, 30, 30) is going to get the events starting after 30 days from now * and ending before 60 days from now. * - * @param {number} [initialTime] Timestamp when the first fetch was done. If not defined, current time. - * @param {number} [daysToStart=0] Number of days from now to start getting events. - * @param {number} [daysInterval=30] Number of days between timestart and timeend. - * @param {string} [siteId] Site to get the events from. If not defined, use current site. - * @return {Promise} Promise to be resolved when the participants are retrieved. + * @param initialTime Timestamp when the first fetch was done. If not defined, current time. + * @param daysToStart Number of days from now to start getting events. + * @param daysInterval Number of days between timestart and timeend. + * @param siteId Site to get the events from. If not defined, use current site. + * @return Promise to be resolved when the events are retrieved. */ getEventsList(initialTime?: number, daysToStart: number = 0, daysInterval: number = AddonCalendarProvider.DAYS_INTERVAL, - siteId?: string): Promise { + siteId?: string): Promise { initialTime = initialTime || this.timeUtils.timestamp(); @@ -1122,7 +1114,9 @@ export class AddonCalendarProvider { updateFrequency: CoreSite.FREQUENCY_SOMETIMES }; - return site.read('core_calendar_get_calendar_events', data, preSets).then((response) => { + return site.read('core_calendar_get_calendar_events', data, preSets) + .then((response: AddonCalendarGetEventsResult) => { + if (!this.canViewMonthInSite(site)) { // Store events only in 3.1-3.3. In 3.4+ we'll use the new WS that return more info. this.storeEventsInLocalDB(response.events, siteId); @@ -1137,7 +1131,7 @@ export class AddonCalendarProvider { /** * Get prefix cache key for events list WS calls. * - * @return {string} Prefix Cache key. + * @return Prefix Cache key. */ protected getEventsListPrefixCacheKey(): string { return this.ROOT_CACHE_KEY + 'events:'; @@ -1146,9 +1140,9 @@ export class AddonCalendarProvider { /** * Get cache key for events list WS calls. * - * @param {number} daysToStart Number of days from now to start getting events. - * @param {number} daysInterval Number of days between timestart and timeend. - * @return {string} Cache key. + * @param daysToStart Number of days from now to start getting events. + * @param daysInterval Number of days between timestart and timeend. + * @return Cache key. */ protected getEventsListCacheKey(daysToStart: number, daysInterval: number): string { return this.getEventsListPrefixCacheKey() + daysToStart + ':' + daysInterval; @@ -1157,9 +1151,9 @@ export class AddonCalendarProvider { /** * Get calendar events from local Db that have the same repeatid. * - * @param {number} [repeatId] Repeat Id of the event. - * @param {string} [siteId] ID of the site the event belongs to. If not defined, use current site. - * @return {Promise} Promise resolved with all the events. + * @param repeatId Repeat Id of the event. + * @param siteId ID of the site the event belongs to. If not defined, use current site. + * @return Promise resolved with all the events. */ getLocalEventsByRepeatIdFromLocalDb(repeatId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -1169,16 +1163,16 @@ export class AddonCalendarProvider { /** * Get monthly calendar events. * - * @param {number} year Year to get. - * @param {number} month Month to get. - * @param {number} [courseId] Course to get. - * @param {number} [categoryId] Category to get. - * @param {boolean} [ignoreCache] True if it should ignore cached data (it will always fail in offline or server down). - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with the response. + * @param year Year to get. + * @param month Month to get. + * @param courseId Course to get. + * @param categoryId Category to get. + * @param ignoreCache True if it should ignore cached data (it will always fail in offline or server down). + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the response. */ getMonthlyEvents(year: number, month: number, courseId?: number, categoryId?: number, ignoreCache?: boolean, siteId?: string) - : Promise { + : Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -1210,7 +1204,9 @@ export class AddonCalendarProvider { preSets.emergencyCache = false; } - return site.read('core_calendar_get_calendar_monthly_view', data, preSets).then((response) => { + return site.read('core_calendar_get_calendar_monthly_view', data, preSets) + .then((response: AddonCalendarMonth) => { + response.weeks.forEach((week) => { week.days.forEach((day) => { this.storeEventsInLocalDB(day.events, siteId); @@ -1230,7 +1226,7 @@ export class AddonCalendarProvider { /** * Get prefix cache key for monthly events WS calls. * - * @return {string} Prefix Cache key. + * @return Prefix Cache key. */ protected getMonthlyEventsPrefixCacheKey(): string { return this.ROOT_CACHE_KEY + 'monthly:'; @@ -1239,9 +1235,9 @@ export class AddonCalendarProvider { /** * Get prefix cache key for a certain month for monthly events WS calls. * - * @param {number} year Year to get. - * @param {number} month Month to get. - * @return {string} Prefix Cache key. + * @param year Year to get. + * @param month Month to get. + * @return Prefix Cache key. */ protected getMonthlyEventsMonthPrefixCacheKey(year: number, month: number): string { return this.getMonthlyEventsPrefixCacheKey() + year + ':' + month + ':'; @@ -1250,11 +1246,11 @@ export class AddonCalendarProvider { /** * Get cache key for monthly events WS calls. * - * @param {number} year Year to get. - * @param {number} month Month to get. - * @param {number} [courseId] Course to get. - * @param {number} [categoryId] Category to get. - * @return {string} Cache key. + * @param year Year to get. + * @param month Month to get. + * @param courseId Course to get. + * @param categoryId Category to get. + * @return Cache key. */ protected getMonthlyEventsCacheKey(year: number, month: number, courseId?: number, categoryId?: number): string { return this.getMonthlyEventsMonthPrefixCacheKey(year, month) + (courseId ? courseId : '') + ':' + @@ -1264,13 +1260,14 @@ export class AddonCalendarProvider { /** * Get upcoming calendar events. * - * @param {number} [courseId] Course to get. - * @param {number} [categoryId] Category to get. - * @param {boolean} [ignoreCache] True if it should ignore cached data (it will always fail in offline or server down). - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with the response. + * @param courseId Course to get. + * @param categoryId Category to get. + * @param ignoreCache True if it should ignore cached data (it will always fail in offline or server down). + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the response. */ - getUpcomingEvents(courseId?: number, categoryId?: number, ignoreCache?: boolean, siteId?: string): Promise { + getUpcomingEvents(courseId?: number, categoryId?: number, ignoreCache?: boolean, siteId?: string) + : Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -1293,7 +1290,7 @@ export class AddonCalendarProvider { preSets.emergencyCache = false; } - return site.read('core_calendar_get_calendar_upcoming_view', data, preSets).then((response) => { + return site.read('core_calendar_get_calendar_upcoming_view', data, preSets).then((response: AddonCalendarUpcoming) => { this.storeEventsInLocalDB(response.events, siteId); return response; @@ -1304,7 +1301,7 @@ export class AddonCalendarProvider { /** * Get prefix cache key for upcoming events WS calls. * - * @return {string} Prefix Cache key. + * @return Prefix Cache key. */ protected getUpcomingEventsPrefixCacheKey(): string { return this.ROOT_CACHE_KEY + 'upcoming:'; @@ -1313,9 +1310,9 @@ export class AddonCalendarProvider { /** * Get cache key for upcoming events WS calls. * - * @param {number} [courseId] Course to get. - * @param {number} [categoryId] Category to get. - * @return {string} Cache key. + * @param courseId Course to get. + * @param categoryId Category to get. + * @return Cache key. */ protected getUpcomingEventsCacheKey(courseId?: number, categoryId?: number): string { return this.getUpcomingEventsPrefixCacheKey() + (courseId ? courseId : '') + ':' + (categoryId ? categoryId : ''); @@ -1324,11 +1321,11 @@ export class AddonCalendarProvider { /** * Get URL to view a calendar. * - * @param {string} view The view to load: 'month', 'day', 'upcoming', etc. - * @param {number} [time] Time to load. If not defined, current time. - * @param {string} [courseId] Course to load. If not defined, all courses. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with the URL.x + * @param view The view to load: 'month', 'day', 'upcoming', etc. + * @param time Time to load. If not defined, current time. + * @param courseId Course to load. If not defined, all courses. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the URL.x */ getViewUrl(view: string, time?: number, courseId?: string, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -1349,8 +1346,8 @@ export class AddonCalendarProvider { /** * Get the week days, already ordered according to a specified starting day. * - * @param {number} [startingDay=0] Starting day. 0=Sunday, 1=Monday, ... - * @return {any[]} Week days. + * @param startingDay Starting day. 0=Sunday, 1=Monday, ... + * @return Week days. */ getWeekDays(startingDay?: number): any[] { startingDay = startingDay || 0; @@ -1361,9 +1358,9 @@ export class AddonCalendarProvider { /** * Invalidates access information. * - * @param {number} [courseId] Course ID. If not defined, site calendar. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the data is invalidated. + * @param courseId Course ID. If not defined, site calendar. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the data is invalidated. */ invalidateAccessInformation(courseId?: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -1374,9 +1371,9 @@ export class AddonCalendarProvider { /** * Invalidates allowed event types. * - * @param {number} [courseId] Course ID. If not defined, site calendar. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the data is invalidated. + * @param courseId Course ID. If not defined, site calendar. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the data is invalidated. */ invalidateAllowedEventTypes(courseId?: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -1387,8 +1384,8 @@ export class AddonCalendarProvider { /** * Invalidates day events for all days. * - * @param {string} [siteId] Site Id. If not defined, use current site. - * @return {Promise} Promise resolved when the data is invalidated. + * @param siteId Site Id. If not defined, use current site. + * @return Promise resolved when the data is invalidated. */ invalidateAllDayEvents(siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -1399,10 +1396,10 @@ export class AddonCalendarProvider { /** * Invalidates day events for a certain day. * - * @param {number} year Year. - * @param {number} month Month. - * @param {number} day Day. - * @return {Promise} Promise resolved when the data is invalidated. + * @param year Year. + * @param month Month. + * @param day Day. + * @return Promise resolved when the data is invalidated. */ invalidateDayEvents(year: number, month: number, day: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -1413,8 +1410,8 @@ export class AddonCalendarProvider { /** * Invalidates events list and all the single events and related info. * - * @param {string} [siteId] Site Id. If not defined, use current site. - * @return {Promise} Promise resolved when the list is invalidated. + * @param siteId Site Id. If not defined, use current site. + * @return Promise resolved when the list is invalidated. */ invalidateEventsList(siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -1433,9 +1430,9 @@ export class AddonCalendarProvider { /** * Invalidates a single event. * - * @param {number} eventId List of courses or course ids. - * @param {string} [siteId] Site Id. If not defined, use current site. - * @return {Promise} Promise resolved when the list is invalidated. + * @param eventId List of courses or course ids. + * @param siteId Site Id. If not defined, use current site. + * @return Promise resolved when the list is invalidated. */ invalidateEvent(eventId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -1446,8 +1443,8 @@ export class AddonCalendarProvider { /** * Invalidates monthly events for all months. * - * @param {string} [siteId] Site Id. If not defined, use current site. - * @return {Promise} Promise resolved when the data is invalidated. + * @param siteId Site Id. If not defined, use current site. + * @return Promise resolved when the data is invalidated. */ invalidateAllMonthlyEvents(siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -1458,9 +1455,9 @@ export class AddonCalendarProvider { /** * Invalidates monthly events for a certain months. * - * @param {number} year Year. - * @param {number} month Month. - * @return {Promise} Promise resolved when the data is invalidated. + * @param year Year. + * @param month Month. + * @return Promise resolved when the data is invalidated. */ invalidateMonthlyEvents(year: number, month: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -1471,8 +1468,8 @@ export class AddonCalendarProvider { /** * Invalidates upcoming events for all courses and categories. * - * @param {string} [siteId] Site Id. If not defined, use current site. - * @return {Promise} Promise resolved when the data is invalidated. + * @param siteId Site Id. If not defined, use current site. + * @return Promise resolved when the data is invalidated. */ invalidateAllUpcomingEvents(siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -1483,10 +1480,10 @@ export class AddonCalendarProvider { /** * Invalidates upcoming events for a certain course or category. * - * @param {number} [courseId] Course ID. - * @param {number} [categoryId] Category ID. - * @param {string} [siteId] Site Id. If not defined, use current site. - * @return {Promise} Promise resolved when the data is invalidated. + * @param courseId Course ID. + * @param categoryId Category ID. + * @param siteId Site Id. If not defined, use current site. + * @return Promise resolved when the data is invalidated. */ invalidateUpcomingEvents(courseId?: number, categoryId?: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -1497,8 +1494,8 @@ export class AddonCalendarProvider { /** * Invalidates look ahead setting. * - * @param {string} [siteId] Site Id. If not defined, use current site. - * @return {Promise} Promise resolved when the data is invalidated. + * @param siteId Site Id. If not defined, use current site. + * @return Promise resolved when the data is invalidated. */ invalidateLookAhead(siteId?: string): Promise { return this.userProvider.invalidateUserPreference('calendar_lookahead', siteId); @@ -1507,8 +1504,8 @@ export class AddonCalendarProvider { /** * Invalidates time format setting. * - * @param {string} [siteId] Site Id. If not defined, use current site. - * @return {Promise} Promise resolved when the data is invalidated. + * @param siteId Site Id. If not defined, use current site. + * @return Promise resolved when the data is invalidated. */ invalidateTimeFormat(siteId?: string): Promise { return this.userProvider.invalidateUserPreference('calendar_timeformat', siteId); @@ -1517,8 +1514,8 @@ export class AddonCalendarProvider { /** * Check if Calendar is disabled in a certain site. * - * @param {CoreSite} [site] Site. If not defined, use current site. - * @return {boolean} Whether it's disabled. + * @param site Site. If not defined, use current site. + * @return Whether it's disabled. */ isCalendarDisabledInSite(site?: CoreSite): boolean { site = site || this.sitesProvider.getCurrentSite(); @@ -1529,8 +1526,8 @@ export class AddonCalendarProvider { /** * Check if Calendar is disabled in a certain site. * - * @param {string} [siteId] Site Id. If not defined, use current site. - * @return {Promise} Promise resolved with true if disabled, rejected or resolved with false otherwise. + * @param siteId Site Id. If not defined, use current site. + * @return Promise resolved with true if disabled, rejected or resolved with false otherwise. */ isDisabled(siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -1541,8 +1538,8 @@ export class AddonCalendarProvider { /** * Check if the get event by ID WS is available. * - * @param {string} [siteId] Site Id. If not defined, use current site. - * @return {Promise} Promise resolved with true if available. + * @param siteId Site Id. If not defined, use current site. + * @return Promise resolved with true if available. * @since 3.4 */ isGetEventByIdAvailable(siteId?: string): Promise { @@ -1556,8 +1553,8 @@ export class AddonCalendarProvider { /** * Check if the get event by ID WS is available in a certain site. * - * @param {CoreSite} [site] Site. If not defined, use current site. - * @return {boolean} Whether it's available. + * @param site Site. If not defined, use current site. + * @return Whether it's available. * @since 3.4 */ isGetEventByIdAvailableInSite(site?: CoreSite): boolean { @@ -1571,7 +1568,7 @@ export class AddonCalendarProvider { * If an event notification time is 0, cancel its scheduled notification (if any). * If local notification plugin is not enabled, resolve the promise. * - * @return {Promise} Promise resolved when all the notifications have been scheduled. + * @return Promise resolved when all the notifications have been scheduled. */ scheduleAllSitesEventsNotifications(): Promise { const notificationsEnabled = this.localNotificationsProvider.isAvailable(); @@ -1603,12 +1600,15 @@ export class AddonCalendarProvider { * Schedules an event notification. If time is 0, cancel scheduled notification if any. * If local notification plugin is not enabled, resolve the promise. * - * @param {any} event Event to schedule. - * @param {number} time Notification setting time (in minutes). E.g. 10 means "notificate 10 minutes before start". - * @param {string} [siteId] Site ID the event belongs to. If not defined, use current site. - * @return {Promise} Promise resolved when the notification is scheduled. + * @param event Event to schedule. + * @param reminderId The reminder ID. + * @param time Notification setting time (in minutes). E.g. 10 means "notificate 10 minutes before start". + * @param siteId Site ID the event belongs to. If not defined, use current site. + * @return Promise resolved when the notification is scheduled. */ - protected scheduleEventNotification(event: any, reminderId: number, time: number, siteId?: string): Promise { + protected scheduleEventNotification(event: AddonCalendarAnyEvent, reminderId: number, time: number, siteId?: string) + : Promise { + if (this.localNotificationsProvider.isAvailable()) { siteId = siteId || this.sitesProvider.getCurrentSiteId(); @@ -1668,11 +1668,11 @@ export class AddonCalendarProvider { * If an event notification time is 0, cancel its scheduled notification (if any). * If local notification plugin is not enabled, resolve the promise. * - * @param {any[]} events Events to schedule. - * @param {string} [siteId] ID of the site the events belong to. If not defined, use current site. - * @return {Promise} Promise resolved when all the notifications have been scheduled. + * @param events Events to schedule. + * @param siteId ID of the site the events belong to. If not defined, use current site. + * @return Promise resolved when all the notifications have been scheduled. */ - scheduleEventsNotifications(events: any[], siteId?: string): Promise { + scheduleEventsNotifications(events: AddonCalendarAnyEvent[], siteId?: string): Promise { if (this.localNotificationsProvider.isAvailable()) { siteId = siteId || this.sitesProvider.getCurrentSiteId(); @@ -1699,9 +1699,9 @@ export class AddonCalendarProvider { /** * Set the default notification time. * - * @param {number} time New default time. - * @param {string} [siteId] ID of the site. If not defined, use current site. - * @return {Promise} Promise resolved when stored. + * @param time New default time. + * @param siteId ID of the site. If not defined, use current site. + * @return Promise resolved when stored. */ setDefaultNotificationTime(time: number, siteId?: string): Promise { siteId = siteId || this.sitesProvider.getCurrentSiteId(); @@ -1714,9 +1714,9 @@ export class AddonCalendarProvider { /** * Store an event in local DB as it is. * - * @param {any} event Event to store. - * @param {string} [siteId] ID of the site the event belongs to. If not defined, use current site. - * @return {Promise} Promise resolved when stored. + * @param event Event to store. + * @param siteId ID of the site the event belongs to. If not defined, use current site. + * @return Promise resolved when stored. */ storeEventInLocalDb(event: any, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -1780,9 +1780,9 @@ export class AddonCalendarProvider { /** * Store events in local DB. * - * @param {any[]} events Events to store. - * @param {string} [siteId] ID of the site the event belongs to. If not defined, use current site. - * @return {Promise} Promise resolved when the events are stored. + * @param events Events to store. + * @param siteId ID of the site the event belongs to. If not defined, use current site. + * @return Promise resolved when the events are stored. */ protected storeEventsInLocalDB(events: any[], siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -1798,16 +1798,15 @@ export class AddonCalendarProvider { /** * Submit a calendar event. * - * @param {number} eventId ID of the event. If undefined/null, create a new event. - * @param {any} formData Form data. - * @param {number} [timeCreated] The time the event was created. Only if modifying a new offline event. - * @param {boolean} [forceOffline] True to always save it in offline. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise<{sent: boolean, event: any}>} Promise resolved with the event and a boolean indicating if data was - * sent to server or stored in offline. + * @param eventId ID of the event. If undefined/null, create a new event. + * @param formData Form data. + * @param timeCreated The time the event was created. Only if modifying a new offline event. + * @param forceOffline True to always save it in offline. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the event and a boolean indicating if data was sent to server or stored in offline. */ submitEvent(eventId: number, formData: any, timeCreated?: number, forceOffline?: boolean, siteId?: string): - Promise<{sent: boolean, event: any}> { + Promise<{sent: boolean, event: AddonCalendarEvent}> { siteId = siteId || this.sitesProvider.getCurrentSiteId(); @@ -1842,12 +1841,12 @@ export class AddonCalendarProvider { /** * Submit an event, either to create it or to edit it. It will fail if offline or cannot connect. * - * @param {number} eventId ID of the event. If undefined/null, create a new event. - * @param {any} formData Form data. - * @param {string} [siteId] Site ID. If not provided, current site. - * @return {Promise} Promise resolved when done. + * @param eventId ID of the event. If undefined/null, create a new event. + * @param formData Form data. + * @param siteId Site ID. If not provided, current site. + * @return Promise resolved when done. */ - submitEventOnline(eventId: number, formData: any, siteId?: string): Promise { + submitEventOnline(eventId: number, formData: any, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { // Add data that is "hidden" in web. formData.id = eventId || 0; @@ -1865,10 +1864,12 @@ export class AddonCalendarProvider { formdata: this.utils.objectToGetParams(formData) }; - return site.write('core_calendar_submit_create_update_form', params).then((result) => { + return site.write('core_calendar_submit_create_update_form', params) + .then((result: AddonCalendarSubmitCreateUpdateFormResult): AddonCalendarEvent => { + if (result.validationerror) { // Simulate a WS error. - return Promise.reject({ + return Promise.reject({ message: this.translate.instant('core.invalidformdata'), errorcode: 'validationerror' }); @@ -1879,3 +1880,337 @@ export class AddonCalendarProvider { }); } } + +/** + * Data returned by calendar's events_exporter. + */ +export type AddonCalendarEvents = { + events: AddonCalendarEvent[]; // Events. + firstid: number; // Firstid. + lastid: number; // Lastid. +}; + +/** + * Data returned by calendar's events_grouped_by_course_exporter. + */ +export type AddonCalendarEventsGroupedByCourse = { + groupedbycourse: AddonCalendarEventsSameCourse[]; // Groupped by course. +}; + +/** + * Data returned by calendar's events_same_course_exporter. + */ +export type AddonCalendarEventsSameCourse = AddonCalendarEvents & { + courseid: number; // Courseid. +}; + +/** + * Data returned by calendar's event_exporter_base. + */ +export type AddonCalendarEventBase = { + id: number; // Id. + name: string; // Name. + description?: string; // Description. + descriptionformat: number; // Description format (1 = HTML, 0 = MOODLE, 2 = PLAIN or 4 = MARKDOWN). + location?: string; // @since 3.6. Location. + categoryid?: number; // Categoryid. + groupid?: number; // Groupid. + userid?: number; // Userid. + repeatid?: number; // Repeatid. + eventcount?: number; // Eventcount. + modulename?: string; // Modulename. + instance?: number; // Instance. + eventtype: string; // Eventtype. + timestart: number; // Timestart. + timeduration: number; // Timeduration. + timesort: number; // Timesort. + visible: number; // Visible. + timemodified: number; // Timemodified. + icon: { + key: string; // Key. + component: string; // Component. + alttext: string; // Alttext. + }; + category?: { + id: number; // Id. + name: string; // Name. + idnumber: string; // Idnumber. + description?: string; // Description. + parent: number; // Parent. + coursecount: number; // Coursecount. + visible: number; // Visible. + timemodified: number; // Timemodified. + depth: number; // Depth. + nestedname: string; // Nestedname. + url: string; // Url. + }; + course?: { + id: number; // Id. + fullname: string; // Fullname. + shortname: string; // Shortname. + idnumber: string; // Idnumber. + summary: string; // Summary. + summaryformat: number; // Summary format (1 = HTML, 0 = MOODLE, 2 = PLAIN or 4 = MARKDOWN). + startdate: number; // Startdate. + enddate: number; // Enddate. + visible: boolean; // @since 3.8. Visible. + fullnamedisplay: string; // Fullnamedisplay. + viewurl: string; // Viewurl. + courseimage: string; // @since 3.6. Courseimage. + progress?: number; // @since 3.6. Progress. + hasprogress: boolean; // @since 3.6. Hasprogress. + isfavourite: boolean; // @since 3.6. Isfavourite. + hidden: boolean; // @since 3.6. Hidden. + timeaccess?: number; // @since 3.6. Timeaccess. + showshortname: boolean; // @since 3.6. Showshortname. + coursecategory: string; // @since 3.7. Coursecategory. + }; + subscription?: { + displayeventsource: boolean; // Displayeventsource. + subscriptionname?: string; // Subscriptionname. + subscriptionurl?: string; // Subscriptionurl. + }; + canedit: boolean; // Canedit. + candelete: boolean; // Candelete. + deleteurl: string; // Deleteurl. + editurl: string; // Editurl. + viewurl: string; // Viewurl. + formattedtime: string; // Formattedtime. + isactionevent: boolean; // Isactionevent. + iscourseevent: boolean; // Iscourseevent. + iscategoryevent: boolean; // Iscategoryevent. + groupname?: string; // Groupname. + normalisedeventtype: string; // @since 3.7. Normalisedeventtype. + normalisedeventtypetext: string; // @since 3.7. Normalisedeventtypetext. +}; + +/** + * Data returned by calendar's event_exporter. Don't confuse it with AddonCalendarCalendarEvent. + */ +export type AddonCalendarEvent = AddonCalendarEventBase & { + url: string; // Url. + action?: { + name: string; // Name. + url: string; // Url. + itemcount: number; // Itemcount. + actionable: boolean; // Actionable. + showitemcount: boolean; // Showitemcount. + }; +}; + +/** + * Data returned by calendar's calendar_event_exporter. Don't confuse it with AddonCalendarEvent. + */ +export type AddonCalendarCalendarEvent = AddonCalendarEventBase & { + url: string; // Url. + islastday: boolean; // Islastday. + popupname: string; // Popupname. + mindaytimestamp?: number; // Mindaytimestamp. + mindayerror?: string; // Mindayerror. + maxdaytimestamp?: number; // Maxdaytimestamp. + maxdayerror?: string; // Maxdayerror. + draggable: boolean; // Draggable. +} & AddonCalendarCalendarEventCalculatedData; + +/** + * Any of the possible types of events. + */ +export type AddonCalendarAnyEvent = AddonCalendarGetEventsEvent | AddonCalendarEvent | AddonCalendarCalendarEvent; + +/** + * Data returned by calendar's calendar_day_exporter. Don't confuse it with AddonCalendarDay. + */ +export type AddonCalendarCalendarDay = { + events: AddonCalendarCalendarEvent[]; // Events. + defaulteventcontext: number; // Defaulteventcontext. + filter_selector: string; // Filter_selector. + courseid: number; // Courseid. + categoryid?: number; // Categoryid. + neweventtimestamp: number; // Neweventtimestamp. + date: CoreWSDate; + periodname: string; // Periodname. + previousperiod: CoreWSDate; + previousperiodlink: string; // Previousperiodlink. + previousperiodname: string; // Previousperiodname. + nextperiod: CoreWSDate; + nextperiodname: string; // Nextperiodname. + nextperiodlink: string; // Nextperiodlink. + larrow: string; // Larrow. + rarrow: string; // Rarrow. +}; + +/** + * Data returned by calendar's month_exporter. + */ +export type AddonCalendarMonth = { + url: string; // Url. + courseid: number; // Courseid. + categoryid?: number; // Categoryid. + filter_selector?: string; // Filter_selector. + weeks: AddonCalendarWeek[]; // Weeks. + daynames: AddonCalendarDayName[]; // Daynames. + view: string; // View. + date: CoreWSDate; + periodname: string; // Periodname. + includenavigation: boolean; // Includenavigation. + initialeventsloaded: boolean; // @since 3.5. Initialeventsloaded. + previousperiod: CoreWSDate; + previousperiodlink: string; // Previousperiodlink. + previousperiodname: string; // Previousperiodname. + nextperiod: CoreWSDate; + nextperiodname: string; // Nextperiodname. + nextperiodlink: string; // Nextperiodlink. + larrow: string; // Larrow. + rarrow: string; // Rarrow. + defaulteventcontext: number; // Defaulteventcontext. +}; + +/** + * Data returned by calendar's week_exporter. + */ +export type AddonCalendarWeek = { + prepadding: number[]; // Prepadding. + postpadding: number[]; // Postpadding. + days: AddonCalendarWeekDay[]; // Days. +}; + +/** + * Data returned by calendar's week_day_exporter. + */ +export type AddonCalendarWeekDay = AddonCalendarDay & { + istoday: boolean; // Istoday. + isweekend: boolean; // Isweekend. + popovertitle: string; // Popovertitle. + ispast?: boolean; // Calculated in the app. Whether the day is in the past. + filteredEvents?: AddonCalendarCalendarEvent[]; // Calculated in the app. Filtered events. +}; + +/** + * Data returned by calendar's day_exporter. Don't confuse it with AddonCalendarCalendarDay. + */ +export type AddonCalendarDay = { + seconds: number; // Seconds. + minutes: number; // Minutes. + hours: number; // Hours. + mday: number; // Mday. + wday: number; // Wday. + year: number; // Year. + yday: number; // Yday. + timestamp: number; // Timestamp. + neweventtimestamp: number; // Neweventtimestamp. + viewdaylink?: string; // Viewdaylink. + events: AddonCalendarCalendarEvent[]; // Events. + hasevents: boolean; // Hasevents. + calendareventtypes: string[]; // Calendareventtypes. + previousperiod: number; // Previousperiod. + nextperiod: number; // Nextperiod. + navigation: string; // Navigation. + haslastdayofevent: boolean; // Haslastdayofevent. +}; + +/** + * Data returned by calendar's day_name_exporter. + */ +export type AddonCalendarDayName = { + dayno: number; // Dayno. + shortname: string; // Shortname. + fullname: string; // Fullname. +}; + +/** + * Data returned by calendar's calendar_upcoming_exporter. + */ +export type AddonCalendarUpcoming = { + events: AddonCalendarCalendarEvent[]; // Events. + defaulteventcontext: number; // Defaulteventcontext. + filter_selector: string; // Filter_selector. + courseid: number; // Courseid. + categoryid?: number; // Categoryid. + isloggedin: boolean; // Isloggedin. + date: CoreWSDate; // @since 3.8. Date. +}; + +/** + * Result of WS core_calendar_get_calendar_access_information. + */ +export type AddonCalendarGetAccessInfoResult = { + canmanageentries: boolean; // Whether the user can manage entries. + canmanageownentries: boolean; // Whether the user can manage its own entries. + canmanagegroupentries: boolean; // Whether the user can manage group entries. + warnings?: CoreWSExternalWarning[]; +}; + +/** + * Result of WS core_calendar_get_allowed_event_types. + */ +export type AddonCalendarGetAllowedEventTypesResult = { + allowedeventtypes: string[]; + warnings?: CoreWSExternalWarning[]; +}; + +/** + * Result of WS core_calendar_get_calendar_events. + */ +export type AddonCalendarGetEventsResult = { + events: AddonCalendarGetEventsEvent[]; + warnings?: CoreWSExternalWarning[]; +}; + +/** + * Event data returned by WS core_calendar_get_calendar_events. + */ +export type AddonCalendarGetEventsEvent = { + id: number; // Event id. + name: string; // Event name. + description?: string; // Description. + format: number; // Description format (1 = HTML, 0 = MOODLE, 2 = PLAIN or 4 = MARKDOWN). + courseid: number; // Course id. + categoryid?: number; // @since 3.4. Category id (only for category events). + groupid: number; // Group id. + userid: number; // User id. + repeatid: number; // Repeat id. + modulename?: string; // Module name. + instance: number; // Instance id. + eventtype: string; // Event type. + timestart: number; // Timestart. + timeduration: number; // Time duration. + visible: number; // Visible. + uuid?: string; // Unique id of ical events. + sequence: number; // Sequence. + timemodified: number; // Time modified. + subscriptionid?: number; // Subscription id. + showDate?: boolean; // Calculated in the app. Whether date should be shown before this event. + endsSameDay?: boolean; // Calculated in the app. Whether the event finishes the same day it starts. + deleted?: boolean; // Calculated in the app. Whether it has been deleted in offline. +}; + +/** + * Result of WS core_calendar_get_calendar_event_by_id. + */ +export type AddonCalendarGetEventByIdResult = { + event: AddonCalendarEvent; // Event. + warnings?: CoreWSExternalWarning[]; +}; + +/** + * Result of WS core_calendar_submit_create_update_form. + */ +export type AddonCalendarSubmitCreateUpdateFormResult = { + event?: AddonCalendarEvent; // Event. + validationerror: boolean; // Invalid form data. +}; + +/** + * Calculated data for AddonCalendarCalendarEvent. + */ +export type AddonCalendarCalendarEventCalculatedData = { + eventIcon?: string; // Calculated in the app. Event icon. + moduleIcon?: string; // Calculated in the app. Module icon. + formattedType?: string; // Calculated in the app. Formatted type. + duration?: number; // Calculated in the app. Duration of offline event. + format?: number; // Calculated in the app. Format of offline event. + timedurationuntil?: number; // Calculated in the app. Time duration until of offline event. + timedurationminutes?: number; // Calculated in the app. Time duration in minutes of offline event. + deleted?: boolean; // Calculated in the app. Whether it has been deleted in offline. + ispast?: boolean; // Calculated in the app. Whether the event is in the past. +}; diff --git a/src/addon/calendar/providers/helper.ts b/src/addon/calendar/providers/helper.ts index 7fd222659..a6bed59b5 100644 --- a/src/addon/calendar/providers/helper.ts +++ b/src/addon/calendar/providers/helper.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -29,8 +29,8 @@ import * as moment from 'moment'; export class AddonCalendarHelperProvider { protected logger; - protected EVENTICONS = { - course: 'fa-university', + static EVENTICONS = { + course: 'fa-graduation-cap', group: 'people', site: 'globe', user: 'person', @@ -49,8 +49,8 @@ export class AddonCalendarHelperProvider { /** * Calculate some day data based on a list of events for that day. * - * @param {any} day Day. - * @param {any[]} events Events. + * @param day Day. + * @param events Events. */ calculateDayData(day: any, events: any[]): void { day.hasevents = events.length > 0; @@ -71,9 +71,9 @@ export class AddonCalendarHelperProvider { /** * Check if current user can create/edit events. * - * @param {number} [courseId] Course ID. If not defined, site calendar. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with boolean: whether the user can create events. + * @param courseId Course ID. If not defined, site calendar. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with boolean: whether the user can create events. */ canEditEvents(courseId?: number, siteId?: string): Promise { return this.calendarProvider.canEditEvents(siteId).then((canEdit) => { @@ -94,8 +94,8 @@ export class AddonCalendarHelperProvider { * Classify events into their respective months and days. If an event duration covers more than one day, * it will be included in all the days it lasts. * - * @param {any[]} events Events to classify. - * @return {{[monthId: string]: {[day: number]: any[]}}} Object with the classified events. + * @param events Events to classify. + * @return Object with the classified events. */ classifyIntoMonths(events: any[]): {[monthId: string]: {[day: number]: any[]}} { @@ -128,17 +128,32 @@ export class AddonCalendarHelperProvider { /** * Convenience function to format some event data to be rendered. * - * @param {any} e Event to format. + * @param e Event to format. */ formatEventData(e: any): void { - e.icon = this.EVENTICONS[e.eventtype] || false; - if (!e.icon) { - e.icon = this.courseProvider.getModuleIconSrc(e.modulename); - e.moduleIcon = e.icon; + e.eventIcon = AddonCalendarHelperProvider.EVENTICONS[e.eventtype] || ''; + if (!e.eventIcon) { + e.eventIcon = this.courseProvider.getModuleIconSrc(e.modulename); + e.moduleIcon = e.eventIcon; } e.formattedType = this.calendarProvider.getEventType(e); + // Calculate context. + const categoryId = e.category ? e.category.id : e.categoryid, + courseId = e.course ? e.course.id : e.courseid; + + if (categoryId > 0) { + e.contextLevel = 'coursecat'; + e.contextInstanceId = categoryId; + } else if (courseId > 0) { + e.contextLevel = 'course'; + e.contextInstanceId = courseId; + } else { + e.contextLevel = 'user'; + e.contextInstanceId = e.userid; + } + if (typeof e.duration != 'undefined') { // It's an offline event, add some calculated data. e.format = 1; @@ -157,10 +172,10 @@ export class AddonCalendarHelperProvider { /** * Get options (name & value) for each allowed event type. * - * @param {any} eventTypes Result of getAllowedEventTypes. - * @return {{name: string, value: string}[]} Options. + * @param eventTypes Result of getAllowedEventTypes. + * @return Options. */ - getEventTypeOptions(eventTypes: any): {name: string, value: string}[] { + getEventTypeOptions(eventTypes: {[name: string]: boolean}): {name: string, value: string}[] { const options = []; if (eventTypes.user) { @@ -185,9 +200,9 @@ export class AddonCalendarHelperProvider { /** * Get the month "id" (year + month). * - * @param {number} year Year. - * @param {number} month Month. - * @return {string} The "id". + * @param year Year. + * @param month Month. + * @return The "id". */ getMonthId(year: number, month: number): string { return year + '#' + month; @@ -198,10 +213,10 @@ export class AddonCalendarHelperProvider { * * The result has the same structure than getMonthlyEvents, but it only contains fields that are actually used by the app. * - * @param {number} year Year to get. - * @param {number} month Month to get. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with the response. + * @param year Year to get. + * @param month Month to get. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the response. */ getOfflineMonthWeeks(year: number, month: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -256,9 +271,9 @@ export class AddonCalendarHelperProvider { /** * Check if the data of an event has changed. * - * @param {any} data Current data. - * @param {any} [original] Original data. - * @return {boolean} True if data has changed, false otherwise. + * @param data Current data. + * @param original Original data. + * @return True if data has changed, false otherwise. */ hasEventDataChanged(data: any, original?: any): boolean { if (!original) { @@ -294,14 +309,45 @@ export class AddonCalendarHelperProvider { return false; } + /** + * Filter events to be shown on the events list. + * + * @param events Events without filtering. + * @param filter Filter from popover. + * @param categories Categories indexed by ID. + * @return Filtered events. + */ + getFilteredEvents(events: any[], filter: AddonCalendarFilter, categories: any): any[] { + // Do not filter. + if (!filter.filtered) { + return events; + } + + const courseId = filter.courseId ? Number(filter.courseId) : undefined; + + if (!courseId || courseId < 0) { + // Filter only by type. + return events.filter((event) => { + return filter[event.formattedType]; + }); + } + + const categoryId = filter.categoryId ? Number(filter.categoryId) : undefined; + + return events.filter((event) => { + return filter[event.formattedType] && + this.shouldDisplayEvent(event, courseId, categoryId, categories); + }); + } + /** * Check if an event should be displayed based on the filter. * - * @param {any} event Event object. - * @param {number} courseId Course ID to filter. - * @param {number} categoryId Category ID the course belongs to. - * @param {any} categories Categories indexed by ID. - * @return {boolean} Whether it should be displayed. + * @param event Event object. + * @param courseId Course ID to filter. + * @param categoryId Category ID the course belongs to. + * @param categories Categories indexed by ID. + * @return Whether it should be displayed. */ shouldDisplayEvent(event: any, courseId: number, categoryId: number, categories: any): boolean { if (event.eventtype == 'user' || event.eventtype == 'site') { @@ -337,17 +383,19 @@ export class AddonCalendarHelperProvider { return false; } + const eventCourse = (event.course && event.course.id) || event.courseid; + // Show the event if it is from site home or if it matches the selected course. - return event.course && (event.course.id == this.sitesProvider.getCurrentSiteHomeId() || event.course.id == courseId); + return eventCourse && (eventCourse == this.sitesProvider.getCurrentSiteHomeId() || eventCourse == courseId); } /** * Refresh the month & day for several created/edited/deleted events, and invalidate the months & days * for their repeated events if needed. * - * @param {{event: any, repeated: number}[]} events Events that have been touched and number of times each event is repeated. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Resolved when done. + * @param events Events that have been touched and number of times each event is repeated. + * @param siteId Site ID. If not defined, current site. + * @return Resolved when done. */ refreshAfterChangeEvents(events: {event: any, repeated: number}[], siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -458,12 +506,26 @@ export class AddonCalendarHelperProvider { * Refresh the month & day for a created/edited/deleted event, and invalidate the months & days * for their repeated events if needed. * - * @param {any} event Event that has been touched. - * @param {number} repeated Number of times the event is repeated. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Resolved when done. + * @param event Event that has been touched. + * @param repeated Number of times the event is repeated. + * @param siteId Site ID. If not defined, current site. + * @return Resolved when done. */ refreshAfterChangeEvent(event: any, repeated: number, siteId?: string): Promise { return this.refreshAfterChangeEvents([{event: event, repeated: repeated}], siteId); } } + +/** + * Calculated data for Calendar filtering. + */ +export type AddonCalendarFilter = { + filtered: boolean; // If filter enabled (some filters applied). + courseId: number; // Course Id to filter. + categoryId: string; // Category Id to filter. + course: boolean, // Filter to show course events. + group: boolean, // Filter to show group events. + site: boolean, // Filter to show show site events. + user: boolean, // Filter to show user events. + category: boolean // Filter to show category events. +}; diff --git a/src/addon/calendar/providers/mainmenu-handler.ts b/src/addon/calendar/providers/mainmenu-handler.ts index 0568eeb01..47374f033 100644 --- a/src/addon/calendar/providers/mainmenu-handler.ts +++ b/src/addon/calendar/providers/mainmenu-handler.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -29,7 +29,7 @@ export class AddonCalendarMainMenuHandler implements CoreMainMenuHandler { /** * Check if the handler is enabled on a site level. * - * @return {boolean} Whether or not the handler is enabled on a site level. + * @return Whether or not the handler is enabled on a site level. */ isEnabled(): boolean | Promise { return !this.calendarProvider.isCalendarDisabledInSite(); @@ -38,7 +38,7 @@ export class AddonCalendarMainMenuHandler implements CoreMainMenuHandler { /** * Returns the data needed to render the handler. * - * @return {CoreMainMenuHandlerData} Data needed to render the handler. + * @return Data needed to render the handler. */ getDisplayData(): CoreMainMenuHandlerData { return { diff --git a/src/addon/calendar/providers/sync-cron-handler.ts b/src/addon/calendar/providers/sync-cron-handler.ts index 6aabcca74..90e8b4c16 100644 --- a/src/addon/calendar/providers/sync-cron-handler.ts +++ b/src/addon/calendar/providers/sync-cron-handler.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -29,9 +29,9 @@ export class AddonCalendarSyncCronHandler implements CoreCronHandler { * Execute the process. * Receives the ID of the site affected, undefined for all sites. * - * @param {string} [siteId] ID of the site affected, undefined for all sites. - * @param {boolean} [force] Wether the execution is forced (manual sync). - * @return {Promise} Promise resolved when done, rejected if failure. + * @param siteId ID of the site affected, undefined for all sites. + * @param force Wether the execution is forced (manual sync). + * @return Promise resolved when done, rejected if failure. */ execute(siteId?: string, force?: boolean): Promise { return this.calendarSync.syncAllEvents(siteId, force); @@ -40,7 +40,7 @@ export class AddonCalendarSyncCronHandler implements CoreCronHandler { /** * Get the time between consecutive executions. * - * @return {number} Time between consecutive executions (in ms). + * @return Time between consecutive executions (in ms). */ getInterval(): number { return this.calendarSync.syncInterval; diff --git a/src/addon/calendar/providers/view-link-handler.ts b/src/addon/calendar/providers/view-link-handler.ts index e0ba641c0..15b1ce78f 100644 --- a/src/addon/calendar/providers/view-link-handler.ts +++ b/src/addon/calendar/providers/view-link-handler.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -35,11 +35,11 @@ export class AddonCalendarViewLinkHandler extends CoreContentLinksHandlerBase { /** * Get the list of actions for a link (url). * - * @param {string[]} siteIds List of sites the URL belongs to. - * @param {string} url The URL to treat. - * @param {any} params The params of the URL. E.g. 'mysite.com?id=1' -> {id: 1} - * @param {number} [courseId] Course ID related to the URL. Optional but recommended. - * @return {CoreContentLinksAction[]|Promise} List of (or promise resolved with list of) actions. + * @param siteIds List of sites the URL belongs to. + * @param url The URL to treat. + * @param params The params of the URL. E.g. 'mysite.com?id=1' -> {id: 1} + * @param courseId Course ID related to the URL. Optional but recommended. + * @return List of (or promise resolved with list of) actions. */ getActions(siteIds: string[], url: string, params: any, courseId?: number): CoreContentLinksAction[] | Promise { @@ -90,11 +90,11 @@ export class AddonCalendarViewLinkHandler extends CoreContentLinksHandlerBase { * Check if the handler is enabled for a certain site (site + user) and a URL. * If not defined, defaults to true. * - * @param {string} siteId The site ID. - * @param {string} url The URL to treat. - * @param {any} params The params of the URL. E.g. 'mysite.com?id=1' -> {id: 1} - * @param {number} [courseId] Course ID related to the URL. Optional but recommended. - * @return {boolean|Promise} Whether the handler is enabled for the URL and site. + * @param siteId The site ID. + * @param url The URL to treat. + * @param params The params of the URL. E.g. 'mysite.com?id=1' -> {id: 1} + * @param courseId Course ID related to the URL. Optional but recommended. + * @return Whether the handler is enabled for the URL and site. */ isEnabled(siteId: string, url: string, params: any, courseId?: number): boolean | Promise { if (params.view && this.SUPPORTED_VIEWS.indexOf(params.view) == -1) { diff --git a/src/addon/competency/competency.module.ts b/src/addon/competency/competency.module.ts index b7aecb757..1d05db647 100644 --- a/src/addon/competency/competency.module.ts +++ b/src/addon/competency/competency.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/addon/competency/components/components.module.ts b/src/addon/competency/components/components.module.ts index 670d33720..8a1559965 100644 --- a/src/addon/competency/components/components.module.ts +++ b/src/addon/competency/components/components.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/addon/competency/components/course/addon-competency-course.html b/src/addon/competency/components/course/addon-competency-course.html index 041646947..74722c707 100644 --- a/src/addon/competency/components/course/addon-competency-course.html +++ b/src/addon/competency/components/course/addon-competency-course.html @@ -30,7 +30,7 @@ -

+

{{ user.fullname }}

@@ -43,7 +43,7 @@

- +

{{ 'addon.competency.path' | translate }} @@ -59,9 +59,7 @@
{{ 'addon.competency.uponcoursecompletion' | translate }} - - - + {{ ruleoutcome.text }}
@@ -80,7 +78,7 @@ {{ 'addon.competency.nouserplanswithcompetency' | translate }}

- +
diff --git a/src/addon/competency/components/course/course.ts b/src/addon/competency/components/course/course.ts index 65cdb0643..ba92de5a5 100644 --- a/src/addon/competency/components/course/course.ts +++ b/src/addon/competency/components/course/course.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -16,7 +16,7 @@ import { Component, ViewChild, Input } from '@angular/core'; import { Content, NavController } from 'ionic-angular'; import { CoreAppProvider } from '@providers/app'; import { CoreDomUtilsProvider } from '@providers/utils/dom'; -import { AddonCompetencyProvider } from '../../providers/competency'; +import { AddonCompetencyProvider, AddonCompetencyDataForCourseCompetenciesPageResult } from '../../providers/competency'; import { AddonCompetencyHelperProvider } from '../../providers/helper'; /** @@ -33,7 +33,7 @@ export class AddonCompetencyCourseComponent { @Input() userId: number; competenciesLoaded = false; - competencies: any; + competencies: AddonCompetencyDataForCourseCompetenciesPageResult; user: any; constructor(private navCtrl: NavController, private appProvider: CoreAppProvider, private domUtils: CoreDomUtilsProvider, @@ -52,7 +52,7 @@ export class AddonCompetencyCourseComponent { /** * Fetches the competencies and updates the view. * - * @return {Promise} Promise resolved when done. + * @return Promise resolved when done. */ protected fetchCourseCompetencies(): Promise { return this.competencyProvider.getCourseCompetencies(this.courseId, this.userId).then((competencies) => { @@ -70,7 +70,7 @@ export class AddonCompetencyCourseComponent { /** * Opens a competency. * - * @param {number} competencyId + * @param competencyId */ openCompetency(competencyId: number): void { if (this.appProvider.isWide()) { @@ -83,16 +83,20 @@ export class AddonCompetencyCourseComponent { /** * Opens the summary of a competency. * - * @param {number} competencyId + * @param competencyId */ openCompetencySummary(competencyId: number): void { - this.navCtrl.push('AddonCompetencyCompetencySummaryPage', {competencyId}); + this.navCtrl.push('AddonCompetencyCompetencySummaryPage', { + competencyId, + contextLevel: 'course', + contextInstanceId: this.courseId + }); } /** * Refreshes the competencies. * - * @param {any} refresher Refresher. + * @param refresher Refresher. */ refreshCourseCompetencies(refresher: any): void { this.competencyProvider.invalidateCourseCompetencies(this.courseId, this.userId).finally(() => { diff --git a/src/addon/competency/pages/competencies/competencies.module.ts b/src/addon/competency/pages/competencies/competencies.module.ts index 2f4780f53..8e5f1fca5 100644 --- a/src/addon/competency/pages/competencies/competencies.module.ts +++ b/src/addon/competency/pages/competencies/competencies.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/addon/competency/pages/competencies/competencies.ts b/src/addon/competency/pages/competencies/competencies.ts index 1278e318f..0a103f867 100644 --- a/src/addon/competency/pages/competencies/competencies.ts +++ b/src/addon/competency/pages/competencies/competencies.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -17,7 +17,10 @@ import { IonicPage, NavParams } from 'ionic-angular'; import { TranslateService } from '@ngx-translate/core'; import { CoreDomUtilsProvider } from '@providers/utils/dom'; import { CoreSplitViewComponent } from '@components/split-view/split-view'; -import { AddonCompetencyProvider } from '../../providers/competency'; +import { + AddonCompetencyProvider, AddonCompetencyDataForCourseCompetenciesPageResult, AddonCompetencyDataForPlanPageResult, + AddonCompetencyDataForPlanPageCompetency, AddonCompetencyDataForCourseCompetenciesPageCompetency +} from '../../providers/competency'; /** * Page that displays the list of competencies of a learning plan. @@ -36,7 +39,7 @@ export class AddonCompetencyCompetenciesPage { protected userId: number; competenciesLoaded = false; - competencies = []; + competencies: AddonCompetencyDataForPlanPageCompetency[] | AddonCompetencyDataForCourseCompetenciesPageCompetency[] = []; title: string; constructor(navParams: NavParams, private translate: TranslateService, private domUtils: CoreDomUtilsProvider, @@ -59,7 +62,7 @@ export class AddonCompetencyCompetenciesPage { this.fetchCompetencies().then(() => { if (!this.competencyId && this.splitviewCtrl.isOn() && this.competencies.length > 0) { // Take first and load it. - this.openCompetency(this.competencies[0].id); + this.openCompetency(this.competencies[0].competency.id); } }).finally(() => { this.competenciesLoaded = true; @@ -69,10 +72,10 @@ export class AddonCompetencyCompetenciesPage { /** * Fetches the competencies and updates the view. * - * @return {Promise} Promise resolved when done. + * @return Promise resolved when done. */ protected fetchCompetencies(): Promise { - let promise; + let promise: Promise; if (this.planId) { promise = this.competencyProvider.getLearningPlan(this.planId); @@ -83,13 +86,16 @@ export class AddonCompetencyCompetenciesPage { } return promise.then((response) => { - if (response.competencycount <= 0) { - return Promise.reject(this.translate.instant('addon.competency.errornocompetenciesfound')); - } if (this.planId) { - this.title = response.plan.name; - this.userId = response.plan.userid; + const resp = response; + + if (resp.competencycount <= 0) { + return Promise.reject(this.translate.instant('addon.competency.errornocompetenciesfound')); + } + + this.title = resp.plan.name; + this.userId = resp.plan.userid; } else { this.title = this.translate.instant('addon.competency.coursecompetencies'); } @@ -102,7 +108,7 @@ export class AddonCompetencyCompetenciesPage { /** * Opens a competency. * - * @param {number} competencyId + * @param competencyId */ openCompetency(competencyId: number): void { this.competencyId = competencyId; @@ -118,7 +124,7 @@ export class AddonCompetencyCompetenciesPage { /** * Refreshes the competencies. * - * @param {any} refresher Refresher. + * @param refresher Refresher. */ refreshCompetencies(refresher: any): void { let promise; diff --git a/src/addon/competency/pages/competency/competency.html b/src/addon/competency/pages/competency/competency.html index 4f66d9dc1..664ca9393 100644 --- a/src/addon/competency/pages/competency/competency.html +++ b/src/addon/competency/pages/competency/competency.html @@ -11,13 +11,13 @@ -

+

{{ user.fullname }}

- + {{ 'addon.competency.path' | translate }} @@ -48,25 +48,25 @@

- +
- + {{ 'addon.competency.reviewstatus' | translate }} - {{ competency.usercompetency.statusname }} + {{ userCompetency.statusname }} {{ 'addon.competency.proficient' | translate }} - + {{ 'core.yes' | translate }} - + {{ 'core.no' | translate }} {{ 'addon.competency.rating' | translate }} - {{ competency.usercompetency.gradename }} + {{ userCompetency.gradename }}
@@ -84,7 +84,7 @@

{{ evidence.gradename }}

{{ evidence.description }}

-
+
{{ evidence.note }}
diff --git a/src/addon/competency/pages/competency/competency.module.ts b/src/addon/competency/pages/competency/competency.module.ts index 028abfa2d..ebc8079cd 100644 --- a/src/addon/competency/pages/competency/competency.module.ts +++ b/src/addon/competency/pages/competency/competency.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/addon/competency/pages/competency/competency.ts b/src/addon/competency/pages/competency/competency.ts index c3930b945..e125c51bc 100644 --- a/src/addon/competency/pages/competency/competency.ts +++ b/src/addon/competency/pages/competency/competency.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -18,8 +18,14 @@ import { TranslateService } from '@ngx-translate/core'; import { CoreSitesProvider } from '@providers/sites'; import { CoreDomUtilsProvider } from '@providers/utils/dom'; import { CoreSplitViewComponent } from '@components/split-view/split-view'; -import { AddonCompetencyProvider } from '../../providers/competency'; +import { + AddonCompetencyProvider, AddonCompetencyUserCompetencySummary, AddonCompetencyUserCompetencySummaryInPlan, + AddonCompetencyUserCompetencySummaryInCourse, AddonCompetencyUserCompetencyPlan, + AddonCompetencyUserCompetency, AddonCompetencyUserCompetencyCourse +} from '../../providers/competency'; import { AddonCompetencyHelperProvider } from '../../providers/helper'; +import { CoreUserSummary } from '@core/user/providers/user'; +import { CoreCourseModuleSummary } from '@core/course/providers/course'; /** * Page that displays a learning plan. @@ -36,9 +42,12 @@ export class AddonCompetencyCompetencyPage { courseId: number; userId: number; planStatus: number; - coursemodules: any; - user: any; - competency: any; + coursemodules: CoreCourseModuleSummary[]; + user: CoreUserSummary; + competency: AddonCompetencyUserCompetencySummary; + userCompetency: AddonCompetencyUserCompetencyPlan | AddonCompetencyUserCompetency | AddonCompetencyUserCompetencyCourse; + contextLevel: string; + contextInstanceId: number; constructor(private navCtrl: NavController, navParams: NavParams, private translate: TranslateService, private sitesProvider: CoreSitesProvider, private domUtils: CoreDomUtilsProvider, @@ -76,10 +85,11 @@ export class AddonCompetencyCompetencyPage { /** * Fetches the competency and updates the view. * - * @return {Promise} Promise resolved when done. + * @return Promise resolved when done. */ protected fetchCompetency(): Promise { - let promise; + let promise: Promise; + if (this.planId) { this.planStatus = null; promise = this.competencyProvider.getCompetencyInPlan(this.planId, this.competencyId); @@ -90,23 +100,30 @@ export class AddonCompetencyCompetencyPage { } return promise.then((competency) => { - competency.usercompetencysummary.usercompetency = competency.usercompetencysummary.usercompetencyplan || - competency.usercompetencysummary.usercompetency; + + // Calculate the context. + if (this.courseId) { + this.contextLevel = 'course'; + this.contextInstanceId = this.courseId; + } else { + this.contextLevel = 'user'; + this.contextInstanceId = this.userId || competency.usercompetencysummary.user.id; + } + this.competency = competency.usercompetencysummary; + this.userCompetency = this.competency.usercompetencyplan || this.competency.usercompetency; if (this.planId) { - this.planStatus = competency.plan.status; + this.planStatus = ( competency).plan.status; this.competency.usercompetency.statusname = this.competencyHelperProvider.getCompetencyStatusName(this.competency.usercompetency.status); } else { - this.competency.usercompetency = this.competency.usercompetencycourse; - this.coursemodules = competency.coursemodules; + this.userCompetency = this.competency.usercompetencycourse; + this.coursemodules = ( competency).coursemodules; } if (this.competency.user.id != this.sitesProvider.getCurrentSiteUserId()) { - this.competency.user.profileimageurl = this.competency.user.profileimageurl || true; - - // Get the user profile image from the returned object. + // Get the user profile from the returned object. this.user = this.competency.user; } @@ -124,7 +141,7 @@ export class AddonCompetencyCompetencyPage { /** * Refreshes the competency. * - * @param {any} refresher Refresher. + * @param refresher Refresher. */ refreshCompetency(refresher: any): void { let promise; @@ -144,11 +161,15 @@ export class AddonCompetencyCompetencyPage { /** * Opens the summary of a competency. * - * @param {number} competencyId + * @param competencyId */ openCompetencySummary(competencyId: number): void { // Decide which navCtrl to use. If this page is inside a split view, use the split view's master nav. const navCtrl = this.svComponent ? this.svComponent.getMasterNav() : this.navCtrl; - navCtrl.push('AddonCompetencyCompetencySummaryPage', {competencyId}); + navCtrl.push('AddonCompetencyCompetencySummaryPage', { + competencyId, + contextLevel: this.contextLevel, + contextInstanceId: this.contextInstanceId + }); } } diff --git a/src/addon/competency/pages/competencysummary/competencysummary.html b/src/addon/competency/pages/competencysummary/competencysummary.html index 8df0e9bcc..d00b4ba50 100644 --- a/src/addon/competency/pages/competencysummary/competencysummary.html +++ b/src/addon/competency/pages/competencysummary/competencysummary.html @@ -10,7 +10,7 @@ - + {{ 'addon.competency.path' | translate }} diff --git a/src/addon/competency/pages/competencysummary/competencysummary.module.ts b/src/addon/competency/pages/competencysummary/competencysummary.module.ts index 855095dbf..8be154249 100644 --- a/src/addon/competency/pages/competencysummary/competencysummary.module.ts +++ b/src/addon/competency/pages/competencysummary/competencysummary.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/addon/competency/pages/competencysummary/competencysummary.ts b/src/addon/competency/pages/competencysummary/competencysummary.ts index db4c7a32d..085854ce5 100644 --- a/src/addon/competency/pages/competencysummary/competencysummary.ts +++ b/src/addon/competency/pages/competencysummary/competencysummary.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -16,7 +16,7 @@ import { Component, Optional } from '@angular/core'; import { IonicPage, NavController, NavParams } from 'ionic-angular'; import { CoreDomUtilsProvider } from '@providers/utils/dom'; import { CoreSplitViewComponent } from '@components/split-view/split-view'; -import { AddonCompetencyProvider } from '../../providers/competency'; +import { AddonCompetencyProvider, AddonCompetencySummary } from '../../providers/competency'; /** * Page that displays a learning plan. @@ -29,11 +29,15 @@ import { AddonCompetencyProvider } from '../../providers/competency'; export class AddonCompetencyCompetencySummaryPage { competencyLoaded = false; competencyId: number; - competency: any; + competency: AddonCompetencySummary; + contextLevel: string; + contextInstanceId: number; constructor(private navCtrl: NavController, navParams: NavParams, private domUtils: CoreDomUtilsProvider, @Optional() private svComponent: CoreSplitViewComponent, private competencyProvider: AddonCompetencyProvider) { this.competencyId = navParams.get('competencyId'); + this.contextLevel = navParams.get('contextLevel'); + this.contextInstanceId = navParams.get('contextInstanceId'); } /** @@ -41,8 +45,7 @@ export class AddonCompetencyCompetencySummaryPage { */ ionViewDidLoad(): void { this.fetchCompetency().then(() => { - const name = this.competency.competency && this.competency.competency.competency && - this.competency.competency.competency.shortname; + const name = this.competency.competency && this.competency.competency.shortname; this.competencyProvider.logCompetencyView(this.competencyId, name).catch(() => { // Ignore errors. @@ -55,11 +58,17 @@ export class AddonCompetencyCompetencySummaryPage { /** * Fetches the competency summary and updates the view. * - * @return {Promise} Promise resolved when done. + * @return Promise resolved when done. */ protected fetchCompetency(): Promise { - return this.competencyProvider.getCompetencySummary(this.competencyId).then((competency) => { - this.competency = competency; + return this.competencyProvider.getCompetencySummary(this.competencyId).then((result) => { + if (!this.contextLevel || typeof this.contextInstanceId == 'undefined') { + // Context not specified, use user context. + this.contextLevel = 'user'; + this.contextInstanceId = result.usercompetency.userid; + } + + this.competency = result.competency; }).catch((message) => { this.domUtils.showErrorModalDefault(message, 'Error getting competency summary data.'); }); @@ -68,7 +77,7 @@ export class AddonCompetencyCompetencySummaryPage { /** * Refreshes the competency summary. * - * @param {any} refresher Refresher. + * @param refresher Refresher. */ refreshCompetency(refresher: any): void { this.competencyProvider.invalidateCompetencySummary(this.competencyId).finally(() => { @@ -81,11 +90,15 @@ export class AddonCompetencyCompetencySummaryPage { /** * Opens the summary of a competency. * - * @param {number} competencyId + * @param competencyId */ openCompetencySummary(competencyId: number): void { // Decide which navCtrl to use. If this page is inside a split view, use the split view's master nav. const navCtrl = this.svComponent ? this.svComponent.getMasterNav() : this.navCtrl; - navCtrl.push('AddonCompetencyCompetencySummaryPage', {competencyId}); + navCtrl.push('AddonCompetencyCompetencySummaryPage', { + competencyId, + contextLevel: this.contextLevel, + contextInstanceId: this.contextInstanceId + }); } } diff --git a/src/addon/competency/pages/coursecompetencies/coursecompetencies.module.ts b/src/addon/competency/pages/coursecompetencies/coursecompetencies.module.ts index 321018a96..0dd7b7598 100644 --- a/src/addon/competency/pages/coursecompetencies/coursecompetencies.module.ts +++ b/src/addon/competency/pages/coursecompetencies/coursecompetencies.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/addon/competency/pages/coursecompetencies/coursecompetencies.ts b/src/addon/competency/pages/coursecompetencies/coursecompetencies.ts index 42bb83f71..836d01a45 100644 --- a/src/addon/competency/pages/coursecompetencies/coursecompetencies.ts +++ b/src/addon/competency/pages/coursecompetencies/coursecompetencies.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/addon/competency/pages/plan/plan.html b/src/addon/competency/pages/plan/plan.html index f8c5a91da..f45818688 100644 --- a/src/addon/competency/pages/plan/plan.html +++ b/src/addon/competency/pages/plan/plan.html @@ -11,13 +11,13 @@ -

+

{{ user.fullname }}

- + {{ 'addon.competency.status' | translate }}: @@ -46,7 +46,8 @@

{{competency.competency.shortname}} {{competency.competency.idnumber}}

- {{ competency.usercompetency.gradename }} + {{ competency.usercompetencyplan.gradename }} + {{ competency.usercompetency.gradename }}
diff --git a/src/addon/competency/pages/plan/plan.module.ts b/src/addon/competency/pages/plan/plan.module.ts index bb7f48038..92b4a0cae 100644 --- a/src/addon/competency/pages/plan/plan.module.ts +++ b/src/addon/competency/pages/plan/plan.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/addon/competency/pages/plan/plan.ts b/src/addon/competency/pages/plan/plan.ts index 33a01c8b8..ba4e53f20 100644 --- a/src/addon/competency/pages/plan/plan.ts +++ b/src/addon/competency/pages/plan/plan.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -17,7 +17,7 @@ import { IonicPage, NavController, NavParams } from 'ionic-angular'; import { CoreAppProvider } from '@providers/app'; import { CoreDomUtilsProvider } from '@providers/utils/dom'; import { CoreSplitViewComponent } from '@components/split-view/split-view'; -import { AddonCompetencyProvider } from '../../providers/competency'; +import { AddonCompetencyProvider, AddonCompetencyDataForPlanPageResult } from '../../providers/competency'; import { AddonCompetencyHelperProvider } from '../../providers/helper'; /** @@ -31,7 +31,7 @@ import { AddonCompetencyHelperProvider } from '../../providers/helper'; export class AddonCompetencyPlanPage { protected planId: number; planLoaded = false; - plan: any; + plan: AddonCompetencyDataForPlanPageResult; user: any; constructor(private navCtrl: NavController, navParams: NavParams, private appProvider: CoreAppProvider, @@ -52,7 +52,7 @@ export class AddonCompetencyPlanPage { /** * Fetches the learning plan and updates the view. * - * @return {Promise} Promise resolved when done. + * @return Promise resolved when done. */ protected fetchLearningPlan(): Promise { return this.competencyProvider.getLearningPlan(this.planId).then((plan) => { @@ -62,9 +62,6 @@ export class AddonCompetencyPlanPage { this.user = user; }); - plan.competencies.forEach((competency) => { - competency.usercompetency = competency.usercompetencyplan || competency.usercompetency; - }); this.plan = plan; }).catch((message) => { this.domUtils.showErrorModalDefault(message, 'Error getting learning plan data.'); @@ -74,7 +71,7 @@ export class AddonCompetencyPlanPage { /** * Navigates to a particular competency. * - * @param {number} competencyId + * @param competencyId */ openCompetency(competencyId: number): void { const navCtrl = this.svComponent ? this.svComponent.getMasterNav() : this.navCtrl; @@ -88,7 +85,7 @@ export class AddonCompetencyPlanPage { /** * Refreshes the learning plan. * - * @param {any} refresher Refresher. + * @param refresher Refresher. */ refreshLearningPlan(refresher: any): void { this.competencyProvider.invalidateLearningPlan(this.planId).finally(() => { diff --git a/src/addon/competency/pages/planlist/planlist.module.ts b/src/addon/competency/pages/planlist/planlist.module.ts index 7a4e506fc..81c77d96c 100644 --- a/src/addon/competency/pages/planlist/planlist.module.ts +++ b/src/addon/competency/pages/planlist/planlist.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/addon/competency/pages/planlist/planlist.ts b/src/addon/competency/pages/planlist/planlist.ts index 6c05ab924..3802a02ec 100644 --- a/src/addon/competency/pages/planlist/planlist.ts +++ b/src/addon/competency/pages/planlist/planlist.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -16,7 +16,7 @@ import { Component, ViewChild } from '@angular/core'; import { IonicPage, NavParams } from 'ionic-angular'; import { CoreDomUtilsProvider } from '@providers/utils/dom'; import { CoreSplitViewComponent } from '@components/split-view/split-view'; -import { AddonCompetencyProvider } from '../../providers/competency'; +import { AddonCompetencyProvider, AddonCompetencyPlan } from '../../providers/competency'; import { AddonCompetencyHelperProvider } from '../../providers/helper'; /** @@ -33,7 +33,7 @@ export class AddonCompetencyPlanListPage { protected userId: number; protected planId: number; plansLoaded = false; - plans = []; + plans: AddonCompetencyPlan[] = []; constructor(navParams: NavParams, private domUtils: CoreDomUtilsProvider, private competencyProvider: AddonCompetencyProvider, private competencyHelperProvider: AddonCompetencyHelperProvider) { @@ -62,11 +62,11 @@ export class AddonCompetencyPlanListPage { /** * Fetches the learning plans and updates the view. * - * @return {Promise} Promise resolved when done. + * @return Promise resolved when done. */ protected fetchLearningPlans(): Promise { return this.competencyProvider.getLearningPlans(this.userId).then((plans) => { - plans.forEach((plan) => { + plans.forEach((plan: AddonCompetencyPlanFormatted) => { plan.statusname = this.competencyHelperProvider.getPlanStatusName(plan.status); switch (plan.status) { case AddonCompetencyProvider.STATUS_ACTIVE: @@ -89,7 +89,7 @@ export class AddonCompetencyPlanListPage { /** * Refreshes the learning plans. * - * @param {any} refresher Refresher. + * @param refresher Refresher. */ refreshLearningPlans(refresher: any): void { this.competencyProvider.invalidateLearningPlans(this.userId).finally(() => { @@ -102,10 +102,17 @@ export class AddonCompetencyPlanListPage { /** * Opens a learning plan. * - * @param {number} planId Learning plan to load. + * @param planId Learning plan to load. */ openPlan(planId: number): void { this.planId = planId; this.splitviewCtrl.push('AddonCompetencyPlanPage', { planId }); } } + +/** + * Competency plan with some calculated data. + */ +type AddonCompetencyPlanFormatted = AddonCompetencyPlan & { + statuscolor?: string; // Calculated in the app. Color of the plan's status. +}; diff --git a/src/addon/competency/providers/competency-link-handler.ts b/src/addon/competency/providers/competency-link-handler.ts index a85c8d1bb..95c8362a6 100644 --- a/src/addon/competency/providers/competency-link-handler.ts +++ b/src/addon/competency/providers/competency-link-handler.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -33,11 +33,11 @@ export class AddonCompetencyCompetencyLinkHandler extends CoreContentLinksHandle /** * Get the list of actions for a link (url). * - * @param {string[]} siteIds List of sites the URL belongs to. - * @param {string} url The URL to treat. - * @param {any} params The params of the URL. E.g. 'mysite.com?id=1' -> {id: 1} - * @param {number} [courseId] Course ID related to the URL. Optional but recommended. - * @return {CoreContentLinksAction[]|Promise} List of (or promise resolved with list of) actions. + * @param siteIds List of sites the URL belongs to. + * @param url The URL to treat. + * @param params The params of the URL. E.g. 'mysite.com?id=1' -> {id: 1} + * @param courseId Course ID related to the URL. Optional but recommended. + * @return List of (or promise resolved with list of) actions. */ getActions(siteIds: string[], url: string, params: any, courseId?: number): CoreContentLinksAction[] | Promise { @@ -59,11 +59,11 @@ export class AddonCompetencyCompetencyLinkHandler extends CoreContentLinksHandle * Check if the handler is enabled for a certain site (site + user) and a URL. * If not defined, defaults to true. * - * @param {string} siteId The site ID. - * @param {string} url The URL to treat. - * @param {any} params The params of the URL. E.g. 'mysite.com?id=1' -> {id: 1} - * @param {number} [courseId] Course ID related to the URL. Optional but recommended. - * @return {boolean|Promise} Whether the handler is enabled for the URL and site. + * @param siteId The site ID. + * @param url The URL to treat. + * @param params The params of the URL. E.g. 'mysite.com?id=1' -> {id: 1} + * @param courseId Course ID related to the URL. Optional but recommended. + * @return Whether the handler is enabled for the URL and site. */ isEnabled(siteId: string, url: string, params: any, courseId?: number): boolean | Promise { // Handler is disabled if all competency features are disabled. diff --git a/src/addon/competency/providers/competency.ts b/src/addon/competency/providers/competency.ts index f2ba8f2a6..7d2704da9 100644 --- a/src/addon/competency/providers/competency.ts +++ b/src/addon/competency/providers/competency.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -17,6 +17,9 @@ import { CoreLoggerProvider } from '@providers/logger'; import { CoreSitesProvider } from '@providers/sites'; import { CorePushNotificationsProvider } from '@core/pushnotifications/providers/pushnotifications'; import { CoreSite } from '@classes/site'; +import { CoreCommentsArea } from '@core/comments/providers/comments'; +import { CoreUserSummary } from '@core/user/providers/user'; +import { CoreCourseSummary, CoreCourseModuleSummary } from '@core/course/providers/course'; /** * Service to handle caompetency learning plans. @@ -48,8 +51,8 @@ export class AddonCompetencyProvider { /** * Check if all competencies features are disabled. * - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with boolean: whether all competency features are disabled. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with boolean: whether all competency features are disabled. */ allCompetenciesDisabled(siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -62,8 +65,8 @@ export class AddonCompetencyProvider { /** * Get cache key for user learning plans data WS calls. * - * @param {number} userId User ID. - * @return {string} Cache key. + * @param userId User ID. + * @return Cache key. */ protected getLearningPlansCacheKey(userId: number): string { return this.ROOT_CACHE_KEY + 'userplans:' + userId; @@ -72,8 +75,8 @@ export class AddonCompetencyProvider { /** * Get cache key for learning plan data WS calls. * - * @param {number} planId Plan ID. - * @return {string} Cache key. + * @param planId Plan ID. + * @return Cache key. */ protected getLearningPlanCacheKey(planId: number): string { return this.ROOT_CACHE_KEY + 'learningplan:' + planId; @@ -82,9 +85,9 @@ export class AddonCompetencyProvider { /** * Get cache key for competency in plan data WS calls. * - * @param {number} planId Plan ID. - * @param {number} competencyId Competency ID. - * @return {string} Cache key. + * @param planId Plan ID. + * @param competencyId Competency ID. + * @return Cache key. */ protected getCompetencyInPlanCacheKey(planId: number, competencyId: number): string { return this.ROOT_CACHE_KEY + 'plancompetency:' + planId + ':' + competencyId; @@ -93,10 +96,10 @@ export class AddonCompetencyProvider { /** * Get cache key for competency in course data WS calls. * - * @param {number} courseId Course ID. - * @param {number} competencyId Competency ID. - * @param {number} userId User ID. - * @return {string} Cache key. + * @param courseId Course ID. + * @param competencyId Competency ID. + * @param userId User ID. + * @return Cache key. */ protected getCompetencyInCourseCacheKey(courseId: number, competencyId: number, userId: number): string { return this.ROOT_CACHE_KEY + 'coursecompetency:' + userId + ':' + courseId + ':' + competencyId; @@ -105,9 +108,9 @@ export class AddonCompetencyProvider { /** * Get cache key for competency summary data WS calls. * - * @param {number} competencyId Competency ID. - * @param {number} userId User ID. - * @return {string} Cache key. + * @param competencyId Competency ID. + * @param userId User ID. + * @return Cache key. */ protected getCompetencySummaryCacheKey(competencyId: number, userId: number): string { return this.ROOT_CACHE_KEY + 'competencysummary:' + userId + ':' + competencyId; @@ -116,8 +119,8 @@ export class AddonCompetencyProvider { /** * Get cache key for course competencies data WS calls. * - * @param {number} courseId Course ID. - * @return {string} Cache key. + * @param courseId Course ID. + * @return Cache key. */ protected getCourseCompetenciesCacheKey(courseId: number): string { return this.ROOT_CACHE_KEY + 'coursecompetencies:' + courseId; @@ -126,9 +129,9 @@ export class AddonCompetencyProvider { /** * Returns whether competencies are enabled. * - * @param {number} courseId Course ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} competencies if enabled for the given course, false otherwise. + * @param courseId Course ID. + * @param siteId Site ID. If not defined, current site. + * @return competencies if enabled for the given course, false otherwise. */ isPluginForCourseEnabled(courseId: number, siteId?: string): Promise { if (!this.sitesProvider.isLoggedIn()) { @@ -143,11 +146,11 @@ export class AddonCompetencyProvider { /** * Get plans for a certain user. * - * @param {number} [userId] ID of the user. If not defined, current user. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise to be resolved when the plans are retrieved. + * @param userId ID of the user. If not defined, current user. + * @param siteId Site ID. If not defined, current site. + * @return Promise to be resolved when the plans are retrieved. */ - getLearningPlans(userId?: number, siteId?: string): Promise { + getLearningPlans(userId?: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { userId = userId || site.getUserId(); @@ -161,7 +164,9 @@ export class AddonCompetencyProvider { updateFrequency: CoreSite.FREQUENCY_RARELY }; - return site.read('tool_lp_data_for_plans_page', params, preSets).then((response) => { + return site.read('tool_lp_data_for_plans_page', params, preSets) + .then((response: AddonCompetencyDataForPlansPageResult): any => { + if (response.plans) { return response.plans; } @@ -174,11 +179,11 @@ export class AddonCompetencyProvider { /** * Get a certain plan. * - * @param {number} planId ID of the plan. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise to be resolved when the plans are retrieved. + * @param planId ID of the plan. + * @param siteId Site ID. If not defined, current site. + * @return Promise to be resolved when the plan is retrieved. */ - getLearningPlan(planId: number, siteId?: string): Promise { + getLearningPlan(planId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { this.logger.debug('Get plan ' + planId); @@ -191,7 +196,9 @@ export class AddonCompetencyProvider { updateFrequency: CoreSite.FREQUENCY_RARELY }; - return site.read('tool_lp_data_for_plan_page', params, preSets).then((response) => { + return site.read('tool_lp_data_for_plan_page', params, preSets) + .then((response: AddonCompetencyDataForPlanPageResult): any => { + if (response.plan) { return response; } @@ -204,12 +211,14 @@ export class AddonCompetencyProvider { /** * Get a certain competency in a plan. * - * @param {number} planId ID of the plan. - * @param {number} competencyId ID of the competency. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise to be resolved when the plans are retrieved. + * @param planId ID of the plan. + * @param competencyId ID of the competency. + * @param siteId Site ID. If not defined, current site. + * @return Promise to be resolved when the competency is retrieved. */ - getCompetencyInPlan(planId: number, competencyId: number, siteId?: string): Promise { + getCompetencyInPlan(planId: number, competencyId: number, siteId?: string) + : Promise { + return this.sitesProvider.getSite(siteId).then((site) => { this.logger.debug('Get competency ' + competencyId + ' in plan ' + planId); @@ -223,7 +232,9 @@ export class AddonCompetencyProvider { updateFrequency: CoreSite.FREQUENCY_SOMETIMES }; - return site.read('tool_lp_data_for_user_competency_summary_in_plan', params, preSets).then((response) => { + return site.read('tool_lp_data_for_user_competency_summary_in_plan', params, preSets) + .then((response: AddonCompetencyUserCompetencySummaryInPlan): any => { + if (response.usercompetencysummary) { return response; } @@ -236,15 +247,15 @@ export class AddonCompetencyProvider { /** * Get a certain competency in a course. * - * @param {number} courseId ID of the course. - * @param {number} competencyId ID of the competency. - * @param {number} [userId] ID of the user. If not defined, current user. - * @param {string} [siteId] Site ID. If not defined, current site. - * @param {boolean} [ignoreCache] True if it should ignore cached data (it will always fail in offline or server down). - * @return {Promise} Promise to be resolved when the plans are retrieved. + * @param courseId ID of the course. + * @param competencyId ID of the competency. + * @param userId ID of the user. If not defined, current user. + * @param siteId Site ID. If not defined, current site. + * @param ignoreCache True if it should ignore cached data (it will always fail in offline or server down). + * @return Promise to be resolved when the competency is retrieved. */ getCompetencyInCourse(courseId: number, competencyId: number, userId?: number, siteId?: string, ignoreCache?: boolean) - : Promise { + : Promise { return this.sitesProvider.getSite(siteId).then((site) => { userId = userId || site.getUserId(); @@ -266,7 +277,9 @@ export class AddonCompetencyProvider { preSets.emergencyCache = false; } - return site.read('tool_lp_data_for_user_competency_summary_in_course', params, preSets).then((response) => { + return site.read('tool_lp_data_for_user_competency_summary_in_course', params, preSets) + .then((response: AddonCompetencyUserCompetencySummaryInCourse): any => { + if (response.usercompetencysummary) { return response; } @@ -279,13 +292,15 @@ export class AddonCompetencyProvider { /** * Get a certain competency summary. * - * @param {number} competencyId ID of the competency. - * @param {number} [userId] ID of the user. If not defined, current user. - * @param {string} [siteId] Site ID. If not defined, current site. - * @param {boolean} [ignoreCache] True if it should ignore cached data (it will always fail in offline or server down). - * @return {Promise} Promise to be resolved when the plans are retrieved. + * @param competencyId ID of the competency. + * @param userId ID of the user. If not defined, current user. + * @param siteId Site ID. If not defined, current site. + * @param ignoreCache True if it should ignore cached data (it will always fail in offline or server down). + * @return Promise to be resolved when the competency summary is retrieved. */ - getCompetencySummary(competencyId: number, userId?: number, siteId?: string, ignoreCache?: boolean): Promise { + getCompetencySummary(competencyId: number, userId?: number, siteId?: string, ignoreCache?: boolean) + : Promise { + return this.sitesProvider.getSite(siteId).then((site) => { userId = userId || site.getUserId(); @@ -305,9 +320,11 @@ export class AddonCompetencyProvider { preSets.emergencyCache = false; } - return site.read('tool_lp_data_for_user_competency_summary', params, preSets).then((response) => { + return site.read('tool_lp_data_for_user_competency_summary', params, preSets) + .then((response: AddonCompetencyUserCompetencySummary): any => { + if (response.competency) { - return response.competency; + return response; } return Promise.reject(null); @@ -318,13 +335,15 @@ export class AddonCompetencyProvider { /** * Get all competencies in a course. * - * @param {number} courseId ID of the course. - * @param {number} [userId] ID of the user. - * @param {string} [siteId] Site ID. If not defined, current site. - * @param {boolean} [ignoreCache] True if it should ignore cached data (it will always fail in offline or server down). - * @return {Promise} Promise to be resolved when the course competencies are retrieved. + * @param courseId ID of the course. + * @param userId ID of the user. + * @param siteId Site ID. If not defined, current site. + * @param ignoreCache True if it should ignore cached data (it will always fail in offline or server down). + * @return Promise to be resolved when the course competencies are retrieved. */ - getCourseCompetencies(courseId: number, userId?: number, siteId?: string, ignoreCache?: boolean): Promise { + getCourseCompetencies(courseId: number, userId?: number, siteId?: string, ignoreCache?: boolean) + : Promise { + return this.sitesProvider.getSite(siteId).then((site) => { this.logger.debug('Get course competencies for course ' + courseId); @@ -342,7 +361,9 @@ export class AddonCompetencyProvider { preSets.emergencyCache = false; } - return site.read('tool_lp_data_for_course_competencies_page', params, preSets).then((response) => { + return site.read('tool_lp_data_for_course_competencies_page', params, preSets) + .then((response: AddonCompetencyDataForCourseCompetenciesPageResult): any => { + if (response.competencies) { return response; } @@ -356,11 +377,13 @@ export class AddonCompetencyProvider { return response; } - const promises = response.competencies.map((competency) => + let promises: Promise[]; + + promises = response.competencies.map((competency) => this.getCompetencyInCourse(courseId, competency.competency.id, userId, siteId) ); - return Promise.all(promises).then((responses: any[]) => { + return Promise.all(promises).then((responses: AddonCompetencyUserCompetencySummaryInCourse[]) => { responses.forEach((resp, index) => { response.competencies[index].usercompetencycourse = resp.usercompetencysummary.usercompetencycourse; }); @@ -373,9 +396,9 @@ export class AddonCompetencyProvider { /** * Invalidates User Learning Plans data. * - * @param {number} [userId] ID of the user. If not defined, current user. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the data is invalidated. + * @param userId ID of the user. If not defined, current user. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the data is invalidated. */ invalidateLearningPlans(userId?: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -388,9 +411,9 @@ export class AddonCompetencyProvider { /** * Invalidates Learning Plan data. * - * @param {number} planId ID of the plan. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the data is invalidated. + * @param planId ID of the plan. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the data is invalidated. */ invalidateLearningPlan(planId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -401,10 +424,10 @@ export class AddonCompetencyProvider { /** * Invalidates Competency in Plan data. * - * @param {number} planId ID of the plan. - * @param {number} competencyId ID of the competency. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the data is invalidated. + * @param planId ID of the plan. + * @param competencyId ID of the competency. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the data is invalidated. */ invalidateCompetencyInPlan(planId: number, competencyId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -415,11 +438,11 @@ export class AddonCompetencyProvider { /** * Invalidates Competency in Course data. * - * @param {number} courseId ID of the course. - * @param {number} competencyId ID of the competency. - * @param {number} [userId] ID of the user. If not defined, current user. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the data is invalidated. + * @param courseId ID of the course. + * @param competencyId ID of the competency. + * @param userId ID of the user. If not defined, current user. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the data is invalidated. */ invalidateCompetencyInCourse(courseId: number, competencyId: number, userId?: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -432,10 +455,10 @@ export class AddonCompetencyProvider { /** * Invalidates Competency Summary data. * - * @param {number} competencyId ID of the competency. - * @param {number} [userId] ID of the user. If not defined, current user. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the data is invalidated. + * @param competencyId ID of the competency. + * @param userId ID of the user. If not defined, current user. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the data is invalidated. */ invalidateCompetencySummary(competencyId: number, userId?: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -448,10 +471,10 @@ export class AddonCompetencyProvider { /** * Invalidates Course Competencies data. * - * @param {number} courseId ID of the course. - * @param {number} [userId] ID of the user. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the data is invalidated. + * @param courseId ID of the course. + * @param userId ID of the user. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the data is invalidated. */ invalidateCourseCompetencies(courseId: number, userId?: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -477,16 +500,16 @@ export class AddonCompetencyProvider { /** * Report the competency as being viewed in plan. * - * @param {number} planId ID of the plan. - * @param {number} competencyId ID of the competency. - * @param {number} planStatus Current plan Status to decide what action should be logged. - * @param {string} [name] Name of the competency. - * @param {number} [userId] User ID. If not defined, current user. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the WS call is successful. + * @param planId ID of the plan. + * @param competencyId ID of the competency. + * @param planStatus Current plan Status to decide what action should be logged. + * @param name Name of the competency. + * @param userId User ID. If not defined, current user. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the WS call is successful. */ logCompetencyInPlanView(planId: number, competencyId: number, planStatus: number, name?: string, userId?: number, - siteId?: string): Promise { + siteId?: string): Promise { if (planId && competencyId) { return this.sitesProvider.getSite(siteId).then((site) => { @@ -509,7 +532,11 @@ export class AddonCompetencyProvider { userid: userId }, siteId); - return site.write(wsName, params, preSets); + return site.write(wsName, params, preSets).then((success: boolean) => { + if (!success) { + return Promise.reject(null); + } + }); }); } @@ -519,15 +546,15 @@ export class AddonCompetencyProvider { /** * Report the competency as being viewed in course. * - * @param {number} courseId ID of the course. - * @param {number} competencyId ID of the competency. - * @param {string} [name] Name of the competency. - * @param {number} [userId] User ID. If not defined, current user. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the WS call is successful. + * @param courseId ID of the course. + * @param competencyId ID of the competency. + * @param name Name of the competency. + * @param userId User ID. If not defined, current user. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the WS call is successful. */ logCompetencyInCourseView(courseId: number, competencyId: number, name?: string, userId?: number, siteId?: string) - : Promise { + : Promise { if (courseId && competencyId) { return this.sitesProvider.getSite(siteId).then((site) => { @@ -548,7 +575,11 @@ export class AddonCompetencyProvider { userid: userId }, siteId); - return site.write(wsName, params, preSets); + return site.write(wsName, params, preSets).then((success: boolean) => { + if (!success) { + return Promise.reject(null); + } + }); }); } @@ -558,12 +589,12 @@ export class AddonCompetencyProvider { /** * Report the competency as being viewed. * - * @param {number} competencyId ID of the competency. - * @param {string} [name] Name of the competency. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the WS call is successful. + * @param competencyId ID of the competency. + * @param name Name of the competency. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the WS call is successful. */ - logCompetencyView(competencyId: number, name?: string, siteId?: string): Promise { + logCompetencyView(competencyId: number, name?: string, siteId?: string): Promise { if (competencyId) { return this.sitesProvider.getSite(siteId).then((site) => { const params = { @@ -576,10 +607,401 @@ export class AddonCompetencyProvider { this.pushNotificationsProvider.logViewEvent(competencyId, name, 'competency', wsName, {}, siteId); - return site.write('core_competency_competency_viewed', params, preSets); + return site.write(wsName, params, preSets).then((success: boolean) => { + if (!success) { + return Promise.reject(null); + } + }); }); } return Promise.reject(null); } } + +/** + * Data returned by competency's plan_exporter. + */ +export type AddonCompetencyPlan = { + name: string; // Name. + description: string; // Description. + descriptionformat: number; // Description format (1 = HTML, 0 = MOODLE, 2 = PLAIN or 4 = MARKDOWN). + userid: number; // Userid. + templateid: number; // Templateid. + origtemplateid: number; // Origtemplateid. + status: number; // Status. + duedate: number; // Duedate. + reviewerid: number; // Reviewerid. + id: number; // Id. + timecreated: number; // Timecreated. + timemodified: number; // Timemodified. + usermodified: number; // Usermodified. + statusname: string; // Statusname. + isbasedontemplate: boolean; // Isbasedontemplate. + canmanage: boolean; // Canmanage. + canrequestreview: boolean; // Canrequestreview. + canreview: boolean; // Canreview. + canbeedited: boolean; // Canbeedited. + isactive: boolean; // Isactive. + isdraft: boolean; // Isdraft. + iscompleted: boolean; // Iscompleted. + isinreview: boolean; // Isinreview. + iswaitingforreview: boolean; // Iswaitingforreview. + isreopenallowed: boolean; // Isreopenallowed. + iscompleteallowed: boolean; // Iscompleteallowed. + isunlinkallowed: boolean; // Isunlinkallowed. + isrequestreviewallowed: boolean; // Isrequestreviewallowed. + iscancelreviewrequestallowed: boolean; // Iscancelreviewrequestallowed. + isstartreviewallowed: boolean; // Isstartreviewallowed. + isstopreviewallowed: boolean; // Isstopreviewallowed. + isapproveallowed: boolean; // Isapproveallowed. + isunapproveallowed: boolean; // Isunapproveallowed. + duedateformatted: string; // Duedateformatted. + commentarea: CoreCommentsArea; + reviewer?: CoreUserSummary; + template?: AddonCompetencyTemplate; + url: string; // Url. +}; + +/** + * Data returned by competency's template_exporter. + */ +export type AddonCompetencyTemplate = { + shortname: string; // Shortname. + description: string; // Description. + descriptionformat: number; // Description format (1 = HTML, 0 = MOODLE, 2 = PLAIN or 4 = MARKDOWN). + duedate: number; // Duedate. + visible: boolean; // Visible. + contextid: number; // Contextid. + id: number; // Id. + timecreated: number; // Timecreated. + timemodified: number; // Timemodified. + usermodified: number; // Usermodified. + duedateformatted: string; // Duedateformatted. + cohortscount: number; // Cohortscount. + planscount: number; // Planscount. + canmanage: boolean; // Canmanage. + canread: boolean; // Canread. + contextname: string; // Contextname. + contextnamenoprefix: string; // Contextnamenoprefix. +}; + +/** + * Data returned by competency's competency_exporter. + */ +export type AddonCompetencyCompetency = { + shortname: string; // Shortname. + idnumber: string; // Idnumber. + description: string; // Description. + descriptionformat: number; // Description format (1 = HTML, 0 = MOODLE, 2 = PLAIN or 4 = MARKDOWN). + sortorder: number; // Sortorder. + parentid: number; // Parentid. + path: string; // Path. + ruleoutcome: number; // Ruleoutcome. + ruletype: string; // Ruletype. + ruleconfig: string; // Ruleconfig. + scaleid: number; // Scaleid. + scaleconfiguration: string; // Scaleconfiguration. + competencyframeworkid: number; // Competencyframeworkid. + id: number; // Id. + timecreated: number; // Timecreated. + timemodified: number; // Timemodified. + usermodified: number; // Usermodified. +}; + +/** + * Data returned by competency's competency_path_exporter. + */ +export type AddonCompetencyPath = { + ancestors: AddonCompetencyPathNode[]; // Ancestors. + framework: AddonCompetencyPathNode; + pluginbaseurl: string; // Pluginbaseurl. + pagecontextid: number; // Pagecontextid. + showlinks: boolean; // @since 3.7. Showlinks. +}; + +/** + * Data returned by competency's path_node_exporter. + */ +export type AddonCompetencyPathNode = { + id: number; // Id. + name: string; // Name. + first: boolean; // First. + last: boolean; // Last. + position: number; // Position. +}; + +/** + * Data returned by competency's user_competency_exporter. + */ +export type AddonCompetencyUserCompetency = { + userid: number; // Userid. + competencyid: number; // Competencyid. + status: number; // Status. + reviewerid: number; // Reviewerid. + proficiency: boolean; // Proficiency. + grade: number; // Grade. + id: number; // Id. + timecreated: number; // Timecreated. + timemodified: number; // Timemodified. + usermodified: number; // Usermodified. + canrequestreview: boolean; // Canrequestreview. + canreview: boolean; // Canreview. + gradename: string; // Gradename. + isrequestreviewallowed: boolean; // Isrequestreviewallowed. + iscancelreviewrequestallowed: boolean; // Iscancelreviewrequestallowed. + isstartreviewallowed: boolean; // Isstartreviewallowed. + isstopreviewallowed: boolean; // Isstopreviewallowed. + isstatusidle: boolean; // Isstatusidle. + isstatusinreview: boolean; // Isstatusinreview. + isstatuswaitingforreview: boolean; // Isstatuswaitingforreview. + proficiencyname: string; // Proficiencyname. + reviewer?: CoreUserSummary; + statusname: string; // Statusname. + url: string; // Url. +}; + +/** + * Data returned by competency's user_competency_plan_exporter. + */ +export type AddonCompetencyUserCompetencyPlan = { + userid: number; // Userid. + competencyid: number; // Competencyid. + proficiency: boolean; // Proficiency. + grade: number; // Grade. + planid: number; // Planid. + sortorder: number; // Sortorder. + id: number; // Id. + timecreated: number; // Timecreated. + timemodified: number; // Timemodified. + usermodified: number; // Usermodified. + gradename: string; // Gradename. + proficiencyname: string; // Proficiencyname. +}; + +/** + * Data returned by competency's user_competency_summary_in_plan_exporter. + */ +export type AddonCompetencyUserCompetencySummaryInPlan = { + usercompetencysummary: AddonCompetencyUserCompetencySummary; + plan: AddonCompetencyPlan; +}; + +/** + * Data returned by competency's user_competency_summary_exporter. + */ +export type AddonCompetencyUserCompetencySummary = { + showrelatedcompetencies: boolean; // Showrelatedcompetencies. + cangrade: boolean; // Cangrade. + competency: AddonCompetencySummary; + user: CoreUserSummary; + usercompetency?: AddonCompetencyUserCompetency; + usercompetencyplan?: AddonCompetencyUserCompetencyPlan; + usercompetencycourse?: AddonCompetencyUserCompetencyCourse; + evidence: AddonCompetencyEvidence[]; // Evidence. + commentarea?: CoreCommentsArea; +}; + +/** + * Data returned by competency's competency_summary_exporter. + */ +export type AddonCompetencySummary = { + linkedcourses: CoreCourseSummary; // Linkedcourses. + relatedcompetencies: AddonCompetencyCompetency[]; // Relatedcompetencies. + competency: AddonCompetencyCompetency; + framework: AddonCompetencyFramework; + hascourses: boolean; // Hascourses. + hasrelatedcompetencies: boolean; // Hasrelatedcompetencies. + scaleid: number; // Scaleid. + scaleconfiguration: string; // Scaleconfiguration. + taxonomyterm: string; // Taxonomyterm. + comppath: AddonCompetencyPath; + pluginbaseurl: string; // @since 3.7. Pluginbaseurl. +}; + +/** + * Data returned by competency's competency_framework_exporter. + */ +export type AddonCompetencyFramework = { + shortname: string; // Shortname. + idnumber: string; // Idnumber. + description: string; // Description. + descriptionformat: number; // Description format (1 = HTML, 0 = MOODLE, 2 = PLAIN or 4 = MARKDOWN). + visible: boolean; // Visible. + scaleid: number; // Scaleid. + scaleconfiguration: string; // Scaleconfiguration. + contextid: number; // Contextid. + taxonomies: string; // Taxonomies. + id: number; // Id. + timecreated: number; // Timecreated. + timemodified: number; // Timemodified. + usermodified: number; // Usermodified. + canmanage: boolean; // Canmanage. + competenciescount: number; // Competenciescount. + contextname: string; // Contextname. + contextnamenoprefix: string; // Contextnamenoprefix. +}; + +/** + * Data returned by competency's user_competency_course_exporter. + */ +export type AddonCompetencyUserCompetencyCourse = { + userid: number; // Userid. + courseid: number; // Courseid. + competencyid: number; // Competencyid. + proficiency: boolean; // Proficiency. + grade: number; // Grade. + id: number; // Id. + timecreated: number; // Timecreated. + timemodified: number; // Timemodified. + usermodified: number; // Usermodified. + gradename: string; // Gradename. + proficiencyname: string; // Proficiencyname. +}; + +/** + * Data returned by competency's evidence_exporter. + */ +export type AddonCompetencyEvidence = { + usercompetencyid: number; // Usercompetencyid. + contextid: number; // Contextid. + action: number; // Action. + actionuserid: number; // Actionuserid. + descidentifier: string; // Descidentifier. + desccomponent: string; // Desccomponent. + desca: string; // Desca. + url: string; // Url. + grade: number; // Grade. + note: string; // Note. + id: number; // Id. + timecreated: number; // Timecreated. + timemodified: number; // Timemodified. + usermodified: number; // Usermodified. + actionuser?: CoreUserSummary; + description: string; // Description. + gradename: string; // Gradename. + userdate: string; // Userdate. + candelete: boolean; // Candelete. +}; + +/** + * Data returned by competency's user_competency_summary_in_course_exporter. + */ +export type AddonCompetencyUserCompetencySummaryInCourse = { + usercompetencysummary: AddonCompetencyUserCompetencySummary; + course: CoreCourseSummary; + coursemodules: CoreCourseModuleSummary[]; // Coursemodules. + plans: AddonCompetencyPlan[]; // @since 3.7. Plans. + pluginbaseurl: string; // @since 3.7. Pluginbaseurl. +}; + +/** + * Data returned by competency's course_competency_settings_exporter. + */ +export type AddonCompetencyCourseCompetencySettings = { + courseid: number; // Courseid. + pushratingstouserplans: boolean; // Pushratingstouserplans. + id: number; // Id. + timecreated: number; // Timecreated. + timemodified: number; // Timemodified. + usermodified: number; // Usermodified. +}; + +/** + * Data returned by competency's course_competency_statistics_exporter. + */ +export type AddonCompetencyCourseCompetencyStatistics = { + competencycount: number; // Competencycount. + proficientcompetencycount: number; // Proficientcompetencycount. + proficientcompetencypercentage: number; // Proficientcompetencypercentage. + proficientcompetencypercentageformatted: string; // Proficientcompetencypercentageformatted. + leastproficient: AddonCompetencyCompetency[]; // Leastproficient. + leastproficientcount: number; // Leastproficientcount. + canbegradedincourse: boolean; // Canbegradedincourse. + canmanagecoursecompetencies: boolean; // Canmanagecoursecompetencies. +}; + +/** + * Data returned by competency's course_competency_exporter. + */ +export type AddonCompetencyCourseCompetency = { + courseid: number; // Courseid. + competencyid: number; // Competencyid. + sortorder: number; // Sortorder. + ruleoutcome: number; // Ruleoutcome. + id: number; // Id. + timecreated: number; // Timecreated. + timemodified: number; // Timemodified. + usermodified: number; // Usermodified. +}; + +/** + * Result of WS tool_lp_data_for_plans_page. + */ +export type AddonCompetencyDataForPlansPageResult = { + userid: number; // The learning plan user id. + plans: AddonCompetencyPlan[]; + pluginbaseurl: string; // Url to the tool_lp plugin folder on this Moodle site. + navigation: string[]; + canreaduserevidence: boolean; // Can the current user view the user's evidence. + canmanageuserplans: boolean; // Can the current user manage the user's plans. +}; + +/** + * Result of WS tool_lp_data_for_plan_page. + */ +export type AddonCompetencyDataForPlanPageResult = { + plan: AddonCompetencyPlan; + contextid: number; // Context ID. + pluginbaseurl: string; // Plugin base URL. + competencies: AddonCompetencyDataForPlanPageCompetency[]; + competencycount: number; // Count of competencies. + proficientcompetencycount: number; // Count of proficientcompetencies. + proficientcompetencypercentage: number; // Percentage of competencies proficient. + proficientcompetencypercentageformatted: string; // Displayable percentage. +}; + +/** + * Competency data returned by tool_lp_data_for_plan_page. + */ +export type AddonCompetencyDataForPlanPageCompetency = { + competency: AddonCompetencyCompetency; + comppath: AddonCompetencyPath; + usercompetency?: AddonCompetencyUserCompetency; + usercompetencyplan?: AddonCompetencyUserCompetencyPlan; +}; + +/** + * Result of WS tool_lp_data_for_course_competencies_page. + */ +export type AddonCompetencyDataForCourseCompetenciesPageResult = { + courseid: number; // The current course id. + pagecontextid: number; // The current page context ID. + gradableuserid?: number; // Current user id, if the user is a gradable user. + canmanagecompetencyframeworks: boolean; // User can manage competency frameworks. + canmanagecoursecompetencies: boolean; // User can manage linked course competencies. + canconfigurecoursecompetencies: boolean; // User can configure course competency settings. + cangradecompetencies: boolean; // User can grade competencies. + settings: AddonCompetencyCourseCompetencySettings; + statistics: AddonCompetencyCourseCompetencyStatistics; + competencies: AddonCompetencyDataForCourseCompetenciesPageCompetency[]; + manageurl: string; // Url to the manage competencies page. + pluginbaseurl: string; // @since 3.6. Url to the course competencies page. +}; + +/** + * Competency data returned by tool_lp_data_for_course_competencies_page. + */ +export type AddonCompetencyDataForCourseCompetenciesPageCompetency = { + competency: AddonCompetencyCompetency; + coursecompetency: AddonCompetencyCourseCompetency; + coursemodules: CoreCourseModuleSummary[]; + usercompetencycourse?: AddonCompetencyUserCompetencyCourse; + ruleoutcomeoptions: { + value: number; // The option value. + text: string; // The name of the option. + selected: boolean; // If this is the currently selected option. + }[]; + comppath: AddonCompetencyPath; + plans: AddonCompetencyPlan[]; // @since 3.7. +}; diff --git a/src/addon/competency/providers/course-option-handler.ts b/src/addon/competency/providers/course-option-handler.ts index 2f8176995..c38c2fd2a 100644 --- a/src/addon/competency/providers/course-option-handler.ts +++ b/src/addon/competency/providers/course-option-handler.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -17,6 +17,7 @@ import { CoreCourseOptionsHandler, CoreCourseOptionsHandlerData } from '@core/co import { CoreCourseProvider } from '@core/course/providers/course'; import { AddonCompetencyCourseComponent } from '../components/course/course'; import { AddonCompetencyProvider } from '../providers/competency'; +import { CoreFilterHelperProvider } from '@core/filter/providers/helper'; /** * Course nav handler. @@ -26,11 +27,11 @@ export class AddonCompetencyCourseOptionHandler implements CoreCourseOptionsHand name = 'AddonCompetency'; priority = 300; - constructor(private competencyProvider: AddonCompetencyProvider) {} + constructor(private competencyProvider: AddonCompetencyProvider, protected filterHelper: CoreFilterHelperProvider) {} /** * Whether or not the handler is enabled ona site level. - * @return {boolean|Promise} Whether or not the handler is enabled on a site level. + * @return Whether or not the handler is enabled on a site level. */ isEnabled(): boolean | Promise { return true; @@ -39,11 +40,11 @@ export class AddonCompetencyCourseOptionHandler implements CoreCourseOptionsHand /** * Whether or not the handler is enabled for a certain course. * - * @param {number} courseId The course ID. - * @param {any} accessData Access type and data. Default, guest, ... - * @param {any} [navOptions] Course navigation options for current user. See CoreCoursesProvider.getUserNavigationOptions. - * @param {any} [admOptions] Course admin options for current user. See CoreCoursesProvider.getUserAdministrationOptions. - * @return {boolean|Promise} True or promise resolved with true if enabled. + * @param courseId The course ID. + * @param accessData Access type and data. Default, guest, ... + * @param navOptions Course navigation options for current user. See CoreCoursesProvider.getUserNavigationOptions. + * @param admOptions Course admin options for current user. See CoreCoursesProvider.getUserAdministrationOptions. + * @return True or promise resolved with true if enabled. */ isEnabledForCourse(courseId: number, accessData: any, navOptions?: any, admOptions?: any): boolean | Promise { if (accessData && accessData.type == CoreCourseProvider.ACCESS_GUEST) { @@ -62,9 +63,9 @@ export class AddonCompetencyCourseOptionHandler implements CoreCourseOptionsHand /** * Returns the data needed to render the handler. * - * @param {Injector} injector Injector. - * @param {number} course The course. - * @return {CoreCourseOptionsHandlerData|Promise} Data or promise resolved with the data. + * @param injector Injector. + * @param course The course. + * @return Data or promise resolved with the data. */ getDisplayData?(injector: Injector, course: any): CoreCourseOptionsHandlerData | Promise { return { @@ -77,10 +78,10 @@ export class AddonCompetencyCourseOptionHandler implements CoreCourseOptionsHand /** * Should invalidate the data to determine if the handler is enabled for a certain course. * - * @param {number} courseId The course ID. - * @param {any} [navOptions] Course navigation options for current user. See CoreCoursesProvider.getUserNavigationOptions. - * @param {any} [admOptions] Course admin options for current user. See CoreCoursesProvider.getUserAdministrationOptions. - * @return {Promise} Promise resolved when done. + * @param courseId The course ID. + * @param navOptions Course navigation options for current user. See CoreCoursesProvider.getUserNavigationOptions. + * @param admOptions Course admin options for current user. See CoreCoursesProvider.getUserAdministrationOptions. + * @return Promise resolved when done. */ invalidateEnabledForCourse(courseId: number, navOptions?: any, admOptions?: any): Promise { if (navOptions && typeof navOptions.competencies != 'undefined') { @@ -94,8 +95,8 @@ export class AddonCompetencyCourseOptionHandler implements CoreCourseOptionsHand /** * Called when a course is downloaded. It should prefetch all the data to be able to see the addon in offline. * - * @param {any} course The course. - * @return {Promise} Promise resolved when done. + * @param course The course. + * @return Promise resolved when done. */ prefetch(course: any): Promise { // Get the competencies in the course. @@ -110,6 +111,18 @@ export class AddonCompetencyCourseOptionHandler implements CoreCourseOptionsHand promises.push(this.competencyProvider.getCompetencySummary(competency.competency.id, undefined, undefined, true)); + + if (competency.coursemodules) { + competency.coursemodules.forEach((module) => { + promises.push(this.filterHelper.getFilters('module', module.id, {courseId: course.id})); + }); + } + + if (competency.plans) { + competency.plans.forEach((plan) => { + promises.push(this.filterHelper.getFilters('user', plan.userid)); + }); + } }); } diff --git a/src/addon/competency/providers/helper.ts b/src/addon/competency/providers/helper.ts index bf95bd3ac..b956578d5 100644 --- a/src/addon/competency/providers/helper.ts +++ b/src/addon/competency/providers/helper.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -31,8 +31,8 @@ export class AddonCompetencyHelperProvider { /** * Convenient helper to get the user profile image. * - * @param {number} userId User Id - * @return {Promise} User profile Image URL or true if default icon. + * @param userId User Id + * @return User profile Image URL or true if default icon. */ getProfile(userId: number): Promise { if (!userId || userId == this.sitesProvider.getCurrentSiteUserId()) { @@ -50,8 +50,7 @@ export class AddonCompetencyHelperProvider { /** * Get the review status name translated. * - * @param {number} status - * @return {string} + * @param status */ getCompetencyStatusName(status: number): string { let statusTranslateName; @@ -76,8 +75,7 @@ export class AddonCompetencyHelperProvider { /** * Get the status name translated. * - * @param {number} status - * @return {string} + * @param status */ getPlanStatusName(status: number): string { let statusTranslateName; diff --git a/src/addon/competency/providers/mainmenu-handler.ts b/src/addon/competency/providers/mainmenu-handler.ts index b45e84459..f3040968c 100644 --- a/src/addon/competency/providers/mainmenu-handler.ts +++ b/src/addon/competency/providers/mainmenu-handler.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -29,7 +29,7 @@ export class AddonCompetencyMainMenuHandler implements CoreMainMenuHandler { /** * Check if the handler is enabled on a site level. * - * @return {boolean} Whether or not the handler is enabled on a site level. + * @return Whether or not the handler is enabled on a site level. */ isEnabled(): boolean | Promise { // Check the user has at least one learn plan available. @@ -41,7 +41,7 @@ export class AddonCompetencyMainMenuHandler implements CoreMainMenuHandler { /** * Returns the data needed to render the handler. * - * @return {CoreMainMenuHandlerData} Data needed to render the handler. + * @return Data needed to render the handler. */ getDisplayData(): CoreMainMenuHandlerData { return { diff --git a/src/addon/competency/providers/plan-link-handler.ts b/src/addon/competency/providers/plan-link-handler.ts index 1e20c5fe7..d03060f3b 100644 --- a/src/addon/competency/providers/plan-link-handler.ts +++ b/src/addon/competency/providers/plan-link-handler.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -33,11 +33,11 @@ export class AddonCompetencyPlanLinkHandler extends CoreContentLinksHandlerBase /** * Get the list of actions for a link (url). * - * @param {string[]} siteIds List of sites the URL belongs to. - * @param {string} url The URL to treat. - * @param {any} params The params of the URL. E.g. 'mysite.com?id=1' -> {id: 1} - * @param {number} [courseId] Course ID related to the URL. Optional but recommended. - * @return {CoreContentLinksAction[]|Promise} List of (or promise resolved with list of) actions. + * @param siteIds List of sites the URL belongs to. + * @param url The URL to treat. + * @param params The params of the URL. E.g. 'mysite.com?id=1' -> {id: 1} + * @param courseId Course ID related to the URL. Optional but recommended. + * @return List of (or promise resolved with list of) actions. */ getActions(siteIds: string[], url: string, params: any, courseId?: number): CoreContentLinksAction[] | Promise { @@ -53,11 +53,11 @@ export class AddonCompetencyPlanLinkHandler extends CoreContentLinksHandlerBase * Check if the handler is enabled for a certain site (site + user) and a URL. * If not defined, defaults to true. * - * @param {string} siteId The site ID. - * @param {string} url The URL to treat. - * @param {any} params The params of the URL. E.g. 'mysite.com?id=1' -> {id: 1} - * @param {number} [courseId] Course ID related to the URL. Optional but recommended. - * @return {boolean|Promise} Whether the handler is enabled for the URL and site. + * @param siteId The site ID. + * @param url The URL to treat. + * @param params The params of the URL. E.g. 'mysite.com?id=1' -> {id: 1} + * @param courseId Course ID related to the URL. Optional but recommended. + * @return Whether the handler is enabled for the URL and site. */ isEnabled(siteId: string, url: string, params: any, courseId?: number): boolean | Promise { // Handler is disabled if all competency features are disabled. diff --git a/src/addon/competency/providers/plans-link-handler.ts b/src/addon/competency/providers/plans-link-handler.ts index da08f33da..a423c1fc7 100644 --- a/src/addon/competency/providers/plans-link-handler.ts +++ b/src/addon/competency/providers/plans-link-handler.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -33,11 +33,11 @@ export class AddonCompetencyPlansLinkHandler extends CoreContentLinksHandlerBase /** * Get the list of actions for a link (url). * - * @param {string[]} siteIds List of sites the URL belongs to. - * @param {string} url The URL to treat. - * @param {any} params The params of the URL. E.g. 'mysite.com?id=1' -> {id: 1} - * @param {number} [courseId] Course ID related to the URL. Optional but recommended. - * @return {CoreContentLinksAction[]|Promise} List of (or promise resolved with list of) actions. + * @param siteIds List of sites the URL belongs to. + * @param url The URL to treat. + * @param params The params of the URL. E.g. 'mysite.com?id=1' -> {id: 1} + * @param courseId Course ID related to the URL. Optional but recommended. + * @return List of (or promise resolved with list of) actions. */ getActions(siteIds: string[], url: string, params: any, courseId?: number): CoreContentLinksAction[] | Promise { @@ -54,11 +54,11 @@ export class AddonCompetencyPlansLinkHandler extends CoreContentLinksHandlerBase * Check if the handler is enabled for a certain site (site + user) and a URL. * If not defined, defaults to true. * - * @param {string} siteId The site ID. - * @param {string} url The URL to treat. - * @param {any} params The params of the URL. E.g. 'mysite.com?id=1' -> {id: 1} - * @param {number} [courseId] Course ID related to the URL. Optional but recommended. - * @return {boolean|Promise} Whether the handler is enabled for the URL and site. + * @param siteId The site ID. + * @param url The URL to treat. + * @param params The params of the URL. E.g. 'mysite.com?id=1' -> {id: 1} + * @param courseId Course ID related to the URL. Optional but recommended. + * @return Whether the handler is enabled for the URL and site. */ isEnabled(siteId: string, url: string, params: any, courseId?: number): boolean | Promise { // Handler is disabled if all competency features are disabled. diff --git a/src/addon/competency/providers/push-click-handler.ts b/src/addon/competency/providers/push-click-handler.ts index eca567205..c40509fec 100644 --- a/src/addon/competency/providers/push-click-handler.ts +++ b/src/addon/competency/providers/push-click-handler.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -33,8 +33,8 @@ export class AddonCompetencyPushClickHandler implements CorePushNotificationsCli /** * Check if a notification click is handled by this handler. * - * @param {any} notification The notification to check. - * @return {boolean} Whether the notification click is handled by this handler + * @param notification The notification to check. + * @return Whether the notification click is handled by this handler */ handles(notification: any): boolean | Promise { if (this.utils.isTrueOrOne(notification.notif) && notification.moodlecomponent == 'moodle' && @@ -51,8 +51,8 @@ export class AddonCompetencyPushClickHandler implements CorePushNotificationsCli /** * Handle the notification click. * - * @param {any} notification The notification to check. - * @return {Promise} Promise resolved when done. + * @param notification The notification to check. + * @return Promise resolved when done. */ handleClick(notification: any): Promise { const contextUrlParams = this.urlUtils.extractUrlParams(notification.contexturl); diff --git a/src/addon/competency/providers/user-competency-link-handler.ts b/src/addon/competency/providers/user-competency-link-handler.ts index 06cee4f90..3d1eafe35 100644 --- a/src/addon/competency/providers/user-competency-link-handler.ts +++ b/src/addon/competency/providers/user-competency-link-handler.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -33,11 +33,11 @@ export class AddonCompetencyUserCompetencyLinkHandler extends CoreContentLinksHa /** * Get the list of actions for a link (url). * - * @param {string[]} siteIds List of sites the URL belongs to. - * @param {string} url The URL to treat. - * @param {any} params The params of the URL. E.g. 'mysite.com?id=1' -> {id: 1} - * @param {number} [courseId] Course ID related to the URL. Optional but recommended. - * @return {CoreContentLinksAction[]|Promise} List of (or promise resolved with list of) actions. + * @param siteIds List of sites the URL belongs to. + * @param url The URL to treat. + * @param params The params of the URL. E.g. 'mysite.com?id=1' -> {id: 1} + * @param courseId Course ID related to the URL. Optional but recommended. + * @return List of (or promise resolved with list of) actions. */ getActions(siteIds: string[], url: string, params: any, courseId?: number): CoreContentLinksAction[] | Promise { @@ -53,11 +53,11 @@ export class AddonCompetencyUserCompetencyLinkHandler extends CoreContentLinksHa * Check if the handler is enabled for a certain site (site + user) and a URL. * If not defined, defaults to true. * - * @param {string} siteId The site ID. - * @param {string} url The URL to treat. - * @param {any} params The params of the URL. E.g. 'mysite.com?id=1' -> {id: 1} - * @param {number} [courseId] Course ID related to the URL. Optional but recommended. - * @return {boolean|Promise} Whether the handler is enabled for the URL and site. + * @param siteId The site ID. + * @param url The URL to treat. + * @param params The params of the URL. E.g. 'mysite.com?id=1' -> {id: 1} + * @param courseId Course ID related to the URL. Optional but recommended. + * @return Whether the handler is enabled for the URL and site. */ isEnabled(siteId: string, url: string, params: any, courseId?: number): boolean | Promise { // Handler is disabled if all competency features are disabled. diff --git a/src/addon/competency/providers/user-handler.ts b/src/addon/competency/providers/user-handler.ts index 7cdbaadac..b2c07ebc0 100644 --- a/src/addon/competency/providers/user-handler.ts +++ b/src/addon/competency/providers/user-handler.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -47,7 +47,7 @@ export class AddonCompetencyUserHandler implements CoreUserProfileHandler { /** * Whether or not the handler is enabled on a site level. - * @return {boolean|Promise} Whether or not the handler is enabled on a site level. + * @return Whether or not the handler is enabled on a site level. */ isEnabled(): boolean | Promise { return true; @@ -56,11 +56,11 @@ export class AddonCompetencyUserHandler implements CoreUserProfileHandler { /** * Check if handler is enabled for this user in this context. * - * @param {any} user User to check. - * @param {number} courseId Course ID. - * @param {any} [navOptions] Course navigation options for current user. See CoreCoursesProvider.getUserNavigationOptions. - * @param {any} [admOptions] Course admin options for current user. See CoreCoursesProvider.getUserAdministrationOptions. - * @return {boolean|Promise} Promise resolved with true if enabled, resolved with false otherwise. + * @param user User to check. + * @param courseId Course ID. + * @param navOptions Course navigation options for current user. See CoreCoursesProvider.getUserNavigationOptions. + * @param admOptions Course admin options for current user. See CoreCoursesProvider.getUserAdministrationOptions. + * @return Promise resolved with true if enabled, resolved with false otherwise. */ isEnabledForUser(user: any, courseId: number, navOptions?: any, admOptions?: any): boolean | Promise { if (courseId) { @@ -100,7 +100,7 @@ export class AddonCompetencyUserHandler implements CoreUserProfileHandler { /** * Returns the data needed to render the handler. * - * @return {CoreUserProfileHandlerData} Data needed to render the handler. + * @return Data needed to render the handler. */ getDisplayData(user: any, courseId: number): CoreUserProfileHandlerData { if (courseId) { diff --git a/src/addon/coursecompletion/components/components.module.ts b/src/addon/coursecompletion/components/components.module.ts index 140b2b3ff..5ed07218c 100644 --- a/src/addon/coursecompletion/components/components.module.ts +++ b/src/addon/coursecompletion/components/components.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/addon/coursecompletion/components/report/addon-course-completion-report.html b/src/addon/coursecompletion/components/report/addon-course-completion-report.html index 2b4b92070..954447487 100644 --- a/src/addon/coursecompletion/components/report/addon-course-completion-report.html +++ b/src/addon/coursecompletion/components/report/addon-course-completion-report.html @@ -6,7 +6,7 @@

{{ 'addon.coursecompletion.status' | translate }}

-

{{ completion.statusText | translate }}

+

{{ statusText | translate }}

{{ 'addon.coursecompletion.required' | translate }}

@@ -17,8 +17,8 @@ {{ 'addon.coursecompletion.requiredcriteria' | translate }} -

-

+

+

{{ criteria.status }}
@@ -31,10 +31,10 @@ {{ 'addon.coursecompletion.completiondate' | translate }} - - - - + + + + {{ criteria.status }} {{ criteria.timecompleted * 1000 | coreFormatDate :'strftimedatetimeshort' }} diff --git a/src/addon/coursecompletion/components/report/report.ts b/src/addon/coursecompletion/components/report/report.ts index 50d12cf90..2f3532f0a 100644 --- a/src/addon/coursecompletion/components/report/report.ts +++ b/src/addon/coursecompletion/components/report/report.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -15,7 +15,7 @@ import { Component, Input, OnInit } from '@angular/core'; import { CoreSitesProvider } from '@providers/sites'; import { CoreDomUtilsProvider } from '@providers/utils/dom'; -import { AddonCourseCompletionProvider } from '../../providers/coursecompletion'; +import { AddonCourseCompletionProvider, AddonCourseCompletionCourseCompletionStatus } from '../../providers/coursecompletion'; /** * Component that displays the course completion report. @@ -29,9 +29,10 @@ export class AddonCourseCompletionReportComponent implements OnInit { @Input() userId: number; completionLoaded = false; - completion: any; + completion: AddonCourseCompletionCourseCompletionStatus; showSelfComplete: boolean; tracked = true; // Whether completion is tracked. + statusText: string; constructor( private sitesProvider: CoreSitesProvider, @@ -54,12 +55,12 @@ export class AddonCourseCompletionReportComponent implements OnInit { /** * Fetch compleiton data. * - * @return {Promise} Promise resolved when done. + * @return Promise resolved when done. */ protected fetchCompletion(): Promise { return this.courseCompletionProvider.getCompletion(this.courseId, this.userId).then((completion) => { - completion.statusText = this.courseCompletionProvider.getCompletedStatusText(completion); + this.statusText = this.courseCompletionProvider.getCompletedStatusText(completion); this.completion = completion; this.showSelfComplete = this.courseCompletionProvider.canMarkSelfCompleted(this.userId, completion); @@ -77,7 +78,7 @@ export class AddonCourseCompletionReportComponent implements OnInit { /** * Refresh completion data on PTR. * - * @param {any} [refresher] Refresher instance. + * @param refresher Refresher instance. */ refreshCompletion(refresher?: any): void { this.courseCompletionProvider.invalidateCourseCompletion(this.courseId, this.userId).finally(() => { diff --git a/src/addon/coursecompletion/coursecompletion.module.ts b/src/addon/coursecompletion/coursecompletion.module.ts index db3699489..6ce5ebbfd 100644 --- a/src/addon/coursecompletion/coursecompletion.module.ts +++ b/src/addon/coursecompletion/coursecompletion.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/addon/coursecompletion/pages/report/report.module.ts b/src/addon/coursecompletion/pages/report/report.module.ts index 83f0a10e6..0bb1284d5 100644 --- a/src/addon/coursecompletion/pages/report/report.module.ts +++ b/src/addon/coursecompletion/pages/report/report.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/addon/coursecompletion/pages/report/report.ts b/src/addon/coursecompletion/pages/report/report.ts index d874ae161..eec7e95a8 100644 --- a/src/addon/coursecompletion/pages/report/report.ts +++ b/src/addon/coursecompletion/pages/report/report.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/addon/coursecompletion/providers/course-option-handler.ts b/src/addon/coursecompletion/providers/course-option-handler.ts index d0483b68a..0a7671657 100644 --- a/src/addon/coursecompletion/providers/course-option-handler.ts +++ b/src/addon/coursecompletion/providers/course-option-handler.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -30,7 +30,7 @@ export class AddonCourseCompletionCourseOptionHandler implements CoreCourseOptio /** * Whether or not the handler is enabled on a site level. - * @return {boolean|Promise} Whether or not the handler is enabled on a site level. + * @return Whether or not the handler is enabled on a site level. */ isEnabled(): boolean | Promise { return this.courseCompletionProvider.isPluginViewEnabled(); @@ -39,11 +39,11 @@ export class AddonCourseCompletionCourseOptionHandler implements CoreCourseOptio /** * Whether or not the handler is enabled for a certain course. * - * @param {number} courseId The course ID. - * @param {any} accessData Access type and data. Default, guest, ... - * @param {any} [navOptions] Course navigation options for current user. See CoreCoursesProvider.getUserNavigationOptions. - * @param {any} [admOptions] Course admin options for current user. See CoreCoursesProvider.getUserAdministrationOptions. - * @return {boolean|Promise} True or promise resolved with true if enabled. + * @param courseId The course ID. + * @param accessData Access type and data. Default, guest, ... + * @param navOptions Course navigation options for current user. See CoreCoursesProvider.getUserNavigationOptions. + * @param admOptions Course admin options for current user. See CoreCoursesProvider.getUserAdministrationOptions. + * @return True or promise resolved with true if enabled. */ isEnabledForCourse(courseId: number, accessData: any, navOptions?: any, admOptions?: any): boolean | Promise { if (accessData && accessData.type == CoreCourseProvider.ACCESS_GUEST) { @@ -64,8 +64,8 @@ export class AddonCourseCompletionCourseOptionHandler implements CoreCourseOptio /** * Returns the data needed to render the handler. * - * @param {number} courseId The course ID. - * @return {CoreCourseOptionsHandlerData} Data. + * @param courseId The course ID. + * @return Data. */ getDisplayData?(injector: Injector, courseId: number): CoreCourseOptionsHandlerData { return { @@ -78,10 +78,10 @@ export class AddonCourseCompletionCourseOptionHandler implements CoreCourseOptio /** * Should invalidate the data to determine if the handler is enabled for a certain course. * - * @param {number} courseId The course ID. - * @param {any} [navOptions] Course navigation options for current user. See CoreCoursesProvider.getUserNavigationOptions. - * @param {any} [admOptions] Course admin options for current user. See CoreCoursesProvider.getUserAdministrationOptions. - * @return {Promise} Promise resolved when done. + * @param courseId The course ID. + * @param navOptions Course navigation options for current user. See CoreCoursesProvider.getUserNavigationOptions. + * @param admOptions Course admin options for current user. See CoreCoursesProvider.getUserAdministrationOptions. + * @return Promise resolved when done. */ invalidateEnabledForCourse(courseId: number, navOptions?: any, admOptions?: any): Promise { return this.courseCompletionProvider.invalidateCourseCompletion(courseId); @@ -90,8 +90,8 @@ export class AddonCourseCompletionCourseOptionHandler implements CoreCourseOptio /** * Called when a course is downloaded. It should prefetch all the data to be able to see the addon in offline. * - * @param {any} course The course. - * @return {Promise} Promise resolved when done. + * @param course The course. + * @return Promise resolved when done. */ prefetch(course: any): Promise { return this.courseCompletionProvider.getCompletion(course.id, undefined, { diff --git a/src/addon/coursecompletion/providers/coursecompletion.ts b/src/addon/coursecompletion/providers/coursecompletion.ts index 0a79a4766..85ab726fd 100644 --- a/src/addon/coursecompletion/providers/coursecompletion.ts +++ b/src/addon/coursecompletion/providers/coursecompletion.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -18,6 +18,7 @@ import { CoreSitesProvider } from '@providers/sites'; import { CoreUtilsProvider } from '@providers/utils/utils'; import { CoreCoursesProvider } from '@core/courses/providers/courses'; import { CoreSite } from '@classes/site'; +import { CoreWSExternalWarning } from '@providers/ws'; /** * Service to handle course completion. @@ -39,11 +40,11 @@ export class AddonCourseCompletionProvider { * Returns whether or not the user can mark a course as self completed. * It can if it's configured in the course and it hasn't been completed yet. * - * @param {number} userId User ID. - * @param {any} completion Course completion. - * @return {boolean} True if user can mark course as self completed, false otherwise. + * @param userId User ID. + * @param completion Course completion. + * @return True if user can mark course as self completed, false otherwise. */ - canMarkSelfCompleted(userId: number, completion: any): boolean { + canMarkSelfCompleted(userId: number, completion: AddonCourseCompletionCourseCompletionStatus): boolean { let selfCompletionActive = false, alreadyMarked = false; @@ -65,10 +66,10 @@ export class AddonCourseCompletionProvider { /** * Get completed status text. The language code returned is meant to be translated. * - * @param {any} completion Course completion. - * @return {string} Language code of the text to show. + * @param completion Course completion. + * @return Language code of the text to show. */ - getCompletedStatusText(completion: any): string { + getCompletedStatusText(completion: AddonCourseCompletionCourseCompletionStatus): string { if (completion.completed) { return 'addon.coursecompletion.completed'; } else { @@ -90,13 +91,15 @@ export class AddonCourseCompletionProvider { /** * Get course completion status for a certain course and user. * - * @param {number} courseId Course ID. - * @param {number} [userId] User ID. If not defined, use current user. - * @param {any} [preSets] Presets to use when calling the WebService. - * @param {string} [siteId] Site ID. If not defined, use current site. - * @return {Promise} Promise to be resolved when the completion is retrieved. + * @param courseId Course ID. + * @param userId User ID. If not defined, use current user. + * @param preSets Presets to use when calling the WebService. + * @param siteId Site ID. If not defined, use current site. + * @return Promise to be resolved when the completion is retrieved. */ - getCompletion(courseId: number, userId?: number, preSets?: any, siteId?: string): Promise { + getCompletion(courseId: number, userId?: number, preSets?: any, siteId?: string) + : Promise { + return this.sitesProvider.getSite(siteId).then((site) => { userId = userId || site.getUserId(); preSets = preSets || {}; @@ -112,7 +115,9 @@ export class AddonCourseCompletionProvider { preSets.updateFrequency = preSets.updateFrequency || CoreSite.FREQUENCY_SOMETIMES; preSets.cacheErrors = ['notenroled']; - return site.read('core_completion_get_course_completion_status', data, preSets).then((data) => { + return site.read('core_completion_get_course_completion_status', data, preSets) + .then((data: AddonCourseCompletionGetCourseCompletionStatusResult): any => { + if (data.completionstatus) { return data.completionstatus; } @@ -125,9 +130,9 @@ export class AddonCourseCompletionProvider { /** * Get cache key for get completion WS calls. * - * @param {number} courseId Course ID. - * @param {number} useIid User ID. - * @return {string} Cache key. + * @param courseId Course ID. + * @param useIid User ID. + * @return Cache key. */ protected getCompletionCacheKey(courseId: number, userId: number): string { return this.ROOT_CACHE_KEY + 'view:' + courseId + ':' + userId; @@ -136,9 +141,9 @@ export class AddonCourseCompletionProvider { /** * Invalidates view course completion WS call. * - * @param {number} courseId Course ID. - * @param {number} [userId] User ID. If not defined, use current user. - * @return {Promise} Promise resolved when the list is invalidated. + * @param courseId Course ID. + * @param userId User ID. If not defined, use current user. + * @return Promise resolved when the list is invalidated. */ invalidateCourseCompletion(courseId: number, userId?: number): Promise { userId = userId || this.sitesProvider.getCurrentSiteUserId(); @@ -149,7 +154,7 @@ export class AddonCourseCompletionProvider { /** * Returns whether or not the view course completion plugin is enabled for the current site. * - * @return {boolean} True if plugin enabled, false otherwise. + * @return True if plugin enabled, false otherwise. */ isPluginViewEnabled(): boolean { return this.sitesProvider.isLoggedIn(); @@ -158,9 +163,9 @@ export class AddonCourseCompletionProvider { /** * Returns whether or not the view course completion plugin is enabled for a certain course. * - * @param {number} courseId Course ID. - * @param {boolean} [preferCache=true] True if shouldn't call WS if data is cached, false otherwise. - * @return {Promise} Promise resolved with true if plugin is enabled, rejected or resolved with false otherwise. + * @param courseId Course ID. + * @param preferCache True if shouldn't call WS if data is cached, false otherwise. + * @return Promise resolved with true if plugin is enabled, rejected or resolved with false otherwise. */ isPluginViewEnabledForCourse(courseId: number, preferCache: boolean = true): Promise { if (!courseId) { @@ -187,9 +192,9 @@ export class AddonCourseCompletionProvider { /** * Returns whether or not the view course completion plugin is enabled for a certain user. * - * @param {number} courseId Course ID. - * @param {number} [userId] User ID. If not defined, use current user. - * @return {Promise} Promise resolved with true if plugin is enabled, rejected or resolved with false otherwise. + * @param courseId Course ID. + * @param userId User ID. If not defined, use current user. + * @return Promise resolved with true if plugin is enabled, rejected or resolved with false otherwise. */ isPluginViewEnabledForUser(courseId: number, userId?: number): Promise { // Check if user wants to view his own completion. @@ -242,18 +247,57 @@ export class AddonCourseCompletionProvider { /** * Mark a course as self completed. * - * @param {number} courseId Course ID. - * @return {Promise} Resolved on success. + * @param courseId Course ID. + * @return Promise resolved on success. */ - markCourseAsSelfCompleted(courseId: number): Promise { + markCourseAsSelfCompleted(courseId: number): Promise { const params = { courseid: courseId }; - return this.sitesProvider.getCurrentSite().write('core_completion_mark_course_self_completed', params).then((response) => { + return this.sitesProvider.getCurrentSite().write('core_completion_mark_course_self_completed', params) + .then((response: AddonCourseCompletionMarkCourseSelfCompletedResult) => { + if (!response.status) { return Promise.reject(null); } }); } } + +/** + * Completion status returned by core_completion_get_course_completion_status. + */ +export type AddonCourseCompletionCourseCompletionStatus = { + completed: boolean; // True if the course is complete, false otherwise. + aggregation: number; // Aggregation method 1 means all, 2 means any. + completions: { + type: number; // Completion criteria type. + title: string; // Completion criteria Title. + status: string; // Completion status (Yes/No) a % or number. + complete: boolean; // Completion status (true/false). + timecompleted: number; // Timestamp for criteria completetion. + details: { + type: string; // Type description. + criteria: string; // Criteria description. + requirement: string; // Requirement description. + status: string; // Status description, can be anything. + }; // Details. + }[]; +}; + +/** + * Result of WS core_completion_get_course_completion_status. + */ +export type AddonCourseCompletionGetCourseCompletionStatusResult = { + completionstatus: AddonCourseCompletionCourseCompletionStatus; // Course status. + warnings?: CoreWSExternalWarning[]; +}; + +/** + * Result of WS core_completion_mark_course_self_completed. + */ +export type AddonCourseCompletionMarkCourseSelfCompletedResult = { + status: boolean; // Status, true if success. + warnings?: CoreWSExternalWarning[]; +}; diff --git a/src/addon/coursecompletion/providers/user-handler.ts b/src/addon/coursecompletion/providers/user-handler.ts index 8faf49ccd..51f826b1a 100644 --- a/src/addon/coursecompletion/providers/user-handler.ts +++ b/src/addon/coursecompletion/providers/user-handler.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -42,7 +42,7 @@ export class AddonCourseCompletionUserHandler implements CoreUserProfileHandler /** * Whether or not the handler is enabled on a site level. - * @return {boolean|Promise} Whether or not the handler is enabled on a site level. + * @return Whether or not the handler is enabled on a site level. */ isEnabled(): boolean | Promise { return this.courseCompletionProvider.isPluginViewEnabled(); @@ -51,11 +51,11 @@ export class AddonCourseCompletionUserHandler implements CoreUserProfileHandler /** * Check if handler is enabled for this user in this context. * - * @param {any} user User to check. - * @param {number} courseId Course ID. - * @param {any} [navOptions] Course navigation options for current user. See CoreCoursesProvider.getUserNavigationOptions. - * @param {any} [admOptions] Course admin options for current user. See CoreCoursesProvider.getUserAdministrationOptions. - * @return {boolean|Promise} Promise resolved with true if enabled, resolved with false otherwise. + * @param user User to check. + * @param courseId Course ID. + * @param navOptions Course navigation options for current user. See CoreCoursesProvider.getUserNavigationOptions. + * @param admOptions Course admin options for current user. See CoreCoursesProvider.getUserAdministrationOptions. + * @return Promise resolved with true if enabled, resolved with false otherwise. */ isEnabledForUser(user: any, courseId: number, navOptions?: any, admOptions?: any): boolean | Promise { if (!courseId) { @@ -84,7 +84,7 @@ export class AddonCourseCompletionUserHandler implements CoreUserProfileHandler /** * Returns the data needed to render the handler. * - * @return {CoreUserProfileHandlerData} Data needed to render the handler. + * @return Data needed to render the handler. */ getDisplayData(user: any, courseId: number): CoreUserProfileHandlerData { return { diff --git a/src/addon/files/files.module.ts b/src/addon/files/files.module.ts index f87e1344b..b1233d7b3 100644 --- a/src/addon/files/files.module.ts +++ b/src/addon/files/files.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/addon/files/pages/list/list.html b/src/addon/files/pages/list/list.html index 06fdce330..b119855ca 100644 --- a/src/addon/files/pages/list/list.html +++ b/src/addon/files/pages/list/list.html @@ -1,6 +1,6 @@ - + {{ title }} @@ -8,7 +8,7 @@ - +
diff --git a/src/addon/files/pages/list/list.module.ts b/src/addon/files/pages/list/list.module.ts index 94fd35db4..ad8bf4652 100644 --- a/src/addon/files/pages/list/list.module.ts +++ b/src/addon/files/pages/list/list.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/addon/files/pages/list/list.ts b/src/addon/files/pages/list/list.ts index 2867acb5c..99b58c774 100644 --- a/src/addon/files/pages/list/list.ts +++ b/src/addon/files/pages/list/list.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -20,7 +20,7 @@ import { CoreEventsProvider } from '@providers/events'; import { CoreSitesProvider } from '@providers/sites'; import { CoreDomUtilsProvider } from '@providers/utils/dom'; import { CoreTextUtilsProvider } from '@providers/utils/text'; -import { AddonFilesProvider } from '../../providers/files'; +import { AddonFilesProvider, AddonFilesFile, AddonFilesGetUserPrivateFilesInfoResult } from '../../providers/files'; import { AddonFilesHelperProvider } from '../../providers/helper'; /** @@ -40,10 +40,10 @@ export class AddonFilesListPage implements OnDestroy { root: string; // The root of the files loaded: 'my' or 'site'. path: string; // The path of the directory being loaded. If empty path it means the root is being loaded. userQuota: number; // The user quota (in bytes). - filesInfo: any; // Info about private files (size, number of files, etc.). + filesInfo: AddonFilesGetUserPrivateFilesInfoResult; // Info about private files (size, number of files, etc.). spaceUsed: string; // Space used in a readable format. userQuotaReadable: string; // User quota in a readable format. - files: any[]; // List of files. + files: AddonFilesFile[]; // List of files. component: string; // Component to link the file downloads to. filesLoaded: boolean; // Whether the files are loaded. @@ -89,7 +89,7 @@ export class AddonFilesListPage implements OnDestroy { /** * Refresh the data. * - * @param {any} refresher Refresher. + * @param refresher Refresher. */ refreshData(refresher: any): void { this.refreshFiles().finally(() => { @@ -144,10 +144,10 @@ export class AddonFilesListPage implements OnDestroy { /** * Fetch the files. * - * @return {Promise} Promise resolved when done. + * @return Promise resolved when done. */ protected fetchFiles(): Promise { - let promise; + let promise: Promise; if (!this.path) { // The path is unknown, the user must be requesting a root. @@ -193,7 +193,7 @@ export class AddonFilesListPage implements OnDestroy { /** * Refresh the displayed files. * - * @return {Promise} Promise resolved when done. + * @return Promise resolved when done. */ protected refreshFiles(): Promise { const promises = []; diff --git a/src/addon/files/providers/files.ts b/src/addon/files/providers/files.ts index 1db49f1da..bae3eaf51 100644 --- a/src/addon/files/providers/files.ts +++ b/src/addon/files/providers/files.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -16,6 +16,7 @@ import { Injectable } from '@angular/core'; import { CoreSitesProvider } from '@providers/sites'; import { CoreMimetypeUtilsProvider } from '@providers/utils/mimetype'; import { CoreSite } from '@classes/site'; +import { CoreWSExternalWarning } from '@providers/ws'; /** * Service to handle my files and site files. @@ -31,7 +32,7 @@ export class AddonFilesProvider { /** * Check if core_user_get_private_files_info WS call is available. * - * @return {boolean} Whether the WS is available, false otherwise. + * @return Whether the WS is available, false otherwise. */ canGetPrivateFilesInfo(): boolean { return this.sitesProvider.wsAvailableInCurrentSite('core_user_get_private_files_info'); @@ -40,7 +41,7 @@ export class AddonFilesProvider { /** * Check if user can view his private files. * - * @return {boolean} Whether the user can view his private files. + * @return Whether the user can view his private files. */ canViewPrivateFiles(): boolean { return this.sitesProvider.getCurrentSite().canAccessMyFiles() && !this.isPrivateFilesDisabledInSite(); @@ -49,7 +50,7 @@ export class AddonFilesProvider { /** * Check if user can view site files. * - * @return {boolean} Whether the user can view site files. + * @return Whether the user can view site files. */ canViewSiteFiles(): boolean { return !this.isSiteFilesDisabledInSite(); @@ -58,7 +59,7 @@ export class AddonFilesProvider { /** * Check if user can upload private files. * - * @return {boolean} Whether the user can upload private files. + * @return Whether the user can upload private files. */ canUploadFiles(): boolean { const currentSite = this.sitesProvider.getCurrentSite(); @@ -69,11 +70,11 @@ export class AddonFilesProvider { /** * Get the list of files. * - * @param {any} params A list of parameters accepted by the Web service. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with the files. + * @param params A list of parameters accepted by the Web service. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the files. */ - getFiles(params: any, siteId?: string): Promise { + getFiles(params: any, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { const preSets = { @@ -82,15 +83,15 @@ export class AddonFilesProvider { }; return site.read('core_files_get_files', params, preSets); - }).then((result) => { - const entries = []; + }).then((result: AddonFilesGetFilesResult) => { + const entries: AddonFilesFile[] = []; if (result.files) { result.files.forEach((entry) => { if (entry.isdir) { // Create a "link" to load the folder. entry.link = { - contextid: entry.contextid || '', + contextid: entry.contextid || null, component: entry.component || '', filearea: entry.filearea || '', itemid: entry.itemid || 0, @@ -121,8 +122,8 @@ export class AddonFilesProvider { /** * Get cache key for file list WS calls. * - * @param {any} params Params of the WS. - * @return {string} Cache key. + * @param params Params of the WS. + * @return Cache key. */ protected getFilesListCacheKey(params: any): string { const root = !params.component ? 'site' : 'my'; @@ -133,16 +134,16 @@ export class AddonFilesProvider { /** * Get the private files of the current user. * - * @return {Promise} Promise resolved with the files. + * @return Promise resolved with the files. */ - getPrivateFiles(): Promise { + getPrivateFiles(): Promise { return this.getFiles(this.getPrivateFilesRootParams()); } /** * Get params to get root private files directory. * - * @return {any} Params. + * @return Params. */ protected getPrivateFilesRootParams(): any { return { @@ -160,11 +161,11 @@ export class AddonFilesProvider { /** * Get private files info. * - * @param {number} [userId] User ID. If not defined, current user in the site. - * @param {string} [siteId] Site ID. If not defined, use current site. - * @return {Promise} Promise resolved with the info. + * @param userId User ID. If not defined, current user in the site. + * @param siteId Site ID. If not defined, use current site. + * @return Promise resolved with the info. */ - getPrivateFilesInfo(userId?: number, siteId?: string): Promise { + getPrivateFilesInfo(userId?: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { userId = userId || site.getUserId(); @@ -183,8 +184,8 @@ export class AddonFilesProvider { /** * Get the cache key for private files info WS calls. * - * @param {number} userId User ID. - * @return {string} Cache key. + * @param userId User ID. + * @return Cache key. */ protected getPrivateFilesInfoCacheKey(userId: number): string { return this.getPrivateFilesInfoCommonCacheKey() + ':' + userId; @@ -193,7 +194,7 @@ export class AddonFilesProvider { /** * Get the common part of the cache keys for private files info WS calls. * - * @return {string} Cache key. + * @return Cache key. */ protected getPrivateFilesInfoCommonCacheKey(): string { return this.ROOT_CACHE_KEY + 'privateInfo'; @@ -202,16 +203,16 @@ export class AddonFilesProvider { /** * Get the site files. * - * @return {Promise} Promise resolved with the files. + * @return Promise resolved with the files. */ - getSiteFiles(): Promise { + getSiteFiles(): Promise { return this.getFiles(this.getSiteFilesRootParams()); } /** * Get params to get root site files directory. * - * @return {any} Params. + * @return Params. */ protected getSiteFilesRootParams(): any { return { @@ -227,10 +228,10 @@ export class AddonFilesProvider { /** * Invalidates list of files in a certain directory. * - * @param {string} root Root of the directory ('my' for private files, 'site' for site files). - * @param {string} path Path to the directory. - * @param {string} [siteId] Site ID. If not defined, use current site. - * @return {Promise} Promise resolved when the data is invalidated. + * @param root Root of the directory ('my' for private files, 'site' for site files). + * @param path Path to the directory. + * @param siteId Site ID. If not defined, use current site. + * @return Promise resolved when the data is invalidated. */ invalidateDirectory(root: string, path: string, siteId?: string): Promise { let params; @@ -252,8 +253,8 @@ export class AddonFilesProvider { /** * Invalidates private files info for all users. * - * @param {string} [siteId] Site ID. If not defined, use current site. - * @return {Promise} Promise resolved when the data is invalidated. + * @param siteId Site ID. If not defined, use current site. + * @return Promise resolved when the data is invalidated. */ invalidatePrivateFilesInfo(siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -264,9 +265,9 @@ export class AddonFilesProvider { /** * Invalidates private files info for a certain user. * - * @param {number} [userId] User ID. If not defined, current user in the site. - * @param {string} [siteId] Site ID. If not defined, use current site. - * @return {Promise} Promise resolved when the data is invalidated. + * @param userId User ID. If not defined, current user in the site. + * @param siteId Site ID. If not defined, use current site. + * @return Promise resolved when the data is invalidated. */ invalidatePrivateFilesInfoForUser(userId?: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -279,8 +280,8 @@ export class AddonFilesProvider { /** * Check if Files is disabled in a certain site. * - * @param {string} [siteId] Site Id. If not defined, use current site. - * @return {Promise} Promise resolved with true if disabled, rejected or resolved with false otherwise. + * @param siteId Site Id. If not defined, use current site. + * @return Promise resolved with true if disabled, rejected or resolved with false otherwise. */ isDisabled(siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -291,8 +292,8 @@ export class AddonFilesProvider { /** * Check if Files is disabled in a certain site. * - * @param {CoreSite} [site] Site. If not defined, use current site. - * @return {boolean} Whether it's disabled. + * @param site Site. If not defined, use current site. + * @return Whether it's disabled. */ isDisabledInSite(site: CoreSite): boolean { site = site || this.sitesProvider.getCurrentSite(); @@ -303,7 +304,7 @@ export class AddonFilesProvider { /** * Return whether or not the plugin is enabled. * - * @return {boolean} True if enabled, false otherwise. + * @return True if enabled, false otherwise. */ isPluginEnabled(): boolean { return this.canViewPrivateFiles() || this.canViewSiteFiles() || this.canUploadFiles(); @@ -312,8 +313,8 @@ export class AddonFilesProvider { /** * Check if private files is disabled in a certain site. * - * @param {string} [siteId] Site Id. If not defined, use current site. - * @return {Promise} Promise resolved with true if disabled, rejected or resolved with false otherwise. + * @param siteId Site Id. If not defined, use current site. + * @return Promise resolved with true if disabled, rejected or resolved with false otherwise. */ isPrivateFilesDisabled(siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -324,8 +325,8 @@ export class AddonFilesProvider { /** * Check if private files is disabled in a certain site. * - * @param {CoreSite} [site] Site. If not defined, use current site. - * @return {boolean} Whether it's disabled. + * @param site Site. If not defined, use current site. + * @return Whether it's disabled. */ isPrivateFilesDisabledInSite(site?: CoreSite): boolean { site = site || this.sitesProvider.getCurrentSite(); @@ -336,8 +337,8 @@ export class AddonFilesProvider { /** * Check if site files is disabled in a certain site. * - * @param {string} [siteId] Site Id. If not defined, use current site. - * @return {Promise} Promise resolved with true if disabled, rejected or resolved with false otherwise. + * @param siteId Site Id. If not defined, use current site. + * @return Promise resolved with true if disabled, rejected or resolved with false otherwise. */ isSiteFilesDisabled(siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -348,8 +349,8 @@ export class AddonFilesProvider { /** * Check if site files is disabled in a certain site. * - * @param {CoreSite} [site] Site. If not defined, use current site. - * @return {boolean} Whether it's disabled. + * @param site Site. If not defined, use current site. + * @return Whether it's disabled. */ isSiteFilesDisabledInSite(site?: CoreSite): boolean { site = site || this.sitesProvider.getCurrentSite(); @@ -360,8 +361,8 @@ export class AddonFilesProvider { /** * Check if upload files is disabled in a certain site. * - * @param {string} [siteId] Site Id. If not defined, use current site. - * @return {Promise} Promise resolved with true if disabled, rejected or resolved with false otherwise. + * @param siteId Site Id. If not defined, use current site. + * @return Promise resolved with true if disabled, rejected or resolved with false otherwise. */ isUploadDisabled(siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -372,8 +373,8 @@ export class AddonFilesProvider { /** * Check if upload files is disabled in a certain site. * - * @param {CoreSite} [site] Site. If not defined, use current site. - * @return {boolean} Whether it's disabled. + * @param site Site. If not defined, use current site. + * @return Whether it's disabled. */ isUploadDisabledInSite(site?: CoreSite): boolean { site = site || this.sitesProvider.getCurrentSite(); @@ -384,11 +385,11 @@ export class AddonFilesProvider { /** * Move a file from draft area to private files. * - * @param {number} draftId The draft area ID of the file. - * @param {string} [siteid] ID of the site. If not defined, use current site. - * @return {Promise} Promise resolved in success, rejected otherwise. + * @param draftId The draft area ID of the file. + * @param siteid ID of the site. If not defined, use current site. + * @return Promise resolved in success, rejected otherwise. */ - moveFromDraftToPrivate(draftId: number, siteId?: string): Promise { + moveFromDraftToPrivate(draftId: number, siteId?: string): Promise { const params = { draftid: draftId }, @@ -404,8 +405,8 @@ export class AddonFilesProvider { /** * Check the Moodle version in order to check if upload files is working. * - * @param {string} [siteId] Site ID. If not defined, use current site. - * @return {Promise} Promise resolved with true if WS is working, false otherwise. + * @param siteId Site ID. If not defined, use current site. + * @return Promise resolved with true if WS is working, false otherwise. */ versionCanUploadFiles(siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -414,3 +415,63 @@ export class AddonFilesProvider { }); } } + +/** + * File data returned by core_files_get_files. + */ +export type AddonFilesFile = { + contextid: number; + component: string; + filearea: string; + itemid: number; + filepath: string; + filename: string; + isdir: boolean; + url: string; + timemodified: number; + timecreated?: number; // Time created. + filesize?: number; // File size. + author?: string; // File owner. + license?: string; // File license. +} & AddonFilesFileCalculatedData; + +/** + * Result of WS core_files_get_files. + */ +export type AddonFilesGetFilesResult = { + parents: { + contextid: number; + component: string; + filearea: string; + itemid: number; + filepath: string; + filename: string; + }[]; + files: AddonFilesFile[]; +}; + +/** + * Result of WS core_user_get_private_files_info. + */ +export type AddonFilesGetUserPrivateFilesInfoResult = { + filecount: number; // Number of files in the area. + foldercount: number; // Number of folders in the area. + filesize: number; // Total size of the files in the area. + filesizewithoutreferences: number; // Total size of the area excluding file references. + warnings?: CoreWSExternalWarning[]; +}; + +/** + * Calculated data for AddonFilesFile. + */ +export type AddonFilesFileCalculatedData = { + link?: { // Calculated in the app. A link to open the folder. + contextid?: number; // Folder's contextid. + component?: string; // Folder's component. + filearea?: string; // Folder's filearea. + itemid?: number; // Folder's itemid. + filepath?: string; // Folder's filepath. + filename?: string; // Folder's filename. + }; + imgPath?: string; // Path to file icon's image. +}; diff --git a/src/addon/files/providers/helper.ts b/src/addon/files/providers/helper.ts index 4962b88e0..d1d0cf6fd 100644 --- a/src/addon/files/providers/helper.ts +++ b/src/addon/files/providers/helper.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -30,8 +30,8 @@ export class AddonFilesHelperProvider { /** * Select a file, upload it and move it to private files. * - * @param {any} [info] Private files info. See AddonFilesProvider.getPrivateFilesInfo. - * @return {Promise} Promise resolved when a file is uploaded, rejected otherwise. + * @param info Private files info. See AddonFilesProvider.getPrivateFilesInfo. + * @return Promise resolved when a file is uploaded, rejected otherwise. */ uploadPrivateFile(info?: any): Promise { // Calculate the max size. diff --git a/src/addon/files/providers/mainmenu-handler.ts b/src/addon/files/providers/mainmenu-handler.ts index bb47f8cb5..acf2c811e 100644 --- a/src/addon/files/providers/mainmenu-handler.ts +++ b/src/addon/files/providers/mainmenu-handler.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -29,7 +29,7 @@ export class AddonFilesMainMenuHandler implements CoreMainMenuHandler { /** * Check if the handler is enabled on a site level. * - * @return {boolean} Whether or not the handler is enabled on a site level. + * @return Whether or not the handler is enabled on a site level. */ isEnabled(): boolean | Promise { return this.filesProvider.isPluginEnabled(); @@ -38,7 +38,7 @@ export class AddonFilesMainMenuHandler implements CoreMainMenuHandler { /** * Returns the data needed to render the handler. * - * @return {CoreMainMenuHandlerData} Data needed to render the handler. + * @return Data needed to render the handler. */ getDisplayData(): CoreMainMenuHandlerData { return { diff --git a/src/addon/filter/activitynames/activitynames.module.ts b/src/addon/filter/activitynames/activitynames.module.ts new file mode 100644 index 000000000..91d9e6f40 --- /dev/null +++ b/src/addon/filter/activitynames/activitynames.module.ts @@ -0,0 +1,34 @@ +// (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 { NgModule } from '@angular/core'; +import { IonicModule } from 'ionic-angular'; +import { CoreFilterDelegate } from '@core/filter/providers/delegate'; +import { AddonFilterActivityNamesHandler } from './providers/handler'; + +@NgModule({ + declarations: [ + ], + imports: [ + IonicModule + ], + providers: [ + AddonFilterActivityNamesHandler + ] +}) +export class AddonFilterActivityNamesModule { + constructor(filterDelegate: CoreFilterDelegate, handler: AddonFilterActivityNamesHandler) { + filterDelegate.registerHandler(handler); + } +} diff --git a/src/addon/filter/activitynames/providers/handler.ts b/src/addon/filter/activitynames/providers/handler.ts new file mode 100644 index 000000000..e82644bc1 --- /dev/null +++ b/src/addon/filter/activitynames/providers/handler.ts @@ -0,0 +1,45 @@ + +// (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 { Injectable } from '@angular/core'; +import { CoreFilterDefaultHandler } from '@core/filter/providers/default-filter'; +import { CoreFilterFormatTextOptions } from '@core/filter/providers/filter'; +import { CoreSite } from '@classes/site'; + +/** + * Handler to support the Activity names filter. + */ +@Injectable() +export class AddonFilterActivityNamesHandler extends CoreFilterDefaultHandler { + name = 'AddonFilterActivityNamesHandler'; + filterName = 'activitynames'; + + constructor() { + super(); + + // This filter is handled by Moodle, nothing to do in the app. + } + + /** + * Check if the filter should be applied in a certain site based on some filter options. + * + * @param options Options. + * @param site Site. + * @return Whether filter should be applied. + */ + shouldBeApplied(options: CoreFilterFormatTextOptions, site?: CoreSite): boolean { + return false; + } +} diff --git a/src/addon/filter/algebra/algebra.module.ts b/src/addon/filter/algebra/algebra.module.ts new file mode 100644 index 000000000..8b34d7afb --- /dev/null +++ b/src/addon/filter/algebra/algebra.module.ts @@ -0,0 +1,34 @@ +// (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 { NgModule } from '@angular/core'; +import { IonicModule } from 'ionic-angular'; +import { CoreFilterDelegate } from '@core/filter/providers/delegate'; +import { AddonFilterAlgebraHandler } from './providers/handler'; + +@NgModule({ + declarations: [ + ], + imports: [ + IonicModule + ], + providers: [ + AddonFilterAlgebraHandler + ] +}) +export class AddonFilterAlgebraModule { + constructor(filterDelegate: CoreFilterDelegate, handler: AddonFilterAlgebraHandler) { + filterDelegate.registerHandler(handler); + } +} diff --git a/src/addon/filter/algebra/providers/handler.ts b/src/addon/filter/algebra/providers/handler.ts new file mode 100644 index 000000000..5079ff659 --- /dev/null +++ b/src/addon/filter/algebra/providers/handler.ts @@ -0,0 +1,45 @@ + +// (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 { Injectable } from '@angular/core'; +import { CoreFilterDefaultHandler } from '@core/filter/providers/default-filter'; +import { CoreFilterFormatTextOptions } from '@core/filter/providers/filter'; +import { CoreSite } from '@classes/site'; + +/** + * Handler to support the Algebra notation filter. + */ +@Injectable() +export class AddonFilterAlgebraHandler extends CoreFilterDefaultHandler { + name = 'AddonFilterAlgebraHandler'; + filterName = 'algebra'; + + constructor() { + super(); + + // This filter is handled by Moodle, nothing to do in the app. + } + + /** + * Check if the filter should be applied in a certain site based on some filter options. + * + * @param options Options. + * @param site Site. + * @return Whether filter should be applied. + */ + shouldBeApplied(options: CoreFilterFormatTextOptions, site?: CoreSite): boolean { + return false; + } +} diff --git a/src/addon/filter/censor/censor.module.ts b/src/addon/filter/censor/censor.module.ts new file mode 100644 index 000000000..6d6fc5521 --- /dev/null +++ b/src/addon/filter/censor/censor.module.ts @@ -0,0 +1,34 @@ +// (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 { NgModule } from '@angular/core'; +import { IonicModule } from 'ionic-angular'; +import { CoreFilterDelegate } from '@core/filter/providers/delegate'; +import { AddonFilterCensorHandler } from './providers/handler'; + +@NgModule({ + declarations: [ + ], + imports: [ + IonicModule + ], + providers: [ + AddonFilterCensorHandler + ] +}) +export class AddonFilterCensorModule { + constructor(filterDelegate: CoreFilterDelegate, handler: AddonFilterCensorHandler) { + filterDelegate.registerHandler(handler); + } +} diff --git a/src/addon/filter/censor/providers/handler.ts b/src/addon/filter/censor/providers/handler.ts new file mode 100644 index 000000000..b9ce9649f --- /dev/null +++ b/src/addon/filter/censor/providers/handler.ts @@ -0,0 +1,45 @@ + +// (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 { Injectable } from '@angular/core'; +import { CoreFilterDefaultHandler } from '@core/filter/providers/default-filter'; +import { CoreFilterFormatTextOptions } from '@core/filter/providers/filter'; +import { CoreSite } from '@classes/site'; + +/** + * Handler to support the Word censorship filter. + */ +@Injectable() +export class AddonFilterCensorHandler extends CoreFilterDefaultHandler { + name = 'AddonFilterCensorHandler'; + filterName = 'censor'; + + constructor() { + super(); + + // This filter is handled by Moodle, nothing to do in the app. + } + + /** + * Check if the filter should be applied in a certain site based on some filter options. + * + * @param options Options. + * @param site Site. + * @return Whether filter should be applied. + */ + shouldBeApplied(options: CoreFilterFormatTextOptions, site?: CoreSite): boolean { + return false; + } +} diff --git a/src/addon/filter/data/data.module.ts b/src/addon/filter/data/data.module.ts new file mode 100644 index 000000000..e76812085 --- /dev/null +++ b/src/addon/filter/data/data.module.ts @@ -0,0 +1,34 @@ +// (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 { NgModule } from '@angular/core'; +import { IonicModule } from 'ionic-angular'; +import { CoreFilterDelegate } from '@core/filter/providers/delegate'; +import { AddonFilterDataHandler } from './providers/handler'; + +@NgModule({ + declarations: [ + ], + imports: [ + IonicModule + ], + providers: [ + AddonFilterDataHandler + ] +}) +export class AddonFilterDataModule { + constructor(filterDelegate: CoreFilterDelegate, handler: AddonFilterDataHandler) { + filterDelegate.registerHandler(handler); + } +} diff --git a/src/addon/filter/data/providers/handler.ts b/src/addon/filter/data/providers/handler.ts new file mode 100644 index 000000000..b0dfc9c7a --- /dev/null +++ b/src/addon/filter/data/providers/handler.ts @@ -0,0 +1,45 @@ + +// (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 { Injectable } from '@angular/core'; +import { CoreFilterDefaultHandler } from '@core/filter/providers/default-filter'; +import { CoreFilterFormatTextOptions } from '@core/filter/providers/filter'; +import { CoreSite } from '@classes/site'; + +/** + * Handler to support the Database auto-link filter. + */ +@Injectable() +export class AddonFilterDataHandler extends CoreFilterDefaultHandler { + name = 'AddonFilterDataHandler'; + filterName = 'data'; + + constructor() { + super(); + + // This filter is handled by Moodle, nothing to do in the app. + } + + /** + * Check if the filter should be applied in a certain site based on some filter options. + * + * @param options Options. + * @param site Site. + * @return Whether filter should be applied. + */ + shouldBeApplied(options: CoreFilterFormatTextOptions, site?: CoreSite): boolean { + return false; + } +} diff --git a/src/addon/filter/displayh5p/displayh5p.module.ts b/src/addon/filter/displayh5p/displayh5p.module.ts new file mode 100644 index 000000000..8a7197c71 --- /dev/null +++ b/src/addon/filter/displayh5p/displayh5p.module.ts @@ -0,0 +1,34 @@ +// (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 { NgModule } from '@angular/core'; +import { IonicModule } from 'ionic-angular'; +import { CoreFilterDelegate } from '@core/filter/providers/delegate'; +import { AddonFilterDisplayH5PHandler } from './providers/handler'; + +@NgModule({ + declarations: [ + ], + imports: [ + IonicModule + ], + providers: [ + AddonFilterDisplayH5PHandler + ] +}) +export class AddonFilterDisplayH5PModule { + constructor(filterDelegate: CoreFilterDelegate, handler: AddonFilterDisplayH5PHandler) { + filterDelegate.registerHandler(handler); + } +} diff --git a/src/addon/filter/displayh5p/providers/handler.ts b/src/addon/filter/displayh5p/providers/handler.ts new file mode 100644 index 000000000..abbfdb957 --- /dev/null +++ b/src/addon/filter/displayh5p/providers/handler.ts @@ -0,0 +1,98 @@ + +// (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 { Injectable, ViewContainerRef, ComponentFactoryResolver } from '@angular/core'; +import { CoreFilterDefaultHandler } from '@core/filter/providers/default-filter'; +import { CoreFilterFilter, CoreFilterFormatTextOptions } from '@core/filter/providers/filter'; +import { CoreH5PPlayerComponent } from '@core/h5p/components/h5p-player/h5p-player'; + +/** + * Handler to support the Display H5P filter. + */ +@Injectable() +export class AddonFilterDisplayH5PHandler extends CoreFilterDefaultHandler { + name = 'AddonFilterDisplayH5PHandler'; + filterName = 'displayh5p'; + + protected template = document.createElement('template'); // A template element to convert HTML to element. + + constructor(protected factoryResolver: ComponentFactoryResolver) { + super(); + } + + /** + * Filter some text. + * + * @param text The text to filter. + * @param filter The filter. + * @param options Options passed to the filters. + * @param siteId Site ID. If not defined, current site. + * @return Filtered text (or promise resolved with the filtered text). + */ + filter(text: string, filter: CoreFilterFilter, options: CoreFilterFormatTextOptions, siteId?: string) + : string | Promise { + + this.template.innerHTML = text; + + const h5pIframes = Array.from(this.template.content.querySelectorAll('iframe.h5p-iframe')); + + // Replace all iframes with an empty div that will be treated in handleHtml. + h5pIframes.forEach((iframe) => { + const placeholder = document.createElement('div'); + + placeholder.classList.add('core-h5p-tmp-placeholder'); + placeholder.setAttribute('data-player-src', iframe.src); + + iframe.parentElement.replaceChild(placeholder, iframe); + }); + + return this.template.innerHTML; + } + + /** + * Handle HTML. This function is called after "filter", and it will receive an HTMLElement containing the text that was + * filtered. + * + * @param container The HTML container to handle. + * @param filter The filter. + * @param options Options passed to the filters. + * @param viewContainerRef The ViewContainerRef where the container is. + * @param component Component. + * @param componentId Component ID. + * @param siteId Site ID. If not defined, current site. + * @return If async, promise resolved when done. + */ + handleHtml(container: HTMLElement, filter: CoreFilterFilter, options: CoreFilterFormatTextOptions, + viewContainerRef: ViewContainerRef, component?: string, componentId?: string | number, siteId?: string) + : void | Promise { + + const placeholders = Array.from(container.querySelectorAll('div.core-h5p-tmp-placeholder')); + + placeholders.forEach((placeholder) => { + const url = placeholder.getAttribute('data-player-src'); + + // Create the component to display the player. + const factory = this.factoryResolver.resolveComponentFactory(CoreH5PPlayerComponent), + componentRef = viewContainerRef.createComponent(factory); + + componentRef.instance.src = url; + componentRef.instance.component = component; + componentRef.instance.componentId = componentId; + + // Move the component to its right position. + placeholder.parentElement.replaceChild(componentRef.instance.elementRef.nativeElement, placeholder); + }); + } +} diff --git a/src/addon/filter/emailprotect/emailprotect.module.ts b/src/addon/filter/emailprotect/emailprotect.module.ts new file mode 100644 index 000000000..a722980be --- /dev/null +++ b/src/addon/filter/emailprotect/emailprotect.module.ts @@ -0,0 +1,34 @@ +// (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 { NgModule } from '@angular/core'; +import { IonicModule } from 'ionic-angular'; +import { CoreFilterDelegate } from '@core/filter/providers/delegate'; +import { AddonFilterEmailProtectHandler } from './providers/handler'; + +@NgModule({ + declarations: [ + ], + imports: [ + IonicModule + ], + providers: [ + AddonFilterEmailProtectHandler + ] +}) +export class AddonFilterEmailProtectModule { + constructor(filterDelegate: CoreFilterDelegate, handler: AddonFilterEmailProtectHandler) { + filterDelegate.registerHandler(handler); + } +} diff --git a/src/addon/filter/emailprotect/providers/handler.ts b/src/addon/filter/emailprotect/providers/handler.ts new file mode 100644 index 000000000..77b431bde --- /dev/null +++ b/src/addon/filter/emailprotect/providers/handler.ts @@ -0,0 +1,45 @@ + +// (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 { Injectable } from '@angular/core'; +import { CoreFilterDefaultHandler } from '@core/filter/providers/default-filter'; +import { CoreFilterFormatTextOptions } from '@core/filter/providers/filter'; +import { CoreSite } from '@classes/site'; + +/** + * Handler to support the Email protection filter. + */ +@Injectable() +export class AddonFilterEmailProtectHandler extends CoreFilterDefaultHandler { + name = 'AddonFilterEmailProtectHandler'; + filterName = 'emailprotect'; + + constructor() { + super(); + + // This filter is handled by Moodle, nothing to do in the app. + } + + /** + * Check if the filter should be applied in a certain site based on some filter options. + * + * @param options Options. + * @param site Site. + * @return Whether filter should be applied. + */ + shouldBeApplied(options: CoreFilterFormatTextOptions, site?: CoreSite): boolean { + return false; + } +} diff --git a/src/addon/filter/emoticon/emoticon.module.ts b/src/addon/filter/emoticon/emoticon.module.ts new file mode 100644 index 000000000..9b975cea4 --- /dev/null +++ b/src/addon/filter/emoticon/emoticon.module.ts @@ -0,0 +1,34 @@ +// (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 { NgModule } from '@angular/core'; +import { IonicModule } from 'ionic-angular'; +import { CoreFilterDelegate } from '@core/filter/providers/delegate'; +import { AddonFilterEmoticonHandler } from './providers/handler'; + +@NgModule({ + declarations: [ + ], + imports: [ + IonicModule + ], + providers: [ + AddonFilterEmoticonHandler + ] +}) +export class AddonFilterEmoticonModule { + constructor(filterDelegate: CoreFilterDelegate, handler: AddonFilterEmoticonHandler) { + filterDelegate.registerHandler(handler); + } +} diff --git a/src/addon/filter/emoticon/providers/handler.ts b/src/addon/filter/emoticon/providers/handler.ts new file mode 100644 index 000000000..7b403231f --- /dev/null +++ b/src/addon/filter/emoticon/providers/handler.ts @@ -0,0 +1,45 @@ + +// (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 { Injectable } from '@angular/core'; +import { CoreFilterDefaultHandler } from '@core/filter/providers/default-filter'; +import { CoreFilterFormatTextOptions } from '@core/filter/providers/filter'; +import { CoreSite } from '@classes/site'; + +/** + * Handler to support the Emoticon filter. + */ +@Injectable() +export class AddonFilterEmoticonHandler extends CoreFilterDefaultHandler { + name = 'AddonFilterEmoticonHandler'; + filterName = 'emoticon'; + + constructor() { + super(); + + // This filter is handled by Moodle, nothing to do in the app. + } + + /** + * Check if the filter should be applied in a certain site based on some filter options. + * + * @param options Options. + * @param site Site. + * @return Whether filter should be applied. + */ + shouldBeApplied(options: CoreFilterFormatTextOptions, site?: CoreSite): boolean { + return false; + } +} diff --git a/src/addon/filter/filter.module.ts b/src/addon/filter/filter.module.ts new file mode 100644 index 000000000..29ab01243 --- /dev/null +++ b/src/addon/filter/filter.module.ts @@ -0,0 +1,53 @@ +// (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 { NgModule } from '@angular/core'; +import { AddonFilterActivityNamesModule } from './activitynames/activitynames.module'; +import { AddonFilterAlgebraModule } from './algebra/algebra.module'; +import { AddonFilterCensorModule } from './censor/censor.module'; +import { AddonFilterDataModule } from './data/data.module'; +import { AddonFilterDisplayH5PModule } from './displayh5p/displayh5p.module'; +import { AddonFilterEmailProtectModule } from './emailprotect/emailprotect.module'; +import { AddonFilterEmoticonModule } from './emoticon/emoticon.module'; +import { AddonFilterGlossaryModule } from './glossary/glossary.module'; +import { AddonFilterMathJaxLoaderModule } from './mathjaxloader/mathjaxloader.module'; +import { AddonFilterMediaPluginModule } from './mediaplugin/mediaplugin.module'; +import { AddonFilterMultilangModule } from './multilang/multilang.module'; +import { AddonFilterTexModule } from './tex/tex.module'; +import { AddonFilterTidyModule } from './tidy/tidy.module'; +import { AddonFilterUrlToLinkModule } from './urltolink/urltolink.module'; + +@NgModule({ + declarations: [], + imports: [ + AddonFilterActivityNamesModule, + AddonFilterAlgebraModule, + AddonFilterCensorModule, + AddonFilterDataModule, + AddonFilterDisplayH5PModule, + AddonFilterEmailProtectModule, + AddonFilterEmoticonModule, + AddonFilterGlossaryModule, + AddonFilterMathJaxLoaderModule, + AddonFilterMediaPluginModule, + AddonFilterMultilangModule, + AddonFilterTexModule, + AddonFilterTidyModule, + AddonFilterUrlToLinkModule + ], + providers: [ + ], + exports: [] +}) +export class AddonFilterModule { } diff --git a/src/addon/filter/glossary/glossary.module.ts b/src/addon/filter/glossary/glossary.module.ts new file mode 100644 index 000000000..a5bae30c8 --- /dev/null +++ b/src/addon/filter/glossary/glossary.module.ts @@ -0,0 +1,34 @@ +// (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 { NgModule } from '@angular/core'; +import { IonicModule } from 'ionic-angular'; +import { CoreFilterDelegate } from '@core/filter/providers/delegate'; +import { AddonFilterGlossaryHandler } from './providers/handler'; + +@NgModule({ + declarations: [ + ], + imports: [ + IonicModule + ], + providers: [ + AddonFilterGlossaryHandler + ] +}) +export class AddonFilterGlossaryModule { + constructor(filterDelegate: CoreFilterDelegate, handler: AddonFilterGlossaryHandler) { + filterDelegate.registerHandler(handler); + } +} diff --git a/src/addon/filter/glossary/providers/handler.ts b/src/addon/filter/glossary/providers/handler.ts new file mode 100644 index 000000000..c89489b12 --- /dev/null +++ b/src/addon/filter/glossary/providers/handler.ts @@ -0,0 +1,45 @@ + +// (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 { Injectable } from '@angular/core'; +import { CoreFilterDefaultHandler } from '@core/filter/providers/default-filter'; +import { CoreFilterFormatTextOptions } from '@core/filter/providers/filter'; +import { CoreSite } from '@classes/site'; + +/** + * Handler to support the Glossary auto-link filter. + */ +@Injectable() +export class AddonFilterGlossaryHandler extends CoreFilterDefaultHandler { + name = 'AddonFilterGlossaryHandler'; + filterName = 'glossary'; + + constructor() { + super(); + + // This filter is handled by Moodle, nothing to do in the app. + } + + /** + * Check if the filter should be applied in a certain site based on some filter options. + * + * @param options Options. + * @param site Site. + * @return Whether filter should be applied. + */ + shouldBeApplied(options: CoreFilterFormatTextOptions, site?: CoreSite): boolean { + return false; + } +} diff --git a/src/addon/filter/mathjaxloader/mathjaxloader.module.ts b/src/addon/filter/mathjaxloader/mathjaxloader.module.ts new file mode 100644 index 000000000..5d78641ca --- /dev/null +++ b/src/addon/filter/mathjaxloader/mathjaxloader.module.ts @@ -0,0 +1,34 @@ +// (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 { NgModule } from '@angular/core'; +import { IonicModule } from 'ionic-angular'; +import { CoreFilterDelegate } from '@core/filter/providers/delegate'; +import { AddonFilterMathJaxLoaderHandler } from './providers/handler'; + +@NgModule({ + declarations: [ + ], + imports: [ + IonicModule + ], + providers: [ + AddonFilterMathJaxLoaderHandler + ] +}) +export class AddonFilterMathJaxLoaderModule { + constructor(filterDelegate: CoreFilterDelegate, handler: AddonFilterMathJaxLoaderHandler) { + filterDelegate.registerHandler(handler); + } +} diff --git a/src/addon/filter/mathjaxloader/providers/handler.ts b/src/addon/filter/mathjaxloader/providers/handler.ts new file mode 100644 index 000000000..beb3a1cb0 --- /dev/null +++ b/src/addon/filter/mathjaxloader/providers/handler.ts @@ -0,0 +1,394 @@ +// (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 { Injectable, ViewContainerRef } from '@angular/core'; +import { CoreFilterDefaultHandler } from '@core/filter/providers/default-filter'; +import { CoreFilterFilter, CoreFilterFormatTextOptions } from '@core/filter/providers/filter'; +import { CoreEventsProvider } from '@providers/events'; +import { CoreLangProvider } from '@providers/lang'; +import { CoreSitesProvider } from '@providers/sites'; +import { CoreTextUtilsProvider } from '@providers/utils/text'; +import { CoreUtilsProvider } from '@providers/utils/utils'; +import { CoreSite } from '@classes/site'; + +/** + * Handler to support the MathJax filter. + */ +@Injectable() +export class AddonFilterMathJaxLoaderHandler extends CoreFilterDefaultHandler { + name = 'AddonFilterMathJaxLoaderHandler'; + filterName = 'mathjaxloader'; + + // Default values for MathJax config for sites where we cannot retrieve it. + protected DEFAULT_URL = 'https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.2/MathJax.js'; + protected DEFAULT_CONFIG = ` + MathJax.Hub.Config({ + extensions: [ + "Safe.js", + "tex2jax.js", + "mml2jax.js", + "MathEvents.js", + "MathZoom.js", + "MathMenu.js", + "toMathML.js", + "TeX/noErrors.js", + "TeX/noUndefined.js", + "TeX/AMSmath.js", + "TeX/AMSsymbols.js", + "fast-preview.js", + "AssistiveMML.js", + "[a11y]/accessibility-menu.js" + ], + jax: ["input/TeX","input/MathML","output/SVG"], + showMathMenu: false, + errorSettings: { message: ["!"] }, + skipStartupTypeset: true, + messageStyle: "none" + }); + `; + + // List of language codes found in the MathJax/localization/ directory. + protected MATHJAX_LANG_CODES = [ + 'ar', 'ast', 'bcc', 'bg', 'br', 'ca', 'cdo', 'ce', 'cs', 'cy', 'da', 'de', 'diq', 'en', 'eo', 'es', 'fa', + 'fi', 'fr', 'gl', 'he', 'ia', 'it', 'ja', 'kn', 'ko', 'lb', 'lki', 'lt', 'mk', 'nl', 'oc', 'pl', 'pt', + 'pt-br', 'qqq', 'ru', 'scn', 'sco', 'sk', 'sl', 'sv', 'th', 'tr', 'uk', 'vi', 'zh-hans', 'zh-hant' + ]; + + // List of explicit mappings and known exceptions (moodle => mathjax). + protected EXPLICIT_MAPPING = { + 'zh-tw': 'zh-hant', + 'zh-cn': 'zh-hans', + }; + + protected window: any = window; // Convert the window to to be able to use non-standard properties like MathJax. + + constructor(eventsProvider: CoreEventsProvider, + private langProvider: CoreLangProvider, + private sitesProvider: CoreSitesProvider, + private textUtils: CoreTextUtilsProvider, + private utils: CoreUtilsProvider) { + super(); + + // Load the JS. + this.loadJS(); + + // Get the current language. + this.langProvider.getCurrentLanguage().then((lang) => { + lang = this.mapLanguageCode(lang); + + // Now call the configure function. + this.window.M.filter_mathjaxloader.configure({ + mathjaxconfig: this.DEFAULT_CONFIG, + lang: lang + }); + }); + + // Update MathJax locale if app language changes. + eventsProvider.on(CoreEventsProvider.LANGUAGE_CHANGED, (lang) => { + if (typeof this.window.MathJax != 'undefined') { + lang = this.mapLanguageCode(lang); + + this.window.MathJax.Hub.Queue(() => { + this.window.MathJax.Localization.setLocale(lang); + }); + } + }); + } + + /** + * Filter some text. + * + * @param text The text to filter. + * @param filter The filter. + * @param options Options passed to the filters. + * @param siteId Site ID. If not defined, current site. + * @return Filtered text (or promise resolved with the filtered text). + */ + filter(text: string, filter: CoreFilterFilter, options: CoreFilterFormatTextOptions, siteId?: string) + : string | Promise { + + return this.sitesProvider.getSite(siteId).then((site) => { + + // Don't apply this filter if Moodle is 3.7 or higher and the WS already filtered the content. + if (!options.wsNotFiltered && site.isVersionGreaterEqualThan('3.7')) { + return text; + } + + if (text.indexOf('class="filter_mathjaxloader_equation"') != -1) { + // The content seems to have treated mathjax already, don't do it. + return text; + } + + // We cannot get the filter settings, so we cannot know if it can be used as a replacement for the TeX filter. + // Assume it cannot (default value). + let hasDisplayOrInline = false; + if (text.match(/\\[\[\(]/) || text.match(/\$\$/)) { + // Only parse the text if there are mathjax symbols in it. + // The recognized math environments are \[ \] and $$ $$ for display mathematics and \( \) for inline mathematics. + // Wrap display and inline math environments in nolink spans. + const result = this.wrapMathInNoLink(text); + text = result.text; + hasDisplayOrInline = result.changed; + } + + if (hasDisplayOrInline) { + return '' + text + ''; + } + + return text; + }); + } + + /** + * Handle HTML. This function is called after "filter", and it will receive an HTMLElement containing the text that was + * filtered. + * + * @param container The HTML container to handle. + * @param filter The filter. + * @param options Options passed to the filters. + * @param viewContainerRef The ViewContainerRef where the container is. + * @param component Component. + * @param componentId Component ID. + * @param siteId Site ID. If not defined, current site. + * @return If async, promise resolved when done. + */ + handleHtml(container: HTMLElement, filter: CoreFilterFilter, options: CoreFilterFormatTextOptions, + viewContainerRef: ViewContainerRef, component?: string, componentId?: string | number, siteId?: string) + : void | Promise { + + return this.waitForReady().then(() => { + this.window.M.filter_mathjaxloader.typeset(container); + }); + } + + /** + * Wrap a portion of the $text inside a no link span. The whole text is then returned. + * + * @param text The text to modify. + * @param start The start index of the substring in text that should be wrapped in the span. + * @param end The end index of the substring in text that should be wrapped in the span. + * @return The whole text with the span inserted around the defined substring. + */ + protected insertSpan(text: string, start: number, end: number): string { + return this.textUtils.substrReplace(text, + '' + text.substr(start, end - start + 1) + '', + start, + end - start + 1); + } + + /** + * Check if the JS library has been loaded. + * + * @return Whether the library has been loaded. + */ + protected jsLoaded(): boolean { + return this.window.M && this.window.M.filter_mathjaxloader; + } + + /** + * Load the JS to make MathJax work in the app. The JS loaded is extracted from Moodle filter's loader JS file. + */ + protected loadJS(): void { + // tslint:disable: no-this-assignment + const that = this; + + this.window.M = this.window.M || {}; + this.window.M.filter_mathjaxloader = this.window.M.filter_mathjaxloader || { + _lang: '', + _configured: false, + // Add the configuration to the head and set the lang. + configure: function (params: any): void { + // Add a js configuration object to the head. + const script = document.createElement('script'); + script.type = 'text/x-mathjax-config'; + script.text = params.mathjaxconfig; + document.head.appendChild(script); + + // Save the lang config until MathJax is actually loaded. + this._lang = params.lang; + }, + // Set the correct language for the MathJax menus. + _setLocale: function (): void { + if (!this._configured) { + const lang = this._lang; + + if (typeof that.window.MathJax != 'undefined') { + that.window.MathJax.Hub.Queue(() => { + that.window.MathJax.Localization.setLocale(lang); + }); + that.window.MathJax.Hub.Configured(); + this._configured = true; + } + } + }, + // Called by the filter when an equation is found while rendering the page. + typeset: function (container: HTMLElement): void { + if (!this._configured) { + this._setLocale(); + } + + if (typeof that.window.MathJax != 'undefined') { + const processDelay = that.window.MathJax.Hub.processSectionDelay; + // Set the process section delay to 0 when updating the formula. + that.window.MathJax.Hub.processSectionDelay = 0; + + const equations = Array.from(container.querySelectorAll('.filter_mathjaxloader_equation')); + equations.forEach((node) => { + that.window.MathJax.Hub.Queue(['Typeset', that.window.MathJax.Hub, node]); + }); + + // Set the delay back to normal after processing. + that.window.MathJax.Hub.processSectionDelay = processDelay; + } + } + }; + } + + /** + * Perform a mapping of the app language code to the equivalent for MathJax. + * + * @param langCode The app language code. + * @return The MathJax language code. + */ + protected mapLanguageCode(langCode: string): string { + + // If defined, explicit mapping takes the highest precedence. + if (this.EXPLICIT_MAPPING[langCode]) { + return this.EXPLICIT_MAPPING[langCode]; + } + + // If there is exact match, it will be probably right. + if (this.MATHJAX_LANG_CODES.indexOf(langCode) != -1) { + return langCode; + } + + // Finally try to find the best matching mathjax pack. + const parts = langCode.split('-'); + if (this.MATHJAX_LANG_CODES.indexOf(parts[0]) != -1) { + return parts[0]; + } + + // No more guessing, use default language. + return this.langProvider.getDefaultLanguage(); + } + + /** + * Check if the filter should be applied in a certain site based on some filter options. + * + * @param options Options. + * @param site Site. + * @return Whether filter should be applied. + */ + shouldBeApplied(options: CoreFilterFormatTextOptions, site?: CoreSite): boolean { + // Only apply the filter if logged in and we're filtering current site. + return site && site.getId() == this.sitesProvider.getCurrentSiteId(); + } + + /** + * Wait for the MathJax library and our JS object to be loaded. + * + * @param retries Number of times this has been retried. + * @return Promise resolved when ready or if it took too long to load. + */ + protected waitForReady(retries: number = 0): Promise { + if ((this.window.MathJax && this.jsLoaded()) || retries >= 20) { + // Loaded or too many retries, stop. + return Promise.resolve(); + } + + const deferred = this.utils.promiseDefer(); + + setTimeout(() => { + return this.waitForReady(retries + 1).finally(() => { + deferred.resolve(); + }); + }, 250); + + return deferred.promise; + } + + /** + * Find math environments in the $text and wrap them in no link spans + * (). If math environments are nested, only + * the outer environment is wrapped in the span. + * + * The recognized math environments are \[ \] and $$ $$ for display + * mathematics and \( \) for inline mathematics. + * + * @param text The text to filter. + * @return Object containing the potentially modified text and a boolean that is true if any changes were made to the text. + */ + protected wrapMathInNoLink(text: string): {text: string, changed: boolean} { + let len = text.length, + i = 1, + displayStart = -1, + displayBracket = false, + displayDollar = false, + inlineStart = -1, + changesDone = false; + + // Loop over the $text once. + while (i < len) { + if (displayStart === -1) { + // No display math has started yet. + if (text[i - 1] === '\\') { + + if (text[i] === '[') { + // Display mode \[ begins. + displayStart = i - 1; + displayBracket = true; + } else if (text[i] === '(') { + // Inline math \( begins, not nested inside display math. + inlineStart = i - 1; + } else if (text[i] === ')' && inlineStart > -1) { + // Inline math ends, not nested inside display math. Wrap the span around it. + text = this.insertSpan(text, inlineStart, i); + + inlineStart = -1; // Reset. + i += 28; // The text length changed due to the . + len += 28; + changesDone = true; + } + + } else if (text[i - 1] === '$' && text[i] === '$') { + // Display mode $$ begins. + displayStart = i - 1; + displayDollar = true; + } + + } else { + // Display math open. + if ((text[i - 1] === '\\' && text[i] === ']' && displayBracket) || + (text[i - 1] === '$' && text[i] === '$' && displayDollar)) { + // Display math ends, wrap the span around it. + text = this.insertSpan(text, displayStart, i); + + displayStart = -1; // Reset. + displayBracket = false; + displayDollar = false; + i += 28; // The text length changed due to the . + len += 28; + changesDone = true; + } + } + + i++; + } + + return { + text: text, + changed: changesDone + }; + } +} diff --git a/src/addon/filter/mediaplugin/mediaplugin.module.ts b/src/addon/filter/mediaplugin/mediaplugin.module.ts new file mode 100644 index 000000000..c29931c91 --- /dev/null +++ b/src/addon/filter/mediaplugin/mediaplugin.module.ts @@ -0,0 +1,34 @@ +// (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 { NgModule } from '@angular/core'; +import { IonicModule } from 'ionic-angular'; +import { CoreFilterDelegate } from '@core/filter/providers/delegate'; +import { AddonFilterMediaPluginHandler } from './providers/handler'; + +@NgModule({ + declarations: [ + ], + imports: [ + IonicModule + ], + providers: [ + AddonFilterMediaPluginHandler + ] +}) +export class AddonFilterMediaPluginModule { + constructor(filterDelegate: CoreFilterDelegate, handler: AddonFilterMediaPluginHandler) { + filterDelegate.registerHandler(handler); + } +} diff --git a/src/addon/filter/mediaplugin/providers/handler.ts b/src/addon/filter/mediaplugin/providers/handler.ts new file mode 100644 index 000000000..e1c5a93fc --- /dev/null +++ b/src/addon/filter/mediaplugin/providers/handler.ts @@ -0,0 +1,91 @@ + +// (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 { Injectable } from '@angular/core'; +import { CoreFilterDefaultHandler } from '@core/filter/providers/default-filter'; +import { CoreFilterFilter, CoreFilterFormatTextOptions } from '@core/filter/providers/filter'; +import { CoreTextUtilsProvider } from '@providers/utils/text'; +import { CoreUrlUtilsProvider } from '@providers/utils/url'; + +/** + * Handler to support the Multimedia filter. + */ +@Injectable() +export class AddonFilterMediaPluginHandler extends CoreFilterDefaultHandler { + name = 'AddonFilterMediaPluginHandler'; + filterName = 'mediaplugin'; + + protected template = document.createElement('template'); // A template element to convert HTML to element. + + constructor(private textUtils: CoreTextUtilsProvider, + private urlUtils: CoreUrlUtilsProvider) { + super(); + } + + /** + * Filter some text. + * + * @param text The text to filter. + * @param filter The filter. + * @param options Options passed to the filters. + * @param siteId Site ID. If not defined, current site. + * @return Filtered text (or promise resolved with the filtered text). + */ + filter(text: string, filter: CoreFilterFilter, options: CoreFilterFormatTextOptions, siteId?: string) + : string | Promise { + + this.template.innerHTML = text; + + const videos = Array.from(this.template.content.querySelectorAll('video')); + + videos.forEach((video) => { + this.treatVideoFilters(video); + }); + + return this.template.innerHTML; + } + + /** + * Treat video filters. Currently only treating youtube video using video JS. + * + * @param el Video element. + * @param navCtrl NavController to use. + */ + protected treatVideoFilters(video: HTMLElement): void { + // Treat Video JS Youtube video links and translate them to iframes. + if (!video.classList.contains('video-js')) { + return; + } + + const data = this.textUtils.parseJSON(video.getAttribute('data-setup') || video.getAttribute('data-setup-lazy') || '{}'), + youtubeUrl = data.techOrder && data.techOrder[0] && data.techOrder[0] == 'youtube' && + this.urlUtils.getYoutubeEmbedUrl(data.sources && data.sources[0] && data.sources[0].src); + + if (!youtubeUrl) { + return; + } + + const iframe = document.createElement('iframe'); + iframe.id = video.id; + iframe.src = youtubeUrl; + iframe.setAttribute('frameborder', '0'); + iframe.setAttribute('allowfullscreen', '1'); + iframe.width = '100%'; + iframe.height = '300'; + + // Replace video tag by the iframe. + video.parentNode.replaceChild(iframe, video); + } +} diff --git a/src/addon/filter/multilang/multilang.module.ts b/src/addon/filter/multilang/multilang.module.ts new file mode 100644 index 000000000..db8328d2b --- /dev/null +++ b/src/addon/filter/multilang/multilang.module.ts @@ -0,0 +1,34 @@ +// (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 { NgModule } from '@angular/core'; +import { IonicModule } from 'ionic-angular'; +import { CoreFilterDelegate } from '@core/filter/providers/delegate'; +import { AddonFilterMultilangHandler } from './providers/handler'; + +@NgModule({ + declarations: [ + ], + imports: [ + IonicModule + ], + providers: [ + AddonFilterMultilangHandler + ] +}) +export class AddonFilterMultilangModule { + constructor(filterDelegate: CoreFilterDelegate, handler: AddonFilterMultilangHandler) { + filterDelegate.registerHandler(handler); + } +} diff --git a/src/addon/filter/multilang/providers/handler.ts b/src/addon/filter/multilang/providers/handler.ts new file mode 100644 index 000000000..8f809624a --- /dev/null +++ b/src/addon/filter/multilang/providers/handler.ts @@ -0,0 +1,88 @@ + +// (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 { Injectable } from '@angular/core'; +import { CoreSitesProvider } from '@providers/sites'; +import { CoreFilterDefaultHandler } from '@core/filter/providers/default-filter'; +import { CoreFilterFilter, CoreFilterFormatTextOptions } from '@core/filter/providers/filter'; +import { CoreLangProvider } from '@providers/lang'; +import { CoreSite } from '@classes/site'; + +/** + * Handler to support the Multilang filter. + */ +@Injectable() +export class AddonFilterMultilangHandler extends CoreFilterDefaultHandler { + name = 'AddonFilterMultilangHandler'; + filterName = 'multilang'; + + constructor(private langProvider: CoreLangProvider, + private sitesProvider: CoreSitesProvider) { + super(); + } + + /** + * Filter some text. + * + * @param text The text to filter. + * @param filter The filter. + * @param options Options passed to the filters. + * @param siteId Site ID. If not defined, current site. + * @return Filtered text (or promise resolved with the filtered text). + */ + filter(text: string, filter: CoreFilterFilter, options: CoreFilterFormatTextOptions, siteId?: string) + : string | Promise { + + return this.sitesProvider.getSite(siteId).then((site) => { + + return this.langProvider.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'); + + 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'); + } else { + // No multi-lang tag found, stop. + return text; + } + } + // Extract contents of current language. + text = text.replace(currentLangRegEx, '$1'); + // Delete the rest of languages + text = text.replace(anyLangRegEx, ''); + + return text; + }); + }); + } + + /** + * Check if the filter should be applied in a certain site based on some filter options. + * + * @param options Options. + * @param site Site. + * @return Whether filter should be applied. + */ + shouldBeApplied(options: CoreFilterFormatTextOptions, site?: CoreSite): boolean { + // The filter should be applied if site is older than 3.7 or the WS didn't filter the text. + return options.wsNotFiltered || !site.isVersionGreaterEqualThan('3.7'); + } +} diff --git a/src/addon/filter/tex/providers/handler.ts b/src/addon/filter/tex/providers/handler.ts new file mode 100644 index 000000000..313f13a9f --- /dev/null +++ b/src/addon/filter/tex/providers/handler.ts @@ -0,0 +1,45 @@ + +// (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 { Injectable } from '@angular/core'; +import { CoreFilterDefaultHandler } from '@core/filter/providers/default-filter'; +import { CoreFilterFormatTextOptions } from '@core/filter/providers/filter'; +import { CoreSite } from '@classes/site'; + +/** + * Handler to support the TeX notation filter. + */ +@Injectable() +export class AddonFilterTexHandler extends CoreFilterDefaultHandler { + name = 'AddonFilterTexHandler'; + filterName = 'tex'; + + constructor() { + super(); + + // This filter is handled by Moodle, nothing to do in the app. + } + + /** + * Check if the filter should be applied in a certain site based on some filter options. + * + * @param options Options. + * @param site Site. + * @return Whether filter should be applied. + */ + shouldBeApplied(options: CoreFilterFormatTextOptions, site?: CoreSite): boolean { + return false; + } +} diff --git a/src/addon/filter/tex/tex.module.ts b/src/addon/filter/tex/tex.module.ts new file mode 100644 index 000000000..2fecf37d7 --- /dev/null +++ b/src/addon/filter/tex/tex.module.ts @@ -0,0 +1,34 @@ +// (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 { NgModule } from '@angular/core'; +import { IonicModule } from 'ionic-angular'; +import { CoreFilterDelegate } from '@core/filter/providers/delegate'; +import { AddonFilterTexHandler } from './providers/handler'; + +@NgModule({ + declarations: [ + ], + imports: [ + IonicModule + ], + providers: [ + AddonFilterTexHandler + ] +}) +export class AddonFilterTexModule { + constructor(filterDelegate: CoreFilterDelegate, handler: AddonFilterTexHandler) { + filterDelegate.registerHandler(handler); + } +} diff --git a/src/addon/filter/tidy/providers/handler.ts b/src/addon/filter/tidy/providers/handler.ts new file mode 100644 index 000000000..d1a3f608a --- /dev/null +++ b/src/addon/filter/tidy/providers/handler.ts @@ -0,0 +1,45 @@ + +// (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 { Injectable } from '@angular/core'; +import { CoreFilterDefaultHandler } from '@core/filter/providers/default-filter'; +import { CoreFilterFormatTextOptions } from '@core/filter/providers/filter'; +import { CoreSite } from '@classes/site'; + +/** + * Handler to support the HTML tidy filter. + */ +@Injectable() +export class AddonFilterTidyHandler extends CoreFilterDefaultHandler { + name = 'AddonFilterTidyHandler'; + filterName = 'tidy'; + + constructor() { + super(); + + // This filter is handled by Moodle, nothing to do in the app. + } + + /** + * Check if the filter should be applied in a certain site based on some filter options. + * + * @param options Options. + * @param site Site. + * @return Whether filter should be applied. + */ + shouldBeApplied(options: CoreFilterFormatTextOptions, site?: CoreSite): boolean { + return false; + } +} diff --git a/src/addon/filter/tidy/tidy.module.ts b/src/addon/filter/tidy/tidy.module.ts new file mode 100644 index 000000000..54782656c --- /dev/null +++ b/src/addon/filter/tidy/tidy.module.ts @@ -0,0 +1,34 @@ +// (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 { NgModule } from '@angular/core'; +import { IonicModule } from 'ionic-angular'; +import { CoreFilterDelegate } from '@core/filter/providers/delegate'; +import { AddonFilterTidyHandler } from './providers/handler'; + +@NgModule({ + declarations: [ + ], + imports: [ + IonicModule + ], + providers: [ + AddonFilterTidyHandler + ] +}) +export class AddonFilterTidyModule { + constructor(filterDelegate: CoreFilterDelegate, handler: AddonFilterTidyHandler) { + filterDelegate.registerHandler(handler); + } +} diff --git a/src/addon/filter/urltolink/providers/handler.ts b/src/addon/filter/urltolink/providers/handler.ts new file mode 100644 index 000000000..cc1aa05cc --- /dev/null +++ b/src/addon/filter/urltolink/providers/handler.ts @@ -0,0 +1,45 @@ + +// (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 { Injectable } from '@angular/core'; +import { CoreFilterDefaultHandler } from '@core/filter/providers/default-filter'; +import { CoreFilterFormatTextOptions } from '@core/filter/providers/filter'; +import { CoreSite } from '@classes/site'; + +/** + * Handler to support the URL to link and images filter. + */ +@Injectable() +export class AddonFilterUrlToLinkHandler extends CoreFilterDefaultHandler { + name = 'AddonFilterUrlToLinkHandler'; + filterName = 'urltolink'; + + constructor() { + super(); + + // This filter is handled by Moodle, nothing to do in the app. + } + + /** + * Check if the filter should be applied in a certain site based on some filter options. + * + * @param options Options. + * @param site Site. + * @return Whether filter should be applied. + */ + shouldBeApplied(options: CoreFilterFormatTextOptions, site?: CoreSite): boolean { + return false; + } +} diff --git a/src/addon/filter/urltolink/urltolink.module.ts b/src/addon/filter/urltolink/urltolink.module.ts new file mode 100644 index 000000000..7316a849a --- /dev/null +++ b/src/addon/filter/urltolink/urltolink.module.ts @@ -0,0 +1,34 @@ +// (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 { NgModule } from '@angular/core'; +import { IonicModule } from 'ionic-angular'; +import { CoreFilterDelegate } from '@core/filter/providers/delegate'; +import { AddonFilterUrlToLinkHandler } from './providers/handler'; + +@NgModule({ + declarations: [ + ], + imports: [ + IonicModule + ], + providers: [ + AddonFilterUrlToLinkHandler + ] +}) +export class AddonFilterUrlToLinkModule { + constructor(filterDelegate: CoreFilterDelegate, handler: AddonFilterUrlToLinkHandler) { + filterDelegate.registerHandler(handler); + } +} diff --git a/src/addon/messageoutput/airnotifier/airnotifier.module.ts b/src/addon/messageoutput/airnotifier/airnotifier.module.ts index 1a060c0a4..b8a3b3ca6 100644 --- a/src/addon/messageoutput/airnotifier/airnotifier.module.ts +++ b/src/addon/messageoutput/airnotifier/airnotifier.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/addon/messageoutput/airnotifier/pages/devices/devices.module.ts b/src/addon/messageoutput/airnotifier/pages/devices/devices.module.ts index 22dc3b201..4f8239e39 100644 --- a/src/addon/messageoutput/airnotifier/pages/devices/devices.module.ts +++ b/src/addon/messageoutput/airnotifier/pages/devices/devices.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/addon/messageoutput/airnotifier/pages/devices/devices.ts b/src/addon/messageoutput/airnotifier/pages/devices/devices.ts index ddb966afa..11b4f4cc6 100644 --- a/src/addon/messageoutput/airnotifier/pages/devices/devices.ts +++ b/src/addon/messageoutput/airnotifier/pages/devices/devices.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -16,7 +16,7 @@ import { Component, OnDestroy } from '@angular/core'; import { IonicPage } from 'ionic-angular'; import { CoreDomUtilsProvider } from '@providers/utils/dom'; import { CorePushNotificationsProvider } from '@core/pushnotifications/providers/pushnotifications'; -import { AddonMessageOutputAirnotifierProvider } from '../../providers/airnotifier'; +import { AddonMessageOutputAirnotifierProvider, AddonMessageOutputAirnotifierDevice } from '../../providers/airnotifier'; /** * Page that displays the list of devices. @@ -28,7 +28,7 @@ import { AddonMessageOutputAirnotifierProvider } from '../../providers/airnotifi }) export class AddonMessageOutputAirnotifierDevicesPage implements OnDestroy { - devices = []; + devices: AddonMessageOutputAirnotifierDeviceFormatted[] = []; devicesLoaded = false; protected updateTimeout: any; @@ -47,14 +47,14 @@ export class AddonMessageOutputAirnotifierDevicesPage implements OnDestroy { /** * Fetches the list of devices. * - * @return {Promise} Promise resolved when done. + * @return Promise resolved when done. */ protected fetchDevices(): Promise { return this.airnotifierProivder.getUserDevices().then((devices) => { const pushId = this.pushNotificationsProvider.getPushId(); // Convert enabled to boolean and search current device. - devices.forEach((device) => { + devices.forEach((device: AddonMessageOutputAirnotifierDeviceFormatted) => { device.enable = !!device.enable; device.current = pushId && pushId == device.pushid; }); @@ -94,7 +94,7 @@ export class AddonMessageOutputAirnotifierDevicesPage implements OnDestroy { /** * Refresh the list of devices. * - * @param {any} refresher Refresher. + * @param refresher Refresher. */ refreshDevices(refresher: any): void { this.airnotifierProivder.invalidateUserDevices().finally(() => { @@ -107,11 +107,12 @@ export class AddonMessageOutputAirnotifierDevicesPage implements OnDestroy { /** * Enable or disable a certain device. * - * @param {any} device The device object. - * @param {boolean} enable True to enable the device, false to disable it. + * @param device The device object. + * @param enable True to enable the device, false to disable it. */ - enableDevice(device: any, enable: boolean): void { + enableDevice(device: AddonMessageOutputAirnotifierDeviceFormatted, enable: boolean): void { device.updating = true; + this.airnotifierProivder.enableDevice(device.id, enable).then(() => { // Update the list of devices since it was modified. this.updateDevicesAfterDelay(); @@ -135,3 +136,11 @@ export class AddonMessageOutputAirnotifierDevicesPage implements OnDestroy { } } } + +/** + * User device with some calculated data. + */ +type AddonMessageOutputAirnotifierDeviceFormatted = AddonMessageOutputAirnotifierDevice & { + current?: boolean; // Calculated in the app. Whether it's the current device. + updating?: boolean; // Calculated in the app. Whether the device enable is being updated right now. +}; diff --git a/src/addon/messageoutput/airnotifier/providers/airnotifier.ts b/src/addon/messageoutput/airnotifier/providers/airnotifier.ts index 129c1ebf0..01d429774 100644 --- a/src/addon/messageoutput/airnotifier/providers/airnotifier.ts +++ b/src/addon/messageoutput/airnotifier/providers/airnotifier.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -17,6 +17,7 @@ import { CoreLoggerProvider } from '@providers/logger'; import { CoreSitesProvider } from '@providers/sites'; import { CoreConfigConstants } from '../../../../configconstants'; import { CoreSite } from '@classes/site'; +import { CoreWSExternalWarning } from '@providers/ws'; /** * Service to handle Airnotifier message output. @@ -34,19 +35,21 @@ export class AddonMessageOutputAirnotifierProvider { /** * Enables or disables a device. * - * @param {number} deviceId Device ID. - * @param {boolean} enable True to enable, false to disable. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved if success. + * @param deviceId Device ID. + * @param enable True to enable, false to disable. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved if success. */ - enableDevice(deviceId: number, enable: boolean, siteId?: string): Promise { + enableDevice(deviceId: number, enable: boolean, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { const data = { deviceid: deviceId, enable: enable ? 1 : 0 }; - return site.write('message_airnotifier_enable_device', data).then((result) => { + return site.write('message_airnotifier_enable_device', data) + .then((result: AddonMessageOutputAirnotifierEnableDeviceResult) => { + if (!result.success) { // Fail. Reject with warning message if any. if (result.warnings && result.warnings.length) { @@ -62,7 +65,7 @@ export class AddonMessageOutputAirnotifierProvider { /** * Get the cache key for the get user devices call. * - * @return {string} Cache key. + * @return Cache key. */ protected getUserDevicesCacheKey(): string { return this.ROOT_CACHE_KEY + 'userDevices'; @@ -71,10 +74,10 @@ export class AddonMessageOutputAirnotifierProvider { /** * Get user devices. * - * @param {string} [siteId] Site ID. If not defined, use current site. - * @return {Promise} Promise resolved with the devices. + * @param siteId Site ID. If not defined, use current site. + * @return Promise resolved with the devices. */ - getUserDevices(siteId?: string): Promise { + getUserDevices(siteId?: string): Promise { this.logger.debug('Get user devices'); return this.sitesProvider.getSite(siteId).then((site) => { @@ -86,7 +89,8 @@ export class AddonMessageOutputAirnotifierProvider { updateFrequency: CoreSite.FREQUENCY_RARELY }; - return site.read('message_airnotifier_get_user_devices', data, preSets).then((data) => { + return site.read('message_airnotifier_get_user_devices', data, preSets) + .then((data: AddonMessageOutputAirnotifierGetUserDevicesResult) => { return data.devices; }); }); @@ -95,8 +99,8 @@ export class AddonMessageOutputAirnotifierProvider { /** * Invalidate get user devices. * - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when data is invalidated. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when data is invalidated. */ invalidateUserDevices(siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -107,7 +111,7 @@ export class AddonMessageOutputAirnotifierProvider { /** * Returns whether or not the plugin is enabled for the current site. * - * @return {boolean} True if enabled, false otherwise. + * @return True if enabled, false otherwise. * @since 3.2 */ isEnabled(): boolean { @@ -115,3 +119,36 @@ export class AddonMessageOutputAirnotifierProvider { this.sitesProvider.wsAvailableInCurrentSite('message_airnotifier_get_user_devices'); } } + +/** + * Device data returned by WS message_airnotifier_get_user_devices. + */ +export type AddonMessageOutputAirnotifierDevice = { + id: number; // Device id (in the message_airnotifier table). + appid: string; // The app id, something like com.moodle.moodlemobile. + name: string; // The device name, 'occam' or 'iPhone' etc. + model: string; // The device model 'Nexus4' or 'iPad1,1' etc. + platform: string; // The device platform 'iOS' or 'Android' etc. + version: string; // The device version '6.1.2' or '4.2.2' etc. + pushid: string; // The device PUSH token/key/identifier/registration id. + uuid: string; // The device UUID. + enable: number | boolean; // Whether the device is enabled or not. + timecreated: number; // Time created. + timemodified: number; // Time modified. +}; + +/** + * Result of WS message_airnotifier_enable_device. + */ +export type AddonMessageOutputAirnotifierEnableDeviceResult = { + success: boolean; // True if success. + warnings?: CoreWSExternalWarning[]; +}; + +/** + * Result of WS message_airnotifier_get_user_devices. + */ +export type AddonMessageOutputAirnotifierGetUserDevicesResult = { + devices: AddonMessageOutputAirnotifierDevice[]; // List of devices. + warnings?: CoreWSExternalWarning[]; +}; diff --git a/src/addon/messageoutput/airnotifier/providers/handler.ts b/src/addon/messageoutput/airnotifier/providers/handler.ts index 8d5cc15ae..7d8d10007 100644 --- a/src/addon/messageoutput/airnotifier/providers/handler.ts +++ b/src/addon/messageoutput/airnotifier/providers/handler.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -29,7 +29,7 @@ export class AddonMessageOutputAirnotifierHandler implements AddonMessageOutputH /** * Whether or not the module is enabled for the site. * - * @return {boolean} True if enabled, false otherwise. + * @return True if enabled, false otherwise. */ isEnabled(): boolean { return this.airnotifierProvider.isEnabled(); @@ -38,8 +38,8 @@ export class AddonMessageOutputAirnotifierHandler implements AddonMessageOutputH /** * Returns the data needed to render the handler. * - * @param {any} processor The processor object. - * @return {CoreMainMenuHandlerData} Data. + * @param processor The processor object. + * @return Data. */ getDisplayData(processor: any): AddonMessageOutputHandlerData { return { diff --git a/src/addon/messageoutput/messageoutput.module.ts b/src/addon/messageoutput/messageoutput.module.ts index 283f5d0a7..a2cc6e96f 100644 --- a/src/addon/messageoutput/messageoutput.module.ts +++ b/src/addon/messageoutput/messageoutput.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/addon/messageoutput/providers/delegate.ts b/src/addon/messageoutput/providers/delegate.ts index 36ef9f416..d7251448d 100644 --- a/src/addon/messageoutput/providers/delegate.ts +++ b/src/addon/messageoutput/providers/delegate.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -24,15 +24,14 @@ import { CoreSitesProvider } from '@providers/sites'; export interface AddonMessageOutputHandler extends CoreDelegateHandler { /** * The name of the processor. E.g. 'airnotifier'. - * @type {string} */ processorName: string; /** * Returns the data needed to render the handler. * - * @param {any} processor The processor object. - * @return {CoreMainMenuHandlerData} Data. + * @param processor The processor object. + * @return Data. */ getDisplayData(processor: any): AddonMessageOutputHandlerData; } @@ -43,31 +42,26 @@ export interface AddonMessageOutputHandler extends CoreDelegateHandler { export interface AddonMessageOutputHandlerData { /** * Handler's priority. - * @type {number} */ priority: number; /** * Name of the page to load for the handler. - * @type {string} */ page: string; /** * Label to display for the handler. - * @type {string} */ label: string; /** * Name of the icon to display for the handler. - * @type {string} */ icon: string; /** * Params to pass to the page. - * @type {any} */ pageParams?: any; } @@ -88,8 +82,8 @@ export interface AddonMessageOutputHandlerData { /** * Get the display data of the handler. * - * @param {string} processor The processor object. - * @return {AddonMessageOutputHandlerData} Data. + * @param processor The processor object. + * @return Data. */ getDisplayData(processor: any): AddonMessageOutputHandlerData { return this.executeFunctionOnEnabled(processor.name, 'getDisplayData', processor); diff --git a/src/addon/messages/components/components.module.ts b/src/addon/messages/components/components.module.ts index 1c8091937..337c794e3 100644 --- a/src/addon/messages/components/components.module.ts +++ b/src/addon/messages/components/components.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/addon/messages/components/confirmed-contacts/addon-messages-confirmed-contacts.html b/src/addon/messages/components/confirmed-contacts/addon-messages-confirmed-contacts.html index ccd7bd268..37e628570 100644 --- a/src/addon/messages/components/confirmed-contacts/addon-messages-confirmed-contacts.html +++ b/src/addon/messages/components/confirmed-contacts/addon-messages-confirmed-contacts.html @@ -7,7 +7,7 @@

- + {{ contact.fullname }}

diff --git a/src/addon/messages/components/confirmed-contacts/confirmed-contacts.ts b/src/addon/messages/components/confirmed-contacts/confirmed-contacts.ts index 528b48a9a..251d46e32 100644 --- a/src/addon/messages/components/confirmed-contacts/confirmed-contacts.ts +++ b/src/addon/messages/components/confirmed-contacts/confirmed-contacts.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -16,7 +16,7 @@ import { Component, EventEmitter, OnDestroy, OnInit, Output, ViewChild } from '@ import { Content } from 'ionic-angular'; import { CoreEventsProvider } from '@providers/events'; import { CoreSitesProvider } from '@providers/sites'; -import { AddonMessagesProvider } from '../../providers/messages'; +import { AddonMessagesProvider, AddonMessagesConversationMember } from '../../providers/messages'; import { CoreDomUtilsProvider } from '@providers/utils/dom'; /** @@ -33,7 +33,7 @@ export class AddonMessagesConfirmedContactsComponent implements OnInit, OnDestro loaded = false; canLoadMore = false; loadMoreError = false; - contacts = []; + contacts: AddonMessagesConversationMember[] = []; selectedUserId: number; protected memberInfoObserver; @@ -80,8 +80,8 @@ export class AddonMessagesConfirmedContactsComponent implements OnInit, OnDestro /** * Fetch contacts. * - * @param {boolean} [refresh=false] True if we are refreshing contacts, false if we are loading more. - * @return {Promise} Promise resolved when done. + * @param refresh True if we are refreshing contacts, false if we are loading more. + * @return Promise resolved when done. */ fetchData(refresh: boolean = false): Promise { this.loadMoreError = false; @@ -112,8 +112,8 @@ export class AddonMessagesConfirmedContactsComponent implements OnInit, OnDestro /** * Refresh contacts. * - * @param {any} [refresher] Refresher. - * @return {Promise} Promise resolved when done. + * @param refresher Refresher. + * @return Promise resolved when done. */ refreshData(refresher?: any): Promise { // No need to invalidate contacts, we always try to get the latest. @@ -125,8 +125,8 @@ export class AddonMessagesConfirmedContactsComponent implements OnInit, OnDestro /** * Load more contacts. * - * @param {any} [infiniteComplete] Infinite scroll complete function. Only used from core-infinite-loading. - * @return {Promise} Resolved when done. + * @param infiniteComplete Infinite scroll complete function. Only used from core-infinite-loading. + * @return Resolved when done. */ loadMore(infiniteComplete?: any): Promise { return this.fetchData().finally(() => { @@ -137,8 +137,8 @@ export class AddonMessagesConfirmedContactsComponent implements OnInit, OnDestro /** * Notify that a contact has been selected. * - * @param {number} userId User id. - * @param {boolean} [onInit=false] Whether the contact is selected on initial load. + * @param userId User id. + * @param onInit Whether the contact is selected on initial load. */ selectUser(userId: number, onInit: boolean = false): void { this.selectedUserId = userId; diff --git a/src/addon/messages/components/contact-requests/addon-messages-contact-requests.html b/src/addon/messages/components/contact-requests/addon-messages-contact-requests.html index b3bffbf68..c37ccea7e 100644 --- a/src/addon/messages/components/contact-requests/addon-messages-contact-requests.html +++ b/src/addon/messages/components/contact-requests/addon-messages-contact-requests.html @@ -6,7 +6,7 @@ -

+

{{ request.fullname }}

{{ 'addon.messages.wouldliketocontactyou' | translate }}

diff --git a/src/addon/messages/components/contact-requests/contact-requests.ts b/src/addon/messages/components/contact-requests/contact-requests.ts index 4a77bfdd1..6c889935d 100644 --- a/src/addon/messages/components/contact-requests/contact-requests.ts +++ b/src/addon/messages/components/contact-requests/contact-requests.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -16,7 +16,7 @@ import { Component, EventEmitter, OnDestroy, OnInit, Output, ViewChild } from '@ import { Content } from 'ionic-angular'; import { CoreEventsProvider } from '@providers/events'; import { CoreSitesProvider } from '@providers/sites'; -import { AddonMessagesProvider } from '../../providers/messages'; +import { AddonMessagesProvider, AddonMessagesConversationMember } from '../../providers/messages'; import { CoreDomUtilsProvider } from '@providers/utils/dom'; /** @@ -33,7 +33,7 @@ export class AddonMessagesContactRequestsComponent implements OnInit, OnDestroy loaded = false; canLoadMore = false; loadMoreError = false; - requests = []; + requests: AddonMessagesConversationMember[] = []; selectedUserId: number; protected memberInfoObserver; @@ -71,8 +71,8 @@ export class AddonMessagesContactRequestsComponent implements OnInit, OnDestroy /** * Fetch contact requests. * - * @param {boolean} [refresh=false] True if we are refreshing contact requests, false if we are loading more. - * @return {Promise} Promise resolved when done. + * @param refresh True if we are refreshing contact requests, false if we are loading more. + * @return Promise resolved when done. */ fetchData(refresh: boolean = false): Promise { this.loadMoreError = false; @@ -103,8 +103,8 @@ export class AddonMessagesContactRequestsComponent implements OnInit, OnDestroy /** * Refresh contact requests. * - * @param {any} [refresher] Refresher. - * @return {Promise} Promise resolved when done. + * @param refresher Refresher. + * @return Promise resolved when done. */ refreshData(refresher?: any): Promise { // Refresh the number of contacts requests to update badges. @@ -119,8 +119,8 @@ export class AddonMessagesContactRequestsComponent implements OnInit, OnDestroy /** * Load more contact requests. * - * @param {any} [infiniteComplete] Infinite scroll complete function. Only used from core-infinite-loading. - * @return {Promise} Resolved when done. + * @param infiniteComplete Infinite scroll complete function. Only used from core-infinite-loading. + * @return Resolved when done. */ loadMore(infiniteComplete?: any): Promise { return this.fetchData().finally(() => { @@ -131,8 +131,8 @@ export class AddonMessagesContactRequestsComponent implements OnInit, OnDestroy /** * Notify that a contact has been selected. * - * @param {number} userId User id. - * @param {boolean} [onInit=false] Whether the contact is selected on initial load. + * @param userId User id. + * @param onInit Whether the contact is selected on initial load. */ selectUser(userId: number, onInit: boolean = false): void { this.selectedUserId = userId; diff --git a/src/addon/messages/components/contacts/addon-messages-contacts.html b/src/addon/messages/components/contacts/addon-messages-contacts.html index 4722b4d74..8d0f1f543 100644 --- a/src/addon/messages/components/contacts/addon-messages-contacts.html +++ b/src/addon/messages/components/contacts/addon-messages-contacts.html @@ -20,7 +20,7 @@ -

+

{{ contact.fullname }}

diff --git a/src/addon/messages/components/contacts/contacts.ts b/src/addon/messages/components/contacts/contacts.ts index 8df67aabb..2ba88635a 100644 --- a/src/addon/messages/components/contacts/contacts.ts +++ b/src/addon/messages/components/contacts/contacts.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -16,7 +16,9 @@ import { Component } from '@angular/core'; import { NavParams } from 'ionic-angular'; import { TranslateService } from '@ngx-translate/core'; import { CoreSitesProvider } from '@providers/sites'; -import { AddonMessagesProvider } from '../../providers/messages'; +import { + AddonMessagesProvider, AddonMessagesGetContactsResult, AddonMessagesSearchContactsContact +} from '../../providers/messages'; import { CoreDomUtilsProvider } from '@providers/utils/dom'; import { CoreAppProvider } from '@providers/app'; import { CoreEventsProvider } from '@providers/events'; @@ -42,7 +44,10 @@ export class AddonMessagesContactsComponent { searchType = 'search'; loadingMessage = ''; hasContacts = false; - contacts = { + contacts: AddonMessagesGetContactsFormatted = { + online: [], + offline: [], + strangers: [], search: [] }; searchString = ''; @@ -101,8 +106,8 @@ export class AddonMessagesContactsComponent { /** * Refresh the data. * - * @param {any} [refresher] Refresher. - * @return {Promise} Promise resolved when done. + * @param refresher Refresher. + * @return Promise resolved when done. */ refreshData(refresher?: any): Promise { let promise; @@ -125,7 +130,7 @@ export class AddonMessagesContactsComponent { /** * Fetch contacts. * - * @return {Promise} Promise resolved when done. + * @return Promise resolved when done. */ protected fetchData(): Promise { this.loadingMessage = this.loadingMessages; @@ -147,8 +152,8 @@ export class AddonMessagesContactsComponent { /** * Sort user list by fullname - * @param {any[]} list List to sort. - * @return {any[]} Sorted list. + * @param list List to sort. + * @return Sorted list. */ protected sortUsers(list: any[]): any[] { return list.sort((a, b) => { @@ -179,8 +184,8 @@ export class AddonMessagesContactsComponent { /** * Search users from the UI. * - * @param {string} query Text to search for. - * @return {Promise} Resolved when done. + * @param query Text to search for. + * @return Resolved when done. */ search(query: string): Promise { this.appProvider.closeKeyboard(); @@ -196,8 +201,8 @@ export class AddonMessagesContactsComponent { /** * Perform the search of users. * - * @param {string} query Text to search for. - * @return {Promise} Resolved when done. + * @param query Text to search for. + * @return Resolved when done. */ protected performSearch(query: string): Promise { return this.messagesProvider.searchContacts(query).then((result) => { @@ -205,7 +210,7 @@ export class AddonMessagesContactsComponent { this.searchString = query; this.contactTypes = ['search']; - this.contacts['search'] = this.sortUsers(result); + this.contacts.search = this.sortUsers(result); }).catch((error) => { this.domUtils.showErrorModalDefault(error, 'addon.messages.errorwhileretrievingcontacts', true); }); @@ -214,8 +219,8 @@ export class AddonMessagesContactsComponent { /** * Navigate to a particular discussion. * - * @param {number} discussionUserId Discussion Id to load. - * @param {boolean} [onlyWithSplitView=false] Only go to Discussion if split view is on. + * @param discussionUserId Discussion Id to load. + * @param onlyWithSplitView Only go to Discussion if split view is on. */ gotoDiscussion(discussionUserId: number, onlyWithSplitView: boolean = false): void { this.discussionUserId = discussionUserId; @@ -234,3 +239,10 @@ export class AddonMessagesContactsComponent { this.memberInfoObserver && this.memberInfoObserver.off(); } } + +/** + * Contacts with some calculated data. + */ +export type AddonMessagesGetContactsFormatted = AddonMessagesGetContactsResult & { + search?: AddonMessagesSearchContactsContact[]; // Calculated in the app. Result of searching users. +}; diff --git a/src/addon/messages/components/discussions/addon-messages-discussions.html b/src/addon/messages/components/discussions/addon-messages-discussions.html index 23c9c6f34..0094d0dc5 100644 --- a/src/addon/messages/components/discussions/addon-messages-discussions.html +++ b/src/addon/messages/components/discussions/addon-messages-discussions.html @@ -14,22 +14,20 @@ -

-

+

{{ result.fullname }}

+

-

- -

+

{{ discussion.fullname }}

{{discussion.message.timecreated / 1000 | coreDateDayOrTime}} -

+

diff --git a/src/addon/messages/components/discussions/discussions.ts b/src/addon/messages/components/discussions/discussions.ts index 983e27a13..629446b1a 100644 --- a/src/addon/messages/components/discussions/discussions.ts +++ b/src/addon/messages/components/discussions/discussions.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -139,9 +139,9 @@ export class AddonMessagesDiscussionsComponent implements OnDestroy { /** * Refresh the data. * - * @param {any} [refresher] Refresher. - * @param {boolean} [refreshUnreadCounts=true] Whteher to refresh unread counts. - * @return {Promise} Promise resolved when done. + * @param refresher Refresher. + * @param refreshUnreadCounts Whteher to refresh unread counts. + * @return Promise resolved when done. */ refreshData(refresher?: any, refreshUnreadCounts: boolean = true): Promise { const promises = []; @@ -163,7 +163,7 @@ export class AddonMessagesDiscussionsComponent implements OnDestroy { /** * Fetch discussions. * - * @return {Promise} Promise resolved when done. + * @return Promise resolved when done. */ protected fetchData(): Promise { this.loadingMessage = this.loadingMessages; @@ -208,8 +208,8 @@ export class AddonMessagesDiscussionsComponent implements OnDestroy { /** * Search messages cotaining text. * - * @param {string} query Text to search for. - * @return {Promise} Resolved when done. + * @param query Text to search for. + * @return Resolved when done. */ searchMessage(query: string): Promise { this.appProvider.closeKeyboard(); @@ -229,9 +229,9 @@ export class AddonMessagesDiscussionsComponent implements OnDestroy { /** * Navigate to a particular discussion. * - * @param {number} discussionUserId Discussion Id to load. - * @param {number} [messageId] Message to scroll after loading the discussion. Used when searching. - * @param {boolean} [onlyWithSplitView=false] Only go to Discussion if split view is on. + * @param discussionUserId Discussion Id to load. + * @param messageId Message to scroll after loading the discussion. Used when searching. + * @param onlyWithSplitView Only go to Discussion if split view is on. */ gotoDiscussion(discussionUserId: number, messageId?: number, onlyWithSplitView: boolean = false): void { this.discussionUserId = discussionUserId; diff --git a/src/addon/messages/messages.module.ts b/src/addon/messages/messages.module.ts index 8cc6a9b7b..a4bdffa38 100644 --- a/src/addon/messages/messages.module.ts +++ b/src/addon/messages/messages.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/addon/messages/pages/contacts/contacts.module.ts b/src/addon/messages/pages/contacts/contacts.module.ts index a69ec60b7..e85d44606 100644 --- a/src/addon/messages/pages/contacts/contacts.module.ts +++ b/src/addon/messages/pages/contacts/contacts.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/addon/messages/pages/contacts/contacts.ts b/src/addon/messages/pages/contacts/contacts.ts index 047961507..5ee6b6ff4 100644 --- a/src/addon/messages/pages/contacts/contacts.ts +++ b/src/addon/messages/pages/contacts/contacts.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -91,9 +91,9 @@ export class AddonMessagesContactsPage implements OnDestroy { /** * Set the selected user and open the conversation in the split view if needed. * - * @param {string} tab Active tab: "contacts" or "requests". - * @param {number} [userId] Id of the selected user, undefined to use the last selected user in the tab. - * @param {boolean} [onInit=false] Whether the contact was selected on initial load. + * @param tab Active tab: "contacts" or "requests". + * @param userId Id of the selected user, undefined to use the last selected user in the tab. + * @param onInit Whether the contact was selected on initial load. */ selectUser(tab: string, userId?: number, onInit: boolean = false): void { userId = userId || this.selectedUserId[tab]; diff --git a/src/addon/messages/pages/conversation-info/conversation-info.html b/src/addon/messages/pages/conversation-info/conversation-info.html index 393b078b6..b5885ddd1 100644 --- a/src/addon/messages/pages/conversation-info/conversation-info.html +++ b/src/addon/messages/pages/conversation-info/conversation-info.html @@ -18,15 +18,15 @@
-

-

+

+

{{ 'addon.messages.numparticipants' | translate:{$a: conversation.membercount} }}

- + {{ member.fullname }}

diff --git a/src/addon/messages/pages/conversation-info/conversation-info.module.ts b/src/addon/messages/pages/conversation-info/conversation-info.module.ts index f2b0cb96c..df49d4eed 100644 --- a/src/addon/messages/pages/conversation-info/conversation-info.module.ts +++ b/src/addon/messages/pages/conversation-info/conversation-info.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/addon/messages/pages/conversation-info/conversation-info.ts b/src/addon/messages/pages/conversation-info/conversation-info.ts index b79ece62b..5dfeb450d 100644 --- a/src/addon/messages/pages/conversation-info/conversation-info.ts +++ b/src/addon/messages/pages/conversation-info/conversation-info.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -14,8 +14,11 @@ import { Component, OnInit } from '@angular/core'; import { IonicPage, NavParams, ViewController } from 'ionic-angular'; -import { AddonMessagesProvider } from '../../providers/messages'; +import { + AddonMessagesProvider, AddonMessagesConversationFormatted, AddonMessagesConversationMember +} from '../../providers/messages'; import { CoreDomUtilsProvider } from '@providers/utils/dom'; +import { CoreSitesProvider } from '@providers/sites'; /** * Page that displays the list of conversations, including group conversations. @@ -28,15 +31,15 @@ import { CoreDomUtilsProvider } from '@providers/utils/dom'; export class AddonMessagesConversationInfoPage implements OnInit { loaded = false; - conversation: any; - members = []; + conversation: AddonMessagesConversationFormatted; + members: AddonMessagesConversationMember[] = []; canLoadMore = false; loadMoreError = false; protected conversationId: number; constructor(private messagesProvider: AddonMessagesProvider, private domUtils: CoreDomUtilsProvider, navParams: NavParams, - protected viewCtrl: ViewController) { + protected viewCtrl: ViewController, sitesProvider: CoreSitesProvider) { this.conversationId = navParams.get('conversationId'); } @@ -52,7 +55,7 @@ export class AddonMessagesConversationInfoPage implements OnInit { /** * Fetch the required data. * - * @return {Promise} Promise resolved when done. + * @return Promise resolved when done. */ protected fetchData(): Promise { // Get the conversation data first. @@ -69,8 +72,8 @@ export class AddonMessagesConversationInfoPage implements OnInit { /** * Get conversation members. * - * @param {boolean} [loadingMore} Whether we are loading more data or just the first ones. - * @return {Promise} Promise resolved when done. + * @param loadingMore Whether we are loading more data or just the first ones. + * @return Promise resolved when done. */ protected fetchMembers(loadingMore?: boolean): Promise { this.loadMoreError = false; @@ -91,8 +94,8 @@ export class AddonMessagesConversationInfoPage implements OnInit { /** * Function to load more members. * - * @param {any} [infiniteComplete] Infinite scroll complete function. Only used from core-infinite-loading. - * @return {Promise} Resolved when done. + * @param infiniteComplete Infinite scroll complete function. Only used from core-infinite-loading. + * @return Resolved when done. */ loadMoreMembers(infiniteComplete?: any): Promise { return this.fetchMembers(true).catch((error) => { @@ -106,8 +109,8 @@ export class AddonMessagesConversationInfoPage implements OnInit { /** * Refresh the data. * - * @param {any} [refresher] Refresher. - * @return {Promise} Promise resolved when done. + * @param refresher Refresher. + * @return Promise resolved when done. */ refreshData(refresher?: any): Promise { const promises = []; @@ -125,7 +128,7 @@ export class AddonMessagesConversationInfoPage implements OnInit { /** * Close modal. * - * @param {number} [userId] User conversation to load. + * @param userId User conversation to load. */ closeModal(userId?: number): void { this.viewCtrl.dismiss(userId); diff --git a/src/addon/messages/pages/discussion/discussion.html b/src/addon/messages/pages/discussion/discussion.html index 8363436b7..a869dad74 100644 --- a/src/addon/messages/pages/discussion/discussion.html +++ b/src/addon/messages/pages/discussion/discussion.html @@ -3,7 +3,7 @@ - + @@ -25,7 +25,7 @@ - + @@ -34,7 +34,7 @@

{{ 'addon.messages.selfconversationdefaultmessage' | translate }}

- +
{{ message.timecreated | coreFormatDate: "strftimedayshort" }} @@ -58,7 +58,7 @@

- +

diff --git a/src/addon/mod/chat/pages/chat/chat.module.ts b/src/addon/mod/chat/pages/chat/chat.module.ts index 24979c427..7b26e45da 100644 --- a/src/addon/mod/chat/pages/chat/chat.module.ts +++ b/src/addon/mod/chat/pages/chat/chat.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/addon/mod/chat/pages/chat/chat.scss b/src/addon/mod/chat/pages/chat/chat.scss index fe62c5619..e9eb2ef73 100644 --- a/src/addon/mod/chat/pages/chat/chat.scss +++ b/src/addon/mod/chat/pages/chat/chat.scss @@ -1,9 +1,8 @@ -ion-app.app-root page-addon-mod-chat-chat { +ion-app.app-root page-addon-mod-chat-chat.ion-page { + @include message-page(); + .addon-mod-chat-notice { margin-top: 10px; margin-bottom: 10px; } - .addon-mod-chat-message { - align-items: flex-start; - } } diff --git a/src/addon/mod/chat/pages/chat/chat.ts b/src/addon/mod/chat/pages/chat/chat.ts index 41bd0e1ca..bd34a6644 100644 --- a/src/addon/mod/chat/pages/chat/chat.ts +++ b/src/addon/mod/chat/pages/chat/chat.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -19,10 +19,11 @@ import { CoreEventsProvider } from '@providers/events'; import { CoreLoggerProvider } from '@providers/logger'; import { CoreSitesProvider } from '@providers/sites'; import { CoreDomUtilsProvider } from '@providers/utils/dom'; -import { CoreTextUtilsProvider } from '@providers/utils/text'; import { AddonModChatProvider } from '../../providers/chat'; +import { AddonModChatHelperProvider, AddonModChatMessageForView } from '../../providers/helper'; import { Network } from '@ionic-native/network'; -import * as moment from 'moment'; +import { coreSlideInOut } from '@classes/animations'; +import { CoreSendMessageFormComponent } from '@components/send-message-form/send-message-form'; /** * Page that displays a chat session. @@ -31,39 +32,46 @@ import * as moment from 'moment'; @Component({ selector: 'page-addon-mod-chat-chat', templateUrl: 'chat.html', + animations: [coreSlideInOut] }) export class AddonModChatChatPage { @ViewChild(Content) content: Content; + @ViewChild(CoreSendMessageFormComponent) sendMessageForm: CoreSendMessageFormComponent; loaded = false; title: string; - messages = []; + messages: AddonModChatMessageForView[] = []; newMessage: string; polling: any; isOnline: boolean; - currentUserBeep: string; + currentUserId: number; + sending: boolean; + cmId: number; protected logger; protected courseId: number; protected chatId: number; - protected sessionId: number; + protected sessionId: string; protected lastTime = 0; protected oldContentHeight = 0; protected onlineObserver: any; protected keyboardObserver: any; protected viewDestroyed = false; protected pollingRunning = false; + protected users = []; constructor(navParams: NavParams, logger: CoreLoggerProvider, network: Network, zone: NgZone, private navCtrl: NavController, private chatProvider: AddonModChatProvider, private appProvider: CoreAppProvider, sitesProvider: CoreSitesProvider, - private modalCtrl: ModalController, private domUtils: CoreDomUtilsProvider, private textUtils: CoreTextUtilsProvider, - private eventsProvider: CoreEventsProvider) { + private modalCtrl: ModalController, private domUtils: CoreDomUtilsProvider, + private eventsProvider: CoreEventsProvider, private chatHelper: AddonModChatHelperProvider) { this.chatId = navParams.get('chatId'); this.courseId = navParams.get('courseId'); this.title = navParams.get('title'); + this.cmId = navParams.get('cmId'); + this.logger = logger.getInstance('AddonModChoiceChoicePage'); - this.currentUserBeep = 'beep ' + sitesProvider.getCurrentSiteUserId(); + this.currentUserId = sitesProvider.getCurrentSiteUserId(); this.isOnline = this.appProvider.isOnline(); this.onlineObserver = network.onchange().subscribe(() => { // Execute the callback in the Angular zone, so change detection doesn't stop working. @@ -116,24 +124,70 @@ export class AddonModChatChatPage { * Display the chat users modal. */ showChatUsers(): void { - const modal = this.modalCtrl.create('AddonModChatUsersPage', {sessionId: this.sessionId}); + // Create the toc modal. + const modal = this.modalCtrl.create('AddonModChatUsersPage', { + sessionId: this.sessionId + }, { cssClass: 'core-modal-lateral', + showBackdrop: true, + enableBackdropDismiss: true, + enterAnimation: 'core-modal-lateral-transition', + leaveAnimation: 'core-modal-lateral-transition' }); + modal.onDidDismiss((data) => { if (data && data.talkTo) { - this.newMessage = `To ${data.talkTo}: `; + this.newMessage = `To ${data.talkTo}: ` + (this.sendMessageForm.message || ''); } if (data && data.beepTo) { this.sendMessage('', data.beepTo); } + if (data && data.users) { + this.users = data.users; + } + }); + + modal.present({ + ev: event + }); + } + + /** + * Get the user fullname for a beep. + * + * @param id User Id before parsing. + * @return User fullname. + */ + protected getUserFullname(id: string): Promise { + if (isNaN(parseInt(id, 10))) { + return Promise.resolve(id); + } + + const user = this.users.find((user) => user.id == id); + + if (user) { + return Promise.resolve(user.fullname); + } + + return this.chatProvider.getChatUsers(this.sessionId).then((data) => { + this.users = data.users; + const user = this.users.find((user) => user.id == id); + + if (user) { + return user.fullname; + } + + return id; + }).catch((error) => { + // Ignore errors. + return id; }); - modal.present(); } /** * Convenience function to login the user. * - * @return {Promise} Resolved when done. + * @return Promise resolved when done. */ - protected loginUser(): Promise { + protected loginUser(): Promise { return this.chatProvider.loginUser(this.chatId).then((sessionId) => { this.sessionId = sessionId; }); @@ -142,15 +196,33 @@ export class AddonModChatChatPage { /** * Convenience function to fetch chat messages. * - * @return {Promise} Promise resolved when done. + * @return Promise resolved when done. */ - protected fetchMessages(): Promise { + protected fetchMessages(): Promise { return this.chatProvider.getLatestMessages(this.sessionId, this.lastTime).then((messagesInfo) => { this.lastTime = messagesInfo.chatnewlasttime || 0; return this.chatProvider.getMessagesUserData(messagesInfo.messages, this.courseId).then((messages) => { - this.messages = this.messages.concat(messages); if (messages.length) { + const previousLength = this.messages.length; + this.messages = this.messages.concat( messages); + + // Calculate which messages need to display the date or user data. + for (let index = previousLength ; index < this.messages.length; index++) { + const message = this.messages[index]; + const prevMessage = index > 0 ? this.messages[index - 1] : null; + + this.chatHelper.formatMessage(this.currentUserId, message, prevMessage); + + if (message.beep && message.beep != this.currentUserId + '') { + this.getUserFullname(message.beep).then((fullname) => { + message.beepWho = fullname; + }); + } + } + + this.messages[this.messages.length - 1].showTail = true; + // New messages or beeps, scroll to bottom. setTimeout(() => this.scrollToBottom()); } @@ -188,9 +260,9 @@ export class AddonModChatChatPage { /** * Convenience function to be called every certain time to fetch chat messages. * - * @return {Promise} Promised resolved when done. + * @return Promise resolved when done. */ - protected fetchMessagesInterval(): Promise { + protected fetchMessagesInterval(): Promise { this.logger.debug('Polling for messages'); if (!this.isOnline || this.pollingRunning) { // Obviously we cannot check for new messages when the app is offline. @@ -218,27 +290,11 @@ export class AddonModChatChatPage { }); } - /** - * Check if the date should be displayed between messages (when the day changes at midnight for example). - * - * @param {any} message New message object. - * @param {any} prevMessage Previous message object. - * @return {boolean} True if messages are from diferent days, false othetwise. - */ - showDate(message: any, prevMessage: any): boolean { - if (!prevMessage) { - return true; - } - - // Check if day has changed. - return !moment(message.timestamp * 1000).isSame(prevMessage.timestamp * 1000, 'day'); - } - /** * Send a message to the chat. * - * @param {string} text Text of the nessage. - * @param {number} [beep=0] ID of the user to beep. + * @param text Text of the nessage. + * @param beep ID of the user to beep. */ sendMessage(text: string, beep: number = 0): void { if (!this.isOnline) { @@ -248,9 +304,8 @@ export class AddonModChatChatPage { // Silent error. return; } - text = this.textUtils.replaceNewLines(text, '
'); - const modal = this.domUtils.showModalLoading('core.sending', true); + this.sending = true; this.chatProvider.sendMessage(this.sessionId, text, beep).then(() => { // Update messages to show the sent message. this.fetchMessagesInterval().catch(() => { @@ -261,13 +316,15 @@ export class AddonModChatChatPage { messages without the keyboard being closed. */ this.appProvider.closeKeyboard(); + this.newMessage = text; + this.domUtils.showErrorModalDefault(error, 'addon.mod_chat.errorwhilesendingmessage', true); }).finally(() => { - modal.dismiss(); + this.sending = false; }); } - reconnect(): Promise { + reconnect(): Promise { const modal = this.domUtils.showModalLoading(); // Call startPolling would take a while for the first execution, so we'll execute it manually to check if it works now. diff --git a/src/addon/mod/chat/pages/index/index.html b/src/addon/mod/chat/pages/index/index.html index 7a2e458ca..52c0b1f3c 100644 --- a/src/addon/mod/chat/pages/index/index.html +++ b/src/addon/mod/chat/pages/index/index.html @@ -1,6 +1,6 @@ - + diff --git a/src/addon/mod/chat/pages/index/index.module.ts b/src/addon/mod/chat/pages/index/index.module.ts index 158d4bfa5..9ee30df8f 100644 --- a/src/addon/mod/chat/pages/index/index.module.ts +++ b/src/addon/mod/chat/pages/index/index.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/addon/mod/chat/pages/index/index.ts b/src/addon/mod/chat/pages/index/index.ts index 391b654fc..b95eaab42 100644 --- a/src/addon/mod/chat/pages/index/index.ts +++ b/src/addon/mod/chat/pages/index/index.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -40,7 +40,7 @@ export class AddonModChatIndexPage { /** * Update some data based on the chat instance. * - * @param {any} chat Chat instance. + * @param chat Chat instance. */ updateData(chat: any): void { this.title = chat.name || this.title; diff --git a/src/addon/mod/chat/pages/session-messages/session-messages.html b/src/addon/mod/chat/pages/session-messages/session-messages.html index a315ebecf..0b662db80 100644 --- a/src/addon/mod/chat/pages/session-messages/session-messages.html +++ b/src/addon/mod/chat/pages/session-messages/session-messages.html @@ -8,33 +8,56 @@ -
-
- - {{ message.timestamp * 1000 | coreFormatDate:"strftimedayshort" }} - -
+ + +
+ {{ message.timestamp * 1000 | coreFormatDate:"strftimedayshort" }} +
-
- - {{ message.timestamp * 1000 | coreFormatDate:"strftimetime" }} {{ 'addon.mod_chat.messageenter' | translate:{$a: message.userfullname} }} - -
+
+ + {{ message.timestamp * 1000 | coreFormatDate:"strftimetime" }} {{ 'addon.mod_chat.messageenter' | translate:{$a: message.userfullname} }} + -
- - {{ message.timestamp * 1000 | coreFormatDate:"strftimetime" }} {{ 'addon.mod_chat.messageexit' | translate:{$a: message.userfullname} }} - -
+ + {{ message.timestamp * 1000 | coreFormatDate:"strftimetime" }} {{ 'addon.mod_chat.messageexit' | translate:{$a: message.userfullname} }} + - - -

-

{{ message.timestamp * 1000 | coreFormatDate:"strftimetime" }}

- -

- -
-
+ + {{ message.timestamp * 1000 | coreFormatDate:"strftimetime" }} + {{ 'addon.mod_chat.messagebeepseveryone' | translate:{$a: message.userfullname} }} + + + + {{ message.timestamp * 1000 | coreFormatDate:"strftimetime" }} + {{ 'addon.mod_chat.messagebeepsyou' | translate:{$a: message.userfullname} }} + + + + {{ message.timestamp * 1000 | coreFormatDate:"strftimetime" }} + {{ 'addon.mod_chat.messageyoubeep' | translate:{$a: message.beepWho} }} + + + + {{ message.timestamp * 1000 | coreFormatDate:"strftimetime" }} + {{ message.userfullname }} + +
+ + + +

+ +
{{ message.userfullname }}
+ {{ message.timestamp * 1000 | coreFormatDate: "strftimetime" }} +

+ +

+ +

+
+
+ +
diff --git a/src/addon/mod/chat/pages/session-messages/session-messages.module.ts b/src/addon/mod/chat/pages/session-messages/session-messages.module.ts index 816b70999..d5c1c8835 100644 --- a/src/addon/mod/chat/pages/session-messages/session-messages.module.ts +++ b/src/addon/mod/chat/pages/session-messages/session-messages.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/addon/mod/chat/pages/session-messages/session-messages.scss b/src/addon/mod/chat/pages/session-messages/session-messages.scss index a8d1e96e8..ef01df6e2 100644 --- a/src/addon/mod/chat/pages/session-messages/session-messages.scss +++ b/src/addon/mod/chat/pages/session-messages/session-messages.scss @@ -1,9 +1,8 @@ -ion-app.app-root page-addon-mod-chat-session-messages { +ion-app.app-root page-addon-mod-chat-session-messages.ion-page { + @include message-page(); + .addon-mod-chat-notice { margin-top: 10px; margin-bottom: 10px; } - .addon-mod-chat-message { - align-items: flex-start; - } } diff --git a/src/addon/mod/chat/pages/session-messages/session-messages.ts b/src/addon/mod/chat/pages/session-messages/session-messages.ts index 91fe8874b..39e15433e 100644 --- a/src/addon/mod/chat/pages/session-messages/session-messages.ts +++ b/src/addon/mod/chat/pages/session-messages/session-messages.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -15,8 +15,10 @@ import { Component } from '@angular/core'; import { IonicPage, NavParams } from 'ionic-angular'; import { CoreDomUtilsProvider } from '@providers/utils/dom'; +import { CoreSitesProvider } from '@providers/sites'; +import { CoreUserProvider } from '@core/user/providers/user'; import { AddonModChatProvider } from '../../providers/chat'; -import * as moment from 'moment'; +import { AddonModChatHelperProvider, AddonModChatSessionMessageForView } from '../../providers/helper'; /** * Page that displays list of chat session messages. @@ -28,20 +30,26 @@ import * as moment from 'moment'; }) export class AddonModChatSessionMessagesPage { + currentUserId: number; + cmId: number; + messages: AddonModChatSessionMessageForView[] = []; + loaded = false; + protected courseId: number; protected chatId: number; protected sessionStart: number; protected sessionEnd: number; protected groupId: number; - protected loaded = false; - protected messages = []; - constructor(navParams: NavParams, private domUtils: CoreDomUtilsProvider, private chatProvider: AddonModChatProvider) { + constructor(navParams: NavParams, private domUtils: CoreDomUtilsProvider, private chatProvider: AddonModChatProvider, + sitesProvider: CoreSitesProvider, private chatHelper: AddonModChatHelperProvider, private userProvider: CoreUserProvider) { this.courseId = navParams.get('courseId'); this.chatId = navParams.get('chatId'); this.groupId = navParams.get('groupId'); this.sessionStart = navParams.get('sessionStart'); this.sessionEnd = navParams.get('sessionEnd'); + this.cmId = navParams.get('cmId'); + this.currentUserId = sitesProvider.getCurrentSiteUserId(); this.fetchMessages(); } @@ -49,13 +57,31 @@ export class AddonModChatSessionMessagesPage { /** * Fetch session messages. * - * @return {Promise} Promise resolved when done. + * @return Promise resolved when done. */ protected fetchMessages(): Promise { return this.chatProvider.getSessionMessages(this.chatId, this.sessionStart, this.sessionEnd, this.groupId) .then((messages) => { return this.chatProvider.getMessagesUserData(messages, this.courseId).then((messages) => { - this.messages = messages; + this.messages = messages; + + if (messages.length) { + // Calculate which messages need to display the date or user data. + for (let index = 0 ; index < this.messages.length; index++) { + const message = this.messages[index]; + const prevMessage = index > 0 ? this.messages[index - 1] : null; + + this.chatHelper.formatMessage(this.currentUserId, message, prevMessage); + + if (message.beep && message.beep != this.currentUserId + '') { + this.getUserFullname(message.beep).then((fullname) => { + message.beepWho = fullname; + }); + } + } + + this.messages[this.messages.length - 1].showTail = true; + } }); }).catch((error) => { this.domUtils.showErrorModalDefault(error, 'core.errorloadingcontent', true); @@ -64,10 +90,29 @@ export class AddonModChatSessionMessagesPage { }); } + /** + * Get the user fullname for a beep. + * + * @param id User Id before parsing. + * @return User fullname. + */ + protected getUserFullname(id: string): Promise { + if (isNaN(parseInt(id, 10))) { + return Promise.resolve(id); + } + + return this.userProvider.getProfile(parseInt(id, 10), this.courseId, true).then((user) => { + return user.fullname; + }).catch(() => { + // Error getting profile. + return id; + }); + } + /** * Refresh session messages. * - * @param {any} refresher Refresher. + * @param refresher Refresher. */ refreshMessages(refresher: any): void { this.chatProvider.invalidateSessionMessages(this.chatId, this.sessionStart, this.groupId).finally(() => { @@ -77,19 +122,4 @@ export class AddonModChatSessionMessagesPage { }); } - /** - * Check if the date should be displayed between messages (when the day changes at midnight for example). - * - * @param {any} message New message object. - * @param {any} prevMessage Previous message object. - * @return {boolean} True if messages are from diferent days, false othetwise. - */ - showDate(message: any, prevMessage: any): boolean { - if (!prevMessage) { - return true; - } - - // Check if day has changed. - return !moment(message.timestamp * 1000).isSame(prevMessage.timestamp * 1000, 'day'); - } } diff --git a/src/addon/mod/chat/pages/sessions/sessions.html b/src/addon/mod/chat/pages/sessions/sessions.html index b78df54bc..acffafa23 100644 --- a/src/addon/mod/chat/pages/sessions/sessions.html +++ b/src/addon/mod/chat/pages/sessions/sessions.html @@ -28,9 +28,9 @@

{{ session.duration | coreDuration }}

-

+ {{ user.userfullname }} ({{ user.messagecount }}) -

+
+
diff --git a/src/addon/mod/data/fields/latlong/component/latlong.ts b/src/addon/mod/data/fields/latlong/component/latlong.ts index bef58067f..912f628d3 100644 --- a/src/addon/mod/data/fields/latlong/component/latlong.ts +++ b/src/addon/mod/data/fields/latlong/component/latlong.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -13,8 +13,11 @@ // limitations under the License. import { Component } from '@angular/core'; import { FormBuilder } from '@angular/forms'; +import { DomSanitizer, SafeUrl } from '@angular/platform-browser'; import { Platform } from 'ionic-angular'; +import { Geolocation, GeolocationOptions } from '@ionic-native/geolocation'; import { AddonModDataFieldPluginComponent } from '../../../classes/field-plugin-component'; +import { CoreDomUtilsProvider } from '@providers/utils/dom'; /** * Component to render data latlong field. @@ -28,16 +31,20 @@ export class AddonModDataFieldLatlongComponent extends AddonModDataFieldPluginCo north: number; east: number; - constructor(protected fb: FormBuilder, private platform: Platform) { + constructor(protected fb: FormBuilder, + protected platform: Platform, + protected geolocation: Geolocation, + protected domUtils: CoreDomUtilsProvider, + protected sanitizer: DomSanitizer) { super(fb); } /** * Format latitude and longitude in a simple text. * - * @param {number} north Degrees north. - * @param {number} east Degrees East. - * @return {string} Readable Latitude and logitude. + * @param north Degrees north. + * @param east Degrees East. + * @return Readable Latitude and logitude. */ formatLatLong(north: number, east: number): string { if (north !== null || east !== null) { @@ -51,20 +58,23 @@ export class AddonModDataFieldLatlongComponent extends AddonModDataFieldPluginCo /** * Get link to maps from latitude and longitude. * - * @param {number} north Degrees north. - * @param {number} east Degrees East. - * @return {string} Link to maps depending on platform. + * @param north Degrees north. + * @param east Degrees East. + * @return Link to maps depending on platform. */ - getLatLongLink(north: number, east: number): string { + getLatLongLink(north: number, east: number): SafeUrl { if (north !== null || east !== null) { - const northFixed = north ? north.toFixed(4) : '0.0000', - eastFixed = east ? east.toFixed(4) : '0.0000'; + const northFixed = north ? north.toFixed(4) : '0.0000'; + const eastFixed = east ? east.toFixed(4) : '0.0000'; + let url; if (this.platform.is('ios')) { - return 'http://maps.apple.com/?ll=' + northFixed + ',' + eastFixed + '&near=' + northFixed + ',' + eastFixed; + url = 'http://maps.apple.com/?ll=' + northFixed + ',' + eastFixed + '&near=' + northFixed + ',' + eastFixed; + } else { + url = 'geo:' + northFixed + ',' + eastFixed; } - return 'geo:' + northFixed + ',' + eastFixed; + return this.sanitizer.bypassSecurityTrustUrl(url); } } @@ -87,11 +97,46 @@ export class AddonModDataFieldLatlongComponent extends AddonModDataFieldPluginCo /** * Update value being shown. * - * @param {any} value New value to be set. + * @param value New value to be set. */ protected updateValue(value: any): void { this.value = value; this.north = (value && parseFloat(value.content)) || null; this.east = (value && parseFloat(value.content1)) || null; } + + /** + * Get user location. + * + * @param $event The event. + */ + getLocation(event: Event): void { + event.preventDefault(); + + const modal = this.domUtils.showModalLoading('addon.mod_data.gettinglocation', true); + + const options: GeolocationOptions = { + enableHighAccuracy: true, + timeout: 30000 + }; + + this.geolocation.getCurrentPosition(options).then((result) => { + this.form.controls['f_' + this.field.id + '_0'].setValue(result.coords.latitude); + this.form.controls['f_' + this.field.id + '_1'].setValue(result.coords.longitude); + }).catch((error) => { + if (this.isPermissionDeniedError(error)) { + this.domUtils.showErrorModal('addon.mod_data.locationpermissiondenied', true); + + return; + } + + this.domUtils.showErrorModalDefault(error, 'Error getting location'); + }).finally(() => { + modal.dismiss(); + }); + } + + protected isPermissionDeniedError(error?: any): boolean { + return error && 'code' in error && 'PERMISSION_DENIED' in error && error.code === error.PERMISSION_DENIED; + } } diff --git a/src/addon/mod/data/fields/latlong/latlong.module.ts b/src/addon/mod/data/fields/latlong/latlong.module.ts index 5b9b4f11a..cfc2af9f6 100644 --- a/src/addon/mod/data/fields/latlong/latlong.module.ts +++ b/src/addon/mod/data/fields/latlong/latlong.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/addon/mod/data/fields/latlong/providers/handler.ts b/src/addon/mod/data/fields/latlong/providers/handler.ts index 606db2878..fb67ca9c6 100644 --- a/src/addon/mod/data/fields/latlong/providers/handler.ts +++ b/src/addon/mod/data/fields/latlong/providers/handler.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -30,9 +30,9 @@ export class AddonModDataFieldLatlongHandler implements AddonModDataFieldHandler * Return the Component to use to display the plugin data. * It's recommended to return the class of the component, but you can also return an instance of the component. * - * @param {Injector} injector Injector. - * @param {any} field The field object. - * @return {any|Promise} The component (or promise resolved with component) to use, undefined if not found. + * @param injector Injector. + * @param field The field object. + * @return The component (or promise resolved with component) to use, undefined if not found. */ getComponent(injector: Injector, plugin: any): any | Promise { return AddonModDataFieldLatlongComponent; @@ -41,9 +41,9 @@ export class AddonModDataFieldLatlongHandler implements AddonModDataFieldHandler /** * Get field search data in the input data. * - * @param {any} field Defines the field to be rendered. - * @param {any} inputData Data entered in the search form. - * @return {any} With name and value of the data to be sent. + * @param field Defines the field to be rendered. + * @param inputData Data entered in the search form. + * @return With name and value of the data to be sent. */ getFieldSearchData(field: any, inputData: any): any { const fieldName = 'f_' + field.id; @@ -61,9 +61,9 @@ export class AddonModDataFieldLatlongHandler implements AddonModDataFieldHandler /** * Get field edit data in the input data. * - * @param {any} field Defines the field to be rendered. - * @param {any} inputData Data entered in the edit form. - * @return {any} With name and value of the data to be sent. + * @param field Defines the field to be rendered. + * @param inputData Data entered in the edit form. + * @return With name and value of the data to be sent. */ getFieldEditData(field: any, inputData: any, originalFieldData: any): any { const fieldName = 'f_' + field.id; @@ -85,10 +85,10 @@ export class AddonModDataFieldLatlongHandler implements AddonModDataFieldHandler /** * Get field data in changed. * - * @param {any} field Defines the field to be rendered. - * @param {any} inputData Data entered in the edit form. - * @param {any} originalFieldData Original field entered data. - * @return {Promise | boolean} If the field has changes. + * @param field Defines the field to be rendered. + * @param inputData Data entered in the edit form. + * @param originalFieldData Original field entered data. + * @return If the field has changes. */ hasFieldDataChanged(field: any, inputData: any, originalFieldData: any): Promise | boolean { const fieldName = 'f_' + field.id, @@ -103,9 +103,9 @@ export class AddonModDataFieldLatlongHandler implements AddonModDataFieldHandler /** * Check and get field requeriments. * - * @param {any} field Defines the field to be rendered. - * @param {any} inputData Data entered in the edit form. - * @return {string | false} String with the notification or false. + * @param field Defines the field to be rendered. + * @param inputData Data entered in the edit form. + * @return String with the notification or false. */ getFieldsNotifications(field: any, inputData: any): string | false { let valueCount = 0; @@ -130,10 +130,10 @@ export class AddonModDataFieldLatlongHandler implements AddonModDataFieldHandler /** * Override field content data with offline submission. * - * @param {any} originalContent Original data to be overriden. - * @param {any} offlineContent Array with all the offline data to override. - * @param {any} [offlineFiles] Array with all the offline files in the field. - * @return {any} Data overriden + * @param originalContent Original data to be overriden. + * @param offlineContent Array with all the offline data to override. + * @param offlineFiles Array with all the offline files in the field. + * @return Data overriden */ overrideData(originalContent: any, offlineContent: any, offlineFiles?: any): any { originalContent.content = offlineContent[0] || ''; @@ -145,7 +145,7 @@ export class AddonModDataFieldLatlongHandler implements AddonModDataFieldHandler /** * Whether or not the handler is enabled on a site level. * - * @return {boolean|Promise} True or promise resolved with true if enabled. + * @return True or promise resolved with true if enabled. */ isEnabled(): boolean | Promise { return true; diff --git a/src/addon/mod/data/fields/menu/component/addon-mod-data-field-menu.html b/src/addon/mod/data/fields/menu/component/addon-mod-data-field-menu.html index 53dabd14c..eb8fbe013 100644 --- a/src/addon/mod/data/fields/menu/component/addon-mod-data-field-menu.html +++ b/src/addon/mod/data/fields/menu/component/addon-mod-data-field-menu.html @@ -7,4 +7,4 @@ - \ No newline at end of file +{{ value.content }} \ No newline at end of file diff --git a/src/addon/mod/data/fields/menu/component/menu.ts b/src/addon/mod/data/fields/menu/component/menu.ts index 7362cb6f6..462163521 100644 --- a/src/addon/mod/data/fields/menu/component/menu.ts +++ b/src/addon/mod/data/fields/menu/component/menu.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/addon/mod/data/fields/menu/menu.module.ts b/src/addon/mod/data/fields/menu/menu.module.ts index 2c4a16427..f2a839e03 100644 --- a/src/addon/mod/data/fields/menu/menu.module.ts +++ b/src/addon/mod/data/fields/menu/menu.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/addon/mod/data/fields/menu/providers/handler.ts b/src/addon/mod/data/fields/menu/providers/handler.ts index e8427cc66..104559f66 100644 --- a/src/addon/mod/data/fields/menu/providers/handler.ts +++ b/src/addon/mod/data/fields/menu/providers/handler.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -30,9 +30,9 @@ export class AddonModDataFieldMenuHandler implements AddonModDataFieldHandler { * Return the Component to use to display the plugin data. * It's recommended to return the class of the component, but you can also return an instance of the component. * - * @param {Injector} injector Injector. - * @param {any} field The field object. - * @return {any|Promise} The component (or promise resolved with component) to use, undefined if not found. + * @param injector Injector. + * @param field The field object. + * @return The component (or promise resolved with component) to use, undefined if not found. */ getComponent(injector: Injector, plugin: any): any | Promise { return AddonModDataFieldMenuComponent; @@ -41,9 +41,9 @@ export class AddonModDataFieldMenuHandler implements AddonModDataFieldHandler { /** * Get field search data in the input data. * - * @param {any} field Defines the field to be rendered. - * @param {any} inputData Data entered in the search form. - * @return {any} With name and value of the data to be sent. + * @param field Defines the field to be rendered. + * @param inputData Data entered in the search form. + * @return With name and value of the data to be sent. */ getFieldSearchData(field: any, inputData: any): any { const fieldName = 'f_' + field.id; @@ -60,9 +60,9 @@ export class AddonModDataFieldMenuHandler implements AddonModDataFieldHandler { /** * Get field edit data in the input data. * - * @param {any} field Defines the field to be rendered. - * @param {any} inputData Data entered in the edit form. - * @return {any} With name and value of the data to be sent. + * @param field Defines the field to be rendered. + * @param inputData Data entered in the edit form. + * @return With name and value of the data to be sent. */ getFieldEditData(field: any, inputData: any, originalFieldData: any): any { const fieldName = 'f_' + field.id; @@ -80,10 +80,10 @@ export class AddonModDataFieldMenuHandler implements AddonModDataFieldHandler { /** * Get field data in changed. * - * @param {any} field Defines the field to be rendered. - * @param {any} inputData Data entered in the edit form. - * @param {any} originalFieldData Original field entered data. - * @return {Promise | boolean} If the field has changes. + * @param field Defines the field to be rendered. + * @param inputData Data entered in the edit form. + * @param originalFieldData Original field entered data. + * @return If the field has changes. */ hasFieldDataChanged(field: any, inputData: any, originalFieldData: any): Promise | boolean { const fieldName = 'f_' + field.id, @@ -96,9 +96,9 @@ export class AddonModDataFieldMenuHandler implements AddonModDataFieldHandler { /** * Check and get field requeriments. * - * @param {any} field Defines the field to be rendered. - * @param {any} inputData Data entered in the edit form. - * @return {string | false} String with the notification or false. + * @param field Defines the field to be rendered. + * @param inputData Data entered in the edit form. + * @return String with the notification or false. */ getFieldsNotifications(field: any, inputData: any): string | false { if (field.required && (!inputData || !inputData.length || !inputData[0].value)) { @@ -111,10 +111,10 @@ export class AddonModDataFieldMenuHandler implements AddonModDataFieldHandler { /** * Override field content data with offline submission. * - * @param {any} originalContent Original data to be overriden. - * @param {any} offlineContent Array with all the offline data to override. - * @param {any} [offlineFiles] Array with all the offline files in the field. - * @return {any} Data overriden + * @param originalContent Original data to be overriden. + * @param offlineContent Array with all the offline data to override. + * @param offlineFiles Array with all the offline files in the field. + * @return Data overriden */ overrideData(originalContent: any, offlineContent: any, offlineFiles?: any): any { originalContent.content = offlineContent[''] || ''; @@ -125,7 +125,7 @@ export class AddonModDataFieldMenuHandler implements AddonModDataFieldHandler { /** * Whether or not the handler is enabled on a site level. * - * @return {boolean|Promise} True or promise resolved with true if enabled. + * @return True or promise resolved with true if enabled. */ isEnabled(): boolean | Promise { return true; diff --git a/src/addon/mod/data/fields/multimenu/component/addon-mod-data-field-multimenu.html b/src/addon/mod/data/fields/multimenu/component/addon-mod-data-field-multimenu.html index 8acf2c8c5..ea3cd13df 100644 --- a/src/addon/mod/data/fields/multimenu/component/addon-mod-data-field-multimenu.html +++ b/src/addon/mod/data/fields/multimenu/component/addon-mod-data-field-multimenu.html @@ -13,4 +13,4 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/src/addon/mod/data/fields/multimenu/component/multimenu.ts b/src/addon/mod/data/fields/multimenu/component/multimenu.ts index b31800229..1373015b0 100644 --- a/src/addon/mod/data/fields/multimenu/component/multimenu.ts +++ b/src/addon/mod/data/fields/multimenu/component/multimenu.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -64,7 +64,7 @@ export class AddonModDataFieldMultimenuComponent extends AddonModDataFieldPlugin /** * Update value being shown. * - * @param {any} value New value to be set. + * @param value New value to be set. */ protected updateValue(value: any): void { this.value = value || {}; diff --git a/src/addon/mod/data/fields/multimenu/multimenu.module.ts b/src/addon/mod/data/fields/multimenu/multimenu.module.ts index 7b6b72d12..e2bdf031e 100644 --- a/src/addon/mod/data/fields/multimenu/multimenu.module.ts +++ b/src/addon/mod/data/fields/multimenu/multimenu.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/addon/mod/data/fields/multimenu/providers/handler.ts b/src/addon/mod/data/fields/multimenu/providers/handler.ts index 0522bc304..1a7e1ce82 100644 --- a/src/addon/mod/data/fields/multimenu/providers/handler.ts +++ b/src/addon/mod/data/fields/multimenu/providers/handler.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -30,9 +30,9 @@ export class AddonModDataFieldMultimenuHandler implements AddonModDataFieldHandl * Return the Component to use to display the plugin data. * It's recommended to return the class of the component, but you can also return an instance of the component. * - * @param {Injector} injector Injector. - * @param {any} field The field object. - * @return {any|Promise} The component (or promise resolved with component) to use, undefined if not found. + * @param injector Injector. + * @param field The field object. + * @return The component (or promise resolved with component) to use, undefined if not found. */ getComponent(injector: Injector, plugin: any): any | Promise { return AddonModDataFieldMultimenuComponent; @@ -41,9 +41,9 @@ export class AddonModDataFieldMultimenuHandler implements AddonModDataFieldHandl /** * Get field search data in the input data. * - * @param {any} field Defines the field to be rendered. - * @param {any} inputData Data entered in the search form. - * @return {any} With name and value of the data to be sent. + * @param field Defines the field to be rendered. + * @param inputData Data entered in the search form. + * @return With name and value of the data to be sent. */ getFieldSearchData(field: any, inputData: any): any { const fieldName = 'f_' + field.id, @@ -73,9 +73,9 @@ export class AddonModDataFieldMultimenuHandler implements AddonModDataFieldHandl /** * Get field edit data in the input data. * - * @param {any} field Defines the field to be rendered. - * @param {any} inputData Data entered in the edit form. - * @return {any} With name and value of the data to be sent. + * @param field Defines the field to be rendered. + * @param inputData Data entered in the edit form. + * @return With name and value of the data to be sent. */ getFieldEditData(field: any, inputData: any, originalFieldData: any): any { const fieldName = 'f_' + field.id; @@ -89,10 +89,10 @@ export class AddonModDataFieldMultimenuHandler implements AddonModDataFieldHandl /** * Get field data in changed. * - * @param {any} field Defines the field to be rendered. - * @param {any} inputData Data entered in the edit form. - * @param {any} originalFieldData Original field entered data. - * @return {Promise | boolean} If the field has changes. + * @param field Defines the field to be rendered. + * @param inputData Data entered in the edit form. + * @param originalFieldData Original field entered data. + * @return If the field has changes. */ hasFieldDataChanged(field: any, inputData: any, originalFieldData: any): Promise | boolean { const fieldName = 'f_' + field.id; @@ -105,9 +105,9 @@ export class AddonModDataFieldMultimenuHandler implements AddonModDataFieldHandl /** * Check and get field requeriments. * - * @param {any} field Defines the field to be rendered. - * @param {any} inputData Data entered in the edit form. - * @return {string | false} String with the notification or false. + * @param field Defines the field to be rendered. + * @param inputData Data entered in the edit form. + * @return String with the notification or false. */ getFieldsNotifications(field: any, inputData: any): string | false { if (field.required && (!inputData || !inputData.length || !inputData[0].value)) { @@ -120,10 +120,10 @@ export class AddonModDataFieldMultimenuHandler implements AddonModDataFieldHandl /** * Override field content data with offline submission. * - * @param {any} originalContent Original data to be overriden. - * @param {any} offlineContent Array with all the offline data to override. - * @param {any} [offlineFiles] Array with all the offline files in the field. - * @return {any} Data overriden + * @param originalContent Original data to be overriden. + * @param offlineContent Array with all the offline data to override. + * @param offlineFiles Array with all the offline files in the field. + * @return Data overriden */ overrideData(originalContent: any, offlineContent: any, offlineFiles?: any): any { originalContent.content = (offlineContent[''] && offlineContent[''].join('##')) || ''; @@ -134,7 +134,7 @@ export class AddonModDataFieldMultimenuHandler implements AddonModDataFieldHandl /** * Whether or not the handler is enabled on a site level. * - * @return {boolean|Promise} True or promise resolved with true if enabled. + * @return True or promise resolved with true if enabled. */ isEnabled(): boolean | Promise { return true; diff --git a/src/addon/mod/data/fields/number/component/addon-mod-data-field-number.html b/src/addon/mod/data/fields/number/component/addon-mod-data-field-number.html index aa71edc43..2e02d9fd5 100644 --- a/src/addon/mod/data/fields/number/component/addon-mod-data-field-number.html +++ b/src/addon/mod/data/fields/number/component/addon-mod-data-field-number.html @@ -4,4 +4,4 @@ - +{{ value.content }} diff --git a/src/addon/mod/data/fields/number/component/number.ts b/src/addon/mod/data/fields/number/component/number.ts index 73823be14..e43c3f43e 100644 --- a/src/addon/mod/data/fields/number/component/number.ts +++ b/src/addon/mod/data/fields/number/component/number.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/addon/mod/data/fields/number/number.module.ts b/src/addon/mod/data/fields/number/number.module.ts index ce2acc789..69fa463d7 100644 --- a/src/addon/mod/data/fields/number/number.module.ts +++ b/src/addon/mod/data/fields/number/number.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/addon/mod/data/fields/number/providers/handler.ts b/src/addon/mod/data/fields/number/providers/handler.ts index 62308e6b5..a9a57d217 100644 --- a/src/addon/mod/data/fields/number/providers/handler.ts +++ b/src/addon/mod/data/fields/number/providers/handler.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -32,9 +32,9 @@ export class AddonModDataFieldNumberHandler extends AddonModDataFieldTextHandler * Return the Component to use to display the plugin data. * It's recommended to return the class of the component, but you can also return an instance of the component. * - * @param {Injector} injector Injector. - * @param {any} field The field object. - * @return {any|Promise} The component (or promise resolved with component) to use, undefined if not found. + * @param injector Injector. + * @param field The field object. + * @return The component (or promise resolved with component) to use, undefined if not found. */ getComponent(injector: Injector, plugin: any): any | Promise { return AddonModDataFieldNumberComponent; @@ -43,10 +43,10 @@ export class AddonModDataFieldNumberHandler extends AddonModDataFieldTextHandler /** * Get field data in changed. * - * @param {any} field Defines the field to be rendered. - * @param {any} inputData Data entered in the edit form. - * @param {any} originalFieldData Original field entered data. - * @return {Promise | boolean} If the field has changes. + * @param field Defines the field to be rendered. + * @param inputData Data entered in the edit form. + * @param originalFieldData Original field entered data. + * @return If the field has changes. */ hasFieldDataChanged(field: any, inputData: any, originalFieldData: any): Promise | boolean { const fieldName = 'f_' + field.id; @@ -59,9 +59,9 @@ export class AddonModDataFieldNumberHandler extends AddonModDataFieldTextHandler /** * Check and get field requeriments. * - * @param {any} field Defines the field to be rendered. - * @param {any} inputData Data entered in the edit form. - * @return {string | false} String with the notification or false. + * @param field Defines the field to be rendered. + * @param inputData Data entered in the edit form. + * @return String with the notification or false. */ getFieldsNotifications(field: any, inputData: any): string | false { if (field.required && (!inputData || !inputData.length || inputData[0].value == '')) { diff --git a/src/addon/mod/data/fields/picture/component/picture.ts b/src/addon/mod/data/fields/picture/component/picture.ts index 0c5836efa..570a706dc 100644 --- a/src/addon/mod/data/fields/picture/component/picture.ts +++ b/src/addon/mod/data/fields/picture/component/picture.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -47,8 +47,8 @@ export class AddonModDataFieldPictureComponent extends AddonModDataFieldPluginCo /** * Get the files from the input value. * - * @param {any} value Input value. - * @return {any} List of files. + * @param value Input value. + * @return List of files. */ protected getFiles(value: any): any { let files = (value && value.files) || []; @@ -64,9 +64,9 @@ export class AddonModDataFieldPictureComponent extends AddonModDataFieldPluginCo /** * Find file in a list. * - * @param {any[]} files File list where to search. - * @param {string} filenameSeek Filename to search. - * @return {any} File found or false. + * @param files File list where to search. + * @param filenameSeek Filename to search. + * @return File found or false. */ protected findFile(files: any[], filenameSeek: string): any { return files.find((file) => file.filename == filenameSeek) || false; @@ -97,7 +97,7 @@ export class AddonModDataFieldPictureComponent extends AddonModDataFieldPluginCo /** * Update value being shown. * - * @param {any} value New value to be set. + * @param value New value to be set. */ protected updateValue(value: any): void { this.value = value; diff --git a/src/addon/mod/data/fields/picture/picture.module.ts b/src/addon/mod/data/fields/picture/picture.module.ts index c666e1bad..ba85a3350 100644 --- a/src/addon/mod/data/fields/picture/picture.module.ts +++ b/src/addon/mod/data/fields/picture/picture.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/addon/mod/data/fields/picture/providers/handler.ts b/src/addon/mod/data/fields/picture/providers/handler.ts index 89abf7009..1ccbbd46c 100644 --- a/src/addon/mod/data/fields/picture/providers/handler.ts +++ b/src/addon/mod/data/fields/picture/providers/handler.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -34,9 +34,9 @@ export class AddonModDataFieldPictureHandler implements AddonModDataFieldHandler * Return the Component to use to display the plugin data. * It's recommended to return the class of the component, but you can also return an instance of the component. * - * @param {Injector} injector Injector. - * @param {any} field The field object. - * @return {any|Promise} The component (or promise resolved with component) to use, undefined if not found. + * @param injector Injector. + * @param field The field object. + * @return The component (or promise resolved with component) to use, undefined if not found. */ getComponent(injector: Injector, plugin: any): any | Promise { return AddonModDataFieldPictureComponent; @@ -45,9 +45,9 @@ export class AddonModDataFieldPictureHandler implements AddonModDataFieldHandler /** * Get field search data in the input data. * - * @param {any} field Defines the field to be rendered. - * @param {any} inputData Data entered in the search form. - * @return {any} With name and value of the data to be sent. + * @param field Defines the field to be rendered. + * @param inputData Data entered in the search form. + * @return With name and value of the data to be sent. */ getFieldSearchData(field: any, inputData: any): any { const fieldName = 'f_' + field.id; @@ -65,9 +65,9 @@ export class AddonModDataFieldPictureHandler implements AddonModDataFieldHandler /** * Get field edit data in the input data. * - * @param {any} field Defines the field to be rendered. - * @param {any} inputData Data entered in the edit form. - * @return {any} With name and value of the data to be sent. + * @param field Defines the field to be rendered. + * @param inputData Data entered in the edit form. + * @return With name and value of the data to be sent. */ getFieldEditData(field: any, inputData: any, originalFieldData: any): any { const files = this.getFieldEditFiles(field); @@ -90,8 +90,8 @@ export class AddonModDataFieldPictureHandler implements AddonModDataFieldHandler /** * Get field edit files in the input data. * - * @param {any} field Defines the field.. - * @return {any} With name and value of the data to be sent. + * @param field Defines the field.. + * @return With name and value of the data to be sent. */ getFieldEditFiles(field: any): any { return this.fileSessionprovider.getFiles(AddonModDataProvider.COMPONENT, field.dataid + '_' + field.id); @@ -100,10 +100,10 @@ export class AddonModDataFieldPictureHandler implements AddonModDataFieldHandler /** * Get field data in changed. * - * @param {any} field Defines the field to be rendered. - * @param {any} inputData Data entered in the edit form. - * @param {any} originalFieldData Original field entered data. - * @return {Promise | boolean} If the field has changes. + * @param field Defines the field to be rendered. + * @param inputData Data entered in the edit form. + * @param originalFieldData Original field entered data. + * @return If the field has changes. */ hasFieldDataChanged(field: any, inputData: any, originalFieldData: any): Promise | boolean { const fieldName = 'f_' + field.id + '_alttext', @@ -129,9 +129,9 @@ export class AddonModDataFieldPictureHandler implements AddonModDataFieldHandler /** * Check and get field requeriments. * - * @param {any} field Defines the field to be rendered. - * @param {any} inputData Data entered in the edit form. - * @return {string | false} String with the notification or false. + * @param field Defines the field to be rendered. + * @param inputData Data entered in the edit form. + * @return String with the notification or false. */ getFieldsNotifications(field: any, inputData: any): string | false { if (field.required) { @@ -158,10 +158,10 @@ export class AddonModDataFieldPictureHandler implements AddonModDataFieldHandler /** * Override field content data with offline submission. * - * @param {any} originalContent Original data to be overriden. - * @param {any} offlineContent Array with all the offline data to override. - * @param {any} [offlineFiles] Array with all the offline files in the field. - * @return {any} Data overriden + * @param originalContent Original data to be overriden. + * @param offlineContent Array with all the offline data to override. + * @param offlineFiles Array with all the offline files in the field. + * @return Data overriden */ overrideData(originalContent: any, offlineContent: any, offlineFiles?: any): any { if (offlineContent && offlineContent.file && offlineContent.file.offline > 0 && offlineFiles && offlineFiles.length > 0) { @@ -180,7 +180,7 @@ export class AddonModDataFieldPictureHandler implements AddonModDataFieldHandler /** * Whether or not the handler is enabled on a site level. * - * @return {boolean|Promise} True or promise resolved with true if enabled. + * @return True or promise resolved with true if enabled. */ isEnabled(): boolean | Promise { return true; diff --git a/src/addon/mod/data/fields/radiobutton/component/addon-mod-data-field-radiobutton.html b/src/addon/mod/data/fields/radiobutton/component/addon-mod-data-field-radiobutton.html index 53dabd14c..eb8fbe013 100644 --- a/src/addon/mod/data/fields/radiobutton/component/addon-mod-data-field-radiobutton.html +++ b/src/addon/mod/data/fields/radiobutton/component/addon-mod-data-field-radiobutton.html @@ -7,4 +7,4 @@ - \ No newline at end of file +{{ value.content }} \ No newline at end of file diff --git a/src/addon/mod/data/fields/radiobutton/component/radiobutton.ts b/src/addon/mod/data/fields/radiobutton/component/radiobutton.ts index 3e4122174..d4be0615d 100644 --- a/src/addon/mod/data/fields/radiobutton/component/radiobutton.ts +++ b/src/addon/mod/data/fields/radiobutton/component/radiobutton.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/addon/mod/data/fields/radiobutton/providers/handler.ts b/src/addon/mod/data/fields/radiobutton/providers/handler.ts index a58b407bf..1f9fc210a 100644 --- a/src/addon/mod/data/fields/radiobutton/providers/handler.ts +++ b/src/addon/mod/data/fields/radiobutton/providers/handler.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -30,9 +30,9 @@ export class AddonModDataFieldRadiobuttonHandler implements AddonModDataFieldHan * Return the Component to use to display the plugin data. * It's recommended to return the class of the component, but you can also return an instance of the component. * - * @param {Injector} injector Injector. - * @param {any} field The field object. - * @return {any|Promise} The component (or promise resolved with component) to use, undefined if not found. + * @param injector Injector. + * @param field The field object. + * @return The component (or promise resolved with component) to use, undefined if not found. */ getComponent(injector: Injector, plugin: any): any | Promise { return AddonModDataFieldRadiobuttonComponent; @@ -41,9 +41,9 @@ export class AddonModDataFieldRadiobuttonHandler implements AddonModDataFieldHan /** * Get field search data in the input data. * - * @param {any} field Defines the field to be rendered. - * @param {any} inputData Data entered in the search form. - * @return {any} With name and value of the data to be sent. + * @param field Defines the field to be rendered. + * @param inputData Data entered in the search form. + * @return With name and value of the data to be sent. */ getFieldSearchData(field: any, inputData: any): any { const fieldName = 'f_' + field.id; @@ -60,9 +60,9 @@ export class AddonModDataFieldRadiobuttonHandler implements AddonModDataFieldHan /** * Get field edit data in the input data. * - * @param {any} field Defines the field to be rendered. - * @param {any} inputData Data entered in the edit form. - * @return {any} With name and value of the data to be sent. + * @param field Defines the field to be rendered. + * @param inputData Data entered in the edit form. + * @return With name and value of the data to be sent. */ getFieldEditData(field: any, inputData: any, originalFieldData: any): any { const fieldName = 'f_' + field.id; @@ -76,10 +76,10 @@ export class AddonModDataFieldRadiobuttonHandler implements AddonModDataFieldHan /** * Get field data in changed. * - * @param {any} field Defines the field to be rendered. - * @param {any} inputData Data entered in the edit form. - * @param {any} originalFieldData Original field entered data. - * @return {Promise | boolean} If the field has changes. + * @param field Defines the field to be rendered. + * @param inputData Data entered in the edit form. + * @param originalFieldData Original field entered data. + * @return If the field has changes. */ hasFieldDataChanged(field: any, inputData: any, originalFieldData: any): Promise | boolean { const fieldName = 'f_' + field.id, @@ -92,9 +92,9 @@ export class AddonModDataFieldRadiobuttonHandler implements AddonModDataFieldHan /** * Check and get field requeriments. * - * @param {any} field Defines the field to be rendered. - * @param {any} inputData Data entered in the edit form. - * @return {string | false} String with the notification or false. + * @param field Defines the field to be rendered. + * @param inputData Data entered in the edit form. + * @return String with the notification or false. */ getFieldsNotifications(field: any, inputData: any): string | false { if (field.required && (!inputData || !inputData.length || !inputData[0].value)) { @@ -107,10 +107,10 @@ export class AddonModDataFieldRadiobuttonHandler implements AddonModDataFieldHan /** * Override field content data with offline submission. * - * @param {any} originalContent Original data to be overriden. - * @param {any} offlineContent Array with all the offline data to override. - * @param {any} [offlineFiles] Array with all the offline files in the field. - * @return {any} Data overriden + * @param originalContent Original data to be overriden. + * @param offlineContent Array with all the offline data to override. + * @param offlineFiles Array with all the offline files in the field. + * @return Data overriden */ overrideData(originalContent: any, offlineContent: any, offlineFiles?: any): any { originalContent.content = offlineContent[''] || ''; @@ -121,7 +121,7 @@ export class AddonModDataFieldRadiobuttonHandler implements AddonModDataFieldHan /** * Whether or not the handler is enabled on a site level. * - * @return {boolean|Promise} True or promise resolved with true if enabled. + * @return True or promise resolved with true if enabled. */ isEnabled(): boolean | Promise { return true; diff --git a/src/addon/mod/data/fields/radiobutton/radiobutton.module.ts b/src/addon/mod/data/fields/radiobutton/radiobutton.module.ts index d361bc934..877e490f5 100644 --- a/src/addon/mod/data/fields/radiobutton/radiobutton.module.ts +++ b/src/addon/mod/data/fields/radiobutton/radiobutton.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/addon/mod/data/fields/text/component/addon-mod-data-field-text.html b/src/addon/mod/data/fields/text/component/addon-mod-data-field-text.html index fa8846daf..9af142d7c 100644 --- a/src/addon/mod/data/fields/text/component/addon-mod-data-field-text.html +++ b/src/addon/mod/data/fields/text/component/addon-mod-data-field-text.html @@ -4,4 +4,4 @@ - \ No newline at end of file +{{ value.content }} \ No newline at end of file diff --git a/src/addon/mod/data/fields/text/component/text.ts b/src/addon/mod/data/fields/text/component/text.ts index 9429a4d73..960759409 100644 --- a/src/addon/mod/data/fields/text/component/text.ts +++ b/src/addon/mod/data/fields/text/component/text.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/addon/mod/data/fields/text/providers/handler.ts b/src/addon/mod/data/fields/text/providers/handler.ts index 1a4fa8c26..5e54570fd 100644 --- a/src/addon/mod/data/fields/text/providers/handler.ts +++ b/src/addon/mod/data/fields/text/providers/handler.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -30,9 +30,9 @@ export class AddonModDataFieldTextHandler implements AddonModDataFieldHandler { * Return the Component to use to display the plugin data. * It's recommended to return the class of the component, but you can also return an instance of the component. * - * @param {Injector} injector Injector. - * @param {any} field The field object. - * @return {any|Promise} The component (or promise resolved with component) to use, undefined if not found. + * @param injector Injector. + * @param field The field object. + * @return The component (or promise resolved with component) to use, undefined if not found. */ getComponent(injector: Injector, plugin: any): any | Promise { return AddonModDataFieldTextComponent; @@ -41,9 +41,9 @@ export class AddonModDataFieldTextHandler implements AddonModDataFieldHandler { /** * Get field search data in the input data. * - * @param {any} field Defines the field to be rendered. - * @param {any} inputData Data entered in the search form. - * @return {any} With name and value of the data to be sent. + * @param field Defines the field to be rendered. + * @param inputData Data entered in the search form. + * @return With name and value of the data to be sent. */ getFieldSearchData(field: any, inputData: any): any { const fieldName = 'f_' + field.id; @@ -61,9 +61,9 @@ export class AddonModDataFieldTextHandler implements AddonModDataFieldHandler { /** * Get field edit data in the input data. * - * @param {any} field Defines the field to be rendered. - * @param {any} inputData Data entered in the edit form. - * @return {any} With name and value of the data to be sent. + * @param field Defines the field to be rendered. + * @param inputData Data entered in the edit form. + * @return With name and value of the data to be sent. */ getFieldEditData(field: any, inputData: any, originalFieldData: any): any { const fieldName = 'f_' + field.id; @@ -77,10 +77,10 @@ export class AddonModDataFieldTextHandler implements AddonModDataFieldHandler { /** * Get field data in changed. * - * @param {any} field Defines the field to be rendered. - * @param {any} inputData Data entered in the edit form. - * @param {any} originalFieldData Original field entered data. - * @return {Promise | boolean} If the field has changes. + * @param field Defines the field to be rendered. + * @param inputData Data entered in the edit form. + * @param originalFieldData Original field entered data. + * @return If the field has changes. */ hasFieldDataChanged(field: any, inputData: any, originalFieldData: any): Promise | boolean { const fieldName = 'f_' + field.id, @@ -93,9 +93,9 @@ export class AddonModDataFieldTextHandler implements AddonModDataFieldHandler { /** * Check and get field requeriments. * - * @param {any} field Defines the field to be rendered. - * @param {any} inputData Data entered in the edit form. - * @return {string | false} String with the notification or false. + * @param field Defines the field to be rendered. + * @param inputData Data entered in the edit form. + * @return String with the notification or false. */ getFieldsNotifications(field: any, inputData: any): string | false { if (field.required && (!inputData || !inputData.length || !inputData[0].value)) { @@ -108,10 +108,10 @@ export class AddonModDataFieldTextHandler implements AddonModDataFieldHandler { /** * Override field content data with offline submission. * - * @param {any} originalContent Original data to be overriden. - * @param {any} offlineContent Array with all the offline data to override. - * @param {any} [offlineFiles] Array with all the offline files in the field. - * @return {any} Data overriden + * @param originalContent Original data to be overriden. + * @param offlineContent Array with all the offline data to override. + * @param offlineFiles Array with all the offline files in the field. + * @return Data overriden */ overrideData(originalContent: any, offlineContent: any, offlineFiles?: any): any { originalContent.content = offlineContent[''] || ''; @@ -122,7 +122,7 @@ export class AddonModDataFieldTextHandler implements AddonModDataFieldHandler { /** * Whether or not the handler is enabled on a site level. * - * @return {boolean|Promise} True or promise resolved with true if enabled. + * @return True or promise resolved with true if enabled. */ isEnabled(): boolean | Promise { return true; diff --git a/src/addon/mod/data/fields/text/text.module.ts b/src/addon/mod/data/fields/text/text.module.ts index a96ddf9de..9f288d343 100644 --- a/src/addon/mod/data/fields/text/text.module.ts +++ b/src/addon/mod/data/fields/text/text.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/addon/mod/data/fields/textarea/component/addon-mod-data-field-textarea.html b/src/addon/mod/data/fields/textarea/component/addon-mod-data-field-textarea.html index 861d1edc9..02ce6f367 100644 --- a/src/addon/mod/data/fields/textarea/component/addon-mod-data-field-textarea.html +++ b/src/addon/mod/data/fields/textarea/component/addon-mod-data-field-textarea.html @@ -6,4 +6,4 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/src/addon/mod/data/fields/textarea/component/textarea.ts b/src/addon/mod/data/fields/textarea/component/textarea.ts index a988de521..ba54212d7 100644 --- a/src/addon/mod/data/fields/textarea/component/textarea.ts +++ b/src/addon/mod/data/fields/textarea/component/textarea.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -36,8 +36,8 @@ export class AddonModDataFieldTextareaComponent extends AddonModDataFieldPluginC /** * Format value to be shown. Replacing plugin file Urls. * - * @param {any} value Value to replace. - * @return {string} Replaced string to be rendered. + * @param value Value to replace. + * @return Replaced string to be rendered. */ format(value: any): string { const files = (value && value.files) || []; diff --git a/src/addon/mod/data/fields/textarea/providers/handler.ts b/src/addon/mod/data/fields/textarea/providers/handler.ts index 28ad00b9a..206a31b27 100644 --- a/src/addon/mod/data/fields/textarea/providers/handler.ts +++ b/src/addon/mod/data/fields/textarea/providers/handler.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -33,9 +33,9 @@ export class AddonModDataFieldTextareaHandler extends AddonModDataFieldTextHandl * Return the Component to use to display the plugin data. * It's recommended to return the class of the component, but you can also return an instance of the component. * - * @param {Injector} injector Injector. - * @param {any} field The field object. - * @return {any|Promise} The component (or promise resolved with component) to use, undefined if not found. + * @param injector Injector. + * @param field The field object. + * @return The component (or promise resolved with component) to use, undefined if not found. */ getComponent(injector: Injector, plugin: any): any | Promise { return AddonModDataFieldTextareaComponent; @@ -44,9 +44,9 @@ export class AddonModDataFieldTextareaHandler extends AddonModDataFieldTextHandl /** * Get field edit data in the input data. * - * @param {any} field Defines the field to be rendered. - * @param {any} inputData Data entered in the edit form. - * @return {any} With name and value of the data to be sent. + * @param field Defines the field to be rendered. + * @param inputData Data entered in the edit form. + * @return With name and value of the data to be sent. */ getFieldEditData(field: any, inputData: any, originalFieldData: any): any { const fieldName = 'f_' + field.id; @@ -81,10 +81,10 @@ export class AddonModDataFieldTextareaHandler extends AddonModDataFieldTextHandl /** * Get field edit files in the input data. * - * @param {any} field Defines the field.. - * @param {any} inputData Data entered in the edit form. - * @param {any} originalFieldData Original field entered data. - * @return {any} With name and value of the data to be sent. + * @param field Defines the field.. + * @param inputData Data entered in the edit form. + * @param originalFieldData Original field entered data. + * @return With name and value of the data to be sent. */ getFieldEditFiles(field: any, inputData: any, originalFieldData: any): any { return (originalFieldData && originalFieldData.files) || []; @@ -93,9 +93,9 @@ export class AddonModDataFieldTextareaHandler extends AddonModDataFieldTextHandl /** * Check and get field requeriments. * - * @param {any} field Defines the field to be rendered. - * @param {any} inputData Data entered in the edit form. - * @return {string | false} String with the notification or false. + * @param field Defines the field to be rendered. + * @param inputData Data entered in the edit form. + * @return String with the notification or false. */ getFieldsNotifications(field: any, inputData: any): string | false { if (field.required) { @@ -116,10 +116,10 @@ export class AddonModDataFieldTextareaHandler extends AddonModDataFieldTextHandl /** * Override field content data with offline submission. * - * @param {any} originalContent Original data to be overriden. - * @param {any} offlineContent Array with all the offline data to override. - * @param {any} [offlineFiles] Array with all the offline files in the field. - * @return {any} Data overriden + * @param originalContent Original data to be overriden. + * @param offlineContent Array with all the offline data to override. + * @param offlineFiles Array with all the offline files in the field. + * @return Data overriden */ overrideData(originalContent: any, offlineContent: any, offlineFiles?: any): any { originalContent.content = offlineContent[''] || ''; diff --git a/src/addon/mod/data/fields/textarea/textarea.module.ts b/src/addon/mod/data/fields/textarea/textarea.module.ts index 8c63d53f7..3f7070ef7 100644 --- a/src/addon/mod/data/fields/textarea/textarea.module.ts +++ b/src/addon/mod/data/fields/textarea/textarea.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/addon/mod/data/fields/url/component/url.ts b/src/addon/mod/data/fields/url/component/url.ts index 767f1e212..14dd7f62b 100644 --- a/src/addon/mod/data/fields/url/component/url.ts +++ b/src/addon/mod/data/fields/url/component/url.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/addon/mod/data/fields/url/providers/handler.ts b/src/addon/mod/data/fields/url/providers/handler.ts index 1854df829..d6da62ad0 100644 --- a/src/addon/mod/data/fields/url/providers/handler.ts +++ b/src/addon/mod/data/fields/url/providers/handler.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -32,9 +32,9 @@ export class AddonModDataFieldUrlHandler extends AddonModDataFieldTextHandler { * Return the Component to use to display the plugin data. * It's recommended to return the class of the component, but you can also return an instance of the component. * - * @param {Injector} injector Injector. - * @param {any} field The field object. - * @return {any|Promise} The component (or promise resolved with component) to use, undefined if not found. + * @param injector Injector. + * @param field The field object. + * @return The component (or promise resolved with component) to use, undefined if not found. */ getComponent(injector: Injector, plugin: any): any | Promise { return AddonModDataFieldUrlComponent; @@ -43,9 +43,9 @@ export class AddonModDataFieldUrlHandler extends AddonModDataFieldTextHandler { /** * Get field edit data in the input data. * - * @param {any} field Defines the field to be rendered. - * @param {any} inputData Data entered in the edit form. - * @return {any} With name and value of the data to be sent. + * @param field Defines the field to be rendered. + * @param inputData Data entered in the edit form. + * @return With name and value of the data to be sent. */ getFieldEditData(field: any, inputData: any, originalFieldData: any): any { const fieldName = 'f_' + field.id; @@ -62,9 +62,9 @@ export class AddonModDataFieldUrlHandler extends AddonModDataFieldTextHandler { /** * Check and get field requeriments. * - * @param {any} field Defines the field to be rendered. - * @param {any} inputData Data entered in the edit form. - * @return {string | false} String with the notification or false. + * @param field Defines the field to be rendered. + * @param inputData Data entered in the edit form. + * @return String with the notification or false. */ getFieldsNotifications(field: any, inputData: any): string | false { if (field.required && (!inputData || !inputData.length || !inputData[0].value)) { diff --git a/src/addon/mod/data/fields/url/url.module.ts b/src/addon/mod/data/fields/url/url.module.ts index 09cd8911e..e92cf2fb5 100644 --- a/src/addon/mod/data/fields/url/url.module.ts +++ b/src/addon/mod/data/fields/url/url.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/addon/mod/data/lang/en.json b/src/addon/mod/data/lang/en.json index 219ec090c..de89c6bca 100644 --- a/src/addon/mod/data/lang/en.json +++ b/src/addon/mod/data/lang/en.json @@ -20,10 +20,13 @@ "expired": "Sorry, this activity closed on {{$a}} and is no longer available", "fields": "Fields", "foundrecords": "Found records: {{$a.num}}/{{$a.max}} (Reset filters)", + "gettinglocation": "Getting location", "latlongboth": "Both latitude and longitude are required.", + "locationpermissiondenied": "Permission to access your location has been denied.", "menuchoose": "Choose...", "modulenameplural": "Databases", "more": "More", + "mylocation": "My location", "nomatch": "No matching entries found!", "norecords": "No entries in database", "notapproved": "Entry is not approved yet.", diff --git a/src/addon/mod/data/pages/edit/edit.html b/src/addon/mod/data/pages/edit/edit.html index 71673625f..d5865e7e3 100644 --- a/src/addon/mod/data/pages/edit/edit.html +++ b/src/addon/mod/data/pages/edit/edit.html @@ -1,6 +1,6 @@ - +
- - - -

-

- {{ 'core.notsent' | translate }} - {{discussion.userfullname}} -

+ + +
+

+ +

+
+
+ +
+

{{discussion.userfullname}}

+

{{ discussion.groupname }}

+

{{ 'core.notsent' | translate }}

+
+
- - - {{ discussion.groupname }} - -

-
-
- - - -

- - - -

-

- - {{discussion.created | coreDateDayOrTime}} -

{{ 'addon.mod_forum.unreadpostsnumber' | translate:{ '$a' : discussion.numunread} }}
- - {{discussion.userfullname}} -

+ + + +
+

+ + + +

+ +
+
+ +
+

{{discussion.userfullname}}

+

{{ discussion.groupname }}

+

{{discussion.created * 1000 | coreFormatDate: "strftimerecentfull"}}

+
+
+ + + + {{ 'addon.mod_forum.lastpost' | translate }} + {{discussion.timemodified | coreTimeAgo}} + {{discussion.created | coreTimeAgo}} + + + + + {{ 'addon.mod_forum.numreplies' | translate:{numreplies: discussion.numreplies} }} + {{ discussion.numunread }} + + +
- - - - - - - {{ discussion.groupname }} - - - - - {{ 'addon.mod_forum.numreplies' | translate:{numreplies: discussion.numreplies} }} - - - - - {{discussion.timemodified | coreTimeAgo}} - - - -
+ diff --git a/src/addon/mod/forum/components/index/index.scss b/src/addon/mod/forum/components/index/index.scss index 733686a59..911c5d809 100644 --- a/src/addon/mod/forum/components/index/index.scss +++ b/src/addon/mod/forum/components/index/index.scss @@ -1,16 +1,54 @@ -ion-app.app-root addon-mod-forum-index { - .addon-forum-discussion-selected { - border-top: 5px solid $core-splitview-selected; - } +$addon-forum-avatar-size: 28px; +ion-app.app-root addon-mod-forum-index { .addon-forum-star { color: $core-star-color; } - button.core-button-select .core-section-selector-text { - overflow: hidden; - text-overflow: ellipsis; - line-height: 2em; - white-space: nowrap; + .addon-mod-forum-discussion.item { + .label { + margin-top: 4px; + + h2 { + margin-top: 8px; + margin-bottom: 8px; + font-weight: bold; + ion-icon { + @include margin(0, 6px, 0, 0); + } + } + h3 { + font-size: 1.6rem; + } + } + + ion-avatar { + width: $addon-forum-avatar-size; + height: $addon-forum-avatar-size; + min-width: $addon-forum-avatar-size; + min-height: $addon-forum-avatar-size; + &[item-start] { + @include margin(0, 8px, 0, 0); + } + img { + width: $addon-forum-avatar-size; + height: $addon-forum-avatar-size; + } + } + + .addon-mod-forum-discussion-title, + .addon-mod-forum-discussion-info { + display: flex; + align-items: center; + } + .addon-mod-forum-discussion-title h2, + .addon-mod-forum-discussion-info .addon-mod-forum-discussion-author { + flex-grow: 1; + } + + .addon-mod-forum-discussion-more-info { + font-size: 1.4rem; + clear: both; + } } } diff --git a/src/addon/mod/forum/components/index/index.ts b/src/addon/mod/forum/components/index/index.ts index b3a4209b6..6795fccac 100644 --- a/src/addon/mod/forum/components/index/index.ts +++ b/src/addon/mod/forum/components/index/index.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -13,7 +13,7 @@ // limitations under the License. import { Component, Optional, Injector, ViewChild } from '@angular/core'; -import { Content, ModalController, NavController } from 'ionic-angular'; +import { Content, ModalController, PopoverController } from 'ionic-angular'; import { CoreSplitViewComponent } from '@components/split-view/split-view'; import { CoreCourseModuleMainActivityComponent } from '@core/course/classes/main-activity-component'; import { CoreCourseModulePrefetchDelegate } from '@core/course/providers/module-prefetch-delegate'; @@ -27,6 +27,7 @@ import { AddonModForumHelperProvider } from '../../providers/helper'; import { AddonModForumOfflineProvider } from '../../providers/offline'; import { AddonModForumSyncProvider } from '../../providers/sync'; import { AddonModForumPrefetchHandler } from '../../providers/prefetch-handler'; +import { AddonForumDiscussionOptionsMenuComponent } from '../discussion-options-menu/discussion-options-menu'; /** * Component that displays a forum entry page. @@ -61,6 +62,7 @@ export class AddonModForumIndexComponent extends CoreCourseModuleMainActivityCom protected page = 0; protected trackPosts = false; protected usesGroups = false; + protected canPin = false; protected syncManualObserver: any; // It will observe the sync manual event. protected replyObserver: any; protected newDiscObserver: any; @@ -73,7 +75,6 @@ export class AddonModForumIndexComponent extends CoreCourseModuleMainActivityCom constructor(injector: Injector, @Optional() protected content: Content, - protected navCtrl: NavController, protected modalCtrl: ModalController, protected groupsProvider: CoreGroupsProvider, protected userProvider: CoreUserProvider, @@ -83,7 +84,8 @@ export class AddonModForumIndexComponent extends CoreCourseModuleMainActivityCom protected forumSync: AddonModForumSyncProvider, protected prefetchDelegate: CoreCourseModulePrefetchDelegate, protected prefetchHandler: AddonModForumPrefetchHandler, - protected ratingOffline: CoreRatingOfflineProvider) { + protected ratingOffline: CoreRatingOfflineProvider, + protected popoverCtrl: PopoverController) { super(injector); this.sortingAvailable = this.forumProvider.isDiscussionListSortingAvailable(); @@ -106,8 +108,40 @@ export class AddonModForumIndexComponent extends CoreCourseModuleMainActivityCom this.eventReceived.bind(this, true)); this.replyObserver = this.eventsProvider.on(AddonModForumProvider.REPLY_DISCUSSION_EVENT, this.eventReceived.bind(this, false)); - this.changeDiscObserver = this.eventsProvider.on(AddonModForumProvider.CHANGE_DISCUSSION_EVENT, - this.eventReceived.bind(this, false)); + this.changeDiscObserver = this.eventsProvider.on(AddonModForumProvider.CHANGE_DISCUSSION_EVENT, (data) => { + if ((this.forum && this.forum.id === data.forumId) || data.cmId === this.module.id) { + this.forumProvider.invalidateDiscussionsList(this.forum.id).finally(() => { + if (data.discussionId) { + // Discussion changed, search it in the list of discussions. + const discussion = this.discussions.find((disc) => { + return data.discussionId == disc.discussion; + }); + + if (discussion) { + if (typeof data.locked != 'undefined') { + discussion.locked = data.locked; + } + if (typeof data.pinned != 'undefined') { + discussion.pinned = data.pinned; + } + if (typeof data.starred != 'undefined') { + discussion.starred = data.starred; + } + + this.showLoadingAndRefresh(false); + } + } + + if (typeof data.deleted != 'undefined' && data.deleted) { + if (data.post.parent == 0 && this.splitviewCtrl && this.splitviewCtrl.isOn()) { + // Discussion deleted, clear details page. + this.splitviewCtrl.emptyDetails(); + } + this.showLoadingAndRefresh(false); + } + }); + } + }); // Select the current opened discussion. this.viewDiscObserver = this.eventsProvider.on(AddonModForumProvider.VIEW_DISCUSSION_EVENT, (data) => { @@ -163,10 +197,10 @@ export class AddonModForumIndexComponent extends CoreCourseModuleMainActivityCom /** * Download the component contents. * - * @param {boolean} [refresh=false] Whether we're refreshing data. - * @param {boolean} [sync=false] If the refresh needs syncing. - * @param {boolean} [showErrors=false] Wether to show errors to the user or hide them. - * @return {Promise} Promise resolved when done. + * @param refresh Whether we're refreshing data. + * @param sync If the refresh needs syncing. + * @param showErrors Wether to show errors to the user or hide them. + * @return Promise resolved when done. */ protected fetchContent(refresh: boolean = false, sync: boolean = false, showErrors: boolean = false): Promise { this.loadMoreError = false; @@ -211,18 +245,30 @@ export class AddonModForumIndexComponent extends CoreCourseModuleMainActivityCom }); } }).then(() => { - return Promise.all([ - // Check if the activity uses groups. - this.groupsProvider.getActivityGroupMode(this.forum.cmid).then((mode) => { - this.usesGroups = (mode === CoreGroupsProvider.SEPARATEGROUPS || mode === CoreGroupsProvider.VISIBLEGROUPS); - }), - this.forumProvider.getAccessInformation(this.forum.id).then((accessInfo) => { - // Disallow adding discussions if cut-off date is reached and the user has not the capability to override it. - // Just in case the forum was fetched from WS when the cut-off date was not reached but it is now. - const cutoffDateReached = this.forumHelper.isCutoffDateReached(this.forum) && !accessInfo.cancanoverridecutoff; - this.canAddDiscussion = this.forum.cancreatediscussions && !cutoffDateReached; - }), - ]); + const promises = []; + // Check if the activity uses groups. + promises.push(this.groupsProvider.getActivityGroupMode(this.forum.cmid).then((mode) => { + this.usesGroups = (mode === CoreGroupsProvider.SEPARATEGROUPS || mode === CoreGroupsProvider.VISIBLEGROUPS); + })); + promises.push(this.forumProvider.getAccessInformation(this.forum.id).then((accessInfo) => { + // Disallow adding discussions if cut-off date is reached and the user has not the capability to override it. + // Just in case the forum was fetched from WS when the cut-off date was not reached but it is now. + const cutoffDateReached = this.forumHelper.isCutoffDateReached(this.forum) && !accessInfo.cancanoverridecutoff; + this.canAddDiscussion = this.forum.cancreatediscussions && !cutoffDateReached; + })); + + if (this.forumProvider.isSetPinStateAvailableForSite()) { + // Use the canAddDiscussion WS to check if the user can pin discussions. + promises.push(this.forumProvider.canAddDiscussionToAll(this.forum.id).then((response) => { + this.canPin = !!response.canpindiscussions; + }).catch(() => { + this.canPin = false; + })); + } else { + this.canPin = false; + } + + return Promise.all(promises); })); promises.push(this.fetchSortOrderPreference()); @@ -244,8 +290,7 @@ export class AddonModForumIndexComponent extends CoreCourseModuleMainActivityCom this.domUtils.showErrorModalDefault(message, 'addon.mod_forum.errorgetforum', true); this.loadMoreError = true; // Set to prevent infinite calls with infinite-loading. - }).then(() => { - // All data obtained, now fill the context menu. + }).finally(() => { this.fillContextMenu(refresh); }); } @@ -253,7 +298,7 @@ export class AddonModForumIndexComponent extends CoreCourseModuleMainActivityCom /** * Convenience function to fetch offline discussions. * - * @return {Promise} Promise resolved when done. + * @return Promise resolved when done. */ protected fetchOfflineDiscussion(): Promise { return this.forumOffline.getNewDiscussions(this.forum.id).then((offlineDiscussions) => { @@ -299,8 +344,8 @@ export class AddonModForumIndexComponent extends CoreCourseModuleMainActivityCom /** * Convenience function to get forum discussions. * - * @param {boolean} refresh Whether we're refreshing data. - * @return {Promise} Promise resolved when done. + * @param refresh Whether we're refreshing data. + * @return Promise resolved when done. */ protected fetchDiscussions(refresh: boolean): Promise { this.loadMoreError = false; @@ -371,8 +416,8 @@ export class AddonModForumIndexComponent extends CoreCourseModuleMainActivityCom /** * Convenience function to load more forum discussions. * - * @param {any} [infiniteComplete] Infinite scroll complete function. Only used from core-infinite-loading. - * @return {Promise} Promise resolved when done. + * @param infiniteComplete Infinite scroll complete function. Only used from core-infinite-loading. + * @return Promise resolved when done. */ fetchMoreDiscussions(infiniteComplete?: any): Promise { return this.fetchDiscussions(false).catch((message) => { @@ -387,7 +432,7 @@ export class AddonModForumIndexComponent extends CoreCourseModuleMainActivityCom /** * Convenience function to fetch the sort order preference. * - * @return {Promise} Promise resolved when done. + * @return Promise resolved when done. */ protected fetchSortOrderPreference(): Promise { let promise; @@ -408,7 +453,7 @@ export class AddonModForumIndexComponent extends CoreCourseModuleMainActivityCom /** * Perform the invalidate content function. * - * @return {Promise} Resolved when done. + * @return Resolved when done. */ protected invalidateContent(): Promise { const promises = []; @@ -431,7 +476,7 @@ export class AddonModForumIndexComponent extends CoreCourseModuleMainActivityCom /** * Performs the sync of the activity. * - * @return {Promise} Promise resolved when done. + * @return Promise resolved when done. */ protected sync(): Promise { return this.prefetchHandler.sync(this.module, this.courseId); @@ -440,8 +485,8 @@ export class AddonModForumIndexComponent extends CoreCourseModuleMainActivityCom /** * Checks if sync has succeed from result sync data. * - * @param {any} result Data returned on the sync function. - * @return {boolean} Whether it succeed or not. + * @param result Data returned on the sync function. + * @return Whether it succeed or not. */ protected hasSyncSucceed(result: any): boolean { return result.updated; @@ -450,8 +495,8 @@ export class AddonModForumIndexComponent extends CoreCourseModuleMainActivityCom /** * Compares sync event data with current data to check if refresh content is needed. * - * @param {any} syncEventData Data receiven on sync observer. - * @return {boolean} True if refresh is needed, false otherwise. + * @param syncEventData Data receiven on sync observer. + * @return True if refresh is needed, false otherwise. */ protected isRefreshSyncNeeded(syncEventData: any): boolean { return this.forum && syncEventData.source != 'index' && syncEventData.forumId == this.forum.id && @@ -461,8 +506,8 @@ export class AddonModForumIndexComponent extends CoreCourseModuleMainActivityCom /** * Function called when we receive an event of new discussion or reply to discussion. * - * @param {boolean} isNewDiscussion Whether it's a new discussion event. - * @param {any} data Event data. + * @param isNewDiscussion Whether it's a new discussion event. + * @param data Event data. */ protected eventReceived(isNewDiscussion: boolean, data: any): void { if ((this.forum && this.forum.id === data.forumId) || data.cmId === this.module.id) { @@ -499,7 +544,7 @@ export class AddonModForumIndexComponent extends CoreCourseModuleMainActivityCom /** * Opens a discussion. * - * @param {any} discussion Discussion object. + * @param discussion Discussion object. */ openDiscussion(discussion: any): void { const params = { @@ -515,7 +560,7 @@ export class AddonModForumIndexComponent extends CoreCourseModuleMainActivityCom /** * Opens the new discussion form. * - * @param {number} [timeCreated=0] Creation time of the offline discussion. + * @param timeCreated Creation time of the offline discussion. */ openNewDiscussion(timeCreated: number = 0): void { const params = { @@ -532,7 +577,7 @@ export class AddonModForumIndexComponent extends CoreCourseModuleMainActivityCom /** * Display the sort order selector modal. * - * @param {MouseEvent} event Event. + * @param event Event. */ showSortOrderSelector(event: MouseEvent): void { if (!this.sortingAvailable) { @@ -560,6 +605,42 @@ export class AddonModForumIndexComponent extends CoreCourseModuleMainActivityCom this.sortOrderSelectorExpanded = true; } + /** + * Show the context menu. + * + * @param e Click Event. + */ + showOptionsMenu(e: Event, discussion: any): void { + e.preventDefault(); + e.stopPropagation(); + + const popover = this.popoverCtrl.create(AddonForumDiscussionOptionsMenuComponent, { + discussion: discussion, + forumId: this.forum.id, + cmId: this.module.id + }); + popover.onDidDismiss((data) => { + if (data && data.action) { + switch (data.action) { + case 'lock': + discussion.locked = data.value; + break; + case 'pin': + discussion.pinned = data.value; + break; + case 'star': + discussion.starred = data.value; + break; + default: + break; + } + } + }); + popover.present({ + ev: e + }); + } + /** * Component being destroyed. */ diff --git a/src/addon/mod/forum/components/post-options-menu/addon-forum-post-options-menu.html b/src/addon/mod/forum/components/post-options-menu/addon-forum-post-options-menu.html new file mode 100644 index 000000000..9b7e34dd6 --- /dev/null +++ b/src/addon/mod/forum/components/post-options-menu/addon-forum-post-options-menu.html @@ -0,0 +1,18 @@ + + + +

{{ 'addon.mod_forum.edit' | translate }}

+
+ + +

{{ 'addon.mod_forum.delete' | translate }}

+

{{ 'core.discard' | translate }}

+
+ +

{{ 'core.numwords' | translate: {'$a': wordCount} }}

+
+ + +

{{ 'core.openinbrowser' | translate }}

+
+
diff --git a/src/addon/mod/forum/components/post-options-menu/post-options-menu.scss b/src/addon/mod/forum/components/post-options-menu/post-options-menu.scss new file mode 100644 index 000000000..f790d9f8d --- /dev/null +++ b/src/addon/mod/forum/components/post-options-menu/post-options-menu.scss @@ -0,0 +1,11 @@ +addon-forum-post-options-menu { + core-loading:not(.core-loading-loaded) > .core-loading-container { + position: relative !important; + padding-top: 10px !important; + padding-bottom: 10px !important; + overflow: hidden; + } + core-loading > .core-loading-container .core-loading-message { + display: none; + } +} \ No newline at end of file diff --git a/src/addon/mod/forum/components/post-options-menu/post-options-menu.ts b/src/addon/mod/forum/components/post-options-menu/post-options-menu.ts new file mode 100644 index 000000000..cd0fb7fdf --- /dev/null +++ b/src/addon/mod/forum/components/post-options-menu/post-options-menu.ts @@ -0,0 +1,104 @@ +// (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 { Component, OnInit } from '@angular/core'; +import { NavParams, ViewController } from 'ionic-angular'; +import { CoreDomUtilsProvider } from '@providers/utils/dom'; +import { CoreSitesProvider } from '@providers/sites'; +import { CoreSite } from '@classes/site'; +import { AddonModForumProvider } from '../../providers/forum'; + +/** + * This component is meant to display a popover with the post options. + */ +@Component({ + selector: 'addon-forum-post-options-menu', + templateUrl: 'addon-forum-post-options-menu.html' +}) +export class AddonForumPostOptionsMenuComponent implements OnInit { + post: any; // The post. + forumId: number; // The forum Id. + wordCount: number; // Number of words when available. + canEdit = false; + canDelete = false; + loaded = false; + url: string; + + constructor(navParams: NavParams, + protected viewCtrl: ViewController, + protected domUtils: CoreDomUtilsProvider, + protected forumProvider: AddonModForumProvider, + protected sitesProvider: CoreSitesProvider) { + this.post = navParams.get('post'); + this.forumId = navParams.get('forumId'); + } + + /** + * Component being initialized. + */ + ngOnInit(): void { + if (this.forumId) { + if (this.post.id) { + const site: CoreSite = this.sitesProvider.getCurrentSite(); + this.url = site.createSiteUrl('/mod/forum/discuss.php', {d: this.post.discussion}, 'p' + this.post.id); + + this.forumProvider.getDiscussionPost(this.forumId, this.post.discussion, this.post.id, true).then((post) => { + this.canDelete = post.capabilities.delete && this.forumProvider.isDeletePostAvailable(); + this.canEdit = post.capabilities.edit && this.forumProvider.isUpdatePostAvailable(); + this.wordCount = post.wordcount; + }).catch((error) => { + this.domUtils.showErrorModalDefault(error, 'Error getting discussion post.'); + }).finally(() => { + this.loaded = true; + }); + } else { + // Offline post, you can edit or discard the post. + this.canEdit = true; + this.canDelete = true; + this.loaded = true; + } + } else { + this.loaded = true; + } + } + + /** + * Close the popover. + */ + dismiss(): void { + this.viewCtrl.dismiss(); + } + + /** + * Delete a post. + */ + deletePost(): void { + if (this.post.id) { + this.viewCtrl.dismiss({action: 'delete'}); + } else { + this.viewCtrl.dismiss({action: 'deleteoffline'}); + } + } + + /** + * Edit a post. + */ + editPost(): void { + if (this.post.id) { + this.viewCtrl.dismiss({action: 'edit'}); + } else { + this.viewCtrl.dismiss({action: 'editoffline'}); + } + } +} diff --git a/src/addon/mod/forum/components/post/addon-mod-forum-post.html b/src/addon/mod/forum/components/post/addon-mod-forum-post.html index 81107408c..4aec682b6 100644 --- a/src/addon/mod/forum/components/post/addon-mod-forum-post.html +++ b/src/addon/mod/forum/components/post/addon-mod-forum-post.html @@ -1,85 +1,92 @@ - - - -

- - - -

-

- {{ 'core.notsent' | translate }} - - {{post.modified | coreDateDayOrTime}} -

{{ 'addon.mod_forum.unread' | translate }}
- - {{post.userfullname}} -

-
-
- -
- {{ 'addon.mod_forum.postisprivatereply' | translate }} +
+ + +
+

+ + + +

+ + + + +
+ +
+
+ +
+ {{ 'addon.mod_forum.postisprivatereply' | translate }} +
+ +
+ +
+
+
+ +
{{ 'core.tag.tags' | translate }}:
+ +
+ + + + + +
- -
- - - - - + + + + {{ 'addon.mod_forum.subject' | translate }} + + + + {{ 'addon.mod_forum.message' | translate }} + + + + {{ 'addon.mod_forum.privatereply' | translate }} + + + + + + {{ 'addon.mod_forum.advanced' | translate }} + + + -
- - -
{{ 'core.tag.tags' | translate }}:
- -
- - - - - - - - - - - {{ 'addon.mod_forum.subject' | translate }} - - - - {{ 'addon.mod_forum.message' | translate }} - - - - {{ 'addon.mod_forum.privatereply' | translate }} - - - - - - {{ 'addon.mod_forum.advanced' | translate }} - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + +
diff --git a/src/addon/mod/forum/components/post/post.scss b/src/addon/mod/forum/components/post/post.scss index 43358381e..96f1b39dd 100644 --- a/src/addon/mod/forum/components/post/post.scss +++ b/src/addon/mod/forum/components/post/post.scss @@ -1,5 +1,72 @@ -ion-app.app-root addon-mod-forum-post { +ion-app.app-root addon-mod-forum-post .addon-mod_forum-post { + background-color: $white; + border-bottom: 1px solid $list-md-border-color; + + @include darkmode() { + background-color: $core-dark-item-bg-color; + } + .addon-forum-star { color: $core-star-color; } + + .card-header .item { + .label { + margin-top: 4px; + + h2 { + margin-top: 8px; + margin-bottom: 8px; + font-weight: bold; + ion-icon { + @include margin(0, 6px, 0, 0); + } + } + h3 { + font-size: 1.6rem; + } + } + + ion-avatar { + width: $addon-forum-avatar-size; + height: $addon-forum-avatar-size; + min-width: $addon-forum-avatar-size; + min-height: $addon-forum-avatar-size; + &[item-start] { + @include margin(0, 8px, 0, 0); + } + img { + width: $addon-forum-avatar-size; + height: $addon-forum-avatar-size; + } + } + + .addon-mod-forum-post-title, + .addon-mod-forum-post-info { + display: flex; + align-items: center; + } + + .addon-mod-forum-post-info { + margin-top: 8px; + } + + .addon-mod-forum-post-title + .addon-mod-forum-post-info { + margin-top: 0px; + } + + .addon-mod-forum-post-title h2, + .addon-mod-forum-post-info .addon-mod-forum-post-author { + flex-grow: 1; + } + } + + .item .item-inner { + border-bottom: 0; + } + + .addon-mod-forum-post-more-info div { + font-size: 1.4rem; + } } + diff --git a/src/addon/mod/forum/components/post/post.ts b/src/addon/mod/forum/components/post/post.ts index d6a9ca54d..8900dc980 100644 --- a/src/addon/mod/forum/components/post/post.ts +++ b/src/addon/mod/forum/components/post/post.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -14,18 +14,21 @@ import { Component, Input, Output, Optional, EventEmitter, OnInit, OnDestroy } from '@angular/core'; import { FormControl } from '@angular/forms'; -import { Content } from 'ionic-angular'; +import { Content, PopoverController, ModalController } from 'ionic-angular'; import { TranslateService } from '@ngx-translate/core'; import { CoreFileUploaderProvider } from '@core/fileuploader/providers/fileuploader'; import { CoreSyncProvider } from '@providers/sync'; import { CoreDomUtilsProvider } from '@providers/utils/dom'; import { CoreTextUtilsProvider } from '@providers/utils/text'; +import { CoreEventsProvider } from '@providers/events'; +import { CoreSitesProvider } from '@providers/sites'; import { AddonModForumProvider } from '../../providers/forum'; import { AddonModForumHelperProvider } from '../../providers/helper'; import { AddonModForumOfflineProvider } from '../../providers/offline'; import { AddonModForumSyncProvider } from '../../providers/sync'; import { CoreRatingInfo } from '@core/rating/providers/rating'; import { CoreTagProvider } from '@core/tag/providers/tag'; +import { AddonForumPostOptionsMenuComponent } from '../post-options-menu/post-options-menu'; /** * Components that shows a discussion post, its attachments and the action buttons allowed (reply, etc.). @@ -45,15 +48,18 @@ export class AddonModForumPostComponent implements OnInit, OnDestroy { @Input() trackPosts: boolean; // True if post is being tracked. @Input() forum: any; // The forum the post belongs to. Required for attachments and offline posts. @Input() accessInfo: any; // Forum access information. - @Input() defaultSubject: string; // Default subject to set to new posts. + @Input() parentSubject?: string; // Subject of parent post. @Input() ratingInfo?: CoreRatingInfo; // Rating info item. @Output() onPostChange: EventEmitter; // Event emitted when a reply is posted or modified. messageControl = new FormControl(); uniqueId: string; + defaultReplySubject: string; advanced = false; // Display all form fields. tagsEnabled: boolean; + displaySubject = true; + optionsMenuEnabled = false; protected syncId: string; @@ -68,7 +74,11 @@ export class AddonModForumPostComponent implements OnInit, OnDestroy { private forumOffline: AddonModForumOfflineProvider, private forumSync: AddonModForumSyncProvider, private tagProvider: CoreTagProvider, - @Optional() private content: Content) { + @Optional() private content: Content, + protected popoverCtrl: PopoverController, + protected modalCtrl: ModalController, + protected eventsProvider: CoreEventsProvider, + protected sitesProvider: CoreSitesProvider) { this.onPostChange = new EventEmitter(); this.tagsEnabled = this.tagProvider.areTagsAvailableInSite(); } @@ -78,26 +88,67 @@ export class AddonModForumPostComponent implements OnInit, OnDestroy { */ ngOnInit(): void { this.uniqueId = this.post.id ? 'reply' + this.post.id : 'edit' + this.post.parent; + + const reTranslated = this.translate.instant('addon.mod_forum.re'); + this.displaySubject = !this.parentSubject || + (this.post.subject != this.parentSubject && this.post.subject != `Re: ${this.parentSubject}` && + this.post.subject != `${reTranslated} ${this.parentSubject}`); + this.defaultReplySubject = (this.post.subject.startsWith('Re: ') || this.post.subject.startsWith(reTranslated)) + ? this.post.subject : `${reTranslated} ${this.post.subject}`; + + this.optionsMenuEnabled = !this.post.id || (this.forumProvider.isGetDiscussionPostAvailable() && + (this.forumProvider.isDeletePostAvailable() || this.forumProvider.isUpdatePostAvailable())); } /** - * Set data to new post, clearing temporary files and updating original data. - * - * @param {number} [replyingTo] Id of post beeing replied. - * @param {boolean} [isEditing] True it's an offline reply beeing edited, false otherwise. - * @param {string} [subject] Subject of the reply. - * @param {string} [message] Message of the reply. - * @param {boolean} [isPrivate] True if it's private reply. - * @param {any[]} [files] Reply attachments. + * Deletes an online post. */ - protected setReplyData(replyingTo?: number, isEditing?: boolean, subject?: string, message?: string, files?: any[], + deletePost(): void { + this.domUtils.showDeleteConfirm('addon.mod_forum.deletesure').then(() => { + const modal = this.domUtils.showModalLoading('core.deleting', true); + + this.forumProvider.deletePost(this.post.id).then((response) => { + + const data = { + forumId: this.forum.id, + discussionId: this.discussionId, + cmId: this.forum.cmid, + deleted: response.status, + post: this.post + }; + + this.eventsProvider.trigger(AddonModForumProvider.CHANGE_DISCUSSION_EVENT, data, + this.sitesProvider.getCurrentSiteId()); + + this.domUtils.showToast('addon.mod_forum.deletedpost', true); + }).catch((error) => { + this.domUtils.showErrorModal(error); + }).finally(() => { + modal.dismiss(); + }); + }).catch((error) => { + // Do nothing. + }); + } + + /** + * Set data to new reply post, clearing temporary files and updating original data. + * + * @param replyingTo Id of post beeing replied. + * @param isEditing True it's an offline reply beeing edited, false otherwise. + * @param subject Subject of the reply. + * @param message Message of the reply. + * @param isPrivate True if it's private reply. + * @param files Reply attachments. + */ + protected setReplyFormData(replyingTo?: number, isEditing?: boolean, subject?: string, message?: string, files?: any[], isPrivate?: boolean): void { // Delete the local files from the tmp folder if any. this.uploaderProvider.clearTmpFiles(this.replyData.files); this.replyData.replyingTo = replyingTo || 0; this.replyData.isEditing = !!isEditing; - this.replyData.subject = subject || this.defaultSubject || ''; + this.replyData.subject = subject || this.defaultReplySubject || ''; this.replyData.message = message || null; this.replyData.files = files || []; this.replyData.isprivatereply = !!isPrivate; @@ -115,14 +166,107 @@ export class AddonModForumPostComponent implements OnInit, OnDestroy { this.advanced = this.replyData.files.length > 0; } + /** + * Show the context menu. + * + * @param e Click Event. + */ + showOptionsMenu(e: Event): void { + e.preventDefault(); + e.stopPropagation(); + + const popover = this.popoverCtrl.create(AddonForumPostOptionsMenuComponent, { + post: this.post, + forumId: this.forum.id + }); + popover.onDidDismiss((data) => { + if (data && data.action) { + switch (data.action) { + case 'edit': + this.editPost(); + break; + case 'editoffline': + this.editOfflineReply(); + break; + case 'delete': + this.deletePost(); + break; + case 'deleteoffline': + this.discardOfflineReply(); + break; + default: + break; + } + } + }); + popover.present({ + ev: e + }); + } + + /** + * Shows a form modal to edit an online post. + */ + editPost(): void { + const modal = this.modalCtrl.create('AddonModForumEditPostPage', { + post: this.post, + component: this.component, + componentId: this.componentId, + forum: this.forum + }); + + modal.present(); + modal.onDidDismiss((data) => { + if (typeof data != 'undefined') { + // Add some HTML to the message if needed. + const message = this.textUtils.formatHtmlLines(data.message); + const files = data.files || []; + const sendingModal = this.domUtils.showModalLoading('core.sending', true); + let promise; + + // Upload attachments first if any. + if (files.length) { + promise = this.forumHelper.uploadOrStoreReplyFiles(this.forum.id, this.post.id, files, false); + } else { + promise = Promise.resolve(); + } + + promise.then((attach) => { + const options: any = {}; + + if (attach) { + options.attachmentsid = attach; + } + + // Try to send it to server. + return this.forumProvider.updatePost(this.post.id, data.subject, message, options); + }).then((sent) => { + if (sent && this.forum.id) { + // Data sent to server, delete stored files (if any). + this.forumHelper.deleteReplyStoredFiles(this.forum.id, this.post.id); + + this.onPostChange.emit(); + this.post.subject = data.subject; + this.post.message = message; + this.post.attachments = data.files; + } + }).catch((message) => { + this.domUtils.showErrorModalDefault(message, 'addon.mod_forum.couldnotupdate', true); + }).finally(() => { + sendingModal.dismiss(); + }); + } + }); + } + /** * Set this post as being replied to. */ - showReply(): void { + showReplyForm(): void { if (this.replyData.isEditing) { // User is editing a post, data needs to be resetted. Ask confirm if there is unsaved data. this.confirmDiscard().then(() => { - this.setReplyData(this.post.id); + this.setReplyFormData(this.post.id); if (this.content) { setTimeout(() => { @@ -137,10 +281,17 @@ export class AddonModForumPostComponent implements OnInit, OnDestroy { return; } else if (!this.replyData.replyingTo) { // User isn't replying, it's a brand new reply. Initialize the data. - this.setReplyData(this.post.id); + this.setReplyFormData(this.post.id); } else { // The post being replied has changed but the data will be kept. this.replyData.replyingTo = this.post.id; + + if (this.replyData.subject == this.originalData.subject) { + // Update subject only if it hadn't been modified + this.replyData.subject = this.defaultReplySubject; + this.originalData.subject = this.defaultReplySubject; + } + this.messageControl.setValue(this.replyData.message); } @@ -156,13 +307,13 @@ export class AddonModForumPostComponent implements OnInit, OnDestroy { /** * Set this post as being edited to. */ - editReply(): void { + editOfflineReply(): void { // Ask confirm if there is unsaved data. this.confirmDiscard().then(() => { this.syncId = this.forumSync.getDiscussionSyncId(this.discussionId); this.syncProvider.blockOperation(AddonModForumProvider.COMPONENT, this.syncId); - this.setReplyData(this.post.parent, true, this.post.subject, this.post.message, this.post.attachments, + this.setReplyFormData(this.post.parent, true, this.post.subject, this.post.message, this.post.attachments, this.post.isprivatereply); }).catch(() => { // Cancelled. @@ -172,7 +323,7 @@ export class AddonModForumPostComponent implements OnInit, OnDestroy { /** * Message changed. * - * @param {string} text The new text. + * @param text The new text. */ onMessageChange(text: string): void { this.replyData.message = text; @@ -253,7 +404,7 @@ export class AddonModForumPostComponent implements OnInit, OnDestroy { } // Reset data. - this.setReplyData(); + this.setReplyFormData(); this.onPostChange.emit(); @@ -273,7 +424,7 @@ export class AddonModForumPostComponent implements OnInit, OnDestroy { cancel(): void { this.confirmDiscard().then(() => { // Reset data. - this.setReplyData(); + this.setReplyFormData(); if (this.syncId) { this.syncProvider.unblockOperation(AddonModForumProvider.COMPONENT, this.syncId); @@ -286,8 +437,8 @@ export class AddonModForumPostComponent implements OnInit, OnDestroy { /** * Discard offline reply. */ - discard(): void { - this.domUtils.showConfirm(this.translate.instant('core.areyousure')).then(() => { + discardOfflineReply(): void { + this.domUtils.showDeleteConfirm().then(() => { const promises = []; promises.push(this.forumOffline.deleteReply(this.post.parent)); @@ -299,7 +450,7 @@ export class AddonModForumPostComponent implements OnInit, OnDestroy { return Promise.all(promises).finally(() => { // Reset data. - this.setReplyData(); + this.setReplyFormData(); this.onPostChange.emit(); @@ -316,7 +467,7 @@ export class AddonModForumPostComponent implements OnInit, OnDestroy { * Function called when rating is updated online. */ ratingUpdated(): void { - this.forumProvider.invalidateDiscussionPosts(this.discussionId); + this.forumProvider.invalidateDiscussionPosts(this.discussionId, this.forum.id); } /** @@ -338,7 +489,7 @@ export class AddonModForumPostComponent implements OnInit, OnDestroy { /** * Confirm discard changes if any. * - * @return {Promise} Promise resolved if the user confirms or data was not changed and rejected otherwise. + * @return Promise resolved if the user confirms or data was not changed and rejected otherwise. */ protected confirmDiscard(): Promise { if (this.forumHelper.hasPostDataChanged(this.replyData, this.originalData)) { diff --git a/src/addon/mod/forum/forum.module.ts b/src/addon/mod/forum/forum.module.ts index b8463c714..f38bd9d5f 100644 --- a/src/addon/mod/forum/forum.module.ts +++ b/src/addon/mod/forum/forum.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/addon/mod/forum/lang/en.json b/src/addon/mod/forum/lang/en.json index 65a9af9df..ba17e36b8 100644 --- a/src/addon/mod/forum/lang/en.json +++ b/src/addon/mod/forum/lang/en.json @@ -8,7 +8,11 @@ "cannotadddiscussionall": "You do not have permission to add a new discussion topic for all participants.", "cannotcreatediscussion": "Could not create new discussion", "couldnotadd": "Could not add your post due to an unknown error", + "couldnotupdate": "Could not update your post due to an unknown error", "cutoffdatereached": "The cut-off date for posting to this forum is reached so you can no longer post to it.", + "delete": "Delete", + "deletedpost": "The post has been deleted", + "deletesure": "Are you sure you want to delete this post?", "discussion": "Discussion", "discussionlistsortbycreatedasc": "Sort by creation date in ascending order", "discussionlistsortbycreateddesc": "Sort by creation date in descending order", @@ -28,6 +32,7 @@ "favouriteupdated": "Your star option has been updated.", "forumnodiscussionsyet": "There are no discussions yet in this forum.", "group": "Group", + "lastpost": "Last post", "lockdiscussion": "Lock this discussion", "lockupdated": "The lock option has been updated.", "message": "Message", @@ -56,5 +61,6 @@ "unlockdiscussion": "Unlock this discussion", "unpindiscussion": "Unpin this discussion", "unread": "Unread", - "unreadpostsnumber": "{{$a}} unread posts" + "unreadpostsnumber": "{{$a}} unread posts", + "yourreply": "Your reply" } \ No newline at end of file diff --git a/src/addon/mod/forum/pages/discussion/discussion.html b/src/addon/mod/forum/pages/discussion/discussion.html index 9111ecdd5..577fe6c57 100644 --- a/src/addon/mod/forum/pages/discussion/discussion.html +++ b/src/addon/mod/forum/pages/discussion/discussion.html @@ -1,6 +1,6 @@ - + @@ -38,17 +38,17 @@ - {{ 'addon.mod_forum.discussionlocked' | translate }} + {{ 'addon.mod_forum.discussionlocked' | translate }} - - - +
+ +
- + @@ -60,7 +60,7 @@ - +
diff --git a/src/addon/mod/forum/pages/discussion/discussion.module.ts b/src/addon/mod/forum/pages/discussion/discussion.module.ts index 9877a3397..90b84af3d 100644 --- a/src/addon/mod/forum/pages/discussion/discussion.module.ts +++ b/src/addon/mod/forum/pages/discussion/discussion.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/addon/mod/forum/pages/discussion/discussion.scss b/src/addon/mod/forum/pages/discussion/discussion.scss index b4151f311..fcc1213fd 100644 --- a/src/addon/mod/forum/pages/discussion/discussion.scss +++ b/src/addon/mod/forum/pages/discussion/discussion.scss @@ -1,6 +1,9 @@ ion-app.app-root page-addon-mod-forum-discussion { - .card.highlight .card-header .item { + .highlight .card-header .item { background-color: $gray-lighter; + @include darkmode() { + background-color: $gray-dark; + } } .addon-forum-reply-button .label { diff --git a/src/addon/mod/forum/pages/discussion/discussion.ts b/src/addon/mod/forum/pages/discussion/discussion.ts index 7c395fc57..48fa5d18a 100644 --- a/src/addon/mod/forum/pages/discussion/discussion.ts +++ b/src/addon/mod/forum/pages/discussion/discussion.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -13,7 +13,7 @@ // limitations under the License. import { Component, Optional, OnDestroy, ViewChild, NgZone } from '@angular/core'; -import { IonicPage, NavParams, Content } from 'ionic-angular'; +import { IonicPage, NavParams, Content, NavController } from 'ionic-angular'; import { Network } from '@ionic-native/network'; import { TranslateService } from '@ngx-translate/core'; import { CoreAppProvider } from '@providers/app'; @@ -22,6 +22,7 @@ import { CoreSitesProvider } from '@providers/sites'; import { CoreDomUtilsProvider } from '@providers/utils/dom'; import { CoreUtilsProvider } from '@providers/utils/utils'; import { CoreFileUploaderProvider } from '@core/fileuploader/providers/fileuploader'; +import { CoreUserProvider } from '@core/user/providers/user'; import { CoreSplitViewComponent } from '@components/split-view/split-view'; import { CoreRatingProvider, CoreRatingInfo } from '@core/rating/providers/rating'; import { CoreRatingOfflineProvider } from '@core/rating/providers/offline'; @@ -33,6 +34,8 @@ import { AddonModForumSyncProvider } from '../../providers/sync'; type SortType = 'flat-newest' | 'flat-oldest' | 'nested'; +type Post = any & { children?: Post[]; }; + /** * Page that displays a forum discussion. */ @@ -46,16 +49,16 @@ export class AddonModForumDiscussionPage implements OnDestroy { courseId: number; discussionId: number; - forum: any; - accessInfo: any; + forum: any = {}; + accessInfo: any = {}; discussion: any; posts: any[]; discussionLoaded = false; - defaultSubject: string; + postSubjects: { [id: string]: string }; isOnline: boolean; isSplitViewOn: boolean; postHasOffline: boolean; - sort: SortType = 'flat-oldest'; + sort: SortType = 'nested'; trackPosts: boolean; replyData = { replyingTo: 0, @@ -89,23 +92,26 @@ export class AddonModForumDiscussionPage implements OnDestroy { hasOfflineRatings: boolean; protected ratingOfflineObserver: any; protected ratingSyncObserver: any; + protected changeDiscObserver: any; constructor(navParams: NavParams, network: Network, zone: NgZone, - private appProvider: CoreAppProvider, - private eventsProvider: CoreEventsProvider, - private sitesProvider: CoreSitesProvider, - private domUtils: CoreDomUtilsProvider, - private utils: CoreUtilsProvider, - private translate: TranslateService, - private uploaderProvider: CoreFileUploaderProvider, - private forumProvider: AddonModForumProvider, - private forumOffline: AddonModForumOfflineProvider, - private forumHelper: AddonModForumHelperProvider, - private forumSync: AddonModForumSyncProvider, - private ratingOffline: CoreRatingOfflineProvider, - @Optional() private svComponent: CoreSplitViewComponent) { + protected appProvider: CoreAppProvider, + protected eventsProvider: CoreEventsProvider, + protected sitesProvider: CoreSitesProvider, + protected domUtils: CoreDomUtilsProvider, + protected utils: CoreUtilsProvider, + protected translate: TranslateService, + protected uploaderProvider: CoreFileUploaderProvider, + protected forumProvider: AddonModForumProvider, + protected forumOffline: AddonModForumOfflineProvider, + protected forumHelper: AddonModForumHelperProvider, + protected forumSync: AddonModForumSyncProvider, + protected ratingOffline: CoreRatingOfflineProvider, + protected userProvider: CoreUserProvider, + @Optional() protected svComponent: CoreSplitViewComponent, + protected navCtrl: NavController) { this.courseId = navParams.get('courseId'); this.cmId = navParams.get('cmId'); this.forumId = navParams.get('forumId'); @@ -130,13 +136,39 @@ export class AddonModForumDiscussionPage implements OnDestroy { * View loaded. */ ionViewDidLoad(): void { - this.fetchPosts(true, false, true).then(() => { - if (this.postId) { - // Scroll to the post. - setTimeout(() => { - this.domUtils.scrollToElementBySelector(this.content, '#addon-mod_forum-post-' + this.postId); - }); - } + this.sitesProvider.getCurrentSite().getLocalSiteConfig('AddonModForumDiscussionSort').catch(() => { + this.userProvider.getUserPreference('forum_displaymode').catch(() => { + // Ignore errors. + }).then((value) => { + const sortValue = value && parseInt(value, 10); + + switch (sortValue) { + case 1: + this.sort = 'flat-oldest'; + break; + case -1: + this.sort = 'flat-newest'; + break; + case 3: + this.sort = 'nested'; + break; + case 2: // Threaded not implemented. + default: + // Not set, use default sort. + // @TODO add fallback to $CFG->forum_displaymode. + } + }); + }).then((value) => { + this.sort = value; + }).finally(() => { + this.fetchPosts(true, false, true).then(() => { + if (this.postId) { + // Scroll to the post. + setTimeout(() => { + this.domUtils.scrollToElementBySelector(this.content, '#addon-mod_forum-post-' + this.postId); + }); + } + }); }); } @@ -183,12 +215,41 @@ export class AddonModForumDiscussionPage implements OnDestroy { this.hasOfflineRatings = false; } }); + + this.changeDiscObserver = this.eventsProvider.on(AddonModForumProvider.CHANGE_DISCUSSION_EVENT, (data) => { + if ((this.forumId && this.forumId === data.forumId) || data.cmId === this.cmId) { + this.forumProvider.invalidateDiscussionsList(this.forumId).finally(() => { + if (typeof data.locked != 'undefined') { + this.discussion.locked = data.locked; + } + if (typeof data.pinned != 'undefined') { + this.discussion.pinned = data.pinned; + } + if (typeof data.starred != 'undefined') { + this.discussion.starred = data.starred; + } + + if (typeof data.deleted != 'undefined' && data.deleted) { + if (data.post.parent == 0) { + if (this.svComponent && this.svComponent.isOn()) { + this.svComponent.emptyDetails(); + } else { + this.navCtrl.pop(); + } + } else { + this.discussionLoaded = false; + this.refreshPosts(); + } + } + }); + } + }); } /** * Check if we can leave the page or not. * - * @return {boolean|Promise} Resolved if we can leave it, rejected if not. + * @return Resolved if we can leave it, rejected if not. */ ionViewCanLeave(): boolean | Promise { let promise: any; @@ -209,7 +270,7 @@ export class AddonModForumDiscussionPage implements OnDestroy { /** * Convenience function to get the forum. * - * @return {Promise} Promise resolved with the forum. + * @return Promise resolved with the forum. */ protected fetchForum(): Promise { if (this.courseId && this.cmId) { @@ -225,10 +286,10 @@ export class AddonModForumDiscussionPage implements OnDestroy { /** * Convenience function to get the posts. * - * @param {boolean} [sync] Whether to try to synchronize the discussion. - * @param {boolean} [showErrors] Whether to show errors in a modal. - * @param {boolean} [forceMarkAsRead] Whether to mark all posts as read. - * @return {Promise} Promise resolved when done. + * @param sync Whether to try to synchronize the discussion. + * @param showErrors Whether to show errors in a modal. + * @param forceMarkAsRead Whether to mark all posts as read. + * @return Promise resolved when done. */ protected fetchPosts(sync?: boolean, showErrors?: boolean, forceMarkAsRead?: boolean): Promise { let syncPromise; @@ -289,10 +350,17 @@ export class AddonModForumDiscussionPage implements OnDestroy { }).then(() => { let posts = offlineReplies.concat(onlinePosts); + const startingPost = this.forumProvider.extractStartingPost(posts); + if (startingPost) { + // Update discussion data from first post. + this.discussion = Object.assign(this.discussion || {}, startingPost); + } + // If sort type is nested, normal sorting is disabled and nested posts will be displayed. if (this.sort == 'nested') { // Sort first by creation date to make format tree work. this.forumProvider.sortDiscussionPosts(posts, 'ASC'); + posts = this.utils.formatTree(posts, 'parent', 'id', this.discussion.id); } else { // Set default reply subject. @@ -309,12 +377,13 @@ export class AddonModForumDiscussionPage implements OnDestroy { this.forumId = forum.id; this.cmId = forum.cmid; + this.courseId = forum.course; this.forum = forum; this.availabilityMessage = this.forumHelper.getAvailabilityMessage(forum); const promises = []; - promises.push(this.forumProvider.getAccessInformation(this.forum.id).then((accessInfo) => { + promises.push(this.forumProvider.getAccessInformation(this.forumId).then((accessInfo) => { this.accessInfo = accessInfo; // Disallow replying if cut-off date is reached and the user has not the capability to override it. @@ -326,37 +395,21 @@ export class AddonModForumDiscussionPage implements OnDestroy { } })); - // Fetch the discussion if not passed as parameter. + // The discussion object was not passed as parameter and there is no starting post. Should not happen. if (!this.discussion) { - promises.push(this.forumHelper.getDiscussionById(forum.id, this.discussionId).then((discussion) => { - this.discussion = discussion; - }).catch(() => { - // Ignore errors. - })); + promises.push(this.loadDiscussion(this.forumId, this.discussionId)); } return Promise.all(promises); }).catch(() => { // Ignore errors. - this.forum = {}; - this.accessInfo = {}; }).then(() => { - this.defaultSubject = this.translate.instant('addon.mod_forum.re') + ' ' + - (this.discussion ? this.discussion.subject : ''); - this.replyData.subject = this.defaultSubject; - const startingPost = this.forumProvider.extractStartingPost(posts); - if (startingPost) { - // Update discussion data from first post. - this.discussion = Object.assign(this.discussion || {}, startingPost); - } else if (!this.discussion) { - // The discussion object was not passed as parameter and there is no starting post. + if (!this.discussion) { + // The discussion object was not passed as parameter and there is no starting post. Should not happen. return Promise.reject('Invalid forum discussion.'); } - this.defaultSubject = this.translate.instant('addon.mod_forum.re') + ' ' + this.discussion.subject; - this.replyData.subject = this.defaultSubject; - if (this.discussion.userfullname && this.discussion.parent == 0 && this.forum.type == 'single') { // Hide author for first post and type single. this.discussion.userfullname = null; @@ -364,6 +417,11 @@ export class AddonModForumDiscussionPage implements OnDestroy { this.posts = posts; this.ratingInfo = ratingInfo; + this.postSubjects = this.getAllPosts().reduce((postSubjects, post) => { + postSubjects[post.id] = post.subject; + + return postSubjects; + }, { [this.discussion.id]: this.discussion.subject }); }); }).then(() => { if (this.forumProvider.isSetPinStateAvailableForSite()) { @@ -402,11 +460,32 @@ export class AddonModForumDiscussionPage implements OnDestroy { }); } + /** + * Convenience function to load discussion. + * + * @param forumId Forum ID. + * @param discussionId Discussion ID. + * @return Promise resolved when done. + */ + protected loadDiscussion(forumId: number, discussionId: number): Promise { + // Fetch the discussion if not passed as parameter. + if (!this.discussion && forumId) { + return this.forumHelper.getDiscussionById(forumId, discussionId).then((discussion) => { + this.discussion = discussion; + this.discussionId = this.discussion.discussion; + }).catch(() => { + // Ignore errors. + }); + } + + return Promise.resolve(); + } + /** * Tries to synchronize the posts discussion. * - * @param {boolean} showErrors Whether to show errors in a modal. - * @return {Promise} Promise resolved when done. + * @param showErrors Whether to show errors in a modal. + * @return Promise resolved when done. */ protected syncDiscussion(showErrors: boolean): Promise { const promises = []; @@ -446,10 +525,10 @@ export class AddonModForumDiscussionPage implements OnDestroy { /** * Refresh the data. * - * @param {any} [refresher] Refresher. - * @param {Function} [done] Function to call when done. - * @param {boolean} [showErrors=false] If show errors to the user of hide them. - * @return {Promise} Promise resolved when done. + * @param refresher Refresher. + * @param done Function to call when done. + * @param showErrors If show errors to the user of hide them. + * @return Promise resolved when done. */ doRefresh(refresher?: any, done?: () => void, showErrors: boolean = false): Promise { if (this.discussionLoaded) { @@ -465,9 +544,9 @@ export class AddonModForumDiscussionPage implements OnDestroy { /** * Refresh posts. * - * @param {boolean} [sync] Whether to try to synchronize the discussion. - * @param {boolean} [showErrors] Whether to show errors in a modal. - * @return {Promise} Promise resolved when done. + * @param sync Whether to try to synchronize the discussion. + * @param showErrors Whether to show errors in a modal. + * @return Promise resolved when done. */ refreshPosts(sync?: boolean, showErrors?: boolean): Promise { this.domUtils.scrollToTop(this.content); @@ -476,7 +555,7 @@ export class AddonModForumDiscussionPage implements OnDestroy { const promises = [ this.forumProvider.invalidateForumData(this.courseId), - this.forumProvider.invalidateDiscussionPosts(this.discussionId), + this.forumProvider.invalidateDiscussionPosts(this.discussionId, this.forumId), this.forumProvider.invalidateAccessInformation(this.forumId), this.forumProvider.invalidateCanAddDiscussion(this.forumId) ]; @@ -491,12 +570,13 @@ export class AddonModForumDiscussionPage implements OnDestroy { /** * Function to change posts sorting * - * @param {SortType} type Sort type. - * @return {Promise} Promised resolved when done. + * @param type Sort type. + * @return Promised resolved when done. */ changeSort(type: SortType): Promise { this.discussionLoaded = false; this.sort = type; + this.sitesProvider.getCurrentSite().setLocalSiteConfig('AddonModForumDiscussionSort', this.sort); this.domUtils.scrollToTop(this.content); return this.fetchPosts(); @@ -505,7 +585,7 @@ export class AddonModForumDiscussionPage implements OnDestroy { /** * Lock or unlock the discussion. * - * @param {boolean} locked True to lock the discussion, false to unlock. + * @param locked True to lock the discussion, false to unlock. */ setLockState(locked: boolean): void { const modal = this.domUtils.showModalLoading('core.sending', true); @@ -532,7 +612,7 @@ export class AddonModForumDiscussionPage implements OnDestroy { /** * Pin or unpin the discussion. * - * @param {boolean} pinned True to pin the discussion, false to unpin it. + * @param pinned True to pin the discussion, false to unpin it. */ setPinState(pinned: boolean): void { const modal = this.domUtils.showModalLoading('core.sending', true); @@ -559,7 +639,7 @@ export class AddonModForumDiscussionPage implements OnDestroy { /** * Star or unstar the discussion. * - * @param {boolean} starred True to star the discussion, false to unstar it. + * @param starred True to star the discussion, false to unstar it. */ toggleFavouriteState(starred: boolean): void { const modal = this.domUtils.showModalLoading('core.sending', true); @@ -609,6 +689,7 @@ export class AddonModForumDiscussionPage implements OnDestroy { this.syncManualObserver && this.syncManualObserver.off(); this.ratingOfflineObserver && this.ratingOfflineObserver.off(); this.ratingSyncObserver && this.ratingSyncObserver.off(); + this.changeDiscObserver && this.changeDiscObserver.off(); } /** @@ -617,4 +698,31 @@ export class AddonModForumDiscussionPage implements OnDestroy { ngOnDestroy(): void { this.onlineObserver && this.onlineObserver.unsubscribe(); } + + /** + * Get all the posts contained in the discussion. + * + * @return Array containing all the posts of the discussion. + */ + protected getAllPosts(): Post[] { + return [].concat(...this.posts.map(this.flattenPostHierarchy.bind(this))); + } + + /** + * Flatten a post's hierarchy into an array. + * + * @param parent Parent post. + * @return Array containing all the posts within the hierarchy (including the parent). + */ + protected flattenPostHierarchy(parent: Post): Post[] { + const posts = [parent]; + const children = parent.children || []; + + for (const child of children) { + posts.push(...this.flattenPostHierarchy(child)); + } + + return posts; + } + } diff --git a/src/addon/mod/forum/pages/edit-post/addon-mod-forum-edit-post.html b/src/addon/mod/forum/pages/edit-post/addon-mod-forum-edit-post.html new file mode 100644 index 000000000..0a934fa27 --- /dev/null +++ b/src/addon/mod/forum/pages/edit-post/addon-mod-forum-edit-post.html @@ -0,0 +1,40 @@ + + + {{ 'addon.mod_forum.yourreply' | translate }} + + + + + + + + + {{ 'addon.mod_forum.subject' | translate }} + + + + {{ 'addon.mod_forum.message' | translate }} + + + + + + {{ 'addon.mod_forum.advanced' | translate }} + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/addon/mod/forum/pages/edit-post/edit-post.module.ts b/src/addon/mod/forum/pages/edit-post/edit-post.module.ts new file mode 100644 index 000000000..07f409742 --- /dev/null +++ b/src/addon/mod/forum/pages/edit-post/edit-post.module.ts @@ -0,0 +1,35 @@ +// (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 { NgModule } from '@angular/core'; +import { IonicPageModule } from 'ionic-angular'; +import { TranslateModule } from '@ngx-translate/core'; +import { CoreComponentsModule } from '@components/components.module'; +import { CoreDirectivesModule } from '@directives/directives.module'; +import { AddonModForumComponentsModule } from '../../components/components.module'; +import { AddonModForumEditPostPage } from './edit-post'; + +@NgModule({ + declarations: [ + AddonModForumEditPostPage, + ], + imports: [ + CoreComponentsModule, + CoreDirectivesModule, + AddonModForumComponentsModule, + IonicPageModule.forChild(AddonModForumEditPostPage), + TranslateModule.forChild() + ], +}) +export class AddonModForumEditPostPageModule {} diff --git a/src/addon/mod/forum/pages/edit-post/edit-post.ts b/src/addon/mod/forum/pages/edit-post/edit-post.ts new file mode 100644 index 000000000..6795c92dc --- /dev/null +++ b/src/addon/mod/forum/pages/edit-post/edit-post.ts @@ -0,0 +1,141 @@ +// (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 { Component } from '@angular/core'; +import { FormControl } from '@angular/forms'; +import { IonicPage, ViewController, NavParams } from 'ionic-angular'; +import { TranslateService } from '@ngx-translate/core'; +import { CoreFileUploaderProvider } from '@core/fileuploader/providers/fileuploader'; +import { CoreDomUtilsProvider } from '@providers/utils/dom'; +import { AddonModForumProvider } from '../../providers/forum'; +import { AddonModForumHelperProvider } from '../../providers/helper'; + +/** + * Page that displays a form to edit discussion post. + */ +@IonicPage({ segment: 'addon-mod-edit-post' }) +@Component({ + selector: 'addon-mod-forum-edit-post', + templateUrl: 'addon-mod-forum-edit-post.html', +}) +export class AddonModForumEditPostPage { + component: string; // Component this post belong to. + componentId: number; // Component ID. + forum: any; // The forum the post belongs to. Required for attachments and offline posts. + + messageControl = new FormControl(); + advanced = false; // Display all form fields. + replyData: any = {}; + originalData: any = {}; // Object with the original post data. Usually shared between posts. + + protected forceLeave = false; // To allow leaving the page without checking for changes. + + constructor( + params: NavParams, + protected forumProvider: AddonModForumProvider, + protected viewCtrl: ViewController, + protected domUtils: CoreDomUtilsProvider, + protected uploaderProvider: CoreFileUploaderProvider, + protected forumHelper: AddonModForumHelperProvider, + protected translate: TranslateService) { + + const post = params.get('post'); + this.component = params.get('component'); + this.componentId = params.get('componentId'); + this.forum = params.get('forum'); + + this.replyData.id = post.id; + this.replyData.subject = post.subject; + this.replyData.message = post.message; + this.replyData.files = post.attachments || []; + + // Delete the local files from the tmp folder if any. + this.uploaderProvider.clearTmpFiles(this.replyData.files); + + // Update rich text editor. + this.messageControl.setValue(this.replyData.message); + + // Update original data. + this.originalData.subject = this.replyData.subject; + this.originalData.message = this.replyData.message; + this.originalData.files = this.replyData.files.slice(); + + // Show advanced fields if any of them has not the default value. + this.advanced = this.replyData.files.length > 0; + } + + /** + * Check if we can leave the page or not. + * + * @return Resolved if we can leave it, rejected if not. + */ + ionViewCanLeave(): boolean | Promise { + if (this.forceLeave) { + return true; + } + + let promise: any; + + if (this.forumHelper.hasPostDataChanged(this.replyData, this.originalData)) { + // Show confirmation if some data has been modified. + promise = this.domUtils.showConfirm(this.translate.instant('core.confirmcanceledit')); + } else { + promise = Promise.resolve(); + } + + return promise.then(() => { + // Delete the local files from the tmp folder. + this.uploaderProvider.clearTmpFiles(this.replyData.files); + }); + } + + /** + * Message changed. + * + * @param text The new text. + */ + onMessageChange(text: string): void { + this.replyData.message = text; + } + + /** + * Close modal. + * + * @param data Data to return to the page. + */ + closeModal(data: any): void { + this.viewCtrl.dismiss(data); + } + + /** + * Reply to this post. + * + * @param e Click event. + */ + reply(e: Event): void { + e.preventDefault(); + e.stopPropagation(); + + // Close the modal, sending the input data. + this.forceLeave = true; + this.closeModal(this.replyData); + } + + /** + * Show or hide advanced form fields. + */ + toggleAdvanced(): void { + this.advanced = !this.advanced; + } +} diff --git a/src/addon/mod/forum/pages/index/index.html b/src/addon/mod/forum/pages/index/index.html index 80f7863eb..fc621ed20 100644 --- a/src/addon/mod/forum/pages/index/index.html +++ b/src/addon/mod/forum/pages/index/index.html @@ -1,6 +1,6 @@ - + diff --git a/src/addon/mod/forum/pages/index/index.module.ts b/src/addon/mod/forum/pages/index/index.module.ts index c677d8d2e..b59f992bf 100644 --- a/src/addon/mod/forum/pages/index/index.module.ts +++ b/src/addon/mod/forum/pages/index/index.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/addon/mod/forum/pages/index/index.ts b/src/addon/mod/forum/pages/index/index.ts index 102e0b88f..be2971ccd 100644 --- a/src/addon/mod/forum/pages/index/index.ts +++ b/src/addon/mod/forum/pages/index/index.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -40,7 +40,7 @@ export class AddonModForumIndexPage { /** * Update some data based on the forum instance. * - * @param {any} forum Forum instance. + * @param forum Forum instance. */ updateData(forum: any): void { this.title = forum.name || this.title; diff --git a/src/addon/mod/forum/pages/new-discussion/new-discussion.module.ts b/src/addon/mod/forum/pages/new-discussion/new-discussion.module.ts index b94e61e1b..c0add2f4c 100644 --- a/src/addon/mod/forum/pages/new-discussion/new-discussion.module.ts +++ b/src/addon/mod/forum/pages/new-discussion/new-discussion.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/addon/mod/forum/pages/new-discussion/new-discussion.ts b/src/addon/mod/forum/pages/new-discussion/new-discussion.ts index 337966e1d..091753865 100644 --- a/src/addon/mod/forum/pages/new-discussion/new-discussion.ts +++ b/src/addon/mod/forum/pages/new-discussion/new-discussion.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -128,8 +128,8 @@ export class AddonModForumNewDiscussionPage implements OnDestroy { /** * Fetch if forum uses groups and the groups it uses. * - * @param {boolean} [refresh] Whether we're refreshing data. - * @return {Promise} Promise resolved when done. + * @param refresh Whether we're refreshing data. + * @return Promise resolved when done. */ protected fetchDiscussionData(refresh?: boolean): Promise { return this.groupsProvider.getActivityGroupMode(this.cmId).then((mode) => { @@ -253,8 +253,8 @@ export class AddonModForumNewDiscussionPage implements OnDestroy { /** * Validate which of the groups returned by getActivityAllowedGroups in visible groups should be shown to post to. * - * @param {any[]} forumGroups Forum groups. - * @return {Promise} Promise resolved with the list of groups. + * @param forumGroups Forum groups. + * @return Promise resolved with the list of groups. */ protected validateVisibleGroups(forumGroups: any[]): Promise { // We first check if the user can post to all the groups. @@ -301,9 +301,9 @@ export class AddonModForumNewDiscussionPage implements OnDestroy { /** * Filter forum groups, returning only those that are inside user groups. * - * @param {any[]} forumGroups Forum groups. - * @param {any[]} userGroups User groups. - * @return {any[]} Filtered groups. + * @param forumGroups Forum groups. + * @param userGroups User groups. + * @return Filtered groups. */ protected filterGroups(forumGroups: any[], userGroups: any[]): any[] { const filtered = []; @@ -321,9 +321,9 @@ export class AddonModForumNewDiscussionPage implements OnDestroy { /** * Add the "All participants" option to a list of groups if the user can add a discussion to all participants. * - * @param {any[]} groups Groups. - * @param {boolean} check True to check if the user can add a discussion to all participants. - * @return {Promise} Promise resolved with the list of groups. + * @param groups Groups. + * @param check True to check if the user can add a discussion to all participants. + * @return Promise resolved with the list of groups. */ protected addAllParticipantsOption(groups: any[], check: boolean): Promise { if (!this.forumProvider.isAllParticipantsFixed()) { @@ -365,7 +365,7 @@ export class AddonModForumNewDiscussionPage implements OnDestroy { /** * Pull to refresh. * - * @param {any} refresher Refresher. + * @param refresher Refresher. */ refreshGroups(refresher: any): void { const promises = [ @@ -384,8 +384,8 @@ export class AddonModForumNewDiscussionPage implements OnDestroy { /** * Convenience function to update or return to discussions depending on device. * - * @param {number} [discussionIds] Ids of the new discussions. - * @param {number} [discTimecreated] The time created of the discussion (if offline). + * @param discussionIds Ids of the new discussions. + * @param discTimecreated The time created of the discussion (if offline). */ protected returnToDiscussions(discussionIds?: number[], discTimecreated?: number): void { const data: any = { @@ -423,7 +423,7 @@ export class AddonModForumNewDiscussionPage implements OnDestroy { /** * Message changed. * - * @param {string} text The new text. + * @param text The new text. */ onMessageChange(text: string): void { this.newDiscussion.message = text; @@ -515,7 +515,7 @@ export class AddonModForumNewDiscussionPage implements OnDestroy { /** * Check if we can leave the page or not. * - * @return {boolean|Promise} Resolved if we can leave it, rejected if not. + * @return Resolved if we can leave it, rejected if not. */ ionViewCanLeave(): boolean | Promise { let promise: any; diff --git a/src/addon/mod/forum/pages/sort-order-selector/sort-order-selector.html b/src/addon/mod/forum/pages/sort-order-selector/sort-order-selector.html index a3411deea..f5c8520c3 100644 --- a/src/addon/mod/forum/pages/sort-order-selector/sort-order-selector.html +++ b/src/addon/mod/forum/pages/sort-order-selector/sort-order-selector.html @@ -12,7 +12,7 @@ -

+

{{ sortOrder.label | translate }}

diff --git a/src/addon/mod/forum/pages/sort-order-selector/sort-order-selector.module.ts b/src/addon/mod/forum/pages/sort-order-selector/sort-order-selector.module.ts index 9a10ae395..8cba267f7 100644 --- a/src/addon/mod/forum/pages/sort-order-selector/sort-order-selector.module.ts +++ b/src/addon/mod/forum/pages/sort-order-selector/sort-order-selector.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/addon/mod/forum/pages/sort-order-selector/sort-order-selector.ts b/src/addon/mod/forum/pages/sort-order-selector/sort-order-selector.ts index 0d83646a6..dcf10ab0f 100644 --- a/src/addon/mod/forum/pages/sort-order-selector/sort-order-selector.ts +++ b/src/addon/mod/forum/pages/sort-order-selector/sort-order-selector.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -43,7 +43,7 @@ export class AddonModForumSortOrderSelectorPage { /** * Select a sort order. * - * @param {any} sortOrder Selected sort order. + * @param sortOrder Selected sort order. */ selectSortOrder(sortOrder: any): void { this.viewCtrl.dismiss(sortOrder); diff --git a/src/addon/mod/forum/providers/discussion-link-handler.ts b/src/addon/mod/forum/providers/discussion-link-handler.ts index d1c5399be..546e477f0 100644 --- a/src/addon/mod/forum/providers/discussion-link-handler.ts +++ b/src/addon/mod/forum/providers/discussion-link-handler.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -34,21 +34,24 @@ export class AddonModForumDiscussionLinkHandler extends CoreContentLinksHandlerB /** * Get the list of actions for a link (url). * - * @param {string[]} siteIds List of sites the URL belongs to. - * @param {string} url The URL to treat. - * @param {any} params The params of the URL. E.g. 'mysite.com?id=1' -> {id: 1} - * @param {number} [courseId] Course ID related to the URL. Optional but recommended. - * @param {any} [data] Extra data to handle the URL. - * @return {CoreContentLinksAction[]|Promise} List of (or promise resolved with list of) actions. + * @param siteIds List of sites the URL belongs to. + * @param url The URL to treat. + * @param params The params of the URL. E.g. 'mysite.com?id=1' -> {id: 1} + * @param courseId Course ID related to the URL. Optional but recommended. + * @param data Extra data to handle the URL. + * @return List of (or promise resolved with list of) actions. */ getActions(siteIds: string[], url: string, params: any, courseId?: number, data?: any): CoreContentLinksAction[] | Promise { data = data || {}; + // On 3.6 downwards, it will open the discussion but without knowing the lock status of the discussion. + // However canreply will be false. + return [{ action: (siteId, navCtrl?): void => { const pageParams: any = { - courseId: courseId || parseInt(params.courseid, 10) || parseInt(params.cid, 10), + courseId: courseId || parseInt(params.courseid, 10) || parseInt(params.cid, 10) || undefined, discussionId: parseInt(params.d, 10), cmId: data.cmid && parseInt(data.cmid, 10), forumId: data.instance && parseInt(data.instance, 10) @@ -67,11 +70,11 @@ export class AddonModForumDiscussionLinkHandler extends CoreContentLinksHandlerB * Check if the handler is enabled for a certain site (site + user) and a URL. * If not defined, defaults to true. * - * @param {string} siteId The site ID. - * @param {string} url The URL to treat. - * @param {any} params The params of the URL. E.g. 'mysite.com?id=1' -> {id: 1} - * @param {number} [courseId] Course ID related to the URL. Optional but recommended. - * @return {boolean|Promise} Whether the handler is enabled for the URL and site. + * @param siteId The site ID. + * @param url The URL to treat. + * @param params The params of the URL. E.g. 'mysite.com?id=1' -> {id: 1} + * @param courseId Course ID related to the URL. Optional but recommended. + * @return Whether the handler is enabled for the URL and site. */ isEnabled(siteId: string, url: string, params: any, courseId?: number): boolean | Promise { return true; diff --git a/src/addon/mod/forum/providers/forum.ts b/src/addon/mod/forum/providers/forum.ts index 6a8d7aa0f..96fd64ce1 100644 --- a/src/addon/mod/forum/providers/forum.ts +++ b/src/addon/mod/forum/providers/forum.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -35,7 +35,7 @@ export class AddonModForumProvider { static NEW_DISCUSSION_EVENT = 'addon_mod_forum_new_discussion'; static REPLY_DISCUSSION_EVENT = 'addon_mod_forum_reply_discussion'; static VIEW_DISCUSSION_EVENT = 'addon_mod_forum_view_discussion'; - static CHANGE_DISCUSSION_EVENT = 'addon_mod_forum_lock_discussion'; + static CHANGE_DISCUSSION_EVENT = 'addon_mod_forum_change_discussion_status'; static MARK_READ_EVENT = 'addon_mod_forum_mark_read'; static PREFERENCE_SORTORDER = 'forum_discussionlistsortorder'; @@ -64,9 +64,9 @@ export class AddonModForumProvider { /** * Get cache key for can add discussion WS calls. * - * @param {number} forumId Forum ID. - * @param {number} groupId Group ID. - * @return {string} Cache key. + * @param forumId Forum ID. + * @param groupId Group ID. + * @return Cache key. */ protected getCanAddDiscussionCacheKey(forumId: number, groupId: number): string { return this.getCommonCanAddDiscussionCacheKey(forumId) + groupId; @@ -74,19 +74,52 @@ export class AddonModForumProvider { /** * Get common part of cache key for can add discussion WS calls. + * TODO: Use getForumDataCacheKey as a prefix. * - * @param {number} forumId Forum ID. - * @return {string} Cache key. + * @param forumId Forum ID. + * @return Cache key. */ protected getCommonCanAddDiscussionCacheKey(forumId: number): string { return this.ROOT_CACHE_KEY + 'canadddiscussion:' + forumId + ':'; } + /** + * Get prefix cache key for all forum activity data WS calls. + * + * @param forumId Forum ID. + * @return Cache key. + */ + protected getForumDataPrefixCacheKey(forumId: number): string { + return this.ROOT_CACHE_KEY + forumId; + } + + /** + * Get cache key for discussion post data WS calls. + * + * @param forumId Forum ID. + * @param discussionId Discussion ID. + * @param postId Course ID. + * @return Cache key. + */ + protected getDiscussionPostDataCacheKey(forumId: number, discussionId: number, postId: number): string { + return this.getForumDiscussionDataCacheKey(forumId, discussionId) + ':post:' + postId; + } + /** * Get cache key for forum data WS calls. * - * @param {number} courseId Course ID. - * @return {string} Cache key. + * @param courseId Course ID. + * @return Cache key. + */ + protected getForumDiscussionDataCacheKey(forumId: number, discussionId: number): string { + return this.getForumDataPrefixCacheKey(forumId) + ':discussion:' + discussionId; + } + + /** + * Get cache key for forum data WS calls. + * + * @param courseId Course ID. + * @return Cache key. */ protected getForumDataCacheKey(courseId: number): string { return this.ROOT_CACHE_KEY + 'forum:' + courseId; @@ -94,9 +127,10 @@ export class AddonModForumProvider { /** * Get cache key for forum access information WS calls. + * TODO: Use getForumDataCacheKey as a prefix. * - * @param {number} forumId Forum ID. - * @return {string} Cache key. + * @param forumId Forum ID. + * @return Cache key. */ protected getAccessInformationCacheKey(forumId: number): string { return this.ROOT_CACHE_KEY + 'accessInformation:' + forumId; @@ -104,9 +138,10 @@ export class AddonModForumProvider { /** * Get cache key for forum discussion posts WS calls. + * TODO: Use getForumDiscussionDataCacheKey instead. * - * @param {number} discussionId Discussion ID. - * @return {string} Cache key. + * @param discussionId Discussion ID. + * @return Cache key. */ protected getDiscussionPostsCacheKey(discussionId: number): string { return this.ROOT_CACHE_KEY + 'discussion:' + discussionId; @@ -115,9 +150,9 @@ export class AddonModForumProvider { /** * Get cache key for forum discussions list WS calls. * - * @param {number} forumId Forum ID. - * @param {number} sortOrder Sort order. - * @return {string} Cache key. + * @param forumId Forum ID. + * @param sortOrder Sort order. + * @return Cache key. */ protected getDiscussionsListCacheKey(forumId: number, sortOrder: number): string { let key = this.ROOT_CACHE_KEY + 'discussions:' + forumId; @@ -132,13 +167,13 @@ export class AddonModForumProvider { /** * Add a new discussion. It will fail if offline or cannot connect. * - * @param {number} forumId Forum ID. - * @param {string} subject New discussion's subject. - * @param {string} message New discussion's message. - * @param {any} [options] Options (subscribe, pin, ...). - * @param {string} [groupId] Group this discussion belongs to. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the discussion is created. + * @param forumId Forum ID. + * @param subject New discussion's subject. + * @param message New discussion's message. + * @param options Options (subscribe, pin, ...). + * @param groupId Group this discussion belongs to. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the discussion is created. */ addNewDiscussionOnline(forumId: number, subject: string, message: string, options?: any, groupId?: number, siteId?: string) : Promise { @@ -168,13 +203,13 @@ export class AddonModForumProvider { /** * Check if a user can post to a certain group. * - * @param {number} forumId Forum ID. - * @param {number} groupId Group ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with an object with the following properties: - * - status (boolean) - * - canpindiscussions (boolean) - * - cancreateattachment (boolean) + * @param forumId Forum ID. + * @param groupId Group ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with an object with the following properties: + * - status (boolean) + * - canpindiscussions (boolean) + * - cancreateattachment (boolean) */ canAddDiscussion(forumId: number, groupId: number, siteId?: string): Promise { const params = { @@ -208,48 +243,91 @@ export class AddonModForumProvider { /** * Check if a user can post to all groups. * - * @param {number} forumId Forum ID. - * @return {Promise} Promise resolved with an object with the following properties: - * - status (boolean) - * - canpindiscussions (boolean) - * - cancreateattachment (boolean) + * @param forumId Forum ID. + * @return Promise resolved with an object with the following properties: + * - status (boolean) + * - canpindiscussions (boolean) + * - cancreateattachment (boolean) */ canAddDiscussionToAll(forumId: number): Promise { return this.canAddDiscussion(forumId, AddonModForumProvider.ALL_PARTICIPANTS); } + /** + * Delete a post. + * + * @param postId Post id. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when done. + * @since 3.8 + */ + deletePost(postId: number, siteId?: string): Promise { + return this.sitesProvider.getSite(siteId).then((site) => { + const params = { + postid: postId + }; + + return site.write('mod_forum_delete_post', params); + }); + } + /** * Extract the starting post of a discussion from a list of posts. The post is removed from the array passed as a parameter. * - * @param {any[]} posts Posts to search. - * @return {any} Starting post or undefined if not found. + * @param posts Posts to search. + * @return Starting post or undefined if not found. */ extractStartingPost(posts: any[]): any { - // Check the last post first, since they'll usually be ordered by create time. - for (let i = posts.length - 1; i >= 0; i--) { - if (posts[i].parent == 0) { - return posts.splice(i, 1).pop(); // Remove it from the array. - } - } + const index = posts.findIndex((post) => post.parent == 0); - return undefined; + return index >= 0 ? posts.splice(index, 1).pop() : undefined; } /** * There was a bug adding new discussions to All Participants (see MDL-57962). Check if it's fixed. * - * @return {boolean} True if fixed, false otherwise. + * @return True if fixed, false otherwise. */ isAllParticipantsFixed(): boolean { return this.sitesProvider.getCurrentSite().isVersionGreaterEqualThan(['3.1.5', '3.2.2']); } + /** + * Returns whether or not getDiscussionPost WS available or not. + * + * @return If WS is avalaible. + * @since 3.8 + */ + isGetDiscussionPostAvailable(): boolean { + return this.sitesProvider.wsAvailableInCurrentSite('mod_forum_get_discussion_post'); + } + + /** + * Returns whether or not deletePost WS available or not. + * + * @return If WS is avalaible. + * @since 3.8 + */ + isDeletePostAvailable(): boolean { + return this.sitesProvider.wsAvailableInCurrentSite('mod_forum_delete_post'); + } + + /** + * Returns whether or not updatePost WS available or not. + * + * @return If WS is avalaible. + * @since 3.8 + */ + isUpdatePostAvailable(): boolean { + return this.sitesProvider.wsAvailableInCurrentSite('mod_forum_update_discussion_post'); + } + /** * Format discussions, setting groupname if the discussion group is valid. * - * @param {number} cmId Forum cmid. - * @param {any[]} discussions List of discussions to format. - * @return {Promise} Promise resolved with the formatted discussions. + * @param cmId Forum cmid. + * @param discussions List of discussions to format. + * @return Promise resolved with the formatted discussions. */ formatDiscussionsGroups(cmId: number, discussions: any[]): Promise { discussions = this.utils.clone(discussions); @@ -288,9 +366,9 @@ export class AddonModForumProvider { /** * Get all course forums. * - * @param {number} courseId Course ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the forums are retrieved. + * @param courseId Course ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the forums are retrieved. */ getCourseForums(courseId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -306,13 +384,48 @@ export class AddonModForumProvider { }); } + /** + * Get a particular discussion post. + * + * @param forumId Forum ID. + * @param discussionId Discussion ID. + * @param postId Post ID. + * @param ignoreCache True if it should ignore cached data (it will always fail in offline or server down). + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the post is retrieved. + */ + getDiscussionPost(forumId: number, discussionId: number, postId: number, ignoreCache?: boolean, siteId?: string): Promise { + return this.sitesProvider.getSite(siteId).then((site) => { + const params = { + postid: postId + }, + preSets: CoreSiteWSPreSets = { + cacheKey: this.getDiscussionPostDataCacheKey(forumId, discussionId, postId), + updateFrequency: CoreSite.FREQUENCY_USUALLY + }; + + if (ignoreCache) { + preSets.getFromCache = false; + preSets.emergencyCache = false; + } + + return site.read('mod_forum_get_discussion_post', params, preSets).then((response) => { + if (response.post) { + return response.post; + } + + return Promise.reject(null); + }); + }); + } + /** * Get a forum by course module ID. * - * @param {number} courseId Course ID. - * @param {number} cmId Course module ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the forum is retrieved. + * @param courseId Course ID. + * @param cmId Course module ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the forum is retrieved. */ getForum(courseId: number, cmId: number, siteId?: string): Promise { return this.getCourseForums(courseId, siteId).then((forums) => { @@ -328,10 +441,10 @@ export class AddonModForumProvider { /** * Get a forum by forum ID. * - * @param {number} courseId Course ID. - * @param {number} forumId Forum ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the forum is retrieved. + * @param courseId Course ID. + * @param forumId Forum ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the forum is retrieved. */ getForumById(courseId: number, forumId: number, siteId?: string): Promise { return this.getCourseForums(courseId, siteId).then((forums) => { @@ -347,10 +460,10 @@ export class AddonModForumProvider { /** * Get access information for a given forum. * - * @param {number} forumId Forum ID. - * @param {boolean} [forceCache] True to always get the value from cache. false otherwise. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Object with access information. + * @param forumId Forum ID. + * @param forceCache True to always get the value from cache. false otherwise. + * @param siteId Site ID. If not defined, current site. + * @return Object with access information. * @since 3.7 */ getAccessInformation(forumId: number, forceCache?: boolean, siteId?: string): Promise { @@ -375,9 +488,9 @@ export class AddonModForumProvider { /** * Get forum discussion posts. * - * @param {number} discussionId Discussion ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise<{posts: any[], ratinginfo?: CoreRatingInfo}>} Promise resolved with forum posts and rating info. + * @param discussionId Discussion ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with forum posts and rating info. */ getDiscussionPosts(discussionId: number, siteId?: string): Promise<{posts: any[], ratinginfo?: CoreRatingInfo}> { const params = { @@ -403,8 +516,8 @@ export class AddonModForumProvider { /** * Sort forum discussion posts by an specified field. * - * @param {any[]} posts Discussion posts to be sorted in place. - * @param {string} direction Direction of the sorting (ASC / DESC). + * @param posts Discussion posts to be sorted in place. + * @param direction Direction of the sorting (ASC / DESC). */ sortDiscussionPosts(posts: any[], direction: string): void { // @todo: Check children when sorting. @@ -422,8 +535,8 @@ export class AddonModForumProvider { /** * Return whether discussion lists can be sorted. * - * @param {CoreSite} [site] Site. If not defined, current site. - * @return {boolean} True if discussion lists can be sorted. + * @param site Site. If not defined, current site. + * @return True if discussion lists can be sorted. */ isDiscussionListSortingAvailable(site?: CoreSite): boolean { site = site || this.sitesProvider.getCurrentSite(); @@ -434,7 +547,7 @@ export class AddonModForumProvider { /** * Return the list of available sort orders. * - * @return {{label: string, value: number}[]} List of sort orders. + * @return List of sort orders. */ getAvailableSortOrders(): {label: string, value: number}[] { const sortOrders = [ @@ -475,14 +588,15 @@ export class AddonModForumProvider { /** * Get forum discussions. * - * @param {number} forumId Forum ID. - * @param {number} [sortOrder] Sort order. - * @param {number} [page=0] Page. - * @param {boolean} [forceCache] True to always get the value from cache. false otherwise. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with an object with: - * - discussions: List of discussions. - * - canLoadMore: True if there may be more discussions to load. + * @param forumId Forum ID. + * @param sortOrder Sort order. + * @param page Page. + * @param forceCache True to always get the value from cache. false otherwise. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with an object with: + * - discussions: List of discussions. Note that for every discussion in the list discussion.id is the main post ID but + * discussion ID is discussion.discussion. + * - canLoadMore: True if there may be more discussions to load. */ getDiscussions(forumId: number, sortOrder?: number, page: number = 0, forceCache?: boolean, siteId?: string): Promise { sortOrder = sortOrder || AddonModForumProvider.SORTORDER_LASTPOST_DESC; @@ -555,15 +669,15 @@ export class AddonModForumProvider { * Get forum discussions in several pages. * If a page fails, the discussions until that page will be returned along with a flag indicating an error occurred. * - * @param {number} forumId Forum ID. - * @param {number} [sortOrder] Sort order. - * @param {boolean} [forceCache] True to always get the value from cache, false otherwise. - * @param {number} [numPages] Number of pages to get. If not defined, all pages. - * @param {number} [startPage] Page to start. If not defined, first page. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with an object with: - * - discussions: List of discussions. - * - error: True if an error occurred, false otherwise. + * @param forumId Forum ID. + * @param sortOrder Sort order. + * @param forceCache True to always get the value from cache, false otherwise. + * @param numPages Number of pages to get. If not defined, all pages. + * @param startPage Page to start. If not defined, first page. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with an object with: + * - discussions: List of discussions. + * - error: True if an error occurred, false otherwise. */ getDiscussionsInPages(forumId: number, sortOrder?: number, forceCache?: boolean, numPages?: number, startPage?: number, siteId?: string): Promise { @@ -606,9 +720,9 @@ export class AddonModForumProvider { /** * Invalidates can add discussion WS calls. * - * @param {number} forumId Forum ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the data is invalidated. + * @param forumId Forum ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the data is invalidated. */ invalidateCanAddDiscussion(forumId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -620,9 +734,9 @@ export class AddonModForumProvider { * Invalidate the prefetched content except files. * To invalidate files, use AddonModForum#invalidateFiles. * - * @param {number} moduleId The module ID. - * @param {number} courseId Course ID. - * @return {Promise} Promise resolved when data is invalidated. + * @param moduleId The module ID. + * @param courseId Course ID. + * @return Promise resolved when data is invalidated. */ invalidateContent(moduleId: number, courseId: number): Promise { // Get the forum first, we need the forum ID. @@ -641,7 +755,7 @@ export class AddonModForumProvider { const promises = []; response.discussions.forEach((discussion) => { - promises.push(this.invalidateDiscussionPosts(discussion.discussion)); + promises.push(this.invalidateDiscussionPosts(discussion.discussion, forum.id)); }); return this.utils.allPromises(promises); @@ -659,9 +773,9 @@ export class AddonModForumProvider { /** * Invalidates access information. * - * @param {number} forumId Forum ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the data is invalidated. + * @param forumId Forum ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the data is invalidated. */ invalidateAccessInformation(forumId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -672,22 +786,29 @@ export class AddonModForumProvider { /** * Invalidates forum discussion posts. * - * @param {number} discussionId Discussion ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the data is invalidated. + * @param discussionId Discussion ID. + * @param forumId Forum ID. If not set, we can't invalidate individual post information. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the data is invalidated. */ - invalidateDiscussionPosts(discussionId: number, siteId?: string): Promise { + invalidateDiscussionPosts(discussionId: number, forumId?: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { - return site.invalidateWsCacheForKey(this.getDiscussionPostsCacheKey(discussionId)); + const promises = [site.invalidateWsCacheForKey(this.getDiscussionPostsCacheKey(discussionId))]; + + if (forumId) { + promises.push(site.invalidateWsCacheForKeyStartingWith(this.getForumDiscussionDataCacheKey(forumId, discussionId))); + } + + return this.utils.allPromises(promises); }); } /** * Invalidates discussion list. * - * @param {number} forumId Forum ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the data is invalidated. + * @param forumId Forum ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the data is invalidated. */ invalidateDiscussionsList(forumId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -700,8 +821,8 @@ export class AddonModForumProvider { /** * Invalidate the prefetched files. * - * @param {number} moduleId The module ID. - * @return {Promise} Promise resolved when the files are invalidated. + * @param moduleId The module ID. + * @return Promise resolved when the files are invalidated. */ invalidateFiles(moduleId: number): Promise { const siteId = this.sitesProvider.getCurrentSiteId(); @@ -712,8 +833,8 @@ export class AddonModForumProvider { /** * Invalidates forum data. * - * @param {number} courseId Course ID. - * @return {Promise} Promise resolved when the data is invalidated. + * @param courseId Course ID. + * @return Promise resolved when the data is invalidated. */ invalidateForumData(courseId: number): Promise { return this.sitesProvider.getCurrentSite().invalidateWsCacheForKey(this.getForumDataCacheKey(courseId)); @@ -722,10 +843,10 @@ export class AddonModForumProvider { /** * Report a forum as being viewed. * - * @param {number} id Module ID. - * @param {string} [name] Name of the forum. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the WS call is successful. + * @param id Module ID. + * @param name Name of the forum. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the WS call is successful. */ logView(id: number, name?: string, siteId?: string): Promise { const params = { @@ -739,11 +860,11 @@ export class AddonModForumProvider { /** * Report a forum discussion as being viewed. * - * @param {number} id Discussion ID. - * @param {number} forumId Forum ID. - * @param {string} [name] Name of the forum. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the WS call is successful. + * @param id Discussion ID. + * @param forumId Forum ID. + * @param name Name of the forum. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the WS call is successful. */ logDiscussionView(id: number, forumId: number, name?: string, siteId?: string): Promise { const params = { @@ -757,17 +878,17 @@ export class AddonModForumProvider { /** * Reply to a certain post. * - * @param {number} postId ID of the post being replied. - * @param {number} discussionId ID of the discussion the user is replying to. - * @param {number} forumId ID of the forum the user is replying to. - * @param {string} name Forum name. - * @param {number} courseId Course ID the forum belongs to. - * @param {string} subject New post's subject. - * @param {string} message New post's message. - * @param {any} [options] Options (subscribe, attachments, ...). - * @param {string} [siteId] Site ID. If not defined, current site. - * @param {boolean} [allowOffline] True if it can be stored in offline, false otherwise. - * @return {Promise} Promise resolved with post ID if sent online, resolved with false if stored offline. + * @param postId ID of the post being replied. + * @param discussionId ID of the discussion the user is replying to. + * @param forumId ID of the forum the user is replying to. + * @param name Forum name. + * @param courseId Course ID the forum belongs to. + * @param subject New post's subject. + * @param message New post's message. + * @param options Options (subscribe, attachments, ...). + * @param siteId Site ID. If not defined, current site. + * @param allowOffline True if it can be stored in offline, false otherwise. + * @return Promise resolved with post ID if sent online, resolved with false if stored offline. */ replyPost(postId: number, discussionId: number, forumId: number, name: string, courseId: number, subject: string, message: string, options?: any, siteId?: string, allowOffline?: boolean): Promise { @@ -811,12 +932,12 @@ export class AddonModForumProvider { /** * Reply to a certain post. It will fail if offline or cannot connect. * - * @param {number} postId ID of the post being replied. - * @param {string} subject New post's subject. - * @param {string} message New post's message. - * @param {any} [options] Options (subscribe, attachments, ...). - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with the created post id. + * @param postId ID of the post being replied. + * @param subject New post's subject. + * @param message New post's message. + * @param options Options (subscribe, attachments, ...). + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the created post id. */ replyPostOnline(postId: number, subject: string, message: string, options?: any, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -840,11 +961,11 @@ export class AddonModForumProvider { /** * Lock or unlock a discussion. * - * @param {number} forumId Forum id. - * @param {number} discussionId DIscussion id. - * @param {boolean} locked True to lock, false to unlock. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resvoled when done. + * @param forumId Forum id. + * @param discussionId DIscussion id. + * @param locked True to lock, false to unlock. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when done. * @since 3.7 */ setLockState(forumId: number, discussionId: number, locked: boolean, siteId?: string): Promise { @@ -862,8 +983,8 @@ export class AddonModForumProvider { /** * Returns whether the set pin state WS is available. * - * @param {CoreSite} [site] Site. If not defined, current site. - * @return {boolean} Whether it's available. + * @param site Site. If not defined, current site. + * @return Whether it's available. * @since 3.7 */ isSetPinStateAvailableForSite(site?: CoreSite): boolean { @@ -875,10 +996,10 @@ export class AddonModForumProvider { /** * Pin or unpin a discussion. * - * @param {number} discussionId Discussion id. - * @param {boolean} locked True to pin, false to unpin. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resvoled when done. + * @param discussionId Discussion id. + * @param locked True to pin, false to unpin. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when done. * @since 3.7 */ setPinState(discussionId: number, pinned: boolean, siteId?: string): Promise { @@ -895,10 +1016,10 @@ export class AddonModForumProvider { /** * Star or unstar a discussion. * - * @param {number} discussionId Discussion id. - * @param {boolean} starred True to star, false to unstar. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resvoled when done. + * @param discussionId Discussion id. + * @param starred True to star, false to unstar. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when done. * @since 3.7 */ toggleFavouriteState(discussionId: number, starred: boolean, siteId?: string): Promise { @@ -915,7 +1036,7 @@ export class AddonModForumProvider { /** * Store the users data from a discussions/posts list. * - * @param {any[]} list Array of posts or discussions. + * @param list Array of posts or discussions. */ protected storeUserData(list: any[]): void { const users = {}; @@ -941,4 +1062,29 @@ export class AddonModForumProvider { this.userProvider.storeUsers(this.utils.objectToArray(users)); } + + /** + * Update a certain post. + * + * @param postId ID of the post being edited. + * @param subject New post's subject. + * @param message New post's message. + * @param options Options (subscribe, attachments, ...). + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with success boolean when done. + */ + updatePost(postId: number, subject: string, message: string, options?: any, siteId?: string): Promise { + return this.sitesProvider.getSite(siteId).then((site) => { + const params = { + postid: postId, + subject: subject, + message: message, + options: this.utils.objectToArrayOfObjects(options, 'name', 'value') + }; + + return site.write('mod_forum_update_discussion_post', params).then((response) => { + return response && response.status; + }); + }); + } } diff --git a/src/addon/mod/forum/providers/helper.ts b/src/addon/mod/forum/providers/helper.ts index fb46fab35..99ca1b487 100644 --- a/src/addon/mod/forum/providers/helper.ts +++ b/src/addon/mod/forum/providers/helper.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -43,17 +43,17 @@ export class AddonModForumHelperProvider { /** * Add a new discussion. * - * @param {number} forumId Forum ID. - * @param {string} name Forum name. - * @param {number} courseId Course ID the forum belongs to. - * @param {string} subject New discussion's subject. - * @param {string} message New discussion's message. - * @param {any[]} [attachments] New discussion's attachments. - * @param {any} [options] Options (subscribe, pin, ...). - * @param {number[]} [groupIds] Groups this discussion belongs to. - * @param {number} [timeCreated] The time the discussion was created. Only used when editing discussion. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with ids of the created discussions or null if stored offline + * @param forumId Forum ID. + * @param name Forum name. + * @param courseId Course ID the forum belongs to. + * @param subject New discussion's subject. + * @param message New discussion's message. + * @param attachments New discussion's attachments. + * @param options Options (subscribe, pin, ...). + * @param groupIds Groups this discussion belongs to. + * @param timeCreated The time the discussion was created. Only used when editing discussion. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with ids of the created discussions or null if stored offline */ addNewDiscussion(forumId: number, name: string, courseId: number, subject: string, message: string, attachments?: any[], options?: any, groupIds?: number[], timeCreated?: number, siteId?: string): Promise { @@ -155,9 +155,9 @@ export class AddonModForumHelperProvider { /** * Convert offline reply to online format in order to be compatible with them. * - * @param {any} offlineReply Offline version of the reply. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with the object converted to Online. + * @param offlineReply Offline version of the reply. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the object converted to Online. */ convertOfflineReplyToOnline(offlineReply: any, siteId?: string): Promise { const reply: any = { @@ -212,10 +212,10 @@ export class AddonModForumHelperProvider { /** * Delete stored attachment files for a new discussion. * - * @param {number} forumId Forum ID. - * @param {number} timecreated The time the discussion was created. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when deleted. + * @param forumId Forum ID. + * @param timecreated The time the discussion was created. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when deleted. */ deleteNewDiscussionStoredFiles(forumId: number, timecreated: number, siteId?: string): Promise { return this.forumOffline.getNewDiscussionFolder(forumId, timecreated, siteId).then((folderPath) => { @@ -228,11 +228,11 @@ export class AddonModForumHelperProvider { /** * Delete stored attachment files for a reply. * - * @param {number} forumId Forum ID. - * @param {number} postId ID of the post being replied. - * @param {string} [siteId] Site ID. If not defined, current site. - * @param {number} [userId] User the reply belongs to. If not defined, current user in site. - * @return {Promise} Promise resolved when deleted. + * @param forumId Forum ID. + * @param postId ID of the post being replied. + * @param siteId Site ID. If not defined, current site. + * @param userId User the reply belongs to. If not defined, current user in site. + * @return Promise resolved when deleted. */ deleteReplyStoredFiles(forumId: number, postId: number, siteId?: string, userId?: number): Promise { return this.forumOffline.getReplyFolder(forumId, postId, siteId, userId).then((folderPath) => { @@ -245,8 +245,8 @@ export class AddonModForumHelperProvider { /** * Returns the availability message of the given forum. * - * @param {any} forum Forum instance. - * @return {string} Message or null if the forum has no cut-off or due date. + * @param forum Forum instance. + * @return Message or null if the forum has no cut-off or due date. */ getAvailabilityMessage(forum: any): string { if (this.isCutoffDateReached(forum)) { @@ -269,10 +269,10 @@ export class AddonModForumHelperProvider { * * This function is inefficient because it needs to fetch all discussion pages in the worst case. * - * @param {number} forumId Forum ID. - * @param {number} discussionId Discussion ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with the discussion data. + * @param forumId Forum ID. + * @param discussionId Discussion ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the discussion data. */ getDiscussionById(forumId: number, discussionId: number, siteId?: string): Promise { siteId = siteId || this.sitesProvider.getCurrentSiteId(); @@ -280,7 +280,8 @@ export class AddonModForumHelperProvider { const findDiscussion = (page: number): Promise => { return this.forumProvider.getDiscussions(forumId, undefined, page, false, siteId).then((response) => { if (response.discussions && response.discussions.length > 0) { - const discussion = response.discussions.find((discussion) => discussion.id == discussionId); + // Note that discussion.id is the main post ID but discussion ID is discussion.discussion. + const discussion = response.discussions.find((discussion) => discussion.discussion == discussionId); if (discussion) { return discussion; } @@ -299,10 +300,10 @@ export class AddonModForumHelperProvider { /** * Get a list of stored attachment files for a new discussion. See AddonModForumHelper#storeNewDiscussionFiles. * - * @param {number} forumId Forum ID. - * @param {number} timecreated The time the discussion was created. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with the files. + * @param forumId Forum ID. + * @param timecreated The time the discussion was created. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the files. */ getNewDiscussionStoredFiles(forumId: number, timecreated: number, siteId?: string): Promise { return this.forumOffline.getNewDiscussionFolder(forumId, timecreated, siteId).then((folderPath) => { @@ -313,11 +314,11 @@ export class AddonModForumHelperProvider { /** * Get a list of stored attachment files for a reply. See AddonModForumHelper#storeReplyFiles. * - * @param {number} forumId Forum ID. - * @param {number} postId ID of the post being replied. - * @param {string} [siteId] Site ID. If not defined, current site. - * @param {number} [userId] User the reply belongs to. If not defined, current user in site. - * @return {Promise} Promise resolved with the files. + * @param forumId Forum ID. + * @param postId ID of the post being replied. + * @param siteId Site ID. If not defined, current site. + * @param userId User the reply belongs to. If not defined, current user in site. + * @return Promise resolved with the files. */ getReplyStoredFiles(forumId: number, postId: number, siteId?: string, userId?: number): Promise { return this.forumOffline.getReplyFolder(forumId, postId, siteId, userId).then((folderPath) => { @@ -328,9 +329,9 @@ export class AddonModForumHelperProvider { /** * Check if the data of a post/discussion has changed. * - * @param {any} post Current data. - * @param {any} [original] Original ata. - * @return {boolean} True if data has changed, false otherwise. + * @param post Current data. + * @param original Original ata. + * @return True if data has changed, false otherwise. */ hasPostDataChanged(post: any, original?: any): boolean { if (!original || original.subject == null) { @@ -352,8 +353,7 @@ export class AddonModForumHelperProvider { /** * Is the cutoff date for the forum reached? * - * @param {any} forum Forum instance. - * @return {boolean} + * @param forum Forum instance. */ isCutoffDateReached(forum: any): boolean { const now = Date.now() / 1000; @@ -364,8 +364,7 @@ export class AddonModForumHelperProvider { /** * Is the due date for the forum reached? * - * @param {any} forum Forum instance. - * @return {boolean} + * @param forum Forum instance. */ isDueDateReached(forum: any): boolean { const now = Date.now() / 1000; @@ -377,11 +376,11 @@ export class AddonModForumHelperProvider { * Given a list of files (either online files or local files), store the local files in a local folder * to be submitted later. * - * @param {number} forumId Forum ID. - * @param {number} timecreated The time the discussion was created. - * @param {any[]} files List of files. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved if success, rejected otherwise. + * @param forumId Forum ID. + * @param timecreated The time the discussion was created. + * @param files List of files. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved if success, rejected otherwise. */ storeNewDiscussionFiles(forumId: number, timecreated: number, files: any[], siteId?: string): Promise { // Get the folder where to store the files. @@ -394,12 +393,12 @@ export class AddonModForumHelperProvider { * Given a list of files (either online files or local files), store the local files in a local folder * to be submitted later. * - * @param {number} forumId Forum ID. - * @param {number} postId ID of the post being replied. - * @param {any[]} files List of files. - * @param {string} [siteId] Site ID. If not defined, current site. - * @param {number} [userId] User the reply belongs to. If not defined, current user in site. - * @return {Promise} Promise resolved if success, rejected otherwise. + * @param forumId Forum ID. + * @param postId ID of the post being replied. + * @param files List of files. + * @param siteId Site ID. If not defined, current site. + * @param userId User the reply belongs to. If not defined, current user in site. + * @return Promise resolved if success, rejected otherwise. */ storeReplyFiles(forumId: number, postId: number, files: any[], siteId?: string, userId?: number): Promise { // Get the folder where to store the files. @@ -411,12 +410,12 @@ export class AddonModForumHelperProvider { /** * Upload or store some files for a new discussion, depending if the user is offline or not. * - * @param {number} forumId Forum ID. - * @param {number} timecreated The time the discussion was created. - * @param {any[]} files List of files. - * @param {boolean} offline True if files sould be stored for offline, false to upload them. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved if success. + * @param forumId Forum ID. + * @param timecreated The time the discussion was created. + * @param files List of files. + * @param offline True if files sould be stored for offline, false to upload them. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved if success. */ uploadOrStoreNewDiscussionFiles(forumId: number, timecreated: number, files: any[], offline: boolean, siteId?: string) : Promise { @@ -430,13 +429,13 @@ export class AddonModForumHelperProvider { /** * Upload or store some files for a reply, depending if the user is offline or not. * - * @param {number} forumId Forum ID. - * @param {number} postId ID of the post being replied. - * @param {any[]} files List of files. - * @param {boolean} offline True if files sould be stored for offline, false to upload them. - * @param {string} [siteId] Site ID. If not defined, current site. - * @param {number} [userId] User the reply belongs to. If not defined, current user in site. - * @return {Promise} Promise resolved if success. + * @param forumId Forum ID. + * @param postId ID of the post being replied. + * @param files List of files. + * @param offline True if files sould be stored for offline, false to upload them. + * @param siteId Site ID. If not defined, current site. + * @param userId User the reply belongs to. If not defined, current user in site. + * @return Promise resolved if success. */ uploadOrStoreReplyFiles(forumId: number, postId: number, files: any[], offline: boolean, siteId?: string, userId?: number) : Promise { diff --git a/src/addon/mod/forum/providers/index-link-handler.ts b/src/addon/mod/forum/providers/index-link-handler.ts index 2b7b23e7c..9e059e491 100644 --- a/src/addon/mod/forum/providers/index-link-handler.ts +++ b/src/addon/mod/forum/providers/index-link-handler.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -15,10 +15,6 @@ import { Injectable } from '@angular/core'; import { CoreContentLinksModuleIndexHandler } from '@core/contentlinks/classes/module-index-handler'; import { CoreCourseHelperProvider } from '@core/course/providers/helper'; -import { AddonModForumProvider } from './forum'; -import { CoreContentLinksAction } from '@core/contentlinks/providers/delegate'; -import { CoreCourseProvider } from '@core/course/providers/course'; -import { CoreDomUtilsProvider } from '@providers/utils/dom'; /** * Handler to treat links to forum index. @@ -27,57 +23,7 @@ import { CoreDomUtilsProvider } from '@providers/utils/dom'; export class AddonModForumIndexLinkHandler extends CoreContentLinksModuleIndexHandler { name = 'AddonModForumIndexLinkHandler'; - constructor(courseHelper: CoreCourseHelperProvider, protected forumProvider: AddonModForumProvider, - private courseProvider: CoreCourseProvider, private domUtils: CoreDomUtilsProvider) { - super(courseHelper, 'AddonModForum', 'forum'); - - // Match the view.php URL with an id param. - this.pattern = new RegExp('\/mod\/forum\/view\.php.*([\&\?](f|id)=\\d+)'); - } - - /** - * Check if the handler is enabled for a certain site (site + user) and a URL. - * If not defined, defaults to true. - * - * @param {string} siteId The site ID. - * @param {string} url The URL to treat. - * @param {any} params The params of the URL. E.g. 'mysite.com?id=1' -> {id: 1} - * @param {number} [courseId] Course ID related to the URL. Optional but recommended. - * @return {boolean|Promise} Whether the handler is enabled for the URL and site. - */ - isEnabled(siteId: string, url: string, params: any, courseId?: number): boolean | Promise { - return true; - } - - /** - * Get the list of actions for a link (url). - * - * @param {string[]} siteIds List of sites the URL belongs to. - * @param {string} url The URL to treat. - * @param {any} params The params of the URL. E.g. 'mysite.com?id=1' -> {id: 1} - * @param {number} [courseId] Course ID related to the URL. Optional but recommended. - * @return {CoreContentLinksAction[]|Promise} List of (or promise resolved with list of) actions. - */ - getActions(siteIds: string[], url: string, params: any, courseId?: number): - CoreContentLinksAction[] | Promise { - - if (typeof params.f != 'undefined') { - return [{ - action: (siteId, navCtrl?): void => { - const modal = this.domUtils.showModalLoading(), - forumId = parseInt(params.f, 10); - - this.courseProvider.getModuleBasicInfoByInstance(forumId, 'forum', siteId).then((module) => { - this.courseHelper.navigateToModule(parseInt(module.id, 10), siteId, module.course, undefined, - undefined, undefined, navCtrl); - }).finally(() => { - // Just in case. In fact we need to dismiss the modal before showing a toast or error message. - modal.dismiss(); - }); - } - }]; - } - - return super.getActions(siteIds, url, params, courseId); + constructor(courseHelper: CoreCourseHelperProvider) { + super(courseHelper, 'AddonModForum', 'forum', 'f'); } } diff --git a/src/addon/mod/forum/providers/list-link-handler.ts b/src/addon/mod/forum/providers/list-link-handler.ts index 49923c89b..1f0093d11 100644 --- a/src/addon/mod/forum/providers/list-link-handler.ts +++ b/src/addon/mod/forum/providers/list-link-handler.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/addon/mod/forum/providers/module-handler.ts b/src/addon/mod/forum/providers/module-handler.ts index f87f93d03..67839c7bc 100644 --- a/src/addon/mod/forum/providers/module-handler.ts +++ b/src/addon/mod/forum/providers/module-handler.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -52,7 +52,7 @@ export class AddonModForumModuleHandler implements CoreCourseModuleHandler { /** * Check if the handler is enabled on a site level. * - * @return {boolean} Whether or not the handler is enabled on a site level. + * @return Whether or not the handler is enabled on a site level. */ isEnabled(): boolean { return true; @@ -61,10 +61,10 @@ export class AddonModForumModuleHandler implements CoreCourseModuleHandler { /** * Get the data required to display the module in the course contents view. * - * @param {any} module The module object. - * @param {number} courseId The course ID. - * @param {number} sectionId The section ID. - * @return {CoreCourseModuleHandlerData} Data to render the module. + * @param module The module object. + * @param courseId The course ID. + * @param sectionId The section ID. + * @return Data to render the module. */ getData(module: any, courseId: number, sectionId: number): CoreCourseModuleHandlerData { const data: CoreCourseModuleHandlerData = { @@ -106,9 +106,9 @@ export class AddonModForumModuleHandler implements CoreCourseModuleHandler { * Get the component to render the module. This is needed to support singleactivity course format. * The component returned must implement CoreCourseModuleMainComponent. * - * @param {any} course The course object. - * @param {any} module The module object. - * @return {any} The component to use, undefined if not found. + * @param course The course object. + * @param module The module object. + * @return The component to use, undefined if not found. */ getMainComponent(course: any, module: any): any { return AddonModForumIndexComponent; @@ -118,7 +118,7 @@ export class AddonModForumModuleHandler implements CoreCourseModuleHandler { * Whether to display the course refresher in single activity course format. If it returns false, a refresher must be * included in the template that calls the doRefresh method of the component. Defaults to true. * - * @return {boolean} Whether the refresher should be displayed. + * @return Whether the refresher should be displayed. */ displayRefresherInSingleActivity(): boolean { return false; @@ -127,10 +127,10 @@ export class AddonModForumModuleHandler implements CoreCourseModuleHandler { /** * Triggers an update for the extra badge text. * - * @param {CoreCourseModuleHandlerData} data Course Module Handler data. - * @param {number} courseId Course ID. - * @param {number} moduleId Course module ID. - * @param {string} [siteId] Site ID. If not defined, current site. + * @param data Course Module Handler data. + * @param courseId Course ID. + * @param moduleId Course module ID. + * @param siteId Site ID. If not defined, current site. */ updateExtraBadge(data: CoreCourseModuleHandlerData, courseId: number, moduleId: number, siteId?: string): void { siteId = siteId || this.sitesProvider.getCurrentSiteId(); diff --git a/src/addon/mod/forum/providers/offline.ts b/src/addon/mod/forum/providers/offline.ts index af6a08b11..4679b8a7b 100644 --- a/src/addon/mod/forum/providers/offline.ts +++ b/src/addon/mod/forum/providers/offline.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -132,11 +132,11 @@ export class AddonModForumOfflineProvider { /** * Delete a forum offline discussion. * - * @param {number} forumId Forum ID. - * @param {number} timeCreated The time the discussion was created. - * @param {string} [siteId] Site ID. If not defined, current site. - * @param {number} [userId] User the discussion belongs to. If not defined, current user in site. - * @return {Promise} Promise resolved if stored, rejected if failure. + * @param forumId Forum ID. + * @param timeCreated The time the discussion was created. + * @param siteId Site ID. If not defined, current site. + * @param userId User the discussion belongs to. If not defined, current user in site. + * @return Promise resolved if stored, rejected if failure. */ deleteNewDiscussion(forumId: number, timeCreated: number, siteId?: string, userId?: number): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -153,11 +153,11 @@ export class AddonModForumOfflineProvider { /** * Get a forum offline discussion. * - * @param {number} forumId Forum ID. - * @param {number} timeCreated The time the discussion was created. - * @param {string} [siteId] Site ID. If not defined, current site. - * @param {number} [userId] User the discussion belongs to. If not defined, current user in site. - * @return {Promise} Promise resolved if stored, rejected if failure. + * @param forumId Forum ID. + * @param timeCreated The time the discussion was created. + * @param siteId Site ID. If not defined, current site. + * @param userId User the discussion belongs to. If not defined, current user in site. + * @return Promise resolved if stored, rejected if failure. */ getNewDiscussion(forumId: number, timeCreated: number, siteId?: string, userId?: number): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -178,8 +178,8 @@ export class AddonModForumOfflineProvider { /** * Get all offline new discussions. * - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with discussions. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with discussions. */ getAllNewDiscussions(siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -190,10 +190,10 @@ export class AddonModForumOfflineProvider { /** * Check if there are offline new discussions to send. * - * @param {number} forumId Forum ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @param {number} [userId] User the discussions belong to. If not defined, current user in site. - * @return {Promise} Promise resolved with boolean: true if has offline answers, false otherwise. + * @param forumId Forum ID. + * @param siteId Site ID. If not defined, current site. + * @param userId User the discussions belong to. If not defined, current user in site. + * @return Promise resolved with boolean: true if has offline answers, false otherwise. */ hasNewDiscussions(forumId: number, siteId?: string, userId?: number): Promise { return this.getNewDiscussions(forumId, siteId, userId).then((discussions) => { @@ -207,10 +207,10 @@ export class AddonModForumOfflineProvider { /** * Get new discussions to be synced. * - * @param {number} forumId Forum ID to get. - * @param {string} [siteId] Site ID. If not defined, current site. - * @param {number} [userId] User the discussions belong to. If not defined, current user in site. - * @return {Promise} Promise resolved with the object to be synced. + * @param forumId Forum ID to get. + * @param siteId Site ID. If not defined, current site. + * @param userId User the discussions belong to. If not defined, current user in site. + * @return Promise resolved with the object to be synced. */ getNewDiscussions(forumId: number, siteId?: string, userId?: number): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -227,17 +227,17 @@ export class AddonModForumOfflineProvider { /** * Offline version for adding a new discussion to a forum. * - * @param {number} forumId Forum ID. - * @param {string} name Forum name. - * @param {number} courseId Course ID the forum belongs to. - * @param {string} subject New discussion's subject. - * @param {string} message New discussion's message. - * @param {any} [options] Options (subscribe, pin, ...). - * @param {string} [groupId] Group this discussion belongs to. - * @param {number} [timeCreated] The time the discussion was created. If not defined, current time. - * @param {string} [siteId] Site ID. If not defined, current site. - * @param {number} [userId] User the discussion belong to. If not defined, current user in site. - * @return {Promise} Promise resolved when new discussion is successfully saved. + * @param forumId Forum ID. + * @param name Forum name. + * @param courseId Course ID the forum belongs to. + * @param subject New discussion's subject. + * @param message New discussion's message. + * @param options Options (subscribe, pin, ...). + * @param groupId Group this discussion belongs to. + * @param timeCreated The time the discussion was created. If not defined, current time. + * @param siteId Site ID. If not defined, current site. + * @param userId User the discussion belong to. If not defined, current user in site. + * @return Promise resolved when new discussion is successfully saved. */ addNewDiscussion(forumId: number, name: string, courseId: number, subject: string, message: string, options?: any, groupId?: number, timeCreated?: number, siteId?: string, userId?: number): Promise { @@ -261,10 +261,10 @@ export class AddonModForumOfflineProvider { /** * Delete forum offline replies. * - * @param {number} postId ID of the post being replied. - * @param {string} [siteId] Site ID. If not defined, current site. - * @param {number} [userId] User the reply belongs to. If not defined, current user in site. - * @return {Promise} Promise resolved if stored, rejected if failure. + * @param postId ID of the post being replied. + * @param siteId Site ID. If not defined, current site. + * @param userId User the reply belongs to. If not defined, current user in site. + * @return Promise resolved if stored, rejected if failure. */ deleteReply(postId: number, siteId?: string, userId?: number): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -280,8 +280,8 @@ export class AddonModForumOfflineProvider { /** * Get all offline replies. * - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with replies. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with replies. */ getAllReplies(siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -292,10 +292,10 @@ export class AddonModForumOfflineProvider { /** * Check if there is an offline reply for a forum to be synced. * - * @param {number} forumId ID of the forum being replied. - * @param {string} [siteId] Site ID. If not defined, current site. - * @param {number} [userId] User the replies belong to. If not defined, current user in site. - * @return {Promise} Promise resolved with boolean: true if has offline answers, false otherwise. + * @param forumId ID of the forum being replied. + * @param siteId Site ID. If not defined, current site. + * @param userId User the replies belong to. If not defined, current user in site. + * @return Promise resolved with boolean: true if has offline answers, false otherwise. */ hasForumReplies(forumId: number, siteId?: string, userId?: number): Promise { return this.getForumReplies(forumId, siteId, userId).then((replies) => { @@ -309,10 +309,10 @@ export class AddonModForumOfflineProvider { /** * Get the replies of a forum to be synced. * - * @param {number} forumId ID of the forum being replied. - * @param {string} [siteId] Site ID. If not defined, current site. - * @param {number} [userId] User the replies belong to. If not defined, current user in site. - * @return {Promise} Promise resolved with replies. + * @param forumId ID of the forum being replied. + * @param siteId Site ID. If not defined, current site. + * @param userId User the replies belong to. If not defined, current user in site. + * @return Promise resolved with replies. */ getForumReplies(forumId: number, siteId?: string, userId?: number): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -329,10 +329,10 @@ export class AddonModForumOfflineProvider { /** * Check if there is an offline reply to be synced. * - * @param {number} discussionId ID of the discussion the user is replying to. - * @param {string} [siteId] Site ID. If not defined, current site. - * @param {number} [userId] User the replies belong to. If not defined, current user in site. - * @return {Promise} Promise resolved with boolean: true if has offline answers, false otherwise. + * @param discussionId ID of the discussion the user is replying to. + * @param siteId Site ID. If not defined, current site. + * @param userId User the replies belong to. If not defined, current user in site. + * @return Promise resolved with boolean: true if has offline answers, false otherwise. */ hasDiscussionReplies(discussionId: number, siteId?: string, userId?: number): Promise { return this.getDiscussionReplies(discussionId, siteId, userId).then((replies) => { @@ -346,10 +346,10 @@ export class AddonModForumOfflineProvider { /** * Get the replies of a discussion to be synced. * - * @param {number} discussionId ID of the discussion the user is replying to. - * @param {string} [siteId] Site ID. If not defined, current site. - * @param {number} [userId] User the replies belong to. If not defined, current user in site. - * @return {Promise} Promise resolved with discussions. + * @param discussionId ID of the discussion the user is replying to. + * @param siteId Site ID. If not defined, current site. + * @param userId User the replies belong to. If not defined, current user in site. + * @return Promise resolved with discussions. */ getDiscussionReplies(discussionId: number, siteId?: string, userId?: number): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -366,17 +366,17 @@ export class AddonModForumOfflineProvider { /** * Offline version for replying to a certain post. * - * @param {number} postId ID of the post being replied. - * @param {number} discussionId ID of the discussion the user is replying to. - * @param {number} forumId ID of the forum the user is replying to. - * @param {string} name Forum name. - * @param {number} courseId Course ID the forum belongs to. - * @param {string} subject New post's subject. - * @param {string} message New post's message. - * @param {any} [options] Options (subscribe, attachments, ...). - * @param {string} [siteId] Site ID. If not defined, current site. - * @param {number} [userId] User the post belong to. If not defined, current user in site. - * @return {Promise} Promise resolved when the post is created. + * @param postId ID of the post being replied. + * @param discussionId ID of the discussion the user is replying to. + * @param forumId ID of the forum the user is replying to. + * @param name Forum name. + * @param courseId Course ID the forum belongs to. + * @param subject New post's subject. + * @param message New post's message. + * @param options Options (subscribe, attachments, ...). + * @param siteId Site ID. If not defined, current site. + * @param userId User the post belong to. If not defined, current user in site. + * @return Promise resolved when the post is created. */ replyPost(postId: number, discussionId: number, forumId: number, name: string, courseId: number, subject: string, message: string, options?: any, siteId?: string, userId?: number): Promise { @@ -401,9 +401,9 @@ export class AddonModForumOfflineProvider { /** * Get the path to the folder where to store files for offline attachments in a forum. * - * @param {number} forumId Forum ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with the path. + * @param forumId Forum ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the path. */ getForumFolder(forumId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -416,10 +416,10 @@ export class AddonModForumOfflineProvider { /** * Get the path to the folder where to store files for a new offline discussion. * - * @param {number} forumId Forum ID. - * @param {number} timeCreated The time the discussion was created. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with the path. + * @param forumId Forum ID. + * @param timeCreated The time the discussion was created. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the path. */ getNewDiscussionFolder(forumId: number, timeCreated: number, siteId?: string): Promise { return this.getForumFolder(forumId, siteId).then((folderPath) => { @@ -430,11 +430,11 @@ export class AddonModForumOfflineProvider { /** * Get the path to the folder where to store files for a new offline reply. * - * @param {number} forumId Forum ID. - * @param {number} postId ID of the post being replied. - * @param {string} [siteId] Site ID. If not defined, current site. - * @param {number} [userId] User the replies belong to. If not defined, current user in site. - * @return {Promise} Promise resolved with the path. + * @param forumId Forum ID. + * @param postId ID of the post being replied. + * @param siteId Site ID. If not defined, current site. + * @param userId User the replies belong to. If not defined, current user in site. + * @return Promise resolved with the path. */ getReplyFolder(forumId: number, postId: number, siteId?: string, userId?: number): Promise { return this.getForumFolder(forumId, siteId).then((folderPath) => { @@ -449,8 +449,8 @@ export class AddonModForumOfflineProvider { /** * Parse "options" column of fetched records. * - * @param {any[]} records List of records. - * @return {any[]} List of records with options parsed. + * @param records List of records. + * @return List of records with options parsed. */ protected parseRecordOptions(records: any[]): any[] { records.forEach((record) => { diff --git a/src/addon/mod/forum/providers/post-link-handler.ts b/src/addon/mod/forum/providers/post-link-handler.ts index 73d691dc1..afe072aea 100644 --- a/src/addon/mod/forum/providers/post-link-handler.ts +++ b/src/addon/mod/forum/providers/post-link-handler.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -37,11 +37,11 @@ export class AddonModForumPostLinkHandler extends CoreContentLinksHandlerBase { /** * Get the list of actions for a link (url). * - * @param {string[]} siteIds List of sites the URL belongs to. - * @param {string} url The URL to treat. - * @param {any} params The params of the URL. E.g. 'mysite.com?id=1' -> {id: 1} - * @param {number} [courseId] Course ID related to the URL. Optional but recommended. - * @return {CoreContentLinksAction[]|Promise} List of (or promise resolved with list of) actions. + * @param siteIds List of sites the URL belongs to. + * @param url The URL to treat. + * @param params The params of the URL. E.g. 'mysite.com?id=1' -> {id: 1} + * @param courseId Course ID related to the URL. Optional but recommended. + * @return List of (or promise resolved with list of) actions. */ getActions(siteIds: string[], url: string, params: any, courseId?: number): CoreContentLinksAction[] | Promise { @@ -72,11 +72,11 @@ export class AddonModForumPostLinkHandler extends CoreContentLinksHandlerBase { * Check if the handler is enabled for a certain site (site + user) and a URL. * If not defined, defaults to true. * - * @param {string} siteId The site ID. - * @param {string} url The URL to treat. - * @param {any} params The params of the URL. E.g. 'mysite.com?id=1' -> {id: 1} - * @param {number} [courseId] Course ID related to the URL. Optional but recommended. - * @return {boolean|Promise} Whether the handler is enabled for the URL and site. + * @param siteId The site ID. + * @param url The URL to treat. + * @param params The params of the URL. E.g. 'mysite.com?id=1' -> {id: 1} + * @param courseId Course ID related to the URL. Optional but recommended. + * @return Whether the handler is enabled for the URL and site. */ isEnabled(siteId: string, url: string, params: any, courseId?: number): boolean | Promise { return typeof params.forum != 'undefined'; diff --git a/src/addon/mod/forum/providers/prefetch-handler.ts b/src/addon/mod/forum/providers/prefetch-handler.ts index e698a4977..743d99b97 100644 --- a/src/addon/mod/forum/providers/prefetch-handler.ts +++ b/src/addon/mod/forum/providers/prefetch-handler.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -25,6 +25,8 @@ import { CoreCourseActivityPrefetchHandlerBase } from '@core/course/classes/acti import { CoreGroupsProvider } from '@providers/groups'; import { AddonModForumProvider } from './forum'; import { AddonModForumSyncProvider } from './sync'; +import { CoreFilterHelperProvider } from '@core/filter/providers/helper'; +import { CorePluginFileDelegate } from '@providers/plugin-file-delegate'; /** * Handler to prefetch forums. @@ -43,21 +45,24 @@ export class AddonModForumPrefetchHandler extends CoreCourseActivityPrefetchHand filepoolProvider: CoreFilepoolProvider, sitesProvider: CoreSitesProvider, domUtils: CoreDomUtilsProvider, + filterHelper: CoreFilterHelperProvider, + pluginFileDelegate: CorePluginFileDelegate, private userProvider: CoreUserProvider, private groupsProvider: CoreGroupsProvider, private forumProvider: AddonModForumProvider, private syncProvider: AddonModForumSyncProvider) { - super(translate, appProvider, utils, courseProvider, filepoolProvider, sitesProvider, domUtils); + super(translate, appProvider, utils, courseProvider, filepoolProvider, sitesProvider, domUtils, filterHelper, + pluginFileDelegate); } /** * Get list of files. If not defined, we'll assume they're in module.contents. * - * @param {any} module Module. - * @param {Number} courseId Course ID the module belongs to. - * @param {boolean} [single] True if we're downloading a single module, false if we're downloading a whole section. - * @return {Promise} Promise resolved with the list of files. + * @param module Module. + * @param courseId Course ID the module belongs to. + * @param single True if we're downloading a single module, false if we're downloading a whole section. + * @return Promise resolved with the list of files. */ getFiles(module: any, courseId: number, single?: boolean): Promise { return this.forumProvider.getForum(courseId, module.id).then((forum) => { @@ -77,8 +82,8 @@ export class AddonModForumPrefetchHandler extends CoreCourseActivityPrefetchHand /** * Given a list of forum posts, return a list with all the files (attachments and embedded files). * - * @param {any[]} posts Forum posts. - * @return {any[]} Files. + * @param posts Forum posts. + * @return Files. */ protected getPostsFiles(posts: any[]): any[] { let files = []; @@ -91,7 +96,7 @@ export class AddonModForumPrefetchHandler extends CoreCourseActivityPrefetchHand if (getInlineFiles && post.messageinlinefiles && post.messageinlinefiles.length) { files = files.concat(post.messageinlinefiles); } else if (post.message && !getInlineFiles) { - files = files.concat(this.domUtils.extractDownloadableFilesFromHtmlAsFakeFileObjects(post.message)); + files = files.concat(this.filepoolProvider.extractDownloadableFilesFromHtmlAsFakeFileObjects(post.message)); } }); @@ -101,8 +106,8 @@ export class AddonModForumPrefetchHandler extends CoreCourseActivityPrefetchHand /** * Get the posts to be prefetched. * - * @param {any} forum Forum instance. - * @return {Promise} Promise resolved with array of posts. + * @param forum Forum instance. + * @return Promise resolved with array of posts. */ protected getPostsForPrefetch(forum: any): Promise { const promises = this.forumProvider.getAvailableSortOrders().map((sortOrder) => { @@ -145,9 +150,9 @@ export class AddonModForumPrefetchHandler extends CoreCourseActivityPrefetchHand /** * Invalidate the prefetched content. * - * @param {number} moduleId The module ID. - * @param {number} courseId The course ID the module belongs to. - * @return {Promise} Promise resolved when the data is invalidated. + * @param moduleId The module ID. + * @param courseId The course ID the module belongs to. + * @return Promise resolved when the data is invalidated. */ invalidateContent(moduleId: number, courseId: number): Promise { return this.forumProvider.invalidateContent(moduleId, courseId); @@ -157,9 +162,9 @@ export class AddonModForumPrefetchHandler extends CoreCourseActivityPrefetchHand * Invalidate WS calls needed to determine module status (usually, to check if module is downloadable). * It doesn't need to invalidate check updates. It should NOT invalidate files nor all the prefetched data. * - * @param {any} module Module. - * @param {number} courseId Course ID the module belongs to. - * @return {Promise} Promise resolved when invalidated. + * @param module Module. + * @param courseId Course ID the module belongs to. + * @return Promise resolved when invalidated. */ invalidateModule(module: any, courseId: number): Promise { // Invalidate forum data to recalculate unread message count badge. @@ -174,11 +179,11 @@ export class AddonModForumPrefetchHandler extends CoreCourseActivityPrefetchHand /** * Prefetch a module. * - * @param {any} module Module. - * @param {number} courseId Course ID the module belongs to. - * @param {boolean} [single] True if we're downloading a single module, false if we're downloading a whole section. - * @param {string} [dirPath] Path of the directory where to store all the content files. - * @return {Promise} Promise resolved when done. + * @param module Module. + * @param courseId Course ID the module belongs to. + * @param single True if we're downloading a single module, false if we're downloading a whole section. + * @param dirPath Path of the directory where to store all the content files. + * @return Promise resolved when done. */ prefetch(module: any, courseId?: number, single?: boolean, dirPath?: string): Promise { return this.prefetchPackage(module, courseId, single, this.prefetchForum.bind(this)); @@ -187,11 +192,11 @@ export class AddonModForumPrefetchHandler extends CoreCourseActivityPrefetchHand /** * Prefetch a forum. * - * @param {any} module The module object returned by WS. - * @param {number} courseId Course ID the module belongs to. - * @param {boolean} single True if we're downloading a single module, false if we're downloading a whole section. - * @param {string} siteId Site ID. - * @return {Promise} Promise resolved when done. + * @param module The module object returned by WS. + * @param courseId Course ID the module belongs to. + * @param single True if we're downloading a single module, false if we're downloading a whole section. + * @param siteId Site ID. + * @return Promise resolved when done. */ protected prefetchForum(module: any, courseId: number, single: boolean, siteId: string): Promise { // Get the forum data. @@ -239,10 +244,10 @@ export class AddonModForumPrefetchHandler extends CoreCourseActivityPrefetchHand /** * Prefetch groups info for a forum. * - * @param {any} module The module object returned by WS. - * @param {number} courseI Course ID the module belongs to. - * @param {boolean} canCreateDiscussions Whether the user can create discussions in the forum. - * @return {Promise} Promise resolved when group data has been prefetched. + * @param module The module object returned by WS. + * @param courseI Course ID the module belongs to. + * @param canCreateDiscussions Whether the user can create discussions in the forum. + * @return Promise resolved when group data has been prefetched. */ protected prefetchGroupsInfo(forum: any, courseId: number, canCreateDiscussions: boolean): any { // Check group mode. @@ -299,10 +304,10 @@ export class AddonModForumPrefetchHandler extends CoreCourseActivityPrefetchHand /** * Sync a module. * - * @param {any} module Module. - * @param {number} courseId Course ID the module belongs to - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when done. + * @param module Module. + * @param courseId Course ID the module belongs to + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when done. */ sync(module: any, courseId: number, siteId?: any): Promise { const promises = []; diff --git a/src/addon/mod/forum/providers/push-click-handler.ts b/src/addon/mod/forum/providers/push-click-handler.ts index 01cff72f8..5d3891d81 100644 --- a/src/addon/mod/forum/providers/push-click-handler.ts +++ b/src/addon/mod/forum/providers/push-click-handler.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -34,8 +34,8 @@ export class AddonModForumPushClickHandler implements CorePushNotificationsClick /** * Check if a notification click is handled by this handler. * - * @param {any} notification The notification to check. - * @return {boolean} Whether the notification click is handled by this handler + * @param notification The notification to check. + * @return Whether the notification click is handled by this handler */ handles(notification: any): boolean | Promise { return this.utils.isTrueOrOne(notification.notif) && notification.moodlecomponent == 'mod_forum' && @@ -45,8 +45,8 @@ export class AddonModForumPushClickHandler implements CorePushNotificationsClick /** * Handle the notification click. * - * @param {any} notification The notification to check. - * @return {Promise} Promise resolved when done. + * @param notification The notification to check. + * @return Promise resolved when done. */ handleClick(notification: any): Promise { const contextUrlParams = this.urlUtils.extractUrlParams(notification.contexturl), @@ -62,7 +62,7 @@ export class AddonModForumPushClickHandler implements CorePushNotificationsClick pageParams.postId = Number(data.postid || contextUrlParams.urlHash.replace('p', '')); } - return this.forumProvider.invalidateDiscussionPosts(pageParams.discussionId, notification.site).catch(() => { + return this.forumProvider.invalidateDiscussionPosts(pageParams.discussionId, undefined, notification.site).catch(() => { // Ignore errors. }).then(() => { return this.linkHelper.goInSite(undefined, 'AddonModForumDiscussionPage', pageParams, notification.site); diff --git a/src/addon/mod/forum/providers/sync-cron-handler.ts b/src/addon/mod/forum/providers/sync-cron-handler.ts index 53a2406e7..440d29068 100644 --- a/src/addon/mod/forum/providers/sync-cron-handler.ts +++ b/src/addon/mod/forum/providers/sync-cron-handler.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -29,9 +29,9 @@ export class AddonModForumSyncCronHandler implements CoreCronHandler { * Execute the process. * Receives the ID of the site affected, undefined for all sites. * - * @param {string} [siteId] ID of the site affected, undefined for all sites. - * @param {boolean} [force] Wether the execution is forced (manual sync). - * @return {Promise} Promise resolved when done, rejected if failure. + * @param siteId ID of the site affected, undefined for all sites. + * @param force Wether the execution is forced (manual sync). + * @return Promise resolved when done, rejected if failure. */ execute(siteId?: string, force?: boolean): Promise { return this.forumSync.syncAllForums(siteId, force); @@ -40,7 +40,7 @@ export class AddonModForumSyncCronHandler implements CoreCronHandler { /** * Get the time between consecutive executions. * - * @return {number} Time between consecutive executions (in ms). + * @return Time between consecutive executions (in ms). */ getInterval(): number { return this.forumSync.syncInterval; diff --git a/src/addon/mod/forum/providers/sync.ts b/src/addon/mod/forum/providers/sync.ts index fcfbc2743..2b204df72 100644 --- a/src/addon/mod/forum/providers/sync.ts +++ b/src/addon/mod/forum/providers/sync.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -70,9 +70,9 @@ export class AddonModForumSyncProvider extends CoreSyncBaseProvider { /** * Try to synchronize all the forums in a certain site or in all sites. * - * @param {string} [siteId] Site ID to sync. If not defined, sync all sites. - * @param {boolean} [force] Wether to force sync not depending on last execution. - * @return {Promise} Promise resolved if sync is successful, rejected if sync fails. + * @param siteId Site ID to sync. If not defined, sync all sites. + * @param force Wether to force sync not depending on last execution. + * @return Promise resolved if sync is successful, rejected if sync fails. */ syncAllForums(siteId?: string, force?: boolean): Promise { return this.syncOnSites('all forums', this.syncAllForumsFunc.bind(this), [force], siteId); @@ -81,9 +81,9 @@ export class AddonModForumSyncProvider extends CoreSyncBaseProvider { /** * Sync all forums on a site. * - * @param {string} siteId Site ID to sync. - * @param {boolean} [force] Wether to force sync not depending on last execution. - * @return {Promise} Promise resolved if sync is successful, rejected if sync fails. + * @param siteId Site ID to sync. + * @param force Wether to force sync not depending on last execution. + * @return Promise resolved if sync is successful, rejected if sync fails. */ protected syncAllForumsFunc(siteId: string, force?: boolean): Promise { const sitePromises = []; @@ -153,10 +153,10 @@ export class AddonModForumSyncProvider extends CoreSyncBaseProvider { /** * Sync a forum only if a certain time has passed since the last time. * - * @param {number} forumId Forum ID. - * @param {number} userId User the discussion belong to. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the forum is synced or if it doesn't need to be synced. + * @param forumId Forum ID. + * @param userId User the discussion belong to. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the forum is synced or if it doesn't need to be synced. */ syncForumDiscussionsIfNeeded(forumId: number, userId: number, siteId?: string): Promise { siteId = siteId || this.sitesProvider.getCurrentSiteId(); @@ -173,10 +173,10 @@ export class AddonModForumSyncProvider extends CoreSyncBaseProvider { /** * Synchronize all offline discussions of a forum. * - * @param {number} forumId Forum ID to be synced. - * @param {number} [userId] User the discussions belong to. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved if sync is successful, rejected otherwise. + * @param forumId Forum ID to be synced. + * @param userId User the discussions belong to. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved if sync is successful, rejected otherwise. */ syncForumDiscussions(forumId: number, userId?: number, siteId?: string): Promise { userId = userId || this.sitesProvider.getCurrentSiteUserId(); @@ -307,11 +307,11 @@ export class AddonModForumSyncProvider extends CoreSyncBaseProvider { /** * Synchronize forum offline ratings. * - * @param {number} [cmId] Course module to be synced. If not defined, sync all forums. - * @param {number} [discussionId] Discussion id to be synced. If not defined, sync all discussions. - * @param {boolean} [force] Wether to force sync not depending on last execution. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved if sync is successful, rejected otherwise. + * @param cmId Course module to be synced. If not defined, sync all forums. + * @param discussionId Discussion id to be synced. If not defined, sync all discussions. + * @param force Wether to force sync not depending on last execution. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved if sync is successful, rejected otherwise. */ syncRatings(cmId?: number, discussionId?: number, force?: boolean, siteId?: string): Promise { siteId = siteId || this.sitesProvider.getCurrentSiteId(); @@ -326,7 +326,7 @@ export class AddonModForumSyncProvider extends CoreSyncBaseProvider { updated = true; // Invalidate discussions of updated ratings. - promises.push(this.forumProvider.invalidateDiscussionPosts(result.itemSet.itemSetId, siteId)); + promises.push(this.forumProvider.invalidateDiscussionPosts(result.itemSet.itemSetId, undefined, siteId)); } if (result.warnings.length) { // Fetch forum to construct the warning message. @@ -352,10 +352,10 @@ export class AddonModForumSyncProvider extends CoreSyncBaseProvider { /** * Synchronize all offline discussion replies of a forum. * - * @param {number} forumId Forum ID to be synced. - * @param {number} [userId] User the discussions belong to. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved if sync is successful, rejected otherwise. + * @param forumId Forum ID to be synced. + * @param userId User the discussions belong to. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved if sync is successful, rejected otherwise. */ syncForumReplies(forumId: number, userId?: number, siteId?: string): Promise { // Get offline forum replies to be sent. @@ -393,10 +393,10 @@ export class AddonModForumSyncProvider extends CoreSyncBaseProvider { /** * Sync a forum discussion replies only if a certain time has passed since the last time. * - * @param {number} discussionId Discussion ID to be synced. - * @param {number} [userId] User the posts belong to. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the forum discussion is synced or if it doesn't need to be synced. + * @param discussionId Discussion ID to be synced. + * @param userId User the posts belong to. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the forum discussion is synced or if it doesn't need to be synced. */ syncDiscussionRepliesIfNeeded(discussionId: number, userId?: number, siteId?: string): Promise { siteId = siteId || this.sitesProvider.getCurrentSiteId(); @@ -413,10 +413,10 @@ export class AddonModForumSyncProvider extends CoreSyncBaseProvider { /** * Synchronize all offline replies from a discussion. * - * @param {number} discussionId Discussion ID to be synced. - * @param {number} [userId] User the posts belong to. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved if sync is successful, rejected otherwise. + * @param discussionId Discussion ID to be synced. + * @param userId User the posts belong to. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved if sync is successful, rejected otherwise. */ syncDiscussionReplies(discussionId: number, userId?: number, siteId?: string): Promise { userId = userId || this.sitesProvider.getCurrentSiteUserId(); @@ -502,7 +502,7 @@ export class AddonModForumSyncProvider extends CoreSyncBaseProvider { if (forumId) { promises.push(this.forumProvider.invalidateDiscussionsList(forumId, siteId)); } - promises.push(this.forumProvider.invalidateDiscussionPosts(discussionId, siteId)); + promises.push(this.forumProvider.invalidateDiscussionPosts(discussionId, forumId, siteId)); return this.utils.allPromises(promises).catch(() => { // Ignore errors. @@ -523,11 +523,11 @@ export class AddonModForumSyncProvider extends CoreSyncBaseProvider { /** * Delete a new discussion. * - * @param {number} forumId Forum ID the discussion belongs to. - * @param {number} timecreated The timecreated of the discussion. - * @param {string} [siteId] Site ID. If not defined, current site. - * @param {number} [userId] User the discussion belongs to. If not defined, current user in site. - * @return {Promise} Promise resolved when deleted. + * @param forumId Forum ID the discussion belongs to. + * @param timecreated The timecreated of the discussion. + * @param siteId Site ID. If not defined, current site. + * @param userId User the discussion belongs to. If not defined, current user in site. + * @return Promise resolved when deleted. */ protected deleteNewDiscussion(forumId: number, timecreated: number, siteId?: string, userId?: number): Promise { const promises = []; @@ -543,11 +543,11 @@ export class AddonModForumSyncProvider extends CoreSyncBaseProvider { /** * Delete a new discussion. * - * @param {number} forumId Forum ID the discussion belongs to. - * @param {number} postId ID of the post being replied. - * @param {string} [siteId] Site ID. If not defined, current site. - * @param {number} [userId] User the discussion belongs to. If not defined, current user in site. - * @return {Promise} Promise resolved when deleted. + * @param forumId Forum ID the discussion belongs to. + * @param postId ID of the post being replied. + * @param siteId Site ID. If not defined, current site. + * @param userId User the discussion belongs to. If not defined, current user in site. + * @return Promise resolved when deleted. */ protected deleteReply(forumId: number, postId: number, siteId?: string, userId?: number): Promise { const promises = []; @@ -563,12 +563,12 @@ export class AddonModForumSyncProvider extends CoreSyncBaseProvider { /** * Upload attachments of an offline post/discussion. * - * @param {number} forumId Forum ID the post belongs to. - * @param {any} post Offline post or discussion. - * @param {boolean} isDisc True if it's a new discussion, false if it's a reply. - * @param {string} [siteId] Site ID. If not defined, current site. - * @param {number} [userId] User the reply belongs to. If not defined, current user in site. - * @return {Promise} Promise resolved with draftid if uploaded, resolved with undefined if nothing to upload. + * @param forumId Forum ID the post belongs to. + * @param post Offline post or discussion. + * @param isDisc True if it's a new discussion, false if it's a reply. + * @param siteId Site ID. If not defined, current site. + * @param userId User the reply belongs to. If not defined, current user in site. + * @return Promise resolved with draftid if uploaded, resolved with undefined if nothing to upload. */ protected uploadAttachments(forumId: number, post: any, isDisc: boolean, siteId?: string, userId?: number): Promise { const attachments = post && post.options && post.options.attachmentsid; @@ -607,9 +607,9 @@ export class AddonModForumSyncProvider extends CoreSyncBaseProvider { /** * Get the ID of a forum sync. * - * @param {number} forumId Forum ID. - * @param {number} [userId] User the responses belong to.. If not defined, current user. - * @return {string} Sync ID. + * @param forumId Forum ID. + * @param userId User the responses belong to.. If not defined, current user. + * @return Sync ID. */ getForumSyncId(forumId: number, userId?: number): string { userId = userId || this.sitesProvider.getCurrentSiteUserId(); @@ -620,9 +620,9 @@ export class AddonModForumSyncProvider extends CoreSyncBaseProvider { /** * Get the ID of a discussion sync. * - * @param {number} discussionId Discussion ID. - * @param {number} [userId] User the responses belong to.. If not defined, current user. - * @return {string} Sync ID. + * @param discussionId Discussion ID. + * @param userId User the responses belong to.. If not defined, current user. + * @return Sync ID. */ getDiscussionSyncId(discussionId: number, userId?: number): string { userId = userId || this.sitesProvider.getCurrentSiteUserId(); diff --git a/src/addon/mod/forum/providers/tag-area-handler.ts b/src/addon/mod/forum/providers/tag-area-handler.ts index 02505b31c..abeb5c680 100644 --- a/src/addon/mod/forum/providers/tag-area-handler.ts +++ b/src/addon/mod/forum/providers/tag-area-handler.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -29,7 +29,7 @@ export class AddonModForumTagAreaHandler implements CoreTagAreaHandler { /** * Whether or not the handler is enabled on a site level. - * @return {boolean|Promise} Whether or not the handler is enabled on a site level. + * @return Whether or not the handler is enabled on a site level. */ isEnabled(): boolean | Promise { return true; @@ -38,8 +38,8 @@ export class AddonModForumTagAreaHandler implements CoreTagAreaHandler { /** * Parses the rendered content of a tag index and returns the items. * - * @param {string} content Rendered content. - * @return {any[]|Promise} Area items (or promise resolved with the items). + * @param content Rendered content. + * @return Area items (or promise resolved with the items). */ parseContent(content: string): any[] | Promise { return this.tagHelper.parseFeedContent(content); @@ -48,8 +48,8 @@ export class AddonModForumTagAreaHandler implements CoreTagAreaHandler { /** * Get the component to use to display items. * - * @param {Injector} injector Injector. - * @return {any|Promise} The component (or promise resolved with component) to use, undefined if not found. + * @param injector Injector. + * @return The component (or promise resolved with component) to use, undefined if not found. */ getComponent(injector: Injector): any | Promise { return CoreTagFeedComponent; diff --git a/src/addon/mod/glossary/components/components.module.ts b/src/addon/mod/glossary/components/components.module.ts index c37c35d36..6567b7ca8 100644 --- a/src/addon/mod/glossary/components/components.module.ts +++ b/src/addon/mod/glossary/components/components.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/addon/mod/glossary/components/index/addon-mod-glossary-index.html b/src/addon/mod/glossary/components/index/addon-mod-glossary-index.html index 4695adab9..fb1fcde3a 100644 --- a/src/addon/mod/glossary/components/index/addon-mod-glossary-index.html +++ b/src/addon/mod/glossary/components/index/addon-mod-glossary-index.html @@ -11,7 +11,7 @@ - + @@ -22,7 +22,7 @@ - + diff --git a/src/addon/mod/glossary/components/index/index.ts b/src/addon/mod/glossary/components/index/index.ts index ed3d807e9..cc1550134 100644 --- a/src/addon/mod/glossary/components/index/index.ts +++ b/src/addon/mod/glossary/components/index/index.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -120,10 +120,10 @@ export class AddonModGlossaryIndexComponent extends CoreCourseModuleMainActivity /** * Download the component contents. * - * @param {boolean} [refresh=false] Whether we're refreshing data. - * @param {boolean} [sync=false] If the refresh needs syncing. - * @param {boolean} [showErrors=false] Wether to show errors to the user or hide them. - * @return {Promise} Promise resolved when done. + * @param refresh Whether we're refreshing data. + * @param sync If the refresh needs syncing. + * @param showErrors Wether to show errors to the user or hide them. + * @return Promise resolved when done. */ protected fetchContent(refresh: boolean = false, sync: boolean = false, showErrors: boolean = false): Promise { return this.glossaryProvider.getGlossary(this.courseId, this.module.id).then((glossary) => { @@ -132,6 +132,8 @@ export class AddonModGlossaryIndexComponent extends CoreCourseModuleMainActivity this.description = glossary.intro || this.description; this.canAdd = (this.glossaryProvider.isPluginEnabledForEditing() && glossary.canaddentry) || false; + this.dataRetrieved.emit(this.glossary); + if (!this.fetchMode) { this.switchMode('letter_all'); } @@ -158,8 +160,7 @@ export class AddonModGlossaryIndexComponent extends CoreCourseModuleMainActivity })); return Promise.all(promises); - }).then(() => { - // All data obtained, now fill the context menu. + }).finally(() => { this.fillContextMenu(refresh); }); } @@ -167,8 +168,8 @@ export class AddonModGlossaryIndexComponent extends CoreCourseModuleMainActivity /** * Convenience function to fetch entries. * - * @param {boolean} [append=false] True if fetched entries are appended to exsiting ones. - * @return {Promise} Promise resolved when done. + * @param append True if fetched entries are appended to exsiting ones. + * @return Promise resolved when done. */ protected fetchEntries(append: boolean = false): Promise { this.loadMoreError = false; @@ -196,7 +197,7 @@ export class AddonModGlossaryIndexComponent extends CoreCourseModuleMainActivity /** * Perform the invalidate content function. * - * @return {Promise} Resolved when done. + * @return Resolved when done. */ protected invalidateContent(): Promise { const promises = []; @@ -217,7 +218,7 @@ export class AddonModGlossaryIndexComponent extends CoreCourseModuleMainActivity /** * Performs the sync of the activity. * - * @return {Promise} Promise resolved when done. + * @return Promise resolved when done. */ protected sync(): Promise { return this.prefetchHandler.sync(this.module, this.courseId); @@ -226,8 +227,8 @@ export class AddonModGlossaryIndexComponent extends CoreCourseModuleMainActivity /** * Checks if sync has succeed from result sync data. * - * @param {any} result Data returned on the sync function. - * @return {boolean} Whether it succeed or not. + * @param result Data returned on the sync function. + * @return Whether it succeed or not. */ protected hasSyncSucceed(result: any): boolean { return result.updated; @@ -236,8 +237,8 @@ export class AddonModGlossaryIndexComponent extends CoreCourseModuleMainActivity /** * Compares sync event data with current data to check if refresh content is needed. * - * @param {any} syncEventData Data receiven on sync observer. - * @return {boolean} True if refresh is needed, false otherwise. + * @param syncEventData Data receiven on sync observer. + * @return True if refresh is needed, false otherwise. */ protected isRefreshSyncNeeded(syncEventData: any): boolean { return this.glossary && syncEventData.glossaryId == this.glossary.id && @@ -247,7 +248,7 @@ export class AddonModGlossaryIndexComponent extends CoreCourseModuleMainActivity /** * Change fetch mode. * - * @param {FetchMode} mode New mode. + * @param mode New mode. */ protected switchMode(mode: FetchMode): void { this.fetchMode = mode; @@ -321,8 +322,8 @@ export class AddonModGlossaryIndexComponent extends CoreCourseModuleMainActivity /** * Convenience function to load more forum discussions. * - * @param {any} [infiniteComplete] Infinite scroll complete function. Only used from core-infinite-loading. - * @return {Promise} Promise resolved when done. + * @param infiniteComplete Infinite scroll complete function. Only used from core-infinite-loading. + * @return Promise resolved when done. */ loadMoreEntries(infiniteComplete?: any): Promise { return this.fetchEntries(true).catch((error) => { @@ -336,7 +337,7 @@ export class AddonModGlossaryIndexComponent extends CoreCourseModuleMainActivity /** * Show the mode picker menu. * - * @param {MouseEvent} event Event. + * @param event Event. */ openModePicker(event: MouseEvent): void { const popover = this.popoverCtrl.create(AddonModGlossaryModePickerPopoverComponent, { @@ -371,7 +372,7 @@ export class AddonModGlossaryIndexComponent extends CoreCourseModuleMainActivity /** * Opens an entry. * - * @param {number} entryId Entry id. + * @param entryId Entry id. */ openEntry(entryId: number): void { const params = { @@ -385,7 +386,7 @@ export class AddonModGlossaryIndexComponent extends CoreCourseModuleMainActivity /** * Opens new entry editor. * - * @param {any} [entry] Offline entry to edit. + * @param entry Offline entry to edit. */ openNewEntry(entry?: any): void { const params = { @@ -401,7 +402,7 @@ export class AddonModGlossaryIndexComponent extends CoreCourseModuleMainActivity /** * Search entries. * - * @param {string} query Text entered on the search box. + * @param query Text entered on the search box. */ search(query: string): void { this.loadingMessage = this.translate.instant('core.searching'); @@ -413,7 +414,7 @@ export class AddonModGlossaryIndexComponent extends CoreCourseModuleMainActivity /** * Function called when we receive an event of new entry. * - * @param {any} data Event data. + * @param data Event data. */ protected eventReceived(data: any): void { if (this.glossary && this.glossary.id === data.glossaryId) { diff --git a/src/addon/mod/glossary/components/mode-picker/mode-picker.ts b/src/addon/mod/glossary/components/mode-picker/mode-picker.ts index 1881670c4..d7e51905c 100644 --- a/src/addon/mod/glossary/components/mode-picker/mode-picker.ts +++ b/src/addon/mod/glossary/components/mode-picker/mode-picker.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -57,9 +57,9 @@ export class AddonModGlossaryModePickerPopoverComponent { /** * Function called when a mode is clicked. * - * @param {Event} event Click event. - * @param {string} key Clicked mode key. - * @return {boolean} Return true if success, false if error. + * @param event Click event. + * @param key Clicked mode key. + * @return Return true if success, false if error. */ modePicked(event: Event, key: string): boolean { this.viewCtrl.dismiss(key); diff --git a/src/addon/mod/glossary/glossary.module.ts b/src/addon/mod/glossary/glossary.module.ts index e3520fbe7..e1ad371ea 100644 --- a/src/addon/mod/glossary/glossary.module.ts +++ b/src/addon/mod/glossary/glossary.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/addon/mod/glossary/pages/edit/edit.html b/src/addon/mod/glossary/pages/edit/edit.html index 5678fcba8..3c3bfa0d7 100644 --- a/src/addon/mod/glossary/pages/edit/edit.html +++ b/src/addon/mod/glossary/pages/edit/edit.html @@ -1,6 +1,6 @@ - + diff --git a/src/addon/mod/glossary/pages/edit/edit.module.ts b/src/addon/mod/glossary/pages/edit/edit.module.ts index 50cc3a93c..0f606e93e 100644 --- a/src/addon/mod/glossary/pages/edit/edit.module.ts +++ b/src/addon/mod/glossary/pages/edit/edit.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/addon/mod/glossary/pages/edit/edit.ts b/src/addon/mod/glossary/pages/edit/edit.ts index 837b0e760..7bb9e60eb 100644 --- a/src/addon/mod/glossary/pages/edit/edit.ts +++ b/src/addon/mod/glossary/pages/edit/edit.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -129,7 +129,7 @@ export class AddonModGlossaryEditPage implements OnInit { /** * Definition changed. * - * @param {string} text The new text. + * @param text The new text. */ onDefinitionChange(text: string): void { this.entry.definition = text; @@ -138,7 +138,7 @@ export class AddonModGlossaryEditPage implements OnInit { /** * Check if we can leave the page or not. * - * @return {boolean|Promise} Resolved if we can leave it, rejected if not. + * @return Resolved if we can leave it, rejected if not. */ ionViewCanLeave(): boolean | Promise { let promise: any; diff --git a/src/addon/mod/glossary/pages/entry/entry.html b/src/addon/mod/glossary/pages/entry/entry.html index 5954398ff..47cc11542 100644 --- a/src/addon/mod/glossary/pages/entry/entry.html +++ b/src/addon/mod/glossary/pages/entry/entry.html @@ -1,6 +1,6 @@ - + @@ -12,16 +12,16 @@ -

+

{{ entry.timemodified | coreDateDayOrTime }} -

+

{{ entry.userfullname }}

-

+

{{ entry.timemodified | coreDateDayOrTime }}
- +
@@ -35,6 +35,9 @@

{{ 'addon.mod_glossary.entrypendingapproval' | translate }}

+ + + diff --git a/src/addon/mod/glossary/pages/entry/entry.module.ts b/src/addon/mod/glossary/pages/entry/entry.module.ts index 730943091..c045e4040 100644 --- a/src/addon/mod/glossary/pages/entry/entry.module.ts +++ b/src/addon/mod/glossary/pages/entry/entry.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -18,6 +18,7 @@ import { TranslateModule } from '@ngx-translate/core'; import { CoreComponentsModule } from '@components/components.module'; import { CoreDirectivesModule } from '@directives/directives.module'; import { CorePipesModule } from '@pipes/pipes.module'; +import { CoreCommentsComponentsModule } from '@core/comments/components/components.module'; import { CoreRatingComponentsModule } from '@core/rating/components/components.module'; import { CoreTagComponentsModule } from '@core/tag/components/components.module'; import { AddonModGlossaryEntryPage } from './entry'; @@ -32,6 +33,7 @@ import { AddonModGlossaryEntryPage } from './entry'; CorePipesModule, IonicPageModule.forChild(AddonModGlossaryEntryPage), TranslateModule.forChild(), + CoreCommentsComponentsModule, CoreRatingComponentsModule, CoreTagComponentsModule ], diff --git a/src/addon/mod/glossary/pages/entry/entry.ts b/src/addon/mod/glossary/pages/entry/entry.ts index a5b38641d..d1e22e722 100644 --- a/src/addon/mod/glossary/pages/entry/entry.ts +++ b/src/addon/mod/glossary/pages/entry/entry.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -12,11 +12,13 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { Component } from '@angular/core'; +import { Component, ViewChild } from '@angular/core'; import { IonicPage, NavParams } from 'ionic-angular'; import { CoreDomUtilsProvider } from '@providers/utils/dom'; import { CoreRatingInfo } from '@core/rating/providers/rating'; import { CoreTagProvider } from '@core/tag/providers/tag'; +import { CoreCommentsProvider } from '@core/comments/providers/comments'; +import { CoreCommentsCommentsComponent } from '@core/comments/components/comments/comments'; import { AddonModGlossaryProvider } from '../../providers/glossary'; /** @@ -28,6 +30,8 @@ import { AddonModGlossaryProvider } from '../../providers/glossary'; templateUrl: 'entry.html', }) export class AddonModGlossaryEntryPage { + @ViewChild(CoreCommentsCommentsComponent) comments: CoreCommentsCommentsComponent; + component = AddonModGlossaryProvider.COMPONENT; componentId: number; entry: any; @@ -37,23 +41,27 @@ export class AddonModGlossaryEntryPage { showDate = false; ratingInfo: CoreRatingInfo; tagsEnabled: boolean; + commentsEnabled: boolean; protected courseId: number; protected entryId: number; constructor(navParams: NavParams, - private domUtils: CoreDomUtilsProvider, - private glossaryProvider: AddonModGlossaryProvider, - private tagProvider: CoreTagProvider) { + protected domUtils: CoreDomUtilsProvider, + protected glossaryProvider: AddonModGlossaryProvider, + protected tagProvider: CoreTagProvider, + protected commentsProvider: CoreCommentsProvider) { this.courseId = navParams.get('courseId'); this.entryId = navParams.get('entryId'); - this.tagsEnabled = this.tagProvider.areTagsAvailableInSite(); } /** * View loaded. */ ionViewDidLoad(): void { + this.tagsEnabled = this.tagProvider.areTagsAvailableInSite(); + this.commentsEnabled = !this.commentsProvider.areCommentsDisabledInSite(); + this.fetchEntry().then(() => { this.glossaryProvider.logEntryView(this.entry.id, this.componentId, this.glossary.name).catch(() => { // Ignore errors. @@ -66,10 +74,18 @@ export class AddonModGlossaryEntryPage { /** * Refresh the data. * - * @param {any} [refresher] Refresher. - * @return {Promise} Promise resolved when done. + * @param refresher Refresher. + * @return Promise resolved when done. */ doRefresh(refresher?: any): Promise { + if (this.glossary && this.glossary.allowcomments && this.entry && this.entry.id > 0 && this.commentsEnabled && + this.comments) { + // Refresh comments. Don't add it to promises because we don't want the comments fetch to block the entry fetch. + this.comments.doRefresh().catch(() => { + // Ignore errors. + }); + } + return this.glossaryProvider.invalidateEntry(this.entry.id).catch(() => { // Ignore errors. }).then(() => { @@ -82,8 +98,8 @@ export class AddonModGlossaryEntryPage { /** * Convenience function to get the glossary entry. * - * @param {boolean} [refresh] Whether we're refreshing data. - * @return {Promise} Promise resolved when done. + * @param refresh Whether we're refreshing data. + * @return Promise resolved when done. */ protected fetchEntry(refresh?: boolean): Promise { return this.glossaryProvider.getEntry(this.entryId).then((result) => { diff --git a/src/addon/mod/glossary/pages/index/index.html b/src/addon/mod/glossary/pages/index/index.html index f33735512..bd651f76a 100644 --- a/src/addon/mod/glossary/pages/index/index.html +++ b/src/addon/mod/glossary/pages/index/index.html @@ -1,6 +1,6 @@ - + diff --git a/src/addon/mod/glossary/pages/index/index.module.ts b/src/addon/mod/glossary/pages/index/index.module.ts index 415f45272..4395a0c55 100644 --- a/src/addon/mod/glossary/pages/index/index.module.ts +++ b/src/addon/mod/glossary/pages/index/index.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/addon/mod/glossary/pages/index/index.ts b/src/addon/mod/glossary/pages/index/index.ts index a812235fe..673aa39b1 100644 --- a/src/addon/mod/glossary/pages/index/index.ts +++ b/src/addon/mod/glossary/pages/index/index.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -40,7 +40,7 @@ export class AddonModGlossaryIndexPage { /** * Update some data based on the glossary instance. * - * @param {any} glossary Glossary instance. + * @param glossary Glossary instance. */ updateData(glossary: any): void { this.title = glossary.name || this.title; diff --git a/src/addon/mod/glossary/providers/edit-link-handler.ts b/src/addon/mod/glossary/providers/edit-link-handler.ts index c86557aee..5dd28d5e4 100644 --- a/src/addon/mod/glossary/providers/edit-link-handler.ts +++ b/src/addon/mod/glossary/providers/edit-link-handler.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -39,11 +39,11 @@ export class AddonModGlossaryEditLinkHandler extends CoreContentLinksHandlerBase /** * Get the list of actions for a link (url). * - * @param {string[]} siteIds List of sites the URL belongs to. - * @param {string} url The URL to treat. - * @param {any} params The params of the URL. E.g. 'mysite.com?id=1' -> {id: 1} - * @param {number} [courseId] Course ID related to the URL. Optional but recommended. - * @return {CoreContentLinksAction[]|Promise} List of (or promise resolved with list of) actions. + * @param siteIds List of sites the URL belongs to. + * @param url The URL to treat. + * @param params The params of the URL. E.g. 'mysite.com?id=1' -> {id: 1} + * @param courseId Course ID related to the URL. Optional but recommended. + * @return List of (or promise resolved with list of) actions. */ getActions(siteIds: string[], url: string, params: any, courseId?: number): CoreContentLinksAction[] | Promise { @@ -76,11 +76,11 @@ export class AddonModGlossaryEditLinkHandler extends CoreContentLinksHandlerBase * Check if the handler is enabled for a certain site (site + user) and a URL. * If not defined, defaults to true. * - * @param {string} siteId The site ID. - * @param {string} url The URL to treat. - * @param {any} params The params of the URL. E.g. 'mysite.com?id=1' -> {id: 1} - * @param {number} [courseId] Course ID related to the URL. Optional but recommended. - * @return {boolean|Promise} Whether the handler is enabled for the URL and site. + * @param siteId The site ID. + * @param url The URL to treat. + * @param params The params of the URL. E.g. 'mysite.com?id=1' -> {id: 1} + * @param courseId Course ID related to the URL. Optional but recommended. + * @return Whether the handler is enabled for the URL and site. */ isEnabled(siteId: string, url: string, params: any, courseId?: number): boolean | Promise { return typeof params.cmid != 'undefined'; diff --git a/src/addon/mod/glossary/providers/entry-link-handler.ts b/src/addon/mod/glossary/providers/entry-link-handler.ts index 99acce7b7..98ec8a6e3 100644 --- a/src/addon/mod/glossary/providers/entry-link-handler.ts +++ b/src/addon/mod/glossary/providers/entry-link-handler.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -40,11 +40,11 @@ export class AddonModGlossaryEntryLinkHandler extends CoreContentLinksHandlerBas /** * Get the list of actions for a link (url). * - * @param {string[]} siteIds List of sites the URL belongs to. - * @param {string} url The URL to treat. - * @param {any} params The params of the URL. E.g. 'mysite.com?id=1' -> {id: 1} - * @param {number} [courseId] Course ID related to the URL. Optional but recommended. - * @return {CoreContentLinksAction[]|Promise} List of (or promise resolved with list of) actions. + * @param siteIds List of sites the URL belongs to. + * @param url The URL to treat. + * @param params The params of the URL. E.g. 'mysite.com?id=1' -> {id: 1} + * @param courseId Course ID related to the URL. Optional but recommended. + * @return List of (or promise resolved with list of) actions. */ getActions(siteIds: string[], url: string, params: any, courseId?: number): CoreContentLinksAction[] | Promise { diff --git a/src/addon/mod/glossary/providers/glossary.ts b/src/addon/mod/glossary/providers/glossary.ts index cd494187f..d80820756 100644 --- a/src/addon/mod/glossary/providers/glossary.ts +++ b/src/addon/mod/glossary/providers/glossary.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -81,8 +81,8 @@ export class AddonModGlossaryProvider { /** * Get the course glossary cache key. * - * @param {number} courseId Course Id. - * @return {string} Cache key. + * @param courseId Course Id. + * @return Cache key. */ protected getCourseGlossariesCacheKey(courseId: number): string { return this.ROOT_CACHE_KEY + 'courseGlossaries:' + courseId; @@ -91,9 +91,9 @@ export class AddonModGlossaryProvider { /** * Get all the glossaries in a course. * - * @param {number} courseId Course Id. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Resolved with the glossaries. + * @param courseId Course Id. + * @param siteId Site ID. If not defined, current site. + * @return Resolved with the glossaries. */ getCourseGlossaries(courseId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -114,9 +114,9 @@ export class AddonModGlossaryProvider { /** * Invalidate all glossaries in a course. * - * @param {number} courseId Course Id. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Resolved when data is invalidated. + * @param courseId Course Id. + * @param siteId Site ID. If not defined, current site. + * @return Resolved when data is invalidated. */ invalidateCourseGlossaries(courseId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -129,11 +129,11 @@ export class AddonModGlossaryProvider { /** * Get the entries by author cache key. * - * @param {number} glossaryId Glossary Id. - * @param {string} letter First letter of firstname or lastname, or either keywords: ALL or SPECIAL. - * @param {string} field Search and order using: FIRSTNAME or LASTNAME - * @param {string} sort The direction of the order: ASC or DESC - * @return {string} Cache key. + * @param glossaryId Glossary Id. + * @param letter First letter of firstname or lastname, or either keywords: ALL or SPECIAL. + * @param field Search and order using: FIRSTNAME or LASTNAME + * @param sort The direction of the order: ASC or DESC + * @return Cache key. */ protected getEntriesByAuthorCacheKey(glossaryId: number, letter: string, field: string, sort: string): string { return this.ROOT_CACHE_KEY + 'entriesByAuthor:' + glossaryId + ':' + letter + ':' + field + ':' + sort; @@ -142,16 +142,16 @@ export class AddonModGlossaryProvider { /** * Get entries by author. * - * @param {number} glossaryId Glossary Id. - * @param {string} letter First letter of firstname or lastname, or either keywords: ALL or SPECIAL. - * @param {string} field Search and order using: FIRSTNAME or LASTNAME - * @param {string} sort The direction of the order: ASC or DESC - * @param {number} from Start returning records from here. - * @param {number} limit Number of records to return. - * @param {boolean} omitExpires True to always get the value from cache. If data isn't cached, it will call the WS. - * @param {boolean} forceOffline True to always get the value from cache. If data isn't cached, it won't call the WS. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Resolved with the entries. + * @param glossaryId Glossary Id. + * @param letter First letter of firstname or lastname, or either keywords: ALL or SPECIAL. + * @param field Search and order using: FIRSTNAME or LASTNAME + * @param sort The direction of the order: ASC or DESC + * @param from Start returning records from here. + * @param limit Number of records to return. + * @param omitExpires True to always get the value from cache. If data isn't cached, it will call the WS. + * @param forceOffline True to always get the value from cache. If data isn't cached, it won't call the WS. + * @param siteId Site ID. If not defined, current site. + * @return Resolved with the entries. */ getEntriesByAuthor(glossaryId: number, letter: string, field: string, sort: string, from: number, limit: number, omitExpires: boolean, forceOffline: boolean, siteId?: string): Promise { @@ -178,12 +178,12 @@ export class AddonModGlossaryProvider { /** * Invalidate cache of entries by author. * - * @param {number} glossaryId Glossary Id. - * @param {string} letter First letter of firstname or lastname, or either keywords: ALL or SPECIAL. - * @param {string} field Search and order using: FIRSTNAME or LASTNAME - * @param {string} sort The direction of the order: ASC or DESC - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Resolved when data is invalidated. + * @param glossaryId Glossary Id. + * @param letter First letter of firstname or lastname, or either keywords: ALL or SPECIAL. + * @param field Search and order using: FIRSTNAME or LASTNAME + * @param sort The direction of the order: ASC or DESC + * @param siteId Site ID. If not defined, current site. + * @return Resolved when data is invalidated. */ invalidateEntriesByAuthor(glossaryId: number, letter: string, field: string, sort: string, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -196,15 +196,15 @@ export class AddonModGlossaryProvider { /** * Get entries by category. * - * @param {number} glossaryId Glossary Id. - * @param {string} categoryId The category ID. Use constant SHOW_ALL_CATEGORIES for all categories, or - * constant SHOW_NOT_CATEGORISED for uncategorised entries. - * @param {number} from Start returning records from here. - * @param {number} limit Number of records to return. - * @param {boolean} omitExpires True to always get the value from cache. If data isn't cached, it will call the WS. - * @param {boolean} forceOffline True to always get the value from cache. If data isn't cached, it won't call the WS. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Resolved with the entries. + * @param glossaryId Glossary Id. + * @param categoryId The category ID. Use constant SHOW_ALL_CATEGORIES for all categories, or + * constant SHOW_NOT_CATEGORISED for uncategorised entries. + * @param from Start returning records from here. + * @param limit Number of records to return. + * @param omitExpires True to always get the value from cache. If data isn't cached, it will call the WS. + * @param forceOffline True to always get the value from cache. If data isn't cached, it won't call the WS. + * @param siteId Site ID. If not defined, current site. + * @return Resolved with the entries. */ getEntriesByCategory(glossaryId: number, categoryId: number, from: number, limit: number, omitExpires: boolean, forceOffline: boolean, siteId?: string): Promise { @@ -230,11 +230,11 @@ export class AddonModGlossaryProvider { /** * Invalidate cache of entries by category. * - * @param {number} glossaryId Glossary Id. - * @param {string} categoryId The category ID. Use constant SHOW_ALL_CATEGORIES for all categories, or - * constant SHOW_NOT_CATEGORISED for uncategorised entries. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Resolved when data is invalidated. + * @param glossaryId Glossary Id. + * @param categoryId The category ID. Use constant SHOW_ALL_CATEGORIES for all categories, or + * constant SHOW_NOT_CATEGORISED for uncategorised entries. + * @param siteId Site ID. If not defined, current site. + * @return Resolved when data is invalidated. */ invalidateEntriesByCategory(glossaryId: number, categoryId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -247,10 +247,10 @@ export class AddonModGlossaryProvider { /** * Get the entries by category cache key. * - * @param {number} glossaryId Glossary Id. - * @param {string} categoryId The category ID. Use constant SHOW_ALL_CATEGORIES for all categories, or - * constant SHOW_NOT_CATEGORISED for uncategorised entries. - * @return {string} Cache key. + * @param glossaryId Glossary Id. + * @param categoryId The category ID. Use constant SHOW_ALL_CATEGORIES for all categories, or + * constant SHOW_NOT_CATEGORISED for uncategorised entries. + * @return Cache key. */ getEntriesByCategoryCacheKey(glossaryId: number, categoryId: number): string { return this.ROOT_CACHE_KEY + 'entriesByCategory:' + glossaryId + ':' + categoryId; @@ -259,10 +259,10 @@ export class AddonModGlossaryProvider { /** * Get the entries by date cache key. * - * @param {number} glossaryId Glossary Id. - * @param {string} order The way to order the records. - * @param {string} sort The direction of the order. - * @return {string} Cache key. + * @param glossaryId Glossary Id. + * @param order The way to order the records. + * @param sort The direction of the order. + * @return Cache key. */ getEntriesByDateCacheKey(glossaryId: number, order: string, sort: string): string { return this.ROOT_CACHE_KEY + 'entriesByDate:' + glossaryId + ':' + order + ':' + sort; @@ -271,15 +271,15 @@ export class AddonModGlossaryProvider { /** * Get entries by date. * - * @param {number} glossaryId Glossary Id. - * @param {string} order The way to order the records. - * @param {string} sort The direction of the order. - * @param {number} from Start returning records from here. - * @param {number} limit Number of records to return. - * @param {boolean} omitExpires True to always get the value from cache. If data isn't cached, it will call the WS. - * @param {boolean} forceOffline True to always get the value from cache. If data isn't cached, it won't call the WS. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Resolved with the entries. + * @param glossaryId Glossary Id. + * @param order The way to order the records. + * @param sort The direction of the order. + * @param from Start returning records from here. + * @param limit Number of records to return. + * @param omitExpires True to always get the value from cache. If data isn't cached, it will call the WS. + * @param forceOffline True to always get the value from cache. If data isn't cached, it won't call the WS. + * @param siteId Site ID. If not defined, current site. + * @return Resolved with the entries. */ getEntriesByDate(glossaryId: number, order: string, sort: string, from: number, limit: number, omitExpires: boolean, forceOffline: boolean, siteId?: string): Promise { @@ -306,11 +306,11 @@ export class AddonModGlossaryProvider { /** * Invalidate cache of entries by date. * - * @param {number} glossaryId Glossary Id. - * @param {string} order The way to order the records. - * @param {string} sort The direction of the order. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Resolved when data is invalidated. + * @param glossaryId Glossary Id. + * @param order The way to order the records. + * @param sort The direction of the order. + * @param siteId Site ID. If not defined, current site. + * @return Resolved when data is invalidated. */ invalidateEntriesByDate(glossaryId: number, order: string, sort: string, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -323,9 +323,9 @@ export class AddonModGlossaryProvider { /** * Get the entries by letter cache key. * - * @param {number} glossaryId Glossary Id. - * @param {string} letter A letter, or a special keyword. - * @return {string} Cache key. + * @param glossaryId Glossary Id. + * @param letter A letter, or a special keyword. + * @return Cache key. */ protected getEntriesByLetterCacheKey(glossaryId: number, letter: string): string { return this.ROOT_CACHE_KEY + 'entriesByLetter:' + glossaryId + ':' + letter; @@ -334,14 +334,14 @@ export class AddonModGlossaryProvider { /** * Get entries by letter. * - * @param {number} glossaryId Glossary Id. - * @param {string} letter A letter, or a special keyword. - * @param {number} from Start returning records from here. - * @param {number} limit Number of records to return. - * @param {boolean} omitExpires True to always get the value from cache. If data isn't cached, it will call the WS. - * @param {boolean} forceOffline True to always get the value from cache. If data isn't cached, it won't call the WS. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Resolved with the entries. + * @param glossaryId Glossary Id. + * @param letter A letter, or a special keyword. + * @param from Start returning records from here. + * @param limit Number of records to return. + * @param omitExpires True to always get the value from cache. If data isn't cached, it will call the WS. + * @param forceOffline True to always get the value from cache. If data isn't cached, it won't call the WS. + * @param siteId Site ID. If not defined, current site. + * @return Resolved with the entries. */ getEntriesByLetter(glossaryId: number, letter: string, from: number, limit: number, omitExpires: boolean, forceOffline: boolean, siteId?: string): Promise { @@ -377,10 +377,10 @@ export class AddonModGlossaryProvider { /** * Invalidate cache of entries by letter. * - * @param {number} glossaryId Glossary Id. - * @param {string} letter A letter, or a special keyword. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Resolved when data is invalidated. + * @param glossaryId Glossary Id. + * @param letter A letter, or a special keyword. + * @param siteId Site ID. If not defined, current site. + * @return Resolved when data is invalidated. */ invalidateEntriesByLetter(glossaryId: number, letter: string, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -393,12 +393,12 @@ export class AddonModGlossaryProvider { /** * Get the entries by search cache key. * - * @param {number} glossaryId Glossary Id. - * @param {string} query The search query. - * @param {boolean} fullSearch Whether or not full search is required. - * @param {string} order The way to order the results. - * @param {string} sort The direction of the order. - * @return {string} Cache key. + * @param glossaryId Glossary Id. + * @param query The search query. + * @param fullSearch Whether or not full search is required. + * @param order The way to order the results. + * @param sort The direction of the order. + * @return Cache key. */ protected getEntriesBySearchCacheKey(glossaryId: number, query: string, fullSearch: boolean, order: string, sort: string): string { @@ -408,17 +408,17 @@ export class AddonModGlossaryProvider { /** * Get entries by search. * - * @param {number} glossaryId Glossary Id. - * @param {string} query The search query. - * @param {boolean} fullSearch Whether or not full search is required. - * @param {string} order The way to order the results. - * @param {string} sort The direction of the order. - * @param {number} from Start returning records from here. - * @param {number} limit Number of records to return. - * @param {boolean} omitExpires True to always get the value from cache. If data isn't cached, it will call the WS. - * @param {boolean} forceOffline True to always get the value from cache. If data isn't cached, it won't call the WS. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Resolved with the entries. + * @param glossaryId Glossary Id. + * @param query The search query. + * @param fullSearch Whether or not full search is required. + * @param order The way to order the results. + * @param sort The direction of the order. + * @param from Start returning records from here. + * @param limit Number of records to return. + * @param omitExpires True to always get the value from cache. If data isn't cached, it will call the WS. + * @param forceOffline True to always get the value from cache. If data isn't cached, it won't call the WS. + * @param siteId Site ID. If not defined, current site. + * @return Resolved with the entries. */ getEntriesBySearch(glossaryId: number, query: string, fullSearch: boolean, order: string, sort: string, from: number, limit: number, omitExpires: boolean, forceOffline: boolean, siteId?: string): Promise { @@ -446,13 +446,13 @@ export class AddonModGlossaryProvider { /** * Invalidate cache of entries by search. * - * @param {number} glossaryId Glossary Id. - * @param {string} query The search query. - * @param {boolean} fullSearch Whether or not full search is required. - * @param {string} order The way to order the results. - * @param {string} sort The direction of the order. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Resolved when data is invalidated. + * @param glossaryId Glossary Id. + * @param query The search query. + * @param fullSearch Whether or not full search is required. + * @param order The way to order the results. + * @param sort The direction of the order. + * @param siteId Site ID. If not defined, current site. + * @return Resolved when data is invalidated. */ invalidateEntriesBySearch(glossaryId: number, query: string, fullSearch: boolean, order: string, sort: string, siteId?: string): Promise { @@ -466,8 +466,8 @@ export class AddonModGlossaryProvider { /** * Get the glossary categories cache key. * - * @param {number} glossaryId Glossary Id. - * @return {string} The cache key. + * @param glossaryId Glossary Id. + * @return The cache key. */ protected getCategoriesCacheKey(glossaryId: number): string { return this.ROOT_CACHE_KEY + 'categories:' + glossaryId; @@ -476,9 +476,9 @@ export class AddonModGlossaryProvider { /** * Get all the categories related to the glossary. * - * @param {number} glossaryId Glossary Id. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with the categories if supported or empty array if not. + * @param glossaryId Glossary Id. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the categories if supported or empty array if not. */ getAllCategories(glossaryId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -489,12 +489,12 @@ export class AddonModGlossaryProvider { /** * Get the categories related to the glossary by sections. It's a recursive function see initial call values. * - * @param {number} glossaryId Glossary Id. - * @param {number} from Number of categories already fetched, so fetch will be done from this number. Initial value 0. - * @param {number} limit Number of categories to fetch. Initial value LIMIT_CATEGORIES. - * @param {any[]} categories Already fetched categories where to append the fetch. Initial value []. - * @param {any} site Site object. - * @return {Promise} Promise resolved with the categories. + * @param glossaryId Glossary Id. + * @param from Number of categories already fetched, so fetch will be done from this number. Initial value 0. + * @param limit Number of categories to fetch. Initial value LIMIT_CATEGORIES. + * @param categories Already fetched categories where to append the fetch. Initial value []. + * @param site Site object. + * @return Promise resolved with the categories. */ protected getCategories(glossaryId: number, from: number, limit: number, categories: any[], site: CoreSite): Promise { const params = { @@ -523,9 +523,9 @@ export class AddonModGlossaryProvider { /** * Invalidate cache of categories by glossary id. * - * @param {number} glossaryId Glossary Id. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when categories data has been invalidated, + * @param glossaryId Glossary Id. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when categories data has been invalidated, */ invalidateCategories(glossaryId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -536,8 +536,8 @@ export class AddonModGlossaryProvider { /** * Get an entry by ID cache key. * - * @param {number} entryId Entry Id. - * @return {string} Cache key. + * @param entryId Entry Id. + * @return Cache key. */ protected getEntryCacheKey(entryId: number): string { return this.ROOT_CACHE_KEY + 'getEntry:' + entryId; @@ -546,9 +546,9 @@ export class AddonModGlossaryProvider { /** * Get one entry by ID. * - * @param {number} entryId Entry ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with the entry. + * @param entryId Entry ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the entry. */ getEntry(entryId: number, siteId?: string): Promise<{entry: any, ratinginfo: CoreRatingInfo, from?: number}> { return this.sitesProvider.getSite(siteId).then((site) => { @@ -619,9 +619,9 @@ export class AddonModGlossaryProvider { /** * Get a glossary ID and the "from" of a given entry. * - * @param {number} entryId Entry ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with the glossary ID and the "from". + * @param entryId Entry ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the glossary ID and the "from". */ getStoredDataForEntry(entryId: number, siteId?: string): Promise<{glossaryId: number, from: number}> { return this.sitesProvider.getSite(siteId).then((site) => { @@ -641,14 +641,14 @@ export class AddonModGlossaryProvider { /** * Performs the fetch of the entries using the propper function and arguments. * - * @param {Function} fetchFunction Function to fetch. - * @param {any[]} fetchArguments Arguments to call the fetching. - * @param {number} [limitFrom=0] Number of entries already fetched, so fetch will be done from this number. - * @param {number} [limitNum] Number of records to return. Defaults to LIMIT_ENTRIES. - * @param {boolean} [omitExpires=false] True to always get the value from cache. If data isn't cached, it will call the WS. - * @param {boolean} [forceOffline=false] True to always get the value from cache. If data isn't cached, it won't call the WS. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with the response. + * @param fetchFunction Function to fetch. + * @param fetchArguments Arguments to call the fetching. + * @param limitFrom Number of entries already fetched, so fetch will be done from this number. + * @param limitNum Number of records to return. Defaults to LIMIT_ENTRIES. + * @param omitExpires True to always get the value from cache. If data isn't cached, it will call the WS. + * @param forceOffline True to always get the value from cache. If data isn't cached, it won't call the WS. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the response. */ fetchEntries(fetchFunction: Function, fetchArguments: any[], limitFrom: number = 0, limitNum?: number, omitExpires: boolean = false, forceOffline: boolean = false, siteId?: string): Promise { @@ -668,12 +668,12 @@ export class AddonModGlossaryProvider { /** * Performs the whole fetch of the entries using the propper function and arguments. * - * @param {Function} fetchFunction Function to fetch. - * @param {any[]} fetchArguments Arguments to call the fetching. - * @param {boolean} [omitExpires=false] True to always get the value from cache. If data isn't cached, it will call the WS. - * @param {boolean} [forceOffline=false] True to always get the value from cache. If data isn't cached, it won't call the WS. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with all entrries. + * @param fetchFunction Function to fetch. + * @param fetchArguments Arguments to call the fetching. + * @param omitExpires True to always get the value from cache. If data isn't cached, it will call the WS. + * @param forceOffline True to always get the value from cache. If data isn't cached, it won't call the WS. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with all entrries. */ fetchAllEntries(fetchFunction: Function, fetchArguments: any[], omitExpires: boolean = false, forceOffline: boolean = false, siteId?: string): Promise { @@ -697,9 +697,9 @@ export class AddonModGlossaryProvider { /** * Invalidate cache of entry by ID. * - * @param {number} entryId Entry Id. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Resolved when data is invalidated. + * @param entryId Entry Id. + * @param siteId Site ID. If not defined, current site. + * @return Resolved when data is invalidated. */ invalidateEntry(entryId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -710,9 +710,9 @@ export class AddonModGlossaryProvider { /** * Invalidate cache of all entries in the array. * - * @param {any[]} entries Entry objects to invalidate. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Resolved when data is invalidated. + * @param entries Entry objects to invalidate. + * @param siteId Site ID. If not defined, current site. + * @return Resolved when data is invalidated. */ protected invalidateEntries(entries: any[], siteId?: string): Promise { const keys = []; @@ -729,9 +729,9 @@ export class AddonModGlossaryProvider { * Invalidate the prefetched content except files. * To invalidate files, use AddonModGlossary#invalidateFiles. * - * @param {number} moduleId The module ID. - * @param {number} courseId Course ID. - * @return {Promise} Promise resolved when data is invalidated. + * @param moduleId The module ID. + * @param courseId Course ID. + * @return Promise resolved when data is invalidated. */ invalidateContent(moduleId: number, courseId: number): Promise { return this.getGlossary(courseId, moduleId).then((glossary) => { @@ -748,10 +748,10 @@ export class AddonModGlossaryProvider { * Invalidate the prefetched content for a given glossary, except files. * To invalidate files, use AddonModGlossaryProvider#invalidateFiles. * - * @param {any} glossary The glossary object. - * @param {boolean} [onlyEntriesList] If true, entries won't be invalidated. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when data is invalidated. + * @param glossary The glossary object. + * @param onlyEntriesList If true, entries won't be invalidated. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when data is invalidated. */ invalidateGlossaryEntries(glossary: any, onlyEntriesList?: boolean, siteId?: string): Promise { siteId = siteId || this.sitesProvider.getCurrentSiteId(); @@ -791,9 +791,9 @@ export class AddonModGlossaryProvider { /** * Invalidate the prefetched files. * - * @param {number} moduleId The module ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the files are invalidated. + * @param moduleId The module ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the files are invalidated. */ protected invalidateFiles(moduleId: number, siteId?: string): Promise { return this.filepoolProvider.invalidateFilesByComponent(siteId, AddonModGlossaryProvider.COMPONENT, moduleId); @@ -802,10 +802,10 @@ export class AddonModGlossaryProvider { /** * Get one glossary by cmid. * - * @param {number} courseId Course Id. - * @param {number} cmId Course Module Id. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with the glossary. + * @param courseId Course Id. + * @param cmId Course Module Id. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the glossary. */ getGlossary(courseId: number, cmId: number, siteId?: string): Promise { return this.getCourseGlossaries(courseId, siteId).then((glossaries) => { @@ -822,10 +822,10 @@ export class AddonModGlossaryProvider { /** * Get one glossary by glossary ID. * - * @param {number} courseId Course Id. - * @param {number} glossaryId Glossary Id. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with the glossary. + * @param courseId Course Id. + * @param glossaryId Glossary Id. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the glossary. */ getGlossaryById(courseId: number, glossaryId: number, siteId?: string): Promise { return this.getCourseGlossaries(courseId, siteId).then((glossaries) => { @@ -842,19 +842,19 @@ export class AddonModGlossaryProvider { /** * Create a new entry on a glossary * - * @param {number} glossaryId Glossary ID. - * @param {string} concept Glossary entry concept. - * @param {string} definition Glossary entry concept definition. - * @param {number} courseId Course ID of the glossary. - * @param {any} [options] Array of options for the entry. - * @param {any} [attach] Attachments ID if sending online, result of CoreFileUploaderProvider#storeFilesToUpload - * otherwise. - * @param {number} [timeCreated] The time the entry was created. If not defined, current time. - * @param {string} [siteId] Site ID. If not defined, current site. - * @param {any} [discardEntry] The entry provided will be discarded if found. - * @param {boolean} [allowOffline] True if it can be stored in offline, false otherwise. - * @param {boolean} [checkDuplicates] Check for duplicates before storing offline. Only used if allowOffline is true. - * @return {Promise} Promise resolved with entry ID if entry was created in server, false if stored in device. + * @param glossaryId Glossary ID. + * @param concept Glossary entry concept. + * @param definition Glossary entry concept definition. + * @param courseId Course ID of the glossary. + * @param options Array of options for the entry. + * @param attach Attachments ID if sending online, result of CoreFileUploaderProvider#storeFilesToUpload + * otherwise. + * @param timeCreated The time the entry was created. If not defined, current time. + * @param siteId Site ID. If not defined, current site. + * @param discardEntry The entry provided will be discarded if found. + * @param allowOffline True if it can be stored in offline, false otherwise. + * @param checkDuplicates Check for duplicates before storing offline. Only used if allowOffline is true. + * @return Promise resolved with entry ID if entry was created in server, false if stored in device. */ addEntry(glossaryId: number, concept: string, definition: string, courseId: number, options: any, attach: any, timeCreated: number, siteId?: string, discardEntry?: any, allowOffline?: boolean, checkDuplicates?: boolean): @@ -918,13 +918,13 @@ export class AddonModGlossaryProvider { /** * Create a new entry on a glossary. It does not cache calls. It will fail if offline or cannot connect. * - * @param {number} glossaryId Glossary ID. - * @param {string} concept Glossary entry concept. - * @param {string} definition Glossary entry concept definition. - * @param {any} [options] Array of options for the entry. - * @param {number} [attachId] Attachments ID (if any attachment). - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with the entry ID if created, rejected otherwise. + * @param glossaryId Glossary ID. + * @param concept Glossary entry concept. + * @param definition Glossary entry concept definition. + * @param options Array of options for the entry. + * @param attachId Attachments ID (if any attachment). + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the entry ID if created, rejected otherwise. */ addEntryOnline(glossaryId: number, concept: string, definition: string, options?: any, attachId?: number, siteId?: string): Promise { @@ -962,11 +962,11 @@ export class AddonModGlossaryProvider { /** * Check if a entry concept is already used. * - * @param {number} glossaryId Glossary ID. - * @param {string} concept Concept to check. - * @param {number} [timeCreated] Timecreated to check that is not the timecreated we are editing. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with true if used, resolved with false if not used or error. + * @param glossaryId Glossary ID. + * @param concept Concept to check. + * @param timeCreated Timecreated to check that is not the timecreated we are editing. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with true if used, resolved with false if not used or error. */ isConceptUsed(glossaryId: number, concept: string, timeCreated?: number, siteId?: string): Promise { // Check offline first. @@ -991,7 +991,7 @@ export class AddonModGlossaryProvider { * Return whether or not the plugin is enabled for editing in the current site. Plugin is enabled if the glossary WS are * available. * - * @return {boolean} Whether the glossary editing is available or not. + * @return Whether the glossary editing is available or not. */ isPluginEnabledForEditing(): boolean { return this.sitesProvider.getCurrentSite().wsAvailable('mod_glossary_add_entry'); @@ -1000,11 +1000,11 @@ export class AddonModGlossaryProvider { /** * Report a glossary as being viewed. * - * @param {number} glossaryId Glossary ID. - * @param {string} mode The mode in which the glossary was viewed. - * @param {string} [name] Name of the glossary. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the WS call is successful. + * @param glossaryId Glossary ID. + * @param mode The mode in which the glossary was viewed. + * @param name Name of the glossary. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the WS call is successful. */ logView(glossaryId: number, mode: string, name?: string, siteId?: string): Promise { const params = { @@ -1019,11 +1019,11 @@ export class AddonModGlossaryProvider { /** * Report a glossary entry as being viewed. * - * @param {number} entryId Entry ID. - * @param {number} glossaryId Glossary ID. - * @param {string} [name] Name of the glossary. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the WS call is successful. + * @param entryId Entry ID. + * @param glossaryId Glossary ID. + * @param name Name of the glossary. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the WS call is successful. */ logEntryView(entryId: number, glossaryId: number, name?: string, siteId?: string): Promise { const params = { @@ -1037,11 +1037,11 @@ export class AddonModGlossaryProvider { /** * Store several entries so we can determine their glossaryId in offline. * - * @param {number} glossaryId Glossary ID the entries belongs to. - * @param {any[]} entries Entries. - * @param {number} from The "page" the entries belong to. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when done. + * @param glossaryId Glossary ID the entries belongs to. + * @param entries Entries. + * @param from The "page" the entries belong to. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when done. */ protected storeEntries(glossaryId: number, entries: any[], from: number, siteId?: string): Promise { const promises = []; @@ -1056,11 +1056,11 @@ export class AddonModGlossaryProvider { /** * Store an entry so we can determine its glossaryId in offline. * - * @param {number} glossaryId Glossary ID the entry belongs to. - * @param {number} entryId Entry ID. - * @param {number} from The "page" the entry belongs to. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when done. + * @param glossaryId Glossary ID the entry belongs to. + * @param entryId Entry ID. + * @param from The "page" the entry belongs to. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when done. */ protected storeEntryId(glossaryId: number, entryId: number, from: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { diff --git a/src/addon/mod/glossary/providers/helper.ts b/src/addon/mod/glossary/providers/helper.ts index 4ec569c47..cd5227e50 100644 --- a/src/addon/mod/glossary/providers/helper.ts +++ b/src/addon/mod/glossary/providers/helper.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -31,11 +31,11 @@ export class AddonModGlossaryHelperProvider { /** * Delete stored attachment files for a new entry. * - * @param {number} glossaryId Glossary ID. - * @param {string} entryName The name of the entry. - * @param {number} timeCreated The time the entry was created. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when deleted. + * @param glossaryId Glossary ID. + * @param entryName The name of the entry. + * @param timeCreated The time the entry was created. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when deleted. */ deleteStoredFiles(glossaryId: number, entryName: string, timeCreated: number, siteId?: string): Promise { return this.glossaryOffline.getEntryFolder(glossaryId, entryName, timeCreated, siteId).then((folderPath) => { @@ -48,11 +48,11 @@ export class AddonModGlossaryHelperProvider { /** * Get a list of stored attachment files for a new entry. See AddonModGlossaryHelperProvider#storeFiles. * - * @param {number} glossaryId lossary ID. - * @param {string} entryName The name of the entry. - * @param {number} [timeCreated] The time the entry was created. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with the files. + * @param glossaryId lossary ID. + * @param entryName The name of the entry. + * @param timeCreated The time the entry was created. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the files. */ getStoredFiles(glossaryId: number, entryName: string, timeCreated: number, siteId?: string): Promise { return this.glossaryOffline.getEntryFolder(glossaryId, entryName, timeCreated, siteId).then((folderPath) => { @@ -63,10 +63,10 @@ export class AddonModGlossaryHelperProvider { /** * Check if the data of an entry has changed. * - * @param {any} entry Current data. - * @param {any[]} files Files attached. - * @param {any} original Original content. - * @return {boolean} True if data has changed, false otherwise. + * @param entry Current data. + * @param files Files attached. + * @param original Original content. + * @return True if data has changed, false otherwise. */ hasEntryDataChanged(entry: any, files: any[], original: any): boolean { if (!original || typeof original.concept == 'undefined') { @@ -85,12 +85,12 @@ export class AddonModGlossaryHelperProvider { * Given a list of files (either online files or local files), store the local files in a local folder * to be submitted later. * - * @param {number} glossaryId Glossary ID. - * @param {string} entryName The name of the entry. - * @param {number} [timeCreated] The time the entry was created. - * @param {any[]} files List of files. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved if success, rejected otherwise. + * @param glossaryId Glossary ID. + * @param entryName The name of the entry. + * @param timeCreated The time the entry was created. + * @param files List of files. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved if success, rejected otherwise. */ storeFiles(glossaryId: number, entryName: string, timeCreated: number, files: any[], siteId?: string): Promise { // Get the folder where to store the files. @@ -102,13 +102,13 @@ export class AddonModGlossaryHelperProvider { /** * Upload or store some files, depending if the user is offline or not. * - * @param {number} glossaryId Glossary ID. - * @param {string} entryName The name of the entry. - * @param {number} [timeCreated] The time the entry was created. - * @param {any[]} files List of files. - * @param {boolean} offline True if files sould be stored for offline, false to upload them. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved if success. + * @param glossaryId Glossary ID. + * @param entryName The name of the entry. + * @param timeCreated The time the entry was created. + * @param files List of files. + * @param offline True if files sould be stored for offline, false to upload them. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved if success. */ uploadOrStoreFiles(glossaryId: number, entryName: string, timeCreated: number, files: any[], offline: boolean, siteId?: string): Promise { diff --git a/src/addon/mod/glossary/providers/index-link-handler.ts b/src/addon/mod/glossary/providers/index-link-handler.ts index d71be0005..a4e78aa5e 100644 --- a/src/addon/mod/glossary/providers/index-link-handler.ts +++ b/src/addon/mod/glossary/providers/index-link-handler.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -25,6 +25,6 @@ export class AddonModGlossaryIndexLinkHandler extends CoreContentLinksModuleInde name = 'AddonModGlossaryIndexLinkHandler'; constructor(courseHelper: CoreCourseHelperProvider, protected glossaryProvider: AddonModGlossaryProvider) { - super(courseHelper, 'AddonModGlossary', 'glossary'); + super(courseHelper, 'AddonModGlossary', 'glossary', 'g'); } } diff --git a/src/addon/mod/glossary/providers/list-link-handler.ts b/src/addon/mod/glossary/providers/list-link-handler.ts index 51c6057f6..b6a4ead66 100644 --- a/src/addon/mod/glossary/providers/list-link-handler.ts +++ b/src/addon/mod/glossary/providers/list-link-handler.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/addon/mod/glossary/providers/module-handler.ts b/src/addon/mod/glossary/providers/module-handler.ts index 88b952b13..ea9cb06cb 100644 --- a/src/addon/mod/glossary/providers/module-handler.ts +++ b/src/addon/mod/glossary/providers/module-handler.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -46,7 +46,7 @@ export class AddonModGlossaryModuleHandler implements CoreCourseModuleHandler { /** * Check if the handler is enabled on a site level. * - * @return {boolean} Whether or not the handler is enabled on a site level. + * @return Whether or not the handler is enabled on a site level. */ isEnabled(): boolean { return true; @@ -55,10 +55,10 @@ export class AddonModGlossaryModuleHandler implements CoreCourseModuleHandler { /** * Get the data required to display the module in the course contents view. * - * @param {any} module The module object. - * @param {number} courseId The course ID. - * @param {number} sectionId The section ID. - * @return {CoreCourseModuleHandlerData} Data to render the module. + * @param module The module object. + * @param courseId The course ID. + * @param sectionId The section ID. + * @return Data to render the module. */ getData(module: any, courseId: number, sectionId: number): CoreCourseModuleHandlerData { return { @@ -80,9 +80,9 @@ export class AddonModGlossaryModuleHandler implements CoreCourseModuleHandler { * Get the component to render the module. This is needed to support singleactivity course format. * The component returned must implement CoreCourseModuleMainComponent. * - * @param {any} course The course object. - * @param {any} module The module object. - * @return {any} The component to use, undefined if not found. + * @param course The course object. + * @param module The module object. + * @return The component to use, undefined if not found. */ getMainComponent(course: any, module: any): any { return AddonModGlossaryIndexComponent; @@ -92,7 +92,7 @@ export class AddonModGlossaryModuleHandler implements CoreCourseModuleHandler { * Whether to display the course refresher in single activity course format. If it returns false, a refresher must be * included in the template that calls the doRefresh method of the component. Defaults to true. * - * @return {boolean} Whether the refresher should be displayed. + * @return Whether the refresher should be displayed. */ displayRefresherInSingleActivity(): boolean { return false; diff --git a/src/addon/mod/glossary/providers/offline.ts b/src/addon/mod/glossary/providers/offline.ts index ffe5aef84..181e239d8 100644 --- a/src/addon/mod/glossary/providers/offline.ts +++ b/src/addon/mod/glossary/providers/offline.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -86,11 +86,11 @@ export class AddonModGlossaryOfflineProvider { /** * Delete a new entry. * - * @param {number} glossaryId Glossary ID. - * @param {string} concept Glossary entry concept. - * @param {number} timeCreated The time the entry was created. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved if deleted, rejected if failure. + * @param glossaryId Glossary ID. + * @param concept Glossary entry concept. + * @param timeCreated The time the entry was created. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved if deleted, rejected if failure. */ deleteNewEntry(glossaryId: number, concept: string, timeCreated: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -107,8 +107,8 @@ export class AddonModGlossaryOfflineProvider { /** * Get all the stored new entries from all the glossaries. * - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with entries. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with entries. */ getAllNewEntries(siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -121,11 +121,11 @@ export class AddonModGlossaryOfflineProvider { /** * Get a stored new entry. * - * @param {number} glossaryId Glossary ID. - * @param {string} concept Glossary entry concept. - * @param {number} timeCreated The time the entry was created. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with entry. + * @param glossaryId Glossary ID. + * @param concept Glossary entry concept. + * @param timeCreated The time the entry was created. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with entry. */ getNewEntry(glossaryId: number, concept: string, timeCreated: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -143,10 +143,10 @@ export class AddonModGlossaryOfflineProvider { /** * Get all the stored add entry data from a certain glossary. * - * @param {number} glossaryId Glossary ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @param {number} [userId] User the entries belong to. If not defined, current user in site. - * @return {Promise} Promise resolved with entries. + * @param glossaryId Glossary ID. + * @param siteId Site ID. If not defined, current site. + * @param userId User the entries belong to. If not defined, current user in site. + * @return Promise resolved with entries. */ getGlossaryNewEntries(glossaryId: number, siteId?: string, userId?: number): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -164,11 +164,11 @@ export class AddonModGlossaryOfflineProvider { /** * Check if a concept is used offline. * - * @param {number} glossaryId Glossary ID. - * @param {string} concept Concept to check. - * @param {number} [timeCreated] Time of the entry we are editing. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with true if concept is found, false otherwise. + * @param glossaryId Glossary ID. + * @param concept Concept to check. + * @param timeCreated Time of the entry we are editing. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with true if concept is found, false otherwise. */ isConceptUsed(glossaryId: number, concept: string, timeCreated?: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -198,17 +198,17 @@ export class AddonModGlossaryOfflineProvider { /** * Save a new entry to be sent later. * - * @param {number} glossaryId Glossary ID. - * @param {string} concept Glossary entry concept. - * @param {string} definition Glossary entry concept definition. - * @param {number} courseId Course ID of the glossary. - * @param {any} [options] Options for the entry. - * @param {any} [attachments] Result of CoreFileUploaderProvider#storeFilesToUpload for attachments. - * @param {number} [timeCreated] The time the entry was created. If not defined, current time. - * @param {string} [siteId] Site ID. If not defined, current site. - * @param {number} [userId] User the entry belong to. If not defined, current user in site. - * @param {any} [discardEntry] The entry provided will be discarded if found. - * @return {Promise} Promise resolved if stored, rejected if failure. + * @param glossaryId Glossary ID. + * @param concept Glossary entry concept. + * @param definition Glossary entry concept definition. + * @param courseId Course ID of the glossary. + * @param options Options for the entry. + * @param attachments Result of CoreFileUploaderProvider#storeFilesToUpload for attachments. + * @param timeCreated The time the entry was created. If not defined, current time. + * @param siteId Site ID. If not defined, current site. + * @param userId User the entry belong to. If not defined, current user in site. + * @param discardEntry The entry provided will be discarded if found. + * @return Promise resolved if stored, rejected if failure. */ addNewEntry(glossaryId: number, concept: string, definition: string, courseId: number, options?: any, attachments?: any, timeCreated?: number, siteId?: string, userId?: number, discardEntry?: any): Promise { @@ -242,9 +242,9 @@ export class AddonModGlossaryOfflineProvider { /** * Get the path to the folder where to store files for offline attachments in a glossary. * - * @param {number} glossaryId Glossary ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with the path. + * @param glossaryId Glossary ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the path. */ getGlossaryFolder(glossaryId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -258,11 +258,11 @@ export class AddonModGlossaryOfflineProvider { /** * Get the path to the folder where to store files for a new offline entry. * - * @param {number} glossaryId Glossary ID. - * @param {string} concept The name of the entry. - * @param {number} timeCreated Time to allow duplicated entries. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with the path. + * @param glossaryId Glossary ID. + * @param concept The name of the entry. + * @param timeCreated Time to allow duplicated entries. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the path. */ getEntryFolder(glossaryId: number, concept: string, timeCreated: number, siteId?: string): Promise { return this.getGlossaryFolder(glossaryId, siteId).then((folderPath) => { @@ -273,8 +273,8 @@ export class AddonModGlossaryOfflineProvider { /** * Parse "options" and "attachments" columns of a fetched record. * - * @param {any} records Record object - * @return {any} Record object with columns parsed. + * @param records Record object + * @return Record object with columns parsed. */ protected parseRecord(record: any): any { record.options = this.textUtils.parseJSON(record.options); diff --git a/src/addon/mod/glossary/providers/prefetch-handler.ts b/src/addon/mod/glossary/providers/prefetch-handler.ts index 9235c4b54..590294ad7 100644 --- a/src/addon/mod/glossary/providers/prefetch-handler.ts +++ b/src/addon/mod/glossary/providers/prefetch-handler.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -20,9 +20,12 @@ import { CoreSitesProvider } from '@providers/sites'; import { CoreDomUtilsProvider } from '@providers/utils/dom'; import { CoreUtilsProvider } from '@providers/utils/utils'; import { CoreCourseProvider } from '@core/course/providers/course'; +import { CoreCommentsProvider } from '@core/comments/providers/comments'; import { CoreCourseActivityPrefetchHandlerBase } from '@core/course/classes/activity-prefetch-handler'; import { AddonModGlossaryProvider } from './glossary'; import { AddonModGlossarySyncProvider } from './sync'; +import { CoreFilterHelperProvider } from '@core/filter/providers/helper'; +import { CorePluginFileDelegate } from '@providers/plugin-file-delegate'; /** * Handler to prefetch forums. @@ -41,19 +44,23 @@ export class AddonModGlossaryPrefetchHandler extends CoreCourseActivityPrefetchH filepoolProvider: CoreFilepoolProvider, sitesProvider: CoreSitesProvider, domUtils: CoreDomUtilsProvider, - private glossaryProvider: AddonModGlossaryProvider, - private syncProvider: AddonModGlossarySyncProvider) { + filterHelper: CoreFilterHelperProvider, + pluginFileDelegate: CorePluginFileDelegate, + protected glossaryProvider: AddonModGlossaryProvider, + protected commentsProvider: CoreCommentsProvider, + protected syncProvider: AddonModGlossarySyncProvider) { - super(translate, appProvider, utils, courseProvider, filepoolProvider, sitesProvider, domUtils); + super(translate, appProvider, utils, courseProvider, filepoolProvider, sitesProvider, domUtils, filterHelper, + pluginFileDelegate); } /** * Get list of files. If not defined, we'll assume they're in module.contents. * - * @param {any} module Module. - * @param {Number} courseId Course ID the module belongs to. - * @param {boolean} [single] True if we're downloading a single module, false if we're downloading a whole section. - * @return {Promise} Promise resolved with the list of files. + * @param module Module. + * @param courseId Course ID the module belongs to. + * @param single True if we're downloading a single module, false if we're downloading a whole section. + * @return Promise resolved with the list of files. */ getFiles(module: any, courseId: number, single?: boolean): Promise { return this.glossaryProvider.getGlossary(courseId, module.id).then((glossary) => { @@ -70,10 +77,10 @@ export class AddonModGlossaryPrefetchHandler extends CoreCourseActivityPrefetchH /** * Get the list of downloadable files. It includes entry embedded files. * - * @param {any} module Module to get the files. - * @param {any} glossary Glossary - * @param {any[]} entries Entries of the Glossary. - * @return {any[]} List of Files. + * @param module Module to get the files. + * @param glossary Glossary + * @param entries Entries of the Glossary. + * @return List of Files. */ protected getFilesFromGlossaryAndEntries(module: any, glossary: any, entries: any[]): any[] { let files = this.getIntroFilesFromInstance(module, glossary); @@ -86,7 +93,7 @@ export class AddonModGlossaryPrefetchHandler extends CoreCourseActivityPrefetchH if (getInlineFiles && entry.definitioninlinefiles && entry.definitioninlinefiles.length) { files = files.concat(entry.definitioninlinefiles); } else if (entry.definition && !getInlineFiles) { - files = files.concat(this.domUtils.extractDownloadableFilesFromHtmlAsFakeFileObjects(entry.definition)); + files = files.concat(this.filepoolProvider.extractDownloadableFilesFromHtmlAsFakeFileObjects(entry.definition)); } }); @@ -96,9 +103,9 @@ export class AddonModGlossaryPrefetchHandler extends CoreCourseActivityPrefetchH /** * Invalidate the prefetched content. * - * @param {number} moduleId The module ID. - * @param {number} courseId The course ID the module belongs to. - * @return {Promise} Promise resolved when the data is invalidated. + * @param moduleId The module ID. + * @param courseId The course ID the module belongs to. + * @return Promise resolved when the data is invalidated. */ invalidateContent(moduleId: number, courseId: number): Promise { return this.glossaryProvider.invalidateContent(moduleId, courseId); @@ -107,11 +114,11 @@ export class AddonModGlossaryPrefetchHandler extends CoreCourseActivityPrefetchH /** * Prefetch a module. * - * @param {any} module Module. - * @param {number} courseId Course ID the module belongs to. - * @param {boolean} [single] True if we're downloading a single module, false if we're downloading a whole section. - * @param {string} [dirPath] Path of the directory where to store all the content files. - * @return {Promise} Promise resolved when done. + * @param module Module. + * @param courseId Course ID the module belongs to. + * @param single True if we're downloading a single module, false if we're downloading a whole section. + * @param dirPath Path of the directory where to store all the content files. + * @return Promise resolved when done. */ prefetch(module: any, courseId?: number, single?: boolean, dirPath?: string): Promise { return this.prefetchPackage(module, courseId, single, this.prefetchGlossary.bind(this)); @@ -120,11 +127,11 @@ export class AddonModGlossaryPrefetchHandler extends CoreCourseActivityPrefetchH /** * Prefetch a glossary. * - * @param {any} module The module object returned by WS. - * @param {number} courseId Course ID the module belongs to. - * @param {boolean} single True if we're downloading a single module, false if we're downloading a whole section. - * @param {string} siteId Site ID. - * @return {Promise} Promise resolved when done. + * @param module The module object returned by WS. + * @param courseId Course ID the module belongs to. + * @param single True if we're downloading a single module, false if we're downloading a whole section. + * @param siteId Site ID. + * @return Promise resolved when done. */ protected prefetchGlossary(module: any, courseId: number, single: boolean, siteId: string): Promise { siteId = siteId || this.sitesProvider.getCurrentSiteId(); @@ -158,8 +165,9 @@ export class AddonModGlossaryPrefetchHandler extends CoreCourseActivityPrefetchH // Fetch all entries to get information from. promises.push(this.glossaryProvider.fetchAllEntries(this.glossaryProvider.getEntriesByLetter, [glossary.id, 'ALL'], false, false, siteId).then((entries) => { - const promises = []; - const avatars = {}; // List of user avatars, preventing duplicates. + const promises = [], + commentsEnabled = !this.commentsProvider.areCommentsDisabledInSite(), + avatars = {}; // List of user avatars, preventing duplicates. entries.forEach((entry) => { // Don't fetch individual entries, it's too many WS calls. @@ -167,6 +175,11 @@ export class AddonModGlossaryPrefetchHandler extends CoreCourseActivityPrefetchH if (entry.userpictureurl) { avatars[entry.userpictureurl] = true; } + + if (glossary.allowcomments && commentsEnabled) { + promises.push(this.commentsProvider.getComments('module', glossary.coursemodule, 'mod_glossary', entry.id, + 'glossary_entry', 0, siteId)); + } }); // Prefetch intro files, entries files and user avatars. @@ -193,10 +206,10 @@ export class AddonModGlossaryPrefetchHandler extends CoreCourseActivityPrefetchH /** * Sync a module. * - * @param {any} module Module. - * @param {number} courseId Course ID the module belongs to - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when done. + * @param module Module. + * @param courseId Course ID the module belongs to + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when done. */ sync(module: any, courseId: number, siteId?: any): Promise { const promises = [ diff --git a/src/addon/mod/glossary/providers/sync-cron-handler.ts b/src/addon/mod/glossary/providers/sync-cron-handler.ts index d952ce356..0eb156fe1 100644 --- a/src/addon/mod/glossary/providers/sync-cron-handler.ts +++ b/src/addon/mod/glossary/providers/sync-cron-handler.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -29,9 +29,9 @@ export class AddonModGlossarySyncCronHandler implements CoreCronHandler { * Execute the process. * Receives the ID of the site affected, undefined for all sites. * - * @param {string} [siteId] ID of the site affected, undefined for all sites. - * @param {boolean} [force] Wether the execution is forced (manual sync). - * @return {Promise} Promise resolved when done, rejected if failure. + * @param siteId ID of the site affected, undefined for all sites. + * @param force Wether the execution is forced (manual sync). + * @return Promise resolved when done, rejected if failure. */ execute(siteId?: string, force?: boolean): Promise { return this.glossarySync.syncAllGlossaries(siteId, force); @@ -40,7 +40,7 @@ export class AddonModGlossarySyncCronHandler implements CoreCronHandler { /** * Get the time between consecutive executions. * - * @return {number} Time between consecutive executions (in ms). + * @return Time between consecutive executions (in ms). */ getInterval(): number { return this.glossarySync.syncInterval; diff --git a/src/addon/mod/glossary/providers/sync.ts b/src/addon/mod/glossary/providers/sync.ts index 746563f67..5ad4034c5 100644 --- a/src/addon/mod/glossary/providers/sync.ts +++ b/src/addon/mod/glossary/providers/sync.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -67,9 +67,9 @@ export class AddonModGlossarySyncProvider extends CoreSyncBaseProvider { /** * Try to synchronize all the glossaries in a certain site or in all sites. * - * @param {string} [siteId] Site ID to sync. If not defined, sync all sites. - * @param {boolean} [force] Wether to force sync not depending on last execution. - * @return {Promise} Promise resolved if sync is successful, rejected if sync fails. + * @param siteId Site ID to sync. If not defined, sync all sites. + * @param force Wether to force sync not depending on last execution. + * @return Promise resolved if sync is successful, rejected if sync fails. */ syncAllGlossaries(siteId?: string, force?: boolean): Promise { return this.syncOnSites('all glossaries', this.syncAllGlossariesFunc.bind(this), [force], siteId); @@ -78,9 +78,9 @@ export class AddonModGlossarySyncProvider extends CoreSyncBaseProvider { /** * Sync all glossaries on a site. * - * @param {string} siteId Site ID to sync. - * @param {boolean} [force] Wether to force sync not depending on last execution. - * @return {Promise} Promise resolved if sync is successful, rejected if sync fails. + * @param siteId Site ID to sync. + * @param force Wether to force sync not depending on last execution. + * @return Promise resolved if sync is successful, rejected if sync fails. */ protected syncAllGlossariesFunc(siteId: string, force?: boolean): Promise { siteId = siteId || this.sitesProvider.getCurrentSiteId(); @@ -126,10 +126,10 @@ export class AddonModGlossarySyncProvider extends CoreSyncBaseProvider { /** * Sync a glossary only if a certain time has passed since the last time. * - * @param {number} glossaryId Glossary ID. - * @param {number} userId User the entry belong to. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the glossary is synced or if it doesn't need to be synced. + * @param glossaryId Glossary ID. + * @param userId User the entry belong to. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the glossary is synced or if it doesn't need to be synced. */ syncGlossaryEntriesIfNeeded(glossaryId: number, userId: number, siteId?: string): Promise { siteId = siteId || this.sitesProvider.getCurrentSiteId(); @@ -146,10 +146,10 @@ export class AddonModGlossarySyncProvider extends CoreSyncBaseProvider { /** * Synchronize all offline entries of a glossary. * - * @param {number} glossaryId Glossary ID to be synced. - * @param {number} [userId] User the entries belong to. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved if sync is successful, rejected otherwise. + * @param glossaryId Glossary ID to be synced. + * @param userId User the entries belong to. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved if sync is successful, rejected otherwise. */ syncGlossaryEntries(glossaryId: number, userId?: number, siteId?: string): Promise { userId = userId || this.sitesProvider.getCurrentSiteUserId(); @@ -258,10 +258,10 @@ export class AddonModGlossarySyncProvider extends CoreSyncBaseProvider { /** * Synchronize offline ratings. * - * @param {number} [cmId] Course module to be synced. If not defined, sync all glossaries. - * @param {boolean} [force] Wether to force sync not depending on last execution. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved if sync is successful, rejected otherwise. + * @param cmId Course module to be synced. If not defined, sync all glossaries. + * @param force Wether to force sync not depending on last execution. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved if sync is successful, rejected otherwise. */ syncRatings(cmId?: number, force?: boolean, siteId?: string): Promise { siteId = siteId || this.sitesProvider.getCurrentSiteId(); @@ -303,11 +303,11 @@ export class AddonModGlossarySyncProvider extends CoreSyncBaseProvider { /** * Delete a new entry. * - * @param {number} glossaryId Glossary ID. - * @param {string} concept Glossary entry concept. - * @param {number} timeCreated Time to allow duplicated entries. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when deleted. + * @param glossaryId Glossary ID. + * @param concept Glossary entry concept. + * @param timeCreated Time to allow duplicated entries. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when deleted. */ protected deleteAddEntry(glossaryId: number, concept: string, timeCreated: number, siteId?: string): Promise { const promises = []; @@ -323,10 +323,10 @@ export class AddonModGlossarySyncProvider extends CoreSyncBaseProvider { /** * Upload attachments of an offline entry. * - * @param {number} glossaryId Glossary ID. - * @param {any} entry Offline entry. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with draftid if uploaded, resolved with 0 if nothing to upload. + * @param glossaryId Glossary ID. + * @param entry Offline entry. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with draftid if uploaded, resolved with 0 if nothing to upload. */ protected uploadAttachments(glossaryId: number, entry: any, siteId?: string): Promise { if (entry.attachments) { @@ -357,9 +357,9 @@ export class AddonModGlossarySyncProvider extends CoreSyncBaseProvider { /** * Get the ID of a glossary sync. * - * @param {number} glossaryId Glossary ID. - * @param {number} [userId] User the entries belong to.. If not defined, current user. - * @return {string} Sync ID. + * @param glossaryId Glossary ID. + * @param userId User the entries belong to.. If not defined, current user. + * @return Sync ID. */ protected getGlossarySyncId(glossaryId: number, userId?: number): string { userId = userId || this.sitesProvider.getCurrentSiteUserId(); diff --git a/src/addon/mod/glossary/providers/tag-area-handler.ts b/src/addon/mod/glossary/providers/tag-area-handler.ts index 4c62f698d..a7323de00 100644 --- a/src/addon/mod/glossary/providers/tag-area-handler.ts +++ b/src/addon/mod/glossary/providers/tag-area-handler.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -29,7 +29,7 @@ export class AddonModGlossaryTagAreaHandler implements CoreTagAreaHandler { /** * Whether or not the handler is enabled on a site level. - * @return {boolean|Promise} Whether or not the handler is enabled on a site level. + * @return Whether or not the handler is enabled on a site level. */ isEnabled(): boolean | Promise { return true; @@ -38,8 +38,8 @@ export class AddonModGlossaryTagAreaHandler implements CoreTagAreaHandler { /** * Parses the rendered content of a tag index and returns the items. * - * @param {string} content Rendered content. - * @return {any[]|Promise} Area items (or promise resolved with the items). + * @param content Rendered content. + * @return Area items (or promise resolved with the items). */ parseContent(content: string): any[] | Promise { return this.tagHelper.parseFeedContent(content); @@ -48,8 +48,8 @@ export class AddonModGlossaryTagAreaHandler implements CoreTagAreaHandler { /** * Get the component to use to display items. * - * @param {Injector} injector Injector. - * @return {any|Promise} The component (or promise resolved with component) to use, undefined if not found. + * @param injector Injector. + * @return The component (or promise resolved with component) to use, undefined if not found. */ getComponent(injector: Injector): any | Promise { return CoreTagFeedComponent; diff --git a/src/addon/mod/imscp/components/components.module.ts b/src/addon/mod/imscp/components/components.module.ts index 37a1cbeb2..9ff28b872 100644 --- a/src/addon/mod/imscp/components/components.module.ts +++ b/src/addon/mod/imscp/components/components.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/addon/mod/imscp/components/index/addon-mod-imscp-index.html b/src/addon/mod/imscp/components/index/addon-mod-imscp-index.html index a07aa6be4..35cbae509 100644 --- a/src/addon/mod/imscp/components/index/addon-mod-imscp-index.html +++ b/src/addon/mod/imscp/components/index/addon-mod-imscp-index.html @@ -9,14 +9,14 @@ - + - +
- +
diff --git a/src/addon/mod/imscp/components/index/index.ts b/src/addon/mod/imscp/components/index/index.ts index 3b793da19..c6690b324 100644 --- a/src/addon/mod/imscp/components/index/index.ts +++ b/src/addon/mod/imscp/components/index/index.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -62,7 +62,7 @@ export class AddonModImscpIndexComponent extends CoreCourseModuleMainResourceCom /** * Perform the invalidate content function. * - * @return {Promise} Resolved when done. + * @return Resolved when done. */ protected invalidateContent(): Promise { return this.imscpProvider.invalidateContent(this.module.id, this.courseId); @@ -71,15 +71,15 @@ export class AddonModImscpIndexComponent extends CoreCourseModuleMainResourceCom /** * Download imscp contents. * - * @param {boolean} [refresh] Whether we're refreshing data. - * @return {Promise} Promise resolved when done. + * @param refresh Whether we're refreshing data. + * @return Promise resolved when done. */ protected fetchContent(refresh?: boolean): Promise { let downloadFailed = false; const promises = []; promises.push(this.imscpProvider.getImscp(this.courseId, this.module.id).then((imscp) => { - this.description = imscp.intro || imscp.description; + this.description = imscp.intro; this.dataRetrieved.emit(imscp); })); @@ -112,7 +112,7 @@ export class AddonModImscpIndexComponent extends CoreCourseModuleMainResourceCom this.domUtils.showErrorModal('core.errordownloadingsomefiles', true); } - // All data obtained, now fill the context menu. + }).finally(() => { this.fillContextMenu(refresh); }); } @@ -120,8 +120,8 @@ export class AddonModImscpIndexComponent extends CoreCourseModuleMainResourceCom /** * Loads an item. * - * @param {string} itemId Item ID. - * @return {Promise} Promise resolved when done. + * @param itemId Item ID. + * @return Promise resolved when done. */ loadItem(itemId: string): Promise { return this.imscpProvider.getIframeSrc(this.module, itemId).then((src) => { @@ -144,7 +144,7 @@ export class AddonModImscpIndexComponent extends CoreCourseModuleMainResourceCom /** * Show the TOC. * - * @param {MouseEvent} event Event. + * @param event Event. */ showToc(event: MouseEvent): void { // Create the toc modal. diff --git a/src/addon/mod/imscp/imscp.module.ts b/src/addon/mod/imscp/imscp.module.ts index 825af274f..bcafdaae5 100644 --- a/src/addon/mod/imscp/imscp.module.ts +++ b/src/addon/mod/imscp/imscp.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/addon/mod/imscp/pages/index/index.html b/src/addon/mod/imscp/pages/index/index.html index 8d8662c3d..b99777991 100644 --- a/src/addon/mod/imscp/pages/index/index.html +++ b/src/addon/mod/imscp/pages/index/index.html @@ -1,6 +1,6 @@ - + diff --git a/src/addon/mod/imscp/pages/index/index.module.ts b/src/addon/mod/imscp/pages/index/index.module.ts index bc7feec3c..7aad119ef 100644 --- a/src/addon/mod/imscp/pages/index/index.module.ts +++ b/src/addon/mod/imscp/pages/index/index.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/addon/mod/imscp/pages/index/index.ts b/src/addon/mod/imscp/pages/index/index.ts index b94d81b8b..56d80259e 100644 --- a/src/addon/mod/imscp/pages/index/index.ts +++ b/src/addon/mod/imscp/pages/index/index.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -40,7 +40,7 @@ export class AddonModImscpIndexPage { /** * Update some data based on the imscp instance. * - * @param {any} imscp Imscp instance. + * @param imscp Imscp instance. */ updateData(imscp: any): void { this.title = imscp.name || this.title; diff --git a/src/addon/mod/imscp/pages/toc/toc.module.ts b/src/addon/mod/imscp/pages/toc/toc.module.ts index 28f970142..d28469b58 100644 --- a/src/addon/mod/imscp/pages/toc/toc.module.ts +++ b/src/addon/mod/imscp/pages/toc/toc.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/addon/mod/imscp/pages/toc/toc.ts b/src/addon/mod/imscp/pages/toc/toc.ts index 1880e4813..fe27fb416 100644 --- a/src/addon/mod/imscp/pages/toc/toc.ts +++ b/src/addon/mod/imscp/pages/toc/toc.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -35,7 +35,7 @@ export class AddonModImscpTocPage { /** * Function called when an item is clicked. * - * @param {string} id ID of the clicked item. + * @param id ID of the clicked item. */ loadItem(id: string): void { this.viewCtrl.dismiss(id); @@ -44,8 +44,8 @@ export class AddonModImscpTocPage { /** * Get dummy array for padding. * - * @param {number} n Array length. - * @return {number[]} Dummy array with n elements. + * @param n Array length. + * @return Dummy array with n elements. */ getNumberForPadding(n: number): number[] { return new Array(n); diff --git a/src/addon/mod/imscp/providers/imscp.ts b/src/addon/mod/imscp/providers/imscp.ts index 86166f3c2..bd7eb8cf4 100644 --- a/src/addon/mod/imscp/providers/imscp.ts +++ b/src/addon/mod/imscp/providers/imscp.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -21,6 +21,7 @@ import { CoreUtilsProvider } from '@providers/utils/utils'; import { CoreCourseProvider } from '@core/course/providers/course'; import { CoreCourseLogHelperProvider } from '@core/course/providers/log-helper'; import { CoreSite } from '@classes/site'; +import { CoreWSExternalWarning, CoreWSExternalFile } from '@providers/ws'; /** * Service that provides some features for IMSCP. @@ -39,8 +40,8 @@ export class AddonModImscpProvider { /** * Get the IMSCP toc as an array. * - * @param {any[]} contents The module contents. - * @return {any} The toc. + * @param contents The module contents. + * @return The toc. */ protected getToc(contents: any[]): any { if (!contents || !contents.length) { @@ -53,8 +54,8 @@ export class AddonModImscpProvider { /** * Get the imscp toc as an array of items (not nested) to build the navigation tree. * - * @param {any[]} contents The module contents. - * @return {any[]} The toc as a list. + * @param contents The module contents. + * @return The toc as a list. */ createItemList(contents: any[]): any[] { const items = []; @@ -72,9 +73,9 @@ export class AddonModImscpProvider { /** * Get the previous item to the given one. * - * @param {any[]} items The items list. - * @param {string} itemId The current item. - * @return {string} The previous item id. + * @param items The items list. + * @param itemId The current item. + * @return The previous item id. */ getPreviousItem(items: any[], itemId: string): string { const position = this.getItemPosition(items, itemId); @@ -93,9 +94,9 @@ export class AddonModImscpProvider { /** * Get the next item to the given one. * - * @param {any[]} items The items list. - * @param {string} itemId The current item. - * @return {string} The next item id. + * @param items The items list. + * @param itemId The current item. + * @return The next item id. */ getNextItem(items: any[], itemId: string): string { const position = this.getItemPosition(items, itemId); @@ -114,9 +115,9 @@ export class AddonModImscpProvider { /** * Get the position of a item. * - * @param {any[]} items The items list. - * @param {string} itemId The item to search. - * @return {number} The item position. + * @param items The items list. + * @param itemId The item to search. + * @return The item position. */ protected getItemPosition(items: any[], itemId: string): number { for (let i = 0; i < items.length; i++) { @@ -131,8 +132,8 @@ export class AddonModImscpProvider { /** * Check if we should ommit the file download. * - * @param {string} fileName The file name - * @return {boolean} True if we should ommit the file. + * @param fileName The file name + * @return True if we should ommit the file. */ protected checkSpecialFiles(fileName: string): boolean { return fileName == 'imsmanifest.xml'; @@ -141,8 +142,8 @@ export class AddonModImscpProvider { /** * Get cache key for imscp data WS calls. * - * @param {number} courseId Course ID. - * @return {string} Cache key. + * @param courseId Course ID. + * @return Cache key. */ protected getImscpDataCacheKey(courseId: number): string { return this.ROOT_CACHE_KEY + 'imscp:' + courseId; @@ -151,13 +152,13 @@ export class AddonModImscpProvider { /** * Get a imscp with key=value. If more than one is found, only the first will be returned. * - * @param {number} courseId Course ID. - * @param {string} key Name of the property to check. - * @param {any} value Value to search. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the imscp is retrieved. + * @param courseId Course ID. + * @param key Name of the property to check. + * @param value Value to search. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the imscp is retrieved. */ - protected getImscpByKey(courseId: number, key: string, value: any, siteId?: string): Promise { + protected getImscpByKey(courseId: number, key: string, value: any, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { const params = { courseids: [courseId] @@ -167,7 +168,9 @@ export class AddonModImscpProvider { updateFrequency: CoreSite.FREQUENCY_RARELY }; - return site.read('mod_imscp_get_imscps_by_courses', params, preSets).then((response) => { + return site.read('mod_imscp_get_imscps_by_courses', params, preSets) + .then((response: AddonModImscpGetImscpsByCoursesResult): any => { + if (response && response.imscps) { const currentImscp = response.imscps.find((imscp) => imscp[key] == value); if (currentImscp) { @@ -183,21 +186,21 @@ export class AddonModImscpProvider { /** * Get a imscp by course module ID. * - * @param {number} courseId Course ID. - * @param {number} cmId Course module ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the imscp is retrieved. + * @param courseId Course ID. + * @param cmId Course module ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the imscp is retrieved. */ - getImscp(courseId: number, cmId: number, siteId?: string): Promise { + getImscp(courseId: number, cmId: number, siteId?: string): Promise { return this.getImscpByKey(courseId, 'coursemodule', cmId, siteId); } /** * Given a filepath, get a certain fileurl from module contents. * - * @param {any[]} contents Module contents. - * @param {string} targetFilePath Path of the searched file. - * @return {string} File URL. + * @param contents Module contents. + * @param targetFilePath Path of the searched file. + * @return File URL. */ protected getFileUrlFromContents(contents: any[], targetFilePath: string): string { let indexUrl; @@ -218,9 +221,9 @@ export class AddonModImscpProvider { /** * Get src of a imscp item. * - * @param {any} module The module object. - * @param {string} [itemHref] Href of item to get. If not defined, gets src of main item. - * @return {Promise} Promise resolved with the item src. + * @param module The module object. + * @param itemHref Href of item to get. If not defined, gets src of main item. + * @return Promise resolved with the item src. */ getIframeSrc(module: any, itemHref?: string): Promise { if (!itemHref) { @@ -242,7 +245,7 @@ export class AddonModImscpProvider { if (indexUrl) { return this.sitesProvider.getSite(siteId).then((site) => { - return site.fixPluginfileURL(indexUrl); + return site.checkAndFixPluginfileURL(indexUrl); }); } } @@ -254,10 +257,10 @@ export class AddonModImscpProvider { /** * Invalidate the prefetched content. * - * @param {number} moduleId The module ID. - * @param {number} courseId Course ID of the module. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the content is invalidated. + * @param moduleId The module ID. + * @param courseId Course ID of the module. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the content is invalidated. */ invalidateContent(moduleId: number, courseId: number, siteId?: string): Promise { siteId = siteId || this.sitesProvider.getCurrentSiteId(); @@ -274,9 +277,9 @@ export class AddonModImscpProvider { /** * Invalidates imscp data. * - * @param {number} courseId Course ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the data is invalidated. + * @param courseId Course ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the data is invalidated. */ invalidateImscpData(courseId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -288,8 +291,8 @@ export class AddonModImscpProvider { * Check if a file is downloadable. The file param must have 'type' and 'filename' attributes * like in core_course_get_contents response. * - * @param {any} file File to check. - * @return {boolean} True if downloadable, false otherwise. + * @param file File to check. + * @return True if downloadable, false otherwise. */ isFileDownloadable(file: any): boolean { return file.type === 'file' && !this.checkSpecialFiles(file.filename); @@ -298,10 +301,10 @@ export class AddonModImscpProvider { /** * Return whether or not the plugin is enabled in a certain site. * - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with true if plugin is enabled, rejected or resolved with false otherwise. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with true if plugin is enabled, rejected or resolved with false otherwise. */ - isPluginEnabled(siteId?: string): Promise { + isPluginEnabled(siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { return site.canDownloadFiles(); }); @@ -310,10 +313,10 @@ export class AddonModImscpProvider { /** * Report a IMSCP as being viewed. * - * @param {string} id Module ID. - * @param {string} [name] Name of the imscp. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the WS call is successful. + * @param id Module ID. + * @param name Name of the imscp. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the WS call is successful. */ logView(id: number, name?: string, siteId?: string): Promise { const params = { @@ -324,3 +327,32 @@ export class AddonModImscpProvider { siteId); } } + +/** + * IMSCP returned by mod_imscp_get_imscps_by_courses. + */ +export type AddonModImscpImscp = { + id: number; // IMSCP id. + coursemodule: number; // Course module id. + course: number; // Course id. + name: string; // Activity name. + intro?: string; // The IMSCP intro. + introformat?: number; // Intro format (1 = HTML, 0 = MOODLE, 2 = PLAIN or 4 = MARKDOWN). + introfiles?: CoreWSExternalFile[]; // @since 3.2. + revision?: number; // Revision. + keepold?: number; // Number of old IMSCP to keep. + structure?: string; // IMSCP structure. + timemodified?: string; // Time of last modification. + section?: number; // Course section id. + visible?: boolean; // If visible. + groupmode?: number; // Group mode. + groupingid?: number; // Group id. +}; + +/** + * Result of WS mod_imscp_get_imscps_by_courses. + */ +export type AddonModImscpGetImscpsByCoursesResult = { + imscps: AddonModImscpImscp[]; + warnings?: CoreWSExternalWarning[]; +}; diff --git a/src/addon/mod/imscp/providers/link-handler.ts b/src/addon/mod/imscp/providers/link-handler.ts index 7e7be9821..f7e4c14bd 100644 --- a/src/addon/mod/imscp/providers/link-handler.ts +++ b/src/addon/mod/imscp/providers/link-handler.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -15,6 +15,7 @@ import { Injectable } from '@angular/core'; import { CoreContentLinksModuleIndexHandler } from '@core/contentlinks/classes/module-index-handler'; import { CoreCourseHelperProvider } from '@core/course/providers/helper'; +import { AddonModImscpProvider } from './imscp'; /** * Handler to treat links to IMSCP. @@ -23,7 +24,21 @@ import { CoreCourseHelperProvider } from '@core/course/providers/helper'; export class AddonModImscpLinkHandler extends CoreContentLinksModuleIndexHandler { name = 'AddonModImscpLinkHandler'; - constructor(courseHelper: CoreCourseHelperProvider) { - super(courseHelper, 'AddonModImscp', 'imscp'); + constructor(courseHelper: CoreCourseHelperProvider, + protected imscpProvider: AddonModImscpProvider) { + super(courseHelper, 'AddonModImscp', 'imscp', 'i'); + } + + /** + * Check if the handler is enabled for a certain site (site + user) and a URL. + * + * @param siteId The site ID. + * @param url The URL to treat. + * @param params The params of the URL. E.g. 'mysite.com?id=1' -> {id: 1} + * @param courseId Course ID related to the URL. Optional but recommended. + * @return Whether the handler is enabled for the URL and site. + */ + isEnabled(siteId: string, url: string, params: any, courseId?: number): boolean | Promise { + return this.imscpProvider.isPluginEnabled(); } } diff --git a/src/addon/mod/imscp/providers/list-link-handler.ts b/src/addon/mod/imscp/providers/list-link-handler.ts index 21bb3dbc4..e973aadf8 100644 --- a/src/addon/mod/imscp/providers/list-link-handler.ts +++ b/src/addon/mod/imscp/providers/list-link-handler.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -33,7 +33,7 @@ export class AddonModImscpListLinkHandler extends CoreContentLinksModuleListHand /** * Check if the handler is enabled on a site level. * - * @return {boolean|Promise} Whether or not the handler is enabled on a site level. + * @return Whether or not the handler is enabled on a site level. */ isEnabled(): boolean | Promise { return this.imscpProvider.isPluginEnabled(); diff --git a/src/addon/mod/imscp/providers/module-handler.ts b/src/addon/mod/imscp/providers/module-handler.ts index 94f5e48ea..577e7bfbe 100644 --- a/src/addon/mod/imscp/providers/module-handler.ts +++ b/src/addon/mod/imscp/providers/module-handler.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -45,7 +45,7 @@ export class AddonModImscpModuleHandler implements CoreCourseModuleHandler { /** * Check if the handler is enabled on a site level. * - * @return {boolean|Promise} Whether or not the handler is enabled on a site level. + * @return Whether or not the handler is enabled on a site level. */ isEnabled(): boolean | Promise { return this.imscpProvider.isPluginEnabled(); @@ -54,10 +54,10 @@ export class AddonModImscpModuleHandler implements CoreCourseModuleHandler { /** * Get the data required to display the module in the course contents view. * - * @param {any} module The module object. - * @param {number} courseId The course ID. - * @param {number} sectionId The section ID. - * @return {CoreCourseModuleHandlerData} Data to render the module. + * @param module The module object. + * @param courseId The course ID. + * @param sectionId The section ID. + * @return Data to render the module. */ getData(module: any, courseId: number, sectionId: number): CoreCourseModuleHandlerData { return { @@ -79,9 +79,9 @@ export class AddonModImscpModuleHandler implements CoreCourseModuleHandler { * Get the component to render the module. This is needed to support singleactivity course format. * The component returned must implement CoreCourseModuleMainComponent. * - * @param {any} course The course object. - * @param {any} module The module object. - * @return {any} The component to use, undefined if not found. + * @param course The course object. + * @param module The module object. + * @return The component to use, undefined if not found. */ getMainComponent(course: any, module: any): any { return AddonModImscpIndexComponent; diff --git a/src/addon/mod/imscp/providers/pluginfile-handler.ts b/src/addon/mod/imscp/providers/pluginfile-handler.ts index f3cd322d6..4d283a90e 100644 --- a/src/addon/mod/imscp/providers/pluginfile-handler.ts +++ b/src/addon/mod/imscp/providers/pluginfile-handler.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -26,8 +26,8 @@ export class AddonModImscpPluginFileHandler implements CorePluginFileHandler { /** * Return the RegExp to match the revision on pluginfile URLs. * - * @param {string[]} args Arguments of the pluginfile URL defining component and filearea at least. - * @return {RegExp} RegExp to match the revision on pluginfile URLs. + * @param args Arguments of the pluginfile URL defining component and filearea at least. + * @return RegExp to match the revision on pluginfile URLs. */ getComponentRevisionRegExp(args: string[]): RegExp { // Check filearea. @@ -47,11 +47,20 @@ export class AddonModImscpPluginFileHandler implements CorePluginFileHandler { /** * Should return the string to remove the revision on pluginfile url. * - * @param {string[]} args Arguments of the pluginfile URL defining component and filearea at least. - * @return {string} String to remove the revision on pluginfile url. + * @param args Arguments of the pluginfile URL defining component and filearea at least. + * @return String to remove the revision on pluginfile url. */ getComponentRevisionReplace(args: string[]): string { // Component + Filearea + Revision return '/mod_imscp/' + args[2] + '/0/'; } + + /** + * Whether or not the handler is enabled on a site level. + * + * @return Whether or not the handler is enabled on a site level. + */ + isEnabled(): boolean | Promise { + return true; + } } diff --git a/src/addon/mod/imscp/providers/prefetch-handler.ts b/src/addon/mod/imscp/providers/prefetch-handler.ts index 6f8a10c9e..6fe5ab2b9 100644 --- a/src/addon/mod/imscp/providers/prefetch-handler.ts +++ b/src/addon/mod/imscp/providers/prefetch-handler.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -22,6 +22,8 @@ import { CoreUtilsProvider } from '@providers/utils/utils'; import { CoreCourseProvider } from '@core/course/providers/course'; import { CoreCourseResourcePrefetchHandlerBase } from '@core/course/classes/resource-prefetch-handler'; import { AddonModImscpProvider } from './imscp'; +import { CoreFilterHelperProvider } from '@core/filter/providers/helper'; +import { CorePluginFileDelegate } from '@providers/plugin-file-delegate'; /** * Handler to prefetch IMSCPs. @@ -32,23 +34,31 @@ export class AddonModImscpPrefetchHandler extends CoreCourseResourcePrefetchHand modName = 'imscp'; component = AddonModImscpProvider.COMPONENT; - constructor(translate: TranslateService, appProvider: CoreAppProvider, utils: CoreUtilsProvider, - courseProvider: CoreCourseProvider, filepoolProvider: CoreFilepoolProvider, sitesProvider: CoreSitesProvider, - domUtils: CoreDomUtilsProvider, protected imscpProvider: AddonModImscpProvider) { + constructor(translate: TranslateService, + appProvider: CoreAppProvider, + utils: CoreUtilsProvider, + courseProvider: CoreCourseProvider, + filepoolProvider: CoreFilepoolProvider, + sitesProvider: CoreSitesProvider, + domUtils: CoreDomUtilsProvider, + filterHelper: CoreFilterHelperProvider, + pluginFileDelegate: CorePluginFileDelegate, + protected imscpProvider: AddonModImscpProvider) { - super(translate, appProvider, utils, courseProvider, filepoolProvider, sitesProvider, domUtils); + super(translate, appProvider, utils, courseProvider, filepoolProvider, sitesProvider, domUtils, filterHelper, + pluginFileDelegate); } /** * Download or prefetch the content. * - * @param {any} module The module object returned by WS. - * @param {number} courseId Course ID. - * @param {boolean} [prefetch] True to prefetch, false to download right away. - * @param {string} [dirPath] Path of the directory where to store all the content files. This is to keep the files - * relative paths and make the package work in an iframe. Undefined to download the files - * in the filepool root folder. - * @return {Promise} Promise resolved when all content is downloaded. Data returned is not reliable. + * @param module The module object returned by WS. + * @param courseId Course ID. + * @param prefetch True to prefetch, false to download right away. + * @param dirPath Path of the directory where to store all the content files. This is to keep the files + * relative paths and make the package work in an iframe. Undefined to download the files + * in the filepool root folder. + * @return Promise resolved when all content is downloaded. Data returned is not reliable. */ downloadOrPrefetch(module: any, courseId: number, prefetch?: boolean, dirPath?: string): Promise { const siteId = this.sitesProvider.getCurrentSiteId(); @@ -66,9 +76,9 @@ export class AddonModImscpPrefetchHandler extends CoreCourseResourcePrefetchHand /** * Returns module intro files. * - * @param {any} module The module object returned by WS. - * @param {number} courseId Course ID. - * @return {Promise} Promise resolved with list of intro files. + * @param module The module object returned by WS. + * @param courseId Course ID. + * @return Promise resolved with list of intro files. */ getIntroFiles(module: any, courseId: number): Promise { return this.imscpProvider.getImscp(courseId, module.id).catch(() => { @@ -81,9 +91,9 @@ export class AddonModImscpPrefetchHandler extends CoreCourseResourcePrefetchHand /** * Invalidate the prefetched content. * - * @param {number} moduleId The module ID. - * @param {number} courseId Course ID the module belongs to. - * @return {Promise} Promise resolved when the data is invalidated. + * @param moduleId The module ID. + * @param courseId Course ID the module belongs to. + * @return Promise resolved when the data is invalidated. */ invalidateContent(moduleId: number, courseId: number): Promise { return this.imscpProvider.invalidateContent(moduleId, courseId); @@ -92,9 +102,9 @@ export class AddonModImscpPrefetchHandler extends CoreCourseResourcePrefetchHand /** * Invalidate WS calls needed to determine module status. * - * @param {any} module Module. - * @param {number} courseId Course ID the module belongs to. - * @return {Promise} Promise resolved when invalidated. + * @param module Module. + * @param courseId Course ID the module belongs to. + * @return Promise resolved when invalidated. */ invalidateModule(module: any, courseId: number): Promise { const promises = []; @@ -108,7 +118,7 @@ export class AddonModImscpPrefetchHandler extends CoreCourseResourcePrefetchHand /** * Whether or not the handler is enabled on a site level. * - * @return {boolean|Promise} A boolean, or a promise resolved with a boolean, indicating if the handler is enabled. + * @return A boolean, or a promise resolved with a boolean, indicating if the handler is enabled. */ isEnabled(): boolean | Promise { return this.imscpProvider.isPluginEnabled(); @@ -117,8 +127,8 @@ export class AddonModImscpPrefetchHandler extends CoreCourseResourcePrefetchHand /** * Check if a file is downloadable. * - * @param {any} file File to check. - * @return {boolean} Whether the file is downloadable. + * @param file File to check. + * @return Whether the file is downloadable. */ isFileDownloadable(file: any): boolean { return this.imscpProvider.isFileDownloadable(file); diff --git a/src/addon/mod/label/label.module.ts b/src/addon/mod/label/label.module.ts index 951242b36..696652a49 100644 --- a/src/addon/mod/label/label.module.ts +++ b/src/addon/mod/label/label.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/addon/mod/label/providers/label.ts b/src/addon/mod/label/providers/label.ts index 2b4e79534..ec924ebd0 100644 --- a/src/addon/mod/label/providers/label.ts +++ b/src/addon/mod/label/providers/label.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -17,6 +17,7 @@ import { CoreSitesProvider } from '@providers/sites'; import { CoreUtilsProvider } from '@providers/utils/utils'; import { CoreFilepoolProvider } from '@providers/filepool'; import { CoreSite, CoreSiteWSPreSets } from '@classes/site'; +import { CoreWSExternalWarning, CoreWSExternalFile } from '@providers/ws'; /** * Service that provides some features for labels. @@ -33,8 +34,8 @@ export class AddonModLabelProvider { /** * Get cache key for label data WS calls. * - * @param {number} courseId Course ID. - * @return {string} Cache key. + * @param courseId Course ID. + * @return Cache key. */ protected getLabelDataCacheKey(courseId: number): string { return this.ROOT_CACHE_KEY + 'label:' + courseId; @@ -43,16 +44,16 @@ export class AddonModLabelProvider { /** * Get a label with key=value. If more than one is found, only the first will be returned. * - * @param {number} courseId Course ID. - * @param {string} key Name of the property to check. - * @param {any} value Value to search. - * @param {boolean} [forceCache] True to always get the value from cache, false otherwise. - * @param {boolean} [ignoreCache] True if it should ignore cached data (it will always fail in offline or server down). - * @param {string} [siteId] Site ID. If not provided, current site. - * @return {Promise} Promise resolved when the label is retrieved. + * @param courseId Course ID. + * @param key Name of the property to check. + * @param value Value to search. + * @param forceCache True to always get the value from cache, false otherwise. + * @param ignoreCache True if it should ignore cached data (it will always fail in offline or server down). + * @param siteId Site ID. If not provided, current site. + * @return Promise resolved when the label is retrieved. */ protected getLabelByField(courseId: number, key: string, value: any, forceCache?: boolean, ignoreCache?: boolean, - siteId?: string): Promise { + siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { const params = { @@ -70,7 +71,9 @@ export class AddonModLabelProvider { preSets.emergencyCache = false; } - return site.read('mod_label_get_labels_by_courses', params, preSets).then((response) => { + return site.read('mod_label_get_labels_by_courses', params, preSets) + .then((response: AddonModLabelGetLabelsByCoursesResult): any => { + if (response && response.labels) { const currentLabel = response.labels.find((label) => label[key] == value); if (currentLabel) { @@ -86,37 +89,39 @@ export class AddonModLabelProvider { /** * Get a label by course module ID. * - * @param {number} courseId Course ID. - * @param {number} cmId Course module ID. - * @param {boolean} [forceCache] True to always get the value from cache, false otherwise. - * @param {boolean} [ignoreCache] True if it should ignore cached data (it will always fail in offline or server down). - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the label is retrieved. + * @param courseId Course ID. + * @param cmId Course module ID. + * @param forceCache True to always get the value from cache, false otherwise. + * @param ignoreCache True if it should ignore cached data (it will always fail in offline or server down). + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the label is retrieved. */ - getLabel(courseId: number, cmId: number, forceCache?: boolean, ignoreCache?: boolean, siteId?: string): Promise { + getLabel(courseId: number, cmId: number, forceCache?: boolean, ignoreCache?: boolean, siteId?: string) + : Promise { return this.getLabelByField(courseId, 'coursemodule', cmId, forceCache, ignoreCache, siteId); } /** * Get a label by ID. * - * @param {number} courseId Course ID. - * @param {number} labelId Label ID. - * @param {boolean} [forceCache] True to always get the value from cache, false otherwise. - * @param {boolean} [ignoreCache] True if it should ignore cached data (it will always fail in offline or server down). - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the label is retrieved. + * @param courseId Course ID. + * @param labelId Label ID. + * @param forceCache True to always get the value from cache, false otherwise. + * @param ignoreCache True if it should ignore cached data (it will always fail in offline or server down). + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the label is retrieved. */ - getLabelById(courseId: number, labelId: number, forceCache?: boolean, ignoreCache?: boolean, siteId?: string): Promise { + getLabelById(courseId: number, labelId: number, forceCache?: boolean, ignoreCache?: boolean, siteId?: string) + : Promise { return this.getLabelByField(courseId, 'id', labelId, forceCache, ignoreCache, siteId); } /** * Invalidate label data. * - * @param {number} courseId Course ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the data is invalidated. + * @param courseId Course ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the data is invalidated. */ invalidateLabelData(courseId: number, siteId?: string): Promise { return this.sitesProvider.getSite(null).then((site) => { @@ -127,10 +132,10 @@ export class AddonModLabelProvider { /** * Invalidate the prefetched content. * - * @param {number} moduleId The module ID. - * @param {number} courseId Course ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when data is invalidated. + * @param moduleId The module ID. + * @param courseId Course ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when data is invalidated. */ invalidateContent(moduleId: number, courseId: number, siteId?: string): Promise { siteId = siteId || this.sitesProvider.getCurrentSiteId(); @@ -147,8 +152,8 @@ export class AddonModLabelProvider { /** * Check if the site has the WS to get label data. * - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with boolean: whether it's available. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with boolean: whether it's available. * @since 3.3 */ isGetLabelAvailable(siteId?: string): Promise { @@ -160,8 +165,8 @@ export class AddonModLabelProvider { /** * Check if the site has the WS to get label data. * - * @param {CoreSite} [site] Site. If not defined, current site. - * @return {boolean} Whether it's available. + * @param site Site. If not defined, current site. + * @return Whether it's available. * @since 3.3 */ isGetLabelAvailableForSite(site?: CoreSite): boolean { @@ -170,3 +175,29 @@ export class AddonModLabelProvider { return site.wsAvailable('mod_label_get_labels_by_courses'); } } + +/** + * Label returned by mod_label_get_labels_by_courses. + */ +export type AddonModLabelLabel = { + id: number; // Module id. + coursemodule: number; // Course module id. + course: number; // Course id. + name: string; // Label name. + intro: string; // Label contents. + introformat: number; // Intro format (1 = HTML, 0 = MOODLE, 2 = PLAIN or 4 = MARKDOWN). + introfiles: CoreWSExternalFile[]; + timemodified: number; // Last time the label was modified. + section: number; // Course section id. + visible: number; // Module visibility. + groupmode: number; // Group mode. + groupingid: number; // Grouping id. +}; + +/** + * Result of WS mod_label_get_labels_by_courses. + */ +export type AddonModLabelGetLabelsByCoursesResult = { + labels: AddonModLabelLabel[]; + warnings?: CoreWSExternalWarning[]; +}; diff --git a/src/addon/mod/label/providers/link-handler.ts b/src/addon/mod/label/providers/link-handler.ts index c1ef6fd67..75bb57f31 100644 --- a/src/addon/mod/label/providers/link-handler.ts +++ b/src/addon/mod/label/providers/link-handler.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -24,6 +24,6 @@ export class AddonModLabelLinkHandler extends CoreContentLinksModuleIndexHandler name = 'AddonModLabelLinkHandler'; constructor(courseHelper: CoreCourseHelperProvider) { - super(courseHelper, 'AddonModLabel', 'label'); + super(courseHelper, 'AddonModLabel', 'label', 'l'); } } diff --git a/src/addon/mod/label/providers/module-handler.ts b/src/addon/mod/label/providers/module-handler.ts index 8c8ecfeb8..06bfed785 100644 --- a/src/addon/mod/label/providers/module-handler.ts +++ b/src/addon/mod/label/providers/module-handler.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -44,7 +44,7 @@ export class AddonModLabelModuleHandler implements CoreCourseModuleHandler { /** * Check if the handler is enabled on a site level. * - * @return {boolean} Whether or not the handler is enabled on a site level. + * @return Whether or not the handler is enabled on a site level. */ isEnabled(): boolean | Promise { return true; @@ -53,10 +53,10 @@ export class AddonModLabelModuleHandler implements CoreCourseModuleHandler { /** * Get the data required to display the module in the course contents view. * - * @param {any} module The module object. - * @param {number} courseId The course ID. - * @param {number} sectionId The section ID. - * @return {CoreCourseModuleHandlerData} Data to render the module. + * @param module The module object. + * @param courseId The course ID. + * @param sectionId The section ID. + * @return Data to render the module. */ getData(module: any, courseId: number, sectionId: number): CoreCourseModuleHandlerData { // Remove the description from the module so it isn't rendered twice. @@ -76,10 +76,10 @@ export class AddonModLabelModuleHandler implements CoreCourseModuleHandler { * The component returned must implement CoreCourseModuleMainComponent. * It's recommended to return the class of the component, but you can also return an instance of the component. * - * @param {Injector} injector Injector. - * @param {any} course The course object. - * @param {any} module The module object. - * @return {any|Promise} The component (or promise resolved with component) to use, undefined if not found. + * @param injector Injector. + * @param course The course object. + * @param module The module object. + * @return The component (or promise resolved with component) to use, undefined if not found. */ getMainComponent(injector: Injector, course: any, module: any): any | Promise { // There's no need to implement this because label cannot be used in singleactivity course format. diff --git a/src/addon/mod/label/providers/prefetch-handler.ts b/src/addon/mod/label/providers/prefetch-handler.ts index 3f86dec31..e05f9ac0c 100644 --- a/src/addon/mod/label/providers/prefetch-handler.ts +++ b/src/addon/mod/label/providers/prefetch-handler.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -22,6 +22,8 @@ import { CoreUtilsProvider } from '@providers/utils/utils'; import { CoreCourseProvider } from '@core/course/providers/course'; import { CoreCourseResourcePrefetchHandlerBase } from '@core/course/classes/resource-prefetch-handler'; import { AddonModLabelProvider } from './label'; +import { CoreFilterHelperProvider } from '@core/filter/providers/helper'; +import { CorePluginFileDelegate } from '@providers/plugin-file-delegate'; /** * Handler to prefetch labels. @@ -34,20 +36,28 @@ export class AddonModLabelPrefetchHandler extends CoreCourseResourcePrefetchHand updatesNames = /^.*files$/; skipListStatus = true; - constructor(translate: TranslateService, appProvider: CoreAppProvider, utils: CoreUtilsProvider, - courseProvider: CoreCourseProvider, filepoolProvider: CoreFilepoolProvider, sitesProvider: CoreSitesProvider, - domUtils: CoreDomUtilsProvider, protected labelProvider: AddonModLabelProvider) { + constructor(translate: TranslateService, + appProvider: CoreAppProvider, + utils: CoreUtilsProvider, + courseProvider: CoreCourseProvider, + filepoolProvider: CoreFilepoolProvider, + sitesProvider: CoreSitesProvider, + domUtils: CoreDomUtilsProvider, + filterHelper: CoreFilterHelperProvider, + pluginFileDelegate: CorePluginFileDelegate, + protected labelProvider: AddonModLabelProvider) { - super(translate, appProvider, utils, courseProvider, filepoolProvider, sitesProvider, domUtils); + super(translate, appProvider, utils, courseProvider, filepoolProvider, sitesProvider, domUtils, filterHelper, + pluginFileDelegate); } /** * Returns module intro files. * - * @param {any} module The module object returned by WS. - * @param {number} courseId Course ID. - * @param {boolean} [ignoreCache] True if it should ignore cached data (it will always fail in offline or server down). - * @return {Promise} Promise resolved with list of intro files. + * @param module The module object returned by WS. + * @param courseId Course ID. + * @param ignoreCache True if it should ignore cached data (it will always fail in offline or server down). + * @return Promise resolved with list of intro files. */ getIntroFiles(module: any, courseId: number, ignoreCache?: boolean): Promise { let promise; @@ -66,9 +76,9 @@ export class AddonModLabelPrefetchHandler extends CoreCourseResourcePrefetchHand /** * Invalidate the prefetched content. * - * @param {number} moduleId The module ID. - * @param {number} courseId Course ID the module belongs to. - * @return {Promise} Promise resolved when the data is invalidated. + * @param moduleId The module ID. + * @param courseId Course ID the module belongs to. + * @return Promise resolved when the data is invalidated. */ invalidateContent(moduleId: number, courseId: number): Promise { return this.labelProvider.invalidateContent(moduleId, courseId); @@ -77,9 +87,9 @@ export class AddonModLabelPrefetchHandler extends CoreCourseResourcePrefetchHand /** * Invalidate WS calls needed to determine module status. * - * @param {any} module Module. - * @param {number} courseId Course ID the module belongs to. - * @return {Promise} Promise resolved when invalidated. + * @param module Module. + * @param courseId Course ID the module belongs to. + * @return Promise resolved when invalidated. */ invalidateModule(module: any, courseId: number): Promise { const promises = []; diff --git a/src/addon/mod/lesson/components/components.module.ts b/src/addon/mod/lesson/components/components.module.ts index 0acaaf970..a3ec028aa 100644 --- a/src/addon/mod/lesson/components/components.module.ts +++ b/src/addon/mod/lesson/components/components.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/addon/mod/lesson/components/index/addon-mod-lesson-index.html b/src/addon/mod/lesson/components/index/addon-mod-lesson-index.html index 655de6301..2bf84b83a 100644 --- a/src/addon/mod/lesson/components/index/addon-mod-lesson-index.html +++ b/src/addon/mod/lesson/components/index/addon-mod-lesson-index.html @@ -7,7 +7,7 @@ - + @@ -18,12 +18,12 @@ - +
- +
diff --git a/src/addon/mod/lesson/components/index/index.ts b/src/addon/mod/lesson/components/index/index.ts index b179e7993..17dec12ee 100644 --- a/src/addon/mod/lesson/components/index/index.ts +++ b/src/addon/mod/lesson/components/index/index.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -93,7 +93,7 @@ export class AddonModLessonIndexComponent extends CoreCourseModuleMainActivityCo /** * Change the group displayed. * - * @param {number} groupId Group ID to display. + * @param groupId Group ID to display. */ changeGroup(groupId: number): void { this.reportLoaded = false; @@ -108,10 +108,10 @@ export class AddonModLessonIndexComponent extends CoreCourseModuleMainActivityCo /** * Get the lesson data. * - * @param {boolean} [refresh=false] If it's refreshing content. - * @param {boolean} [sync=false] If it should try to sync. - * @param {boolean} [showErrors=false] If show errors to the user of hide them. - * @return {Promise} Promise resolved when done. + * @param refresh If it's refreshing content. + * @param sync If it should try to sync. + * @param showErrors If show errors to the user of hide them. + * @return Promise resolved when done. */ protected fetchContent(refresh: boolean = false, sync: boolean = false, showErrors: boolean = false): Promise { @@ -211,13 +211,15 @@ export class AddonModLessonIndexComponent extends CoreCourseModuleMainActivityCo this.lessonReady(refresh); } }); + }).finally(() => { + this.fillContextMenu(refresh); }); } /** * Fetch the reports data. * - * @return {Promise} Promise resolved when done. + * @return Promise resolved when done. */ protected fetchReportData(): Promise { return this.groupsProvider.getActivityGroupInfo(this.module.id).then((groupInfo) => { @@ -232,8 +234,8 @@ export class AddonModLessonIndexComponent extends CoreCourseModuleMainActivityCo /** * Checks if sync has succeed from result sync data. * - * @param {any} result Data returned on the sync function. - * @return {boolean} If suceed or not. + * @param result Data returned on the sync function. + * @return If suceed or not. */ protected hasSyncSucceed(result: any): boolean { if (result.updated || this.dataSent) { @@ -292,7 +294,7 @@ export class AddonModLessonIndexComponent extends CoreCourseModuleMainActivityCo /** * Perform the invalidate content function. * - * @return {Promise} Resolved when done. + * @return Resolved when done. */ protected invalidateContent(): Promise { const promises = []; @@ -316,8 +318,8 @@ export class AddonModLessonIndexComponent extends CoreCourseModuleMainActivityCo /** * Compares sync event data with current data to check if refresh content is needed. * - * @param {any} syncEventData Data receiven on sync observer. - * @return {boolean} True if refresh is needed, false otherwise. + * @param syncEventData Data receiven on sync observer. + * @return True if refresh is needed, false otherwise. */ protected isRefreshSyncNeeded(syncEventData: any): boolean { return this.lesson && syncEventData.lessonId == this.lesson.id; @@ -326,7 +328,7 @@ export class AddonModLessonIndexComponent extends CoreCourseModuleMainActivityCo /** * Function called when the lesson is ready to be seen (no pending prevent access reasons). * - * @param {boolean} [refresh=false] If it's refreshing content. + * @param refresh If it's refreshing content. */ protected lessonReady(refresh?: boolean): void { this.askPassword = false; @@ -336,9 +338,6 @@ export class AddonModLessonIndexComponent extends CoreCourseModuleMainActivityCo // Store the password in DB. this.lessonProvider.storePassword(this.lesson.id, this.password); } - - // All data obtained, now fill the context menu. - this.fillContextMenu(refresh); } /** @@ -355,8 +354,8 @@ export class AddonModLessonIndexComponent extends CoreCourseModuleMainActivityCo /** * Open the lesson player. * - * @param {boolean} continueLast Whether to continue the last retake. - * @return {Promise} Promise resolved when done. + * @param continueLast Whether to continue the last retake. + * @return Promise resolved when done. */ protected playLesson(continueLast: boolean): Promise { // Calculate the pageId to load. If there is timelimit, lesson is always restarted from the start. @@ -426,8 +425,8 @@ export class AddonModLessonIndexComponent extends CoreCourseModuleMainActivityCo /** * Set a group to view the reports. * - * @param {number} groupId Group ID. - * @return {Promise} Promise resolved when done. + * @param groupId Group ID. + * @return Promise resolved when done. */ protected setGroup(groupId: number): Promise { this.group = groupId; @@ -498,8 +497,8 @@ export class AddonModLessonIndexComponent extends CoreCourseModuleMainActivityCo /** * Displays some data based on the current status. * - * @param {string} status The current status. - * @param {string} [previousStatus] The previous status. If not defined, there is no previous status. + * @param status The current status. + * @param previousStatus The previous status. If not defined, there is no previous status. */ protected showStatus(status: string, previousStatus?: string): void { this.showSpinner = status == CoreConstants.DOWNLOADING; @@ -508,7 +507,7 @@ export class AddonModLessonIndexComponent extends CoreCourseModuleMainActivityCo /** * Start the lesson. * - * @param {boolean} [continueLast] Whether to continue the last attempt. + * @param continueLast Whether to continue the last attempt. */ start(continueLast?: boolean): void { if (this.showSpinner) { @@ -547,8 +546,8 @@ export class AddonModLessonIndexComponent extends CoreCourseModuleMainActivityCo /** * Submit password for password protected lessons. * - * @param {Event} e Event. - * @param {HTMLInputElement} passwordEl The password input. + * @param e Event. + * @param passwordEl The password input. */ submitPassword(e: Event, passwordEl: HTMLInputElement): void { e.preventDefault(); @@ -591,7 +590,7 @@ export class AddonModLessonIndexComponent extends CoreCourseModuleMainActivityCo /** * Performs the sync of the activity. * - * @return {Promise} Promise resolved when done. + * @return Promise resolved when done. */ protected sync(): Promise { return this.lessonSync.syncLesson(this.lesson.id, true).then((result) => { @@ -611,8 +610,8 @@ export class AddonModLessonIndexComponent extends CoreCourseModuleMainActivityCo /** * Validate a password and retrieve extra data. * - * @param {string} password The password to validate. - * @return {Promise} Promise resolved when done. + * @param password The password to validate. + * @return Promise resolved when done. */ protected validatePassword(password: string): Promise { return this.lessonProvider.getLessonWithPassword(this.lesson.id, password).then((lessonData) => { diff --git a/src/addon/mod/lesson/lesson.module.ts b/src/addon/mod/lesson/lesson.module.ts index 0c1e194ec..8fcce6995 100644 --- a/src/addon/mod/lesson/lesson.module.ts +++ b/src/addon/mod/lesson/lesson.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/addon/mod/lesson/pages/index/index.html b/src/addon/mod/lesson/pages/index/index.html index e09f2c477..691bb7d19 100644 --- a/src/addon/mod/lesson/pages/index/index.html +++ b/src/addon/mod/lesson/pages/index/index.html @@ -1,6 +1,6 @@ - + diff --git a/src/addon/mod/lesson/pages/index/index.module.ts b/src/addon/mod/lesson/pages/index/index.module.ts index 890814b05..609d9a349 100644 --- a/src/addon/mod/lesson/pages/index/index.module.ts +++ b/src/addon/mod/lesson/pages/index/index.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/addon/mod/lesson/pages/index/index.ts b/src/addon/mod/lesson/pages/index/index.ts index 838cbe8a3..a48787281 100644 --- a/src/addon/mod/lesson/pages/index/index.ts +++ b/src/addon/mod/lesson/pages/index/index.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -44,7 +44,7 @@ export class AddonModLessonIndexPage { /** * Update some data based on the lesson instance. * - * @param {any} lesson Lesson instance. + * @param lesson Lesson instance. */ updateData(lesson: any): void { this.title = lesson.name || this.title; diff --git a/src/addon/mod/lesson/pages/menu-modal/menu-modal.html b/src/addon/mod/lesson/pages/menu-modal/menu-modal.html index 5744f07bd..a3fe189bc 100644 --- a/src/addon/mod/lesson/pages/menu-modal/menu-modal.html +++ b/src/addon/mod/lesson/pages/menu-modal/menu-modal.html @@ -26,7 +26,7 @@ diff --git a/src/addon/mod/lesson/pages/menu-modal/menu-modal.module.ts b/src/addon/mod/lesson/pages/menu-modal/menu-modal.module.ts index 3147dc86e..28f4a1f40 100644 --- a/src/addon/mod/lesson/pages/menu-modal/menu-modal.module.ts +++ b/src/addon/mod/lesson/pages/menu-modal/menu-modal.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/addon/mod/lesson/pages/menu-modal/menu-modal.ts b/src/addon/mod/lesson/pages/menu-modal/menu-modal.ts index a9406ceb4..dfcece01b 100644 --- a/src/addon/mod/lesson/pages/menu-modal/menu-modal.ts +++ b/src/addon/mod/lesson/pages/menu-modal/menu-modal.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -31,7 +31,6 @@ export class AddonModLessonMenuModalPage { * the menu dynamically based on the data retrieved by the page that opened the modal. * - The onDidDismiss function takes a while to be called, making the app seem slow. This way we can directly call * the functions we need without having to wait for the modal to be dismissed. - * @type {any} */ pageInstance: any; @@ -49,7 +48,7 @@ export class AddonModLessonMenuModalPage { /** * Load a certain page. * - * @param {number} pageId The page ID to load. + * @param pageId The page ID to load. */ loadPage(pageId: number): void { this.pageInstance.changePage && this.pageInstance.changePage(pageId); diff --git a/src/addon/mod/lesson/pages/password-modal/password-modal.module.ts b/src/addon/mod/lesson/pages/password-modal/password-modal.module.ts index 1df852d5c..ea9b63c5c 100644 --- a/src/addon/mod/lesson/pages/password-modal/password-modal.module.ts +++ b/src/addon/mod/lesson/pages/password-modal/password-modal.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/addon/mod/lesson/pages/password-modal/password-modal.ts b/src/addon/mod/lesson/pages/password-modal/password-modal.ts index d748163eb..13fbfcdb1 100644 --- a/src/addon/mod/lesson/pages/password-modal/password-modal.ts +++ b/src/addon/mod/lesson/pages/password-modal/password-modal.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -30,8 +30,8 @@ export class AddonModLessonPasswordModalPage { /** * Send the password back. * - * @param {Event} e Event. - * @param {HTMLInputElement} password The input element. + * @param e Event. + * @param password The input element. */ submitPassword(e: Event, password: HTMLInputElement): void { e.preventDefault(); diff --git a/src/addon/mod/lesson/pages/player/player.html b/src/addon/mod/lesson/pages/player/player.html index 6c83c1f4b..13c7e7f0b 100644 --- a/src/addon/mod/lesson/pages/player/player.html +++ b/src/addon/mod/lesson/pages/player/player.html @@ -1,6 +1,6 @@ - +
diff --git a/src/addon/mod/quiz/pages/player/player.module.ts b/src/addon/mod/quiz/pages/player/player.module.ts index 7275025c4..1f6113f51 100644 --- a/src/addon/mod/quiz/pages/player/player.module.ts +++ b/src/addon/mod/quiz/pages/player/player.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/addon/mod/quiz/pages/player/player.ts b/src/addon/mod/quiz/pages/player/player.ts index af65f378a..81f7989d5 100644 --- a/src/addon/mod/quiz/pages/player/player.ts +++ b/src/addon/mod/quiz/pages/player/player.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -134,7 +134,7 @@ export class AddonModQuizPlayerPage implements OnInit, OnDestroy { /** * Check if we can leave the page or not. * - * @return {boolean|Promise} Resolved if we can leave it, rejected if not. + * @return Resolved if we can leave it, rejected if not. */ ionViewCanLeave(): boolean | Promise { if (this.forceLeave) { @@ -175,7 +175,7 @@ export class AddonModQuizPlayerPage implements OnInit, OnDestroy { /** * A behaviour button in a question was clicked (Check, Redo, ...). * - * @param {any} button Clicked button. + * @param button Clicked button. */ behaviourButtonClicked(button: any): void { // Confirm that the user really wants to do it. @@ -216,9 +216,9 @@ export class AddonModQuizPlayerPage implements OnInit, OnDestroy { /** * Change the current page. If slot is supplied, try to scroll to that question. * - * @param {number} page Page to load. -1 means summary. - * @param {boolean} [fromModal] Whether the page was selected using the navigation modal. - * @param {number} [slot] Slot of the question to scroll to. + * @param page Page to load. -1 means summary. + * @param fromModal Whether the page was selected using the navigation modal. + * @param slot Slot of the question to scroll to. */ changePage(page: number, fromModal?: boolean, slot?: number): void { if (page != -1 && (this.attempt.state == AddonModQuizProvider.ATTEMPT_OVERDUE || this.attempt.finishedOffline)) { @@ -285,7 +285,7 @@ export class AddonModQuizPlayerPage implements OnInit, OnDestroy { /** * Convenience function to get the quiz data. * - * @return {Promise} Promise resolved when done. + * @return Promise resolved when done. */ protected fetchData(): Promise { // Wait for any ongoing sync to finish. We won't sync a quiz while it's being played. @@ -348,9 +348,9 @@ export class AddonModQuizPlayerPage implements OnInit, OnDestroy { /** * Finish an attempt, either by timeup or because the user clicked to finish it. * - * @param {boolean} [userFinish] Whether the user clicked to finish the attempt. - * @param {boolean} [timeUp] Whether the quiz time is up. - * @return {Promise} Promise resolved when done. + * @param userFinish Whether the user clicked to finish the attempt. + * @param timeUp Whether the quiz time is up. + * @return Promise resolved when done. */ finishAttempt(userFinish?: boolean, timeUp?: boolean): Promise { let promise; @@ -387,7 +387,7 @@ export class AddonModQuizPlayerPage implements OnInit, OnDestroy { /** * Fix sequence checks of current page. * - * @return {Promise} Promise resolved when done. + * @return Promise resolved when done. */ protected fixSequenceChecks(): Promise { // Get current page data again to get the latest sequencechecks. @@ -410,7 +410,7 @@ export class AddonModQuizPlayerPage implements OnInit, OnDestroy { /** * Get the input answers. * - * @return {any} Object with the answers. + * @return Object with the answers. */ protected getAnswers(): any { return this.questionHelper.getAnswersFromForm(document.forms['addon-mod_quiz-player-form']); @@ -434,8 +434,8 @@ export class AddonModQuizPlayerPage implements OnInit, OnDestroy { /** * Load a page questions. * - * @param {number} page The page to load. - * @return {Promise} Promise resolved when done. + * @param page The page to load. + * @return Promise resolved when done. */ protected loadPage(page: number): Promise { return this.quizProvider.getAttemptData(this.attempt.id, page, this.preflightData, this.offline, true).then((data) => { @@ -477,7 +477,7 @@ export class AddonModQuizPlayerPage implements OnInit, OnDestroy { /** * Load attempt summary. * - * @return {Promise} Promise resolved when done. + * @return Promise resolved when done. */ protected loadSummary(): Promise { this.summaryQuestions = []; @@ -502,7 +502,7 @@ export class AddonModQuizPlayerPage implements OnInit, OnDestroy { /** * Load data to navigate the questions using the navigation modal. * - * @return {Promise} Promise resolved when done. + * @return Promise resolved when done. */ protected loadNavigation(): Promise { // We use the attempt summary to build the navigation because it contains all the questions. @@ -520,7 +520,7 @@ export class AddonModQuizPlayerPage implements OnInit, OnDestroy { /** * Open the navigation modal. * - * @return {Promise} Promise resolved when done. + * @return Promise resolved when done. */ openNavigation(): Promise { let promise; @@ -552,10 +552,10 @@ export class AddonModQuizPlayerPage implements OnInit, OnDestroy { /** * Process attempt. * - * @param {boolean} [userFinish] Whether the user clicked to finish the attempt. - * @param {boolean} [timeUp] Whether the quiz time is up. - * @return {Promise} Promise resolved when done. - * @param {boolean} [retrying] Whether we're retrying the change. + * @param userFinish Whether the user clicked to finish the attempt. + * @param timeUp Whether the quiz time is up. + * @return Promise resolved when done. + * @param retrying Whether we're retrying the change. */ protected processAttempt(userFinish?: boolean, timeUp?: boolean, retrying?: boolean): Promise { // Get the answers to send. @@ -591,7 +591,7 @@ export class AddonModQuizPlayerPage implements OnInit, OnDestroy { /** * Scroll to a certain question. * - * @param {number} slot Slot of the question to scroll to. + * @param slot Slot of the question to scroll to. */ protected scrollToQuestion(slot: number): void { this.domUtils.scrollToElementBySelector(this.content, '#addon-mod_quiz-question-' + slot); @@ -600,7 +600,7 @@ export class AddonModQuizPlayerPage implements OnInit, OnDestroy { /** * Show connection error. * - * @param {Event} ev Click event. + * @param ev Click event. */ showConnectionError(ev: Event): void { this.autoSave.showAutoSaveError(ev); @@ -633,7 +633,7 @@ export class AddonModQuizPlayerPage implements OnInit, OnDestroy { /** * Start or continue an attempt. * - * @return {Promise} [description] + * @return [description] */ protected startOrContinueAttempt(): Promise { const attempt = this.newAttempt ? undefined : this.lastAttempt; diff --git a/src/addon/mod/quiz/pages/preflight-modal/preflight-modal.module.ts b/src/addon/mod/quiz/pages/preflight-modal/preflight-modal.module.ts index a5fc75aa1..655074507 100644 --- a/src/addon/mod/quiz/pages/preflight-modal/preflight-modal.module.ts +++ b/src/addon/mod/quiz/pages/preflight-modal/preflight-modal.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/addon/mod/quiz/pages/preflight-modal/preflight-modal.ts b/src/addon/mod/quiz/pages/preflight-modal/preflight-modal.ts index 7317b528d..590d03137 100644 --- a/src/addon/mod/quiz/pages/preflight-modal/preflight-modal.ts +++ b/src/addon/mod/quiz/pages/preflight-modal/preflight-modal.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -99,7 +99,7 @@ export class AddonModQuizPreflightModalPage implements OnInit { /** * Check that the data is valid and send it back. * - * @param {Event} e Event. + * @param e Event. */ sendData(e: Event): void { e.preventDefault(); diff --git a/src/addon/mod/quiz/pages/review/review.html b/src/addon/mod/quiz/pages/review/review.html index 3dba297ac..e61d8388b 100644 --- a/src/addon/mod/quiz/pages/review/review.html +++ b/src/addon/mod/quiz/pages/review/review.html @@ -18,8 +18,10 @@ -

{{ 'addon.mod_quiz.reviewofpreview' | translate }}

-

{{ 'addon.mod_quiz.reviewofattempt' | translate:{$a: attempt.attempt} }}

+
+

{{ 'addon.mod_quiz.reviewofpreview' | translate }}

+

{{ 'addon.mod_quiz.reviewofattempt' | translate:{$a: attempt.attempt} }}

+
@@ -44,7 +46,7 @@

{{ 'addon.mod_quiz.marks' | translate }}

-

+

{{ attempt.readableMark }}

{{ 'addon.mod_quiz.grade' | translate }}

@@ -52,7 +54,7 @@

{{ data.title }}

- +
@@ -71,11 +73,11 @@

{{ 'core.question.information' | translate }}

{{question.status}}

-

+

{{question.readableMark}}

- +
diff --git a/src/addon/mod/quiz/pages/review/review.module.ts b/src/addon/mod/quiz/pages/review/review.module.ts index a0d03f60f..d3b30b8bc 100644 --- a/src/addon/mod/quiz/pages/review/review.module.ts +++ b/src/addon/mod/quiz/pages/review/review.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/addon/mod/quiz/pages/review/review.ts b/src/addon/mod/quiz/pages/review/review.ts index 218d7cd60..5c2ef225d 100644 --- a/src/addon/mod/quiz/pages/review/review.ts +++ b/src/addon/mod/quiz/pages/review/review.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -92,9 +92,9 @@ export class AddonModQuizReviewPage implements OnInit { /** * Change the current page. If slot is supplied, try to scroll to that question. * - * @param {number} page Page to load. -1 means all questions in same page. - * @param {boolean} [fromModal] Whether the page was selected using the navigation modal. - * @param {number} [slot] Slot of the question to scroll to. + * @param page Page to load. -1 means all questions in same page. + * @param fromModal Whether the page was selected using the navigation modal. + * @param slot Slot of the question to scroll to. */ changePage(page: number, fromModal?: boolean, slot?: number): void { if (typeof slot != 'undefined' && (this.attempt.currentpage == -1 || page == this.currentPage)) { @@ -127,7 +127,7 @@ export class AddonModQuizReviewPage implements OnInit { /** * Convenience function to get the quiz data. * - * @return {Promise} Promise resolved when done. + * @return Promise resolved when done. */ protected fetchData(): Promise { return this.quizProvider.getQuizById(this.courseId, this.quizId).then((quizData) => { @@ -151,8 +151,8 @@ export class AddonModQuizReviewPage implements OnInit { /** * Load a page questions. * - * @param {number} page The page to load. - * @return {Promise} Promise resolved when done. + * @param page The page to load. + * @return Promise resolved when done. */ protected loadPage(page: number): Promise { return this.quizProvider.getAttemptReview(this.attemptId, page).then((data) => { @@ -183,7 +183,7 @@ export class AddonModQuizReviewPage implements OnInit { /** * Load data to navigate the questions using the navigation modal. * - * @return {Promise} Promise resolved when done. + * @return Promise resolved when done. */ protected loadNavigation(): Promise { // Get all questions in single page to retrieve all the questions. @@ -202,7 +202,7 @@ export class AddonModQuizReviewPage implements OnInit { /** * Refreshes data. * - * @param {any} refresher Refresher + * @param refresher Refresher */ refreshData(refresher: any): void { const promises = []; @@ -221,7 +221,7 @@ export class AddonModQuizReviewPage implements OnInit { /** * Scroll to a certain question. * - * @param {number} slot Slot of the question to scroll to. + * @param slot Slot of the question to scroll to. */ protected scrollToQuestion(slot: number): void { this.domUtils.scrollToElementBySelector(this.content, '#addon-mod_quiz-question-' + slot); @@ -230,7 +230,7 @@ export class AddonModQuizReviewPage implements OnInit { /** * Calculate review summary data. * - * @param {any} data Result of getAttemptReview. + * @param data Result of getAttemptReview. */ protected setSummaryCalculatedData(data: any): void { diff --git a/src/addon/mod/quiz/providers/access-rules-delegate.ts b/src/addon/mod/quiz/providers/access-rules-delegate.ts index ace7c6c69..919f8f1a2 100644 --- a/src/addon/mod/quiz/providers/access-rules-delegate.ts +++ b/src/addon/mod/quiz/providers/access-rules-delegate.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -26,30 +26,29 @@ export interface AddonModQuizAccessRuleHandler extends CoreDelegateHandler { /** * Name of the rule the handler supports. E.g. 'password'. - * @type {string} */ ruleName: string; /** * Whether the rule requires a preflight check when prefetch/start/continue an attempt. * - * @param {any} quiz The quiz the rule belongs to. - * @param {any} [attempt] The attempt started/continued. If not supplied, user is starting a new attempt. - * @param {boolean} [prefetch] Whether the user is prefetching the quiz. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {boolean|Promise} Whether the rule requires a preflight check. + * @param quiz The quiz the rule belongs to. + * @param attempt The attempt started/continued. If not supplied, user is starting a new attempt. + * @param prefetch Whether the user is prefetching the quiz. + * @param siteId Site ID. If not defined, current site. + * @return Whether the rule requires a preflight check. */ isPreflightCheckRequired(quiz: any, attempt?: any, prefetch?: boolean, siteId?: string): boolean | Promise; /** * Add preflight data that doesn't require user interaction. The data should be added to the preflightData param. * - * @param {any} quiz The quiz the rule belongs to. - * @param {any} preflightData Object where to add the preflight data. - * @param {any} [attempt] The attempt started/continued. If not supplied, user is starting a new attempt. - * @param {boolean} [prefetch] Whether the user is prefetching the quiz. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {void|Promise} Promise resolved when done if async, void if it's synchronous. + * @param quiz The quiz the rule belongs to. + * @param preflightData Object where to add the preflight data. + * @param attempt The attempt started/continued. If not supplied, user is starting a new attempt. + * @param prefetch Whether the user is prefetching the quiz. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when done if async, void if it's synchronous. */ getFixedPreflightData?(quiz: any, preflightData: any, attempt?: any, prefetch?: boolean, siteId?: string): void | Promise; @@ -58,20 +57,20 @@ export interface AddonModQuizAccessRuleHandler extends CoreDelegateHandler { * Implement this if your access rule requires a preflight check with user interaction. * It's recommended to return the class of the component, but you can also return an instance of the component. * - * @param {Injector} injector Injector. - * @return {any|Promise} The component (or promise resolved with component) to use, undefined if not found. + * @param injector Injector. + * @return The component (or promise resolved with component) to use, undefined if not found. */ getPreflightComponent?(injector: Injector): any | Promise; /** * Function called when the preflight check has passed. This is a chance to record that fact in some way. * - * @param {any} quiz The quiz the rule belongs to. - * @param {any} attempt The attempt started/continued. - * @param {any} preflightData Preflight data gathered. - * @param {boolean} [prefetch] Whether the user is prefetching the quiz. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {void|Promise} Promise resolved when done if async, void if it's synchronous. + * @param quiz The quiz the rule belongs to. + * @param attempt The attempt started/continued. + * @param preflightData Preflight data gathered. + * @param prefetch Whether the user is prefetching the quiz. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when done if async, void if it's synchronous. */ notifyPreflightCheckPassed?(quiz: any, attempt: any, preflightData: any, prefetch?: boolean, siteId?: string) : void | Promise; @@ -79,12 +78,12 @@ export interface AddonModQuizAccessRuleHandler extends CoreDelegateHandler { /** * Function called when the preflight check fails. This is a chance to record that fact in some way. * - * @param {any} quiz The quiz the rule belongs to. - * @param {any} attempt The attempt started/continued. - * @param {any} preflightData Preflight data gathered. - * @param {boolean} [prefetch] Whether the user is prefetching the quiz. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {void|Promise} Promise resolved when done if async, void if it's synchronous. + * @param quiz The quiz the rule belongs to. + * @param attempt The attempt started/continued. + * @param preflightData Preflight data gathered. + * @param prefetch Whether the user is prefetching the quiz. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when done if async, void if it's synchronous. */ notifyPreflightCheckFailed?(quiz: any, attempt: any, preflightData: any, prefetch?: boolean, siteId?: string) : void | Promise; @@ -92,10 +91,10 @@ export interface AddonModQuizAccessRuleHandler extends CoreDelegateHandler { /** * Whether or not the time left of an attempt should be displayed. * - * @param {any} attempt The attempt. - * @param {number} endTime The attempt end time (in seconds). - * @param {number} timeNow The current time in seconds. - * @return {boolean} Whether it should be displayed. + * @param attempt The attempt. + * @param endTime The attempt end time (in seconds). + * @param timeNow The current time in seconds. + * @return Whether it should be displayed. */ shouldShowTimeLeft?(attempt: any, endTime: number, timeNow: number): boolean; } @@ -116,8 +115,8 @@ export class AddonModQuizAccessRuleDelegate extends CoreDelegate { /** * Get the handler for a certain rule. * - * @param {string} ruleName Name of the access rule. - * @return {AddonModQuizAccessRuleHandler} Handler. Undefined if no handler found for the rule. + * @param ruleName Name of the access rule. + * @return Handler. Undefined if no handler found for the rule. */ getAccessRuleHandler(ruleName: string): AddonModQuizAccessRuleHandler { return this.getHandler(ruleName, true); @@ -126,13 +125,13 @@ export class AddonModQuizAccessRuleDelegate extends CoreDelegate { /** * Given a list of rules, get some fixed preflight data (data that doesn't require user interaction). * - * @param {string[]} rules List of active rules names. - * @param {any} quiz Quiz. - * @param {any} preflightData Object where to store the preflight data. - * @param {any} [attempt] The attempt started/continued. If not supplied, user is starting a new attempt. - * @param {boolean} [prefetch] Whether the user is prefetching the quiz. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when all the data has been gathered. + * @param rules List of active rules names. + * @param quiz Quiz. + * @param preflightData Object where to store the preflight data. + * @param attempt The attempt started/continued. If not supplied, user is starting a new attempt. + * @param prefetch Whether the user is prefetching the quiz. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when all the data has been gathered. */ getFixedPreflightData(rules: string[], quiz: any, preflightData: any, attempt?: any, prefetch?: boolean, siteId?: string) : Promise { @@ -153,8 +152,8 @@ export class AddonModQuizAccessRuleDelegate extends CoreDelegate { /** * Get the Component to use to display the access rule preflight. * - * @param {Injector} injector Injector. - * @return {Promise} Promise resolved with the component to use, undefined if not found. + * @param injector Injector. + * @return Promise resolved with the component to use, undefined if not found. */ getPreflightComponent(rule: string, injector: Injector): Promise { return Promise.resolve(this.executeFunctionOnEnabled(rule, 'getPreflightComponent', [injector])); @@ -163,8 +162,8 @@ export class AddonModQuizAccessRuleDelegate extends CoreDelegate { /** * Check if an access rule is supported. * - * @param {string} ruleName Name of the rule. - * @return {boolean} Whether it's supported. + * @param ruleName Name of the rule. + * @return Whether it's supported. */ isAccessRuleSupported(ruleName: string): boolean { return this.hasHandler(ruleName, true); @@ -173,12 +172,12 @@ export class AddonModQuizAccessRuleDelegate extends CoreDelegate { /** * Given a list of rules, check if preflight check is required. * - * @param {string[]} rules List of active rules names. - * @param {any} quiz Quiz. - * @param {any} [attempt] The attempt started/continued. If not supplied, user is starting a new attempt. - * @param {boolean} [prefetch] Whether the user is prefetching the quiz. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with boolean: whether it's required. + * @param rules List of active rules names. + * @param quiz Quiz. + * @param attempt The attempt started/continued. If not supplied, user is starting a new attempt. + * @param prefetch Whether the user is prefetching the quiz. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with boolean: whether it's required. */ isPreflightCheckRequired(rules: string[], quiz: any, attempt: any, prefetch?: boolean, siteId?: string): Promise { rules = rules || []; @@ -205,12 +204,12 @@ export class AddonModQuizAccessRuleDelegate extends CoreDelegate { /** * Check if preflight check is required for a certain rule. * - * @param {string} rule Rule name. - * @param {any} quiz Quiz. - * @param {any} [attempt] The attempt started/continued. If not supplied, user is starting a new attempt. - * @param {boolean} [prefetch] Whether the user is prefetching the quiz. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with boolean: whether it's required. + * @param rule Rule name. + * @param quiz Quiz. + * @param attempt The attempt started/continued. If not supplied, user is starting a new attempt. + * @param prefetch Whether the user is prefetching the quiz. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with boolean: whether it's required. */ isPreflightCheckRequiredForRule(rule: string, quiz: any, attempt: any, prefetch?: boolean, siteId?: string): Promise { return Promise.resolve(this.executeFunctionOnEnabled(rule, 'isPreflightCheckRequired', [quiz, attempt, prefetch, siteId])); @@ -219,13 +218,13 @@ export class AddonModQuizAccessRuleDelegate extends CoreDelegate { /** * Notify all rules that the preflight check has passed. * - * @param {string[]} rules List of active rules names. - * @param {any} quiz Quiz. - * @param {any} attempt Attempt. - * @param {any} preflightData Preflight data gathered. - * @param {boolean} [prefetch] Whether the user is prefetching the quiz. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when done. + * @param rules List of active rules names. + * @param quiz Quiz. + * @param attempt Attempt. + * @param preflightData Preflight data gathered. + * @param prefetch Whether the user is prefetching the quiz. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when done. */ notifyPreflightCheckPassed(rules: string[], quiz: any, attempt: any, preflightData: any, prefetch?: boolean, siteId?: string) : Promise { @@ -246,13 +245,13 @@ export class AddonModQuizAccessRuleDelegate extends CoreDelegate { /** * Notify all rules that the preflight check has failed. * - * @param {string[]} rules List of active rules names. - * @param {any} quiz Quiz. - * @param {any} attempt Attempt. - * @param {any} preflightData Preflight data gathered. - * @param {boolean} [prefetch] Whether the user is prefetching the quiz. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when done. + * @param rules List of active rules names. + * @param quiz Quiz. + * @param attempt Attempt. + * @param preflightData Preflight data gathered. + * @param prefetch Whether the user is prefetching the quiz. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when done. */ notifyPreflightCheckFailed(rules: string[], quiz: any, attempt: any, preflightData: any, prefetch?: boolean, siteId?: string) : Promise { @@ -273,11 +272,11 @@ export class AddonModQuizAccessRuleDelegate extends CoreDelegate { /** * Whether or not the time left of an attempt should be displayed. * - * @param {string[]} rules List of active rules names. - * @param {any} attempt The attempt. - * @param {number} endTime The attempt end time (in seconds). - * @param {number} timeNow The current time in seconds. - * @return {boolean} Whether it should be displayed. + * @param rules List of active rules names. + * @param attempt The attempt. + * @param endTime The attempt end time (in seconds). + * @param timeNow The current time in seconds. + * @return Whether it should be displayed. */ shouldShowTimeLeft(rules: string[], attempt: any, endTime: number, timeNow: number): boolean { rules = rules || []; diff --git a/src/addon/mod/quiz/providers/grade-link-handler.ts b/src/addon/mod/quiz/providers/grade-link-handler.ts index 1cb006b8c..87b85fd38 100644 --- a/src/addon/mod/quiz/providers/grade-link-handler.ts +++ b/src/addon/mod/quiz/providers/grade-link-handler.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -36,11 +36,11 @@ export class AddonModQuizGradeLinkHandler extends CoreContentLinksModuleGradeHan * Check if the handler is enabled for a certain site (site + user) and a URL. * If not defined, defaults to true. * - * @param {string} siteId The site ID. - * @param {string} url The URL to treat. - * @param {any} params The params of the URL. E.g. 'mysite.com?id=1' -> {id: 1} - * @param {number} [courseId] Course ID related to the URL. Optional but recommended. - * @return {boolean|Promise} Whether the handler is enabled for the URL and site. + * @param siteId The site ID. + * @param url The URL to treat. + * @param params The params of the URL. E.g. 'mysite.com?id=1' -> {id: 1} + * @param courseId Course ID related to the URL. Optional but recommended. + * @return Whether the handler is enabled for the URL and site. */ isEnabled(siteId: string, url: string, params: any, courseId?: number): boolean | Promise { return this.quizProvider.isPluginEnabled(); diff --git a/src/addon/mod/quiz/providers/helper.ts b/src/addon/mod/quiz/providers/helper.ts index 71897ff50..706c43f8a 100644 --- a/src/addon/mod/quiz/providers/helper.ts +++ b/src/addon/mod/quiz/providers/helper.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -40,16 +40,16 @@ export class AddonModQuizHelperProvider { * Validate a preflight data or show a modal to input the preflight data if required. * It calls AddonModQuizProvider.startAttempt if a new attempt is needed. * - * @param {any} quiz Quiz. - * @param {any} accessInfo Quiz access info returned by AddonModQuizProvider.getQuizAccessInformation. - * @param {any} preflightData Object where to store the preflight data. - * @param {any} [attempt] Attempt to continue. Don't pass any value if the user needs to start a new attempt. - * @param {boolean} [offline] Whether the attempt is offline. - * @param {boolean} [prefetch] Whether user is prefetching. - * @param {string} [title] The title to display in the modal and in the submit button. - * @param {string} [siteId] Site ID. If not defined, current site. - * @param {boolean} [retrying] Whether we're retrying after a failure. - * @return {Promise} Promise resolved when the preflight data is validated. The resolve param is the attempt. + * @param quiz Quiz. + * @param accessInfo Quiz access info returned by AddonModQuizProvider.getQuizAccessInformation. + * @param preflightData Object where to store the preflight data. + * @param attempt Attempt to continue. Don't pass any value if the user needs to start a new attempt. + * @param offline Whether the attempt is offline. + * @param prefetch Whether user is prefetching. + * @param title The title to display in the modal and in the submit button. + * @param siteId Site ID. If not defined, current site. + * @param retrying Whether we're retrying after a failure. + * @return Promise resolved when the preflight data is validated. The resolve param is the attempt. */ getAndCheckPreflightData(quiz: any, accessInfo: any, preflightData: any, attempt: any, offline?: boolean, prefetch?: boolean, title?: string, siteId?: string, retrying?: boolean): Promise { @@ -101,13 +101,13 @@ export class AddonModQuizHelperProvider { /** * Get the preflight data from the user using a modal. * - * @param {any} quiz Quiz. - * @param {any} accessInfo Quiz access info returned by AddonModQuizProvider.getQuizAccessInformation. - * @param {any} [attempt] The attempt started/continued. If not supplied, user is starting a new attempt. - * @param {boolean} [prefetch] Whether the user is prefetching the quiz. - * @param {string} [title] The title to display in the modal and in the submit button. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with the preflight data. Rejected if user cancels. + * @param quiz Quiz. + * @param accessInfo Quiz access info returned by AddonModQuizProvider.getQuizAccessInformation. + * @param attempt The attempt started/continued. If not supplied, user is starting a new attempt. + * @param prefetch Whether the user is prefetching the quiz. + * @param title The title to display in the modal and in the submit button. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the preflight data. Rejected if user cancels. */ getPreflightData(quiz: any, accessInfo: any, attempt: any, prefetch?: boolean, title?: string, siteId?: string): Promise { const notSupported: string[] = []; @@ -152,8 +152,8 @@ export class AddonModQuizHelperProvider { * Gets the mark string from a question HTML. * Example result: "Marked out of 1.00". * - * @param {string} html Question's HTML. - * @return {string} Question's mark. + * @param html Question's HTML. + * @return Question's mark. */ getQuestionMarkFromHtml(html: string): string { const element = this.domUtils.convertToElement(html); @@ -164,9 +164,9 @@ export class AddonModQuizHelperProvider { /** * Get a quiz ID by attempt ID. * - * @param {number} attemptId Attempt ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with the quiz ID. + * @param attemptId Attempt ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the quiz ID. */ getQuizIdByAttemptId(attemptId: number, siteId?: string): Promise { // Use getAttemptReview to retrieve the quiz ID. @@ -182,13 +182,13 @@ export class AddonModQuizHelperProvider { /** * Handle a review link. * - * @param {NavController} navCtrl Nav controller, can be undefined/null. - * @param {number} attemptId Attempt ID. - * @param {number} [page] Page to load, -1 to all questions in same page. - * @param {number} [courseId] Course ID. - * @param {number} [quizId] Quiz ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when done. + * @param navCtrl Nav controller, can be undefined/null. + * @param attemptId Attempt ID. + * @param page Page to load, -1 to all questions in same page. + * @param courseId Course ID. + * @param quizId Quiz ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when done. */ handleReviewLink(navCtrl: NavController, attemptId: number, page?: number, courseId?: number, quizId?: number, siteId?: string): Promise { @@ -234,10 +234,10 @@ export class AddonModQuizHelperProvider { /** * Add some calculated data to the attempt. * - * @param {any} quiz Quiz. - * @param {any} attempt Attempt. - * @param {boolean} highlight Whether we should check if attempt should be highlighted. - * @param {number} [bestGrade] Quiz's best grade (formatted). Required if highlight=true. + * @param quiz Quiz. + * @param attempt Attempt. + * @param highlight Whether we should check if attempt should be highlighted. + * @param bestGrade Quiz's best grade (formatted). Required if highlight=true. */ setAttemptCalculatedData(quiz: any, attempt: any, highlight?: boolean, bestGrade?: string): void { @@ -265,8 +265,8 @@ export class AddonModQuizHelperProvider { /** * Add some calculated data to the quiz. * - * @param {any} quiz Quiz. - * @param {any} options Options returned by AddonModQuizProvider.getCombinedReviewOptions. + * @param quiz Quiz. + * @param options Options returned by AddonModQuizProvider.getCombinedReviewOptions. */ setQuizCalculatedData(quiz: any, options: any): void { quiz.sumGradesFormatted = this.quizProvider.formatGrade(quiz.sumgrades, quiz.decimalpoints); @@ -282,16 +282,16 @@ export class AddonModQuizHelperProvider { /** * Validate the preflight data. It calls AddonModQuizProvider.startAttempt if a new attempt is needed. * - * @param {any} quiz Quiz. - * @param {any} accessInfo Quiz access info returned by AddonModQuizProvider.getQuizAccessInformation. - * @param {any} preflightData Object where to store the preflight data. - * @param {any} [attempt] Attempt to continue. Don't pass any value if the user needs to start a new attempt. - * @param {boolean} [offline] Whether the attempt is offline. - * @param {boolean} [sent] Whether preflight data has been entered by the user. - * @param {boolean} [prefetch] Whether user is prefetching. - * @param {string} [title] The title to display in the modal and in the submit button. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the preflight data is validated. + * @param quiz Quiz. + * @param accessInfo Quiz access info returned by AddonModQuizProvider.getQuizAccessInformation. + * @param preflightData Object where to store the preflight data. + * @param attempt Attempt to continue. Don't pass any value if the user needs to start a new attempt. + * @param offline Whether the attempt is offline. + * @param sent Whether preflight data has been entered by the user. + * @param prefetch Whether user is prefetching. + * @param title The title to display in the modal and in the submit button. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the preflight data is validated. */ validatePreflightData(quiz: any, accessInfo: any, preflightData: any, attempt: any, offline?: boolean, prefetch?: boolean, siteId?: string): Promise { diff --git a/src/addon/mod/quiz/providers/index-link-handler.ts b/src/addon/mod/quiz/providers/index-link-handler.ts index e3181f2b8..dbb36cc8e 100644 --- a/src/addon/mod/quiz/providers/index-link-handler.ts +++ b/src/addon/mod/quiz/providers/index-link-handler.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -25,20 +25,6 @@ export class AddonModQuizIndexLinkHandler extends CoreContentLinksModuleIndexHan name = 'AddonModQuizIndexLinkHandler'; constructor(courseHelper: CoreCourseHelperProvider, protected quizProvider: AddonModQuizProvider) { - super(courseHelper, 'AddonModQuiz', 'quiz'); - } - - /** - * Check if the handler is enabled for a certain site (site + user) and a URL. - * If not defined, defaults to true. - * - * @param {string} siteId The site ID. - * @param {string} url The URL to treat. - * @param {any} params The params of the URL. E.g. 'mysite.com?id=1' -> {id: 1} - * @param {number} [courseId] Course ID related to the URL. Optional but recommended. - * @return {boolean|Promise} Whether the handler is enabled for the URL and site. - */ - isEnabled(siteId: string, url: string, params: any, courseId?: number): boolean | Promise { - return this.quizProvider.isPluginEnabled(); + super(courseHelper, 'AddonModQuiz', 'quiz', 'q'); } } diff --git a/src/addon/mod/quiz/providers/list-link-handler.ts b/src/addon/mod/quiz/providers/list-link-handler.ts index 1a19d89e2..61bfaa8f0 100644 --- a/src/addon/mod/quiz/providers/list-link-handler.ts +++ b/src/addon/mod/quiz/providers/list-link-handler.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/addon/mod/quiz/providers/module-handler.ts b/src/addon/mod/quiz/providers/module-handler.ts index f3b484ce9..822f196d7 100644 --- a/src/addon/mod/quiz/providers/module-handler.ts +++ b/src/addon/mod/quiz/providers/module-handler.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -46,7 +46,7 @@ export class AddonModQuizModuleHandler implements CoreCourseModuleHandler { /** * Check if the handler is enabled on a site level. * - * @return {boolean} Whether or not the handler is enabled on a site level. + * @return Whether or not the handler is enabled on a site level. */ isEnabled(): boolean { return true; @@ -55,10 +55,10 @@ export class AddonModQuizModuleHandler implements CoreCourseModuleHandler { /** * Get the data required to display the module in the course contents view. * - * @param {any} module The module object. - * @param {number} courseId The course ID. - * @param {number} sectionId The section ID. - * @return {CoreCourseModuleHandlerData} Data to render the module. + * @param module The module object. + * @param courseId The course ID. + * @param sectionId The section ID. + * @return Data to render the module. */ getData(module: any, courseId: number, sectionId: number): CoreCourseModuleHandlerData { return { @@ -80,9 +80,9 @@ export class AddonModQuizModuleHandler implements CoreCourseModuleHandler { * Get the component to render the module. This is needed to support singleactivity course format. * The component returned must implement CoreCourseModuleMainComponent. * - * @param {any} course The course object. - * @param {any} module The module object. - * @return {any} The component to use, undefined if not found. + * @param course The course object. + * @param module The module object. + * @return The component to use, undefined if not found. */ getMainComponent(course: any, module: any): any { return AddonModQuizIndexComponent; diff --git a/src/addon/mod/quiz/providers/prefetch-handler.ts b/src/addon/mod/quiz/providers/prefetch-handler.ts index 132dd3f09..0040dd8e8 100644 --- a/src/addon/mod/quiz/providers/prefetch-handler.ts +++ b/src/addon/mod/quiz/providers/prefetch-handler.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -28,6 +28,8 @@ import { AddonModQuizHelperProvider } from './helper'; import { AddonModQuizAccessRuleDelegate } from './access-rules-delegate'; import { AddonModQuizSyncProvider } from './quiz-sync'; import { CoreConstants } from '@core/constants'; +import { CoreFilterHelperProvider } from '@core/filter/providers/helper'; +import { CorePluginFileDelegate } from '@providers/plugin-file-delegate'; /** * Handler to prefetch quizzes. @@ -41,24 +43,35 @@ export class AddonModQuizPrefetchHandler extends CoreCourseActivityPrefetchHandl protected syncProvider: AddonModQuizSyncProvider; // It will be injected later to prevent circular dependencies. - constructor(translate: TranslateService, appProvider: CoreAppProvider, utils: CoreUtilsProvider, - courseProvider: CoreCourseProvider, filepoolProvider: CoreFilepoolProvider, sitesProvider: CoreSitesProvider, - domUtils: CoreDomUtilsProvider, protected injector: Injector, protected quizProvider: AddonModQuizProvider, - protected textUtils: CoreTextUtilsProvider, protected quizHelper: AddonModQuizHelperProvider, - protected accessRuleDelegate: AddonModQuizAccessRuleDelegate, protected questionHelper: CoreQuestionHelperProvider) { + constructor(translate: TranslateService, + appProvider: CoreAppProvider, + utils: CoreUtilsProvider, + courseProvider: CoreCourseProvider, + filepoolProvider: CoreFilepoolProvider, + sitesProvider: CoreSitesProvider, + domUtils: CoreDomUtilsProvider, + filterHelper: CoreFilterHelperProvider, + pluginFileDelegate: CorePluginFileDelegate, + protected injector: Injector, + protected quizProvider: AddonModQuizProvider, + protected textUtils: CoreTextUtilsProvider, + protected quizHelper: AddonModQuizHelperProvider, + protected accessRuleDelegate: AddonModQuizAccessRuleDelegate, + protected questionHelper: CoreQuestionHelperProvider) { - super(translate, appProvider, utils, courseProvider, filepoolProvider, sitesProvider, domUtils); + super(translate, appProvider, utils, courseProvider, filepoolProvider, sitesProvider, domUtils, filterHelper, + pluginFileDelegate); } /** * Download the module. * - * @param {any} module The module object returned by WS. - * @param {number} courseId Course ID. - * @param {string} [dirPath] Path of the directory where to store all the content files. - * @param {boolean} [single] True if we're downloading a single module, false if we're downloading a whole section. - * @param {boolean} [canStart=true] If true, start a new attempt if needed. - * @return {Promise} Promise resolved when all content is downloaded. + * @param module The module object returned by WS. + * @param courseId Course ID. + * @param dirPath Path of the directory where to store all the content files. + * @param single True if we're downloading a single module, false if we're downloading a whole section. + * @param canStart If true, start a new attempt if needed. + * @return Promise resolved when all content is downloaded. */ download(module: any, courseId: number, dirPath?: string, single?: boolean, canStart: boolean = true): Promise { // Same implementation for download and prefetch. @@ -68,10 +81,10 @@ export class AddonModQuizPrefetchHandler extends CoreCourseActivityPrefetchHandl /** * Get list of files. If not defined, we'll assume they're in module.contents. * - * @param {any} module Module. - * @param {Number} courseId Course ID the module belongs to. - * @param {boolean} [single] True if we're downloading a single module, false if we're downloading a whole section. - * @return {Promise} Promise resolved with the list of files. + * @param module Module. + * @param courseId Course ID the module belongs to. + * @param single True if we're downloading a single module, false if we're downloading a whole section. + * @return Promise resolved with the list of files. */ getFiles(module: any, courseId: number, single?: boolean): Promise { return this.quizProvider.getQuiz(courseId, module.id).then((quiz) => { @@ -91,9 +104,9 @@ export class AddonModQuizPrefetchHandler extends CoreCourseActivityPrefetchHandl /** * Get the list of downloadable files on feedback attemptss. * - * @param {any} quiz Quiz. - * @param {any[]} attempts Quiz user attempts. - * @return {Promise} List of Files. + * @param quiz Quiz. + * @param attempts Quiz user attempts. + * @return List of Files. */ protected getAttemptsFeedbackFiles(quiz: any, attempts: any[]): Promise { // We have quiz data, now we'll get specific data for each attempt. @@ -113,7 +126,7 @@ export class AddonModQuizPrefetchHandler extends CoreCourseActivityPrefetchHandl files = files.concat(feedback.feedbackinlinefiles); } else if (feedback.feedbacktext && !getInlineFiles) { files = files.concat( - this.domUtils.extractDownloadableFilesFromHtmlAsFakeFileObjects(feedback.feedbacktext)); + this.filepoolProvider.extractDownloadableFilesFromHtmlAsFakeFileObjects(feedback.feedbacktext)); } })); } @@ -128,13 +141,13 @@ export class AddonModQuizPrefetchHandler extends CoreCourseActivityPrefetchHandl /** * Gather some preflight data for an attempt. This function will start a new attempt if needed. * - * @param {any} quiz Quiz. - * @param {any} accessInfo Quiz access info returned by AddonModQuizProvider.getQuizAccessInformation. - * @param {any} [attempt] Attempt to continue. Don't pass any value if the user needs to start a new attempt. - * @param {boolean} [askPreflight] Whether it should ask for preflight data if needed. - * @param {string} [modalTitle] Lang key of the title to set to preflight modal (e.g. 'addon.mod_quiz.startattempt'). - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with the preflight data. + * @param quiz Quiz. + * @param accessInfo Quiz access info returned by AddonModQuizProvider.getQuizAccessInformation. + * @param attempt Attempt to continue. Don't pass any value if the user needs to start a new attempt. + * @param askPreflight Whether it should ask for preflight data if needed. + * @param modalTitle Lang key of the title to set to preflight modal (e.g. 'addon.mod_quiz.startattempt'). + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the preflight data. */ getPreflightData(quiz: any, accessInfo: any, attempt?: any, askPreflight?: boolean, title?: string, siteId?: string) : Promise { @@ -165,9 +178,9 @@ export class AddonModQuizPrefetchHandler extends CoreCourseActivityPrefetchHandl /** * Invalidate the prefetched content. * - * @param {number} moduleId The module ID. - * @param {number} courseId The course ID the module belongs to. - * @return {Promise} Promise resolved when the data is invalidated. + * @param moduleId The module ID. + * @param courseId The course ID the module belongs to. + * @return Promise resolved when the data is invalidated. */ invalidateContent(moduleId: number, courseId: number): Promise { return this.quizProvider.invalidateContent(moduleId, courseId); @@ -176,9 +189,9 @@ export class AddonModQuizPrefetchHandler extends CoreCourseActivityPrefetchHandl /** * Invalidate WS calls needed to determine module status. * - * @param {any} module Module. - * @param {number} courseId Course ID the module belongs to. - * @return {Promise} Promise resolved when invalidated. + * @param module Module. + * @param courseId Course ID the module belongs to. + * @return Promise resolved when invalidated. */ invalidateModule(module: any, courseId: number): Promise { // Invalidate the calls required to check if a quiz is downloadable. @@ -193,9 +206,9 @@ export class AddonModQuizPrefetchHandler extends CoreCourseActivityPrefetchHandl /** * Check if a module can be downloaded. If the function is not defined, we assume that all modules are downloadable. * - * @param {any} module Module. - * @param {number} courseId Course ID the module belongs to. - * @return {boolean|Promise} Whether the module can be downloaded. The promise should never be rejected. + * @param module Module. + * @param courseId Course ID the module belongs to. + * @return Whether the module can be downloaded. The promise should never be rejected. */ isDownloadable(module: any, courseId: number): boolean | Promise { if (this.sitesProvider.getCurrentSite().isOfflineDisabled()) { @@ -222,7 +235,7 @@ export class AddonModQuizPrefetchHandler extends CoreCourseActivityPrefetchHandl /** * Whether or not the handler is enabled on a site level. * - * @return {boolean|Promise} A boolean, or a promise resolved with a boolean, indicating if the handler is enabled. + * @return A boolean, or a promise resolved with a boolean, indicating if the handler is enabled. */ isEnabled(): boolean | Promise { return this.quizProvider.isPluginEnabled(); @@ -231,12 +244,12 @@ export class AddonModQuizPrefetchHandler extends CoreCourseActivityPrefetchHandl /** * Prefetch a module. * - * @param {any} module Module. - * @param {number} courseId Course ID the module belongs to. - * @param {boolean} [single] True if we're downloading a single module, false if we're downloading a whole section. - * @param {string} [dirPath] Path of the directory where to store all the content files. - * @param {boolean} [canStart=true] If true, start a new attempt if needed. - * @return {Promise} Promise resolved when done. + * @param module Module. + * @param courseId Course ID the module belongs to. + * @param single True if we're downloading a single module, false if we're downloading a whole section. + * @param dirPath Path of the directory where to store all the content files. + * @param canStart If true, start a new attempt if needed. + * @return Promise resolved when done. */ prefetch(module: any, courseId?: number, single?: boolean, dirPath?: string, canStart: boolean = true): Promise { if (module.attemptFinished) { @@ -254,12 +267,12 @@ export class AddonModQuizPrefetchHandler extends CoreCourseActivityPrefetchHandl /** * Prefetch a quiz. * - * @param {any} module Module. - * @param {number} courseId Course ID the module belongs to. - * @param {boolean} single True if we're downloading a single module, false if we're downloading a whole section. - * @param {String} siteId Site ID. - * @param {boolean} canStart If true, start a new attempt if needed. - * @return {Promise} Promise resolved when done. + * @param module Module. + * @param courseId Course ID the module belongs to. + * @param single True if we're downloading a single module, false if we're downloading a whole section. + * @param siteId Site ID. + * @param canStart If true, start a new attempt if needed. + * @return Promise resolved when done. */ protected prefetchQuiz(module: any, courseId: number, single: boolean, siteId: string, canStart: boolean): Promise { let attempts: any[], @@ -385,11 +398,11 @@ export class AddonModQuizPrefetchHandler extends CoreCourseActivityPrefetchHandl /** * Prefetch all WS data for an attempt. * - * @param {any} quiz Quiz. - * @param {any} attempt Attempt. - * @param {any} preflightData Preflight required data (like password). - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the prefetch is finished. Data returned is not reliable. + * @param quiz Quiz. + * @param attempt Attempt. + * @param preflightData Preflight required data (like password). + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the prefetch is finished. Data returned is not reliable. */ prefetchAttempt(quiz: any, attempt: any, preflightData: any, siteId?: string): Promise { const pages = this.quizProvider.getPagesFromLayout(attempt.layout), @@ -462,10 +475,10 @@ export class AddonModQuizPrefetchHandler extends CoreCourseActivityPrefetchHandl * Prefetches some data for a quiz and its last attempt. * This function will NOT start a new attempt, it only reads data for the quiz and the last attempt. * - * @param {any} quiz Quiz. - * @param {boolean} [askPreflight] Whether it should ask for preflight data if needed. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when done. + * @param quiz Quiz. + * @param askPreflight Whether it should ask for preflight data if needed. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when done. */ prefetchQuizAndLastAttempt(quiz: any, askPreflight?: boolean, siteId?: string): Promise { siteId = siteId || this.sitesProvider.getCurrentSiteId(); @@ -523,13 +536,13 @@ export class AddonModQuizPrefetchHandler extends CoreCourseActivityPrefetchHandl * Set the right status to a quiz after prefetching. * If the last attempt is finished or there isn't one, set it as not downloaded to show download icon. * - * @param {any} quiz Quiz. - * @param {any[]} [attempts] List of attempts. If not provided, they will be calculated. - * @param {boolean} [forceCache] Whether it should always return cached data. Only if attempts is undefined. - * @param {boolean} [ignoreCache] Whether it should ignore cached data (it will always fail in offline or server down). Only if - * attempts is undefined. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when done. + * @param quiz Quiz. + * @param attempts List of attempts. If not provided, they will be calculated. + * @param forceCache Whether it should always return cached data. Only if attempts is undefined. + * @param ignoreCache Whether it should ignore cached data (it will always fail in offline or server down). Only if + * attempts is undefined. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when done. */ setStatusAfterPrefetch(quiz: any, attempts?: any[], forceCache?: boolean, ignoreCache?: boolean, siteId?: string) : Promise { @@ -567,10 +580,10 @@ export class AddonModQuizPrefetchHandler extends CoreCourseActivityPrefetchHandl /** * Sync a module. * - * @param {any} module Module. - * @param {number} courseId Course ID the module belongs to - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when done. + * @param module Module. + * @param courseId Course ID the module belongs to + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when done. */ sync(module: any, courseId: number, siteId?: any): Promise { if (!this.syncProvider) { diff --git a/src/addon/mod/quiz/providers/push-click-handler.ts b/src/addon/mod/quiz/providers/push-click-handler.ts index 43397a59b..9509d096a 100644 --- a/src/addon/mod/quiz/providers/push-click-handler.ts +++ b/src/addon/mod/quiz/providers/push-click-handler.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -38,8 +38,8 @@ export class AddonModQuizPushClickHandler implements CorePushNotificationsClickH /** * Check if a notification click is handled by this handler. * - * @param {any} notification The notification to check. - * @return {boolean} Whether the notification click is handled by this handler + * @param notification The notification to check. + * @return Whether the notification click is handled by this handler */ handles(notification: any): boolean | Promise { return this.utils.isTrueOrOne(notification.notif) && notification.moodlecomponent == 'mod_quiz' && @@ -49,8 +49,8 @@ export class AddonModQuizPushClickHandler implements CorePushNotificationsClickH /** * Handle the notification click. * - * @param {any} notification The notification to check. - * @return {Promise} Promise resolved when done. + * @param notification The notification to check. + * @return Promise resolved when done. */ handleClick(notification: any): Promise { const contextUrlParams = this.urlUtils.extractUrlParams(notification.contexturl), diff --git a/src/addon/mod/quiz/providers/quiz-offline.ts b/src/addon/mod/quiz/providers/quiz-offline.ts index b7fe048d2..6c1f5121e 100644 --- a/src/addon/mod/quiz/providers/quiz-offline.ts +++ b/src/addon/mod/quiz/providers/quiz-offline.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -93,8 +93,8 @@ export class AddonModQuizOfflineProvider { /** * Classify the answers in questions. * - * @param {any} answers List of answers. - * @return {any} Object with the questions, the keys are the slot. Each question contains its answers. + * @param answers List of answers. + * @return Object with the questions, the keys are the slot. Each question contains its answers. */ classifyAnswersInQuestions(answers: any): any { const questionsWithAnswers = {}; @@ -120,8 +120,8 @@ export class AddonModQuizOfflineProvider { * Given a list of questions with answers classified in it (@see AddonModQuizOfflineProvider.classifyAnswersInQuestions), * returns a list of answers (including prefix in the name). * - * @param {any} questions Questions. - * @return {any} Answers. + * @param questions Questions. + * @return Answers. */ extractAnswersFromQuestions(questions: any): any { const answers = {}; @@ -140,8 +140,8 @@ export class AddonModQuizOfflineProvider { /** * Get all the offline attempts in a certain site. * - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with the offline attempts. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the offline attempts. */ getAllAttempts(siteId?: string): Promise { return this.sitesProvider.getSiteDb(siteId).then((db) => { @@ -152,9 +152,9 @@ export class AddonModQuizOfflineProvider { /** * Retrieve an attempt answers from site DB. * - * @param {number} attemptId Attempt ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with the answers. + * @param attemptId Attempt ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the answers. */ getAttemptAnswers(attemptId: number, siteId?: string): Promise { return this.questionProvider.getAttemptAnswers(AddonModQuizProvider.COMPONENT, attemptId, siteId); @@ -163,9 +163,9 @@ export class AddonModQuizOfflineProvider { /** * Retrieve an attempt from site DB. * - * @param {number} attemptId Attempt ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with the attempt. + * @param attemptId Attempt ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the attempt. */ getAttemptById(attemptId: number, siteId?: string): Promise { return this.sitesProvider.getSiteDb(siteId).then((db) => { @@ -176,10 +176,10 @@ export class AddonModQuizOfflineProvider { /** * Retrieve an attempt from site DB. * - * @param {number} attemptId Attempt ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @param {number} [userId] User ID. If not defined, user current site's user. - * @return {Promise} Promise resolved with the attempts. + * @param attemptId Attempt ID. + * @param siteId Site ID. If not defined, current site. + * @param userId User ID. If not defined, user current site's user. + * @return Promise resolved with the attempts. */ getQuizAttempts(quizId: number, siteId?: string, userId?: number): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -192,10 +192,10 @@ export class AddonModQuizOfflineProvider { /** * Load local state in the questions. * - * @param {number} attemptId Attempt ID. - * @param {any[]} questions List of questions. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when done. + * @param attemptId Attempt ID. + * @param questions List of questions. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when done. */ loadQuestionsLocalStates(attemptId: number, questions: any[], siteId?: string): Promise { const promises = []; @@ -220,13 +220,13 @@ export class AddonModQuizOfflineProvider { /** * Process an attempt, saving its data. * - * @param {any} quiz Quiz. - * @param {any} attempt Attempt. - * @param {any} questions Object with the questions of the quiz. The keys should be the question slot. - * @param {any} data Data to save. - * @param {boolean} [finish] Whether to finish the quiz. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved in success, rejected otherwise. + * @param quiz Quiz. + * @param attempt Attempt. + * @param questions Object with the questions of the quiz. The keys should be the question slot. + * @param data Data to save. + * @param finish Whether to finish the quiz. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved in success, rejected otherwise. */ processAttempt(quiz: any, attempt: any, questions: any, data: any, finish?: boolean, siteId?: string): Promise { siteId = siteId || this.sitesProvider.getCurrentSiteId(); @@ -265,9 +265,9 @@ export class AddonModQuizOfflineProvider { /** * Remove an attempt and its answers from local DB. * - * @param {number} attemptId Attempt ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when done. + * @param attemptId Attempt ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when done. */ removeAttemptAndAnswers(attemptId: number, siteId?: string): Promise { siteId = siteId || this.sitesProvider.getCurrentSiteId(); @@ -289,10 +289,10 @@ export class AddonModQuizOfflineProvider { /** * Remove a question and its answers from local DB. * - * @param {number} attemptId Attempt ID. - * @param {number} slot Question slot. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when finished. + * @param attemptId Attempt ID. + * @param slot Question slot. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when finished. */ removeQuestionAndAnswers(attemptId: number, slot: number, siteId?: string): Promise { siteId = siteId || this.sitesProvider.getCurrentSiteId(); @@ -308,13 +308,13 @@ export class AddonModQuizOfflineProvider { /** * Save an attempt's answers and calculate state for questions modified. * - * @param {any} quiz Quiz. - * @param {any} attempt Attempt. - * @param {any} questions Object with the questions of the quiz. The keys should be the question slot. - * @param {any} answers Answers to save. - * @param {number} [timeMod] Time modified to set in the answers. If not defined, current time. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when done. + * @param quiz Quiz. + * @param attempt Attempt. + * @param questions Object with the questions of the quiz. The keys should be the question slot. + * @param answers Answers to save. + * @param timeMod Time modified to set in the answers. If not defined, current time. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when done. */ saveAnswers(quiz: any, attempt: any, questions: any, answers: any, timeMod?: number, siteId?: string): Promise { siteId = siteId || this.sitesProvider.getCurrentSiteId(); @@ -376,10 +376,10 @@ export class AddonModQuizOfflineProvider { /** * Set attempt's current page. * - * @param {number} attemptId Attempt ID. - * @param {number} page Page to set. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved in success, rejected otherwise. + * @param attemptId Attempt ID. + * @param page Page to set. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved in success, rejected otherwise. */ setAttemptCurrentPage(attemptId: number, page: number, siteId?: string): Promise { return this.sitesProvider.getSiteDb(siteId).then((db) => { diff --git a/src/addon/mod/quiz/providers/quiz-sync.ts b/src/addon/mod/quiz/providers/quiz-sync.ts index 9f7226e9d..b49891a58 100644 --- a/src/addon/mod/quiz/providers/quiz-sync.ts +++ b/src/addon/mod/quiz/providers/quiz-sync.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -37,13 +37,11 @@ import { AddonModQuizPrefetchHandler } from './prefetch-handler'; export interface AddonModQuizSyncResult { /** * List of warnings. - * @type {string[]} */ warnings: string[]; /** * Whether an attempt was finished in the site due to the sync, - * @type {boolean} */ attemptFinished: boolean; } @@ -75,16 +73,16 @@ export class AddonModQuizSyncProvider extends CoreCourseActivitySyncBaseProvider /** * Finish a sync process: remove offline data if needed, prefetch quiz data, set sync time and return the result. * - * @param {string} siteId Site ID. - * @param {any} quiz Quiz. - * @param {number} courseId Course ID. - * @param {string[]} warnings List of warnings generated by the sync. - * @param {number} [attemptId] Last attempt ID. - * @param {any} [offlineAttempt] Offline attempt synchronized, if any. - * @param {any} [onlineAttempt] Online data for the offline attempt. - * @param {boolean} [removeAttempt] Whether the offline data should be removed. - * @param {boolean} [updated] Whether some data was sent to the site. - * @return {Promise} Promise resolved on success. + * @param siteId Site ID. + * @param quiz Quiz. + * @param courseId Course ID. + * @param warnings List of warnings generated by the sync. + * @param attemptId Last attempt ID. + * @param offlineAttempt Offline attempt synchronized, if any. + * @param onlineAttempt Online data for the offline attempt. + * @param removeAttempt Whether the offline data should be removed. + * @param updated Whether some data was sent to the site. + * @return Promise resolved on success. */ protected finishSync(siteId: string, quiz: any, courseId: number, warnings: string[], attemptId?: number, offlineAttempt?: any, onlineAttempt?: any, removeAttempt?: boolean, updated?: boolean): Promise { @@ -139,9 +137,9 @@ export class AddonModQuizSyncProvider extends CoreCourseActivitySyncBaseProvider /** * Check if a quiz has data to synchronize. * - * @param {number} quizId Quiz ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with boolean: whether it has data to sync. + * @param quizId Quiz ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with boolean: whether it has data to sync. */ hasDataToSync(quizId: number, siteId?: string): Promise { return this.quizOfflineProvider.getQuizAttempts(quizId, siteId).then((attempts) => { @@ -154,12 +152,12 @@ export class AddonModQuizSyncProvider extends CoreCourseActivitySyncBaseProvider /** * Conveniece function to prefetch data after an update. * - * @param {any} module Module. - * @param {any} quiz Quiz. - * @param {number} courseId Course ID. - * @param {RegExp} [regex] If regex matches, don't download the data. Defaults to check files. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when done. + * @param module Module. + * @param quiz Quiz. + * @param courseId Course ID. + * @param regex If regex matches, don't download the data. Defaults to check files. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when done. */ prefetchAfterUpdateQuiz(module: any, quiz: any, courseId: number, regex?: RegExp, siteId?: string): Promise { regex = regex || /^.*files$/; @@ -189,9 +187,9 @@ export class AddonModQuizSyncProvider extends CoreCourseActivitySyncBaseProvider /** * Try to synchronize all the quizzes in a certain site or in all sites. * - * @param {string} [siteId] Site ID to sync. If not defined, sync all sites. - * @param {boolean} [force] Wether to force sync not depending on last execution. - * @return {Promise} Promise resolved if sync is successful, rejected if sync fails. + * @param siteId Site ID to sync. If not defined, sync all sites. + * @param force Wether to force sync not depending on last execution. + * @return Promise resolved if sync is successful, rejected if sync fails. */ syncAllQuizzes(siteId?: string, force?: boolean): Promise { return this.syncOnSites('all quizzes', this.syncAllQuizzesFunc.bind(this), [force], siteId); @@ -200,9 +198,9 @@ export class AddonModQuizSyncProvider extends CoreCourseActivitySyncBaseProvider /** * Sync all quizzes on a site. * - * @param {string} siteId Site ID to sync. - * @param {boolean} [force] Wether to force sync not depending on last execution. - * @param {Promise} Promise resolved if sync is successful, rejected if sync fails. + * @param siteId Site ID to sync. + * @param force Wether to force sync not depending on last execution. + * @param Promise resolved if sync is successful, rejected if sync fails. */ protected syncAllQuizzesFunc(siteId?: string, force?: boolean): Promise { // Get all offline attempts. @@ -261,10 +259,10 @@ export class AddonModQuizSyncProvider extends CoreCourseActivitySyncBaseProvider /** * Sync a quiz only if a certain time has passed since the last time. * - * @param {any} quiz Quiz. - * @param {boolean} [askPreflight] Whether we should ask for preflight data if needed. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the quiz is synced or if it doesn't need to be synced. + * @param quiz Quiz. + * @param askPreflight Whether we should ask for preflight data if needed. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the quiz is synced or if it doesn't need to be synced. */ syncQuizIfNeeded(quiz: any, askPreflight?: boolean, siteId?: string): Promise { return this.isSyncNeeded(quiz.id, siteId).then((needed) => { @@ -278,10 +276,10 @@ export class AddonModQuizSyncProvider extends CoreCourseActivitySyncBaseProvider * Try to synchronize a quiz. * The promise returned will be resolved with an array with warnings if the synchronization is successful. * - * @param {any} quiz Quiz. - * @param {boolean} [askPreflight] Whether we should ask for preflight data if needed. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved in success. + * @param quiz Quiz. + * @param askPreflight Whether we should ask for preflight data if needed. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved in success. */ syncQuiz(quiz: any, askPreflight?: boolean, siteId?: string): Promise { siteId = siteId || this.sitesProvider.getCurrentSiteId(); @@ -414,11 +412,11 @@ export class AddonModQuizSyncProvider extends CoreCourseActivitySyncBaseProvider /** * Validate questions, discarding the offline answers that can't be synchronized. * - * @param {number} attemptId Attempt ID. - * @param {any} onlineQuestions Online questions - * @param {any} offlineQuestions Offline questions. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with boolean: true if some offline data was discarded, false otherwise. + * @param attemptId Attempt ID. + * @param onlineQuestions Online questions + * @param offlineQuestions Offline questions. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with boolean: true if some offline data was discarded, false otherwise. */ validateQuestions(attemptId: number, onlineQuestions: any, offlineQuestions: any, siteId?: string): Promise { const promises = []; diff --git a/src/addon/mod/quiz/providers/quiz.ts b/src/addon/mod/quiz/providers/quiz.ts index 114963f9e..b4df1d58e 100644 --- a/src/addon/mod/quiz/providers/quiz.ts +++ b/src/addon/mod/quiz/providers/quiz.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -72,9 +72,9 @@ export class AddonModQuizProvider { /** * Formats a grade to be displayed. * - * @param {number} grade Grade. - * @param {number} decimals Decimals to use. - * @return {string} Grade to display. + * @param grade Grade. + * @param decimals Decimals to use. + * @return Grade to display. */ formatGrade(grade: number, decimals: number): string { if (typeof grade == 'undefined' || grade == -1 || grade === null) { @@ -87,14 +87,14 @@ export class AddonModQuizProvider { /** * Get attempt questions. Returns all of them or just the ones in certain pages. * - * @param {any} quiz Quiz. - * @param {any} attempt Attempt. - * @param {any} preflightData Preflight required data (like password). - * @param {number[]} [pages] List of pages to get. If not defined, all pages. - * @param {boolean} [offline] Whether it should return cached data. Has priority over ignoreCache. - * @param {boolean} [ignoreCache] Whether it should ignore cached data (it will always fail in offline or server down). - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with the questions. + * @param quiz Quiz. + * @param attempt Attempt. + * @param preflightData Preflight required data (like password). + * @param pages List of pages to get. If not defined, all pages. + * @param offline Whether it should return cached data. Has priority over ignoreCache. + * @param ignoreCache Whether it should ignore cached data (it will always fail in offline or server down). + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the questions. */ getAllQuestionsData(quiz: any, attempt: any, preflightData: any, pages?: number[], offline?: boolean, ignoreCache?: boolean, siteId?: string): Promise { @@ -130,9 +130,9 @@ export class AddonModQuizProvider { /** * Get cache key for get attempt access information WS calls. * - * @param {number} quizId Quiz ID. - * @param {number} attemptId Attempt ID. - * @return {string} Cache key. + * @param quizId Quiz ID. + * @param attemptId Attempt ID. + * @return Cache key. */ protected getAttemptAccessInformationCacheKey(quizId: number, attemptId: number): string { return this.getAttemptAccessInformationCommonCacheKey(quizId) + ':' + attemptId; @@ -141,8 +141,8 @@ export class AddonModQuizProvider { /** * Get common cache key for get attempt access information WS calls. * - * @param {number} quizId Quiz ID. - * @return {string} Cache key. + * @param quizId Quiz ID. + * @return Cache key. */ protected getAttemptAccessInformationCommonCacheKey(quizId: number): string { return this.ROOT_CACHE_KEY + 'attemptAccessInformation:' + quizId; @@ -151,12 +151,12 @@ export class AddonModQuizProvider { /** * Get access information for an attempt. * - * @param {number} quizId Quiz ID. - * @param {number} attemptId Attempt ID. 0 for user's last attempt. - * @param {boolean} offline Whether it should return cached data. Has priority over ignoreCache. - * @param {boolean} ignoreCache Whether it should ignore cached data (it will always fail in offline or server down). - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with the access information. + * @param quizId Quiz ID. + * @param attemptId Attempt ID. 0 for user's last attempt. + * @param offline Whether it should return cached data. Has priority over ignoreCache. + * @param ignoreCache Whether it should ignore cached data (it will always fail in offline or server down). + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the access information. */ getAttemptAccessInformation(quizId: number, attemptId: number, offline?: boolean, ignoreCache?: boolean, siteId?: string) : Promise { @@ -184,9 +184,9 @@ export class AddonModQuizProvider { /** * Get cache key for get attempt data WS calls. * - * @param {number} attemptId Attempt ID. - * @param {number} page Page. - * @return {string} Cache key. + * @param attemptId Attempt ID. + * @param page Page. + * @return Cache key. */ protected getAttemptDataCacheKey(attemptId: number, page: number): string { return this.getAttemptDataCommonCacheKey(attemptId) + ':' + page; @@ -195,8 +195,8 @@ export class AddonModQuizProvider { /** * Get common cache key for get attempt data WS calls. * - * @param {number} attemptId Attempt ID. - * @return {string} Cache key. + * @param attemptId Attempt ID. + * @return Cache key. */ protected getAttemptDataCommonCacheKey(attemptId: number): string { return this.ROOT_CACHE_KEY + 'attemptData:' + attemptId; @@ -205,13 +205,13 @@ export class AddonModQuizProvider { /** * Get an attempt's data. * - * @param {number} attemptId Attempt ID. - * @param {number} page Page number. - * @param {any} preflightData Preflight required data (like password). - * @param {boolean} [offline] Whether it should return cached data. Has priority over ignoreCache. - * @param {boolean} [ignoreCache] Whether it should ignore cached data (it will always fail in offline or server down). - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with the attempt data. + * @param attemptId Attempt ID. + * @param page Page number. + * @param preflightData Preflight required data (like password). + * @param offline Whether it should return cached data. Has priority over ignoreCache. + * @param ignoreCache Whether it should ignore cached data (it will always fail in offline or server down). + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the attempt data. */ getAttemptData(attemptId: number, page: number, preflightData: any, offline?: boolean, ignoreCache?: boolean, siteId?: string) : Promise { @@ -240,9 +240,9 @@ export class AddonModQuizProvider { /** * Get an attempt's due date. * - * @param {any} quiz Quiz. - * @param {any} attempt Attempt. - * @return {number} Attempt's due date, 0 if no due date or invalid data. + * @param quiz Quiz. + * @param attempt Attempt. + * @return Attempt's due date, 0 if no due date or invalid data. */ getAttemptDueDate(quiz: any, attempt: any): number { const deadlines = []; @@ -281,9 +281,9 @@ export class AddonModQuizProvider { /** * Get an attempt's warning because of due date. * - * @param {any} quiz Quiz. - * @param {any} attempt Attempt. - * @return {string} Attempt's warning, undefined if no due date. + * @param quiz Quiz. + * @param attempt Attempt. + * @return Attempt's warning, undefined if no due date. */ getAttemptDueDateWarning(quiz: any, attempt: any): string { const dueDate = this.getAttemptDueDate(quiz, attempt); @@ -298,9 +298,9 @@ export class AddonModQuizProvider { /** * Turn attempt's state into a readable state, including some extra data depending on the state. * - * @param {any} quiz Quiz. - * @param {any} attempt Attempt. - * @return {string[]} List of state sentences. + * @param quiz Quiz. + * @param attempt Attempt. + * @return List of state sentences. */ getAttemptReadableState(quiz: any, attempt: any): string[] { if (attempt.finishedOffline) { @@ -342,8 +342,8 @@ export class AddonModQuizProvider { /** * Turn attempt's state into a readable state name, without any more data. * - * @param {string} state State. - * @return {string} Readable state name. + * @param state State. + * @return Readable state name. */ getAttemptReadableStateName(state: string): string { switch (state) { @@ -367,9 +367,9 @@ export class AddonModQuizProvider { /** * Get cache key for get attempt review WS calls. * - * @param {number} attemptId Attempt ID. - * @param {number} page Page. - * @return {string} Cache key. + * @param attemptId Attempt ID. + * @param page Page. + * @return Cache key. */ protected getAttemptReviewCacheKey(attemptId: number, page: number): string { return this.getAttemptReviewCommonCacheKey(attemptId) + ':' + page; @@ -378,8 +378,8 @@ export class AddonModQuizProvider { /** * Get common cache key for get attempt review WS calls. * - * @param {number} attemptId Attempt ID. - * @return {string} Cache key. + * @param attemptId Attempt ID. + * @return Cache key. */ protected getAttemptReviewCommonCacheKey(attemptId: number): string { return this.ROOT_CACHE_KEY + 'attemptReview:' + attemptId; @@ -388,11 +388,11 @@ export class AddonModQuizProvider { /** * Get an attempt's review. * - * @param {number} attemptId Attempt ID. - * @param {number} [page] Page number. If not defined, return all the questions in all the pages. - * @param {boolean} [ignoreCache] Whether it should ignore cached data (it will always fail in offline or server down). - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with the attempt review. + * @param attemptId Attempt ID. + * @param page Page number. If not defined, return all the questions in all the pages. + * @param ignoreCache Whether it should ignore cached data (it will always fail in offline or server down). + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the attempt review. */ getAttemptReview(attemptId: number, page?: number, ignoreCache?: boolean, siteId?: string): Promise { if (typeof page == 'undefined') { @@ -421,8 +421,8 @@ export class AddonModQuizProvider { /** * Get cache key for get attempt summary WS calls. * - * @param {number} attemptId Attempt ID. - * @return {string} Cache key. + * @param attemptId Attempt ID. + * @return Cache key. */ protected getAttemptSummaryCacheKey(attemptId: number): string { return this.ROOT_CACHE_KEY + 'attemptSummary:' + attemptId; @@ -431,13 +431,13 @@ export class AddonModQuizProvider { /** * Get an attempt's summary. * - * @param {number} attemptId Attempt ID. - * @param {any} preflightData Preflight required data (like password). - * @param {boolean} [offline] Whether it should return cached data. Has priority over ignoreCache. - * @param {boolean} [ignoreCache] Whether it should ignore cached data (it will always fail in offline or server down). - * @param {boolean} [loadLocal] Whether it should load local state for each question. Only applicable if offline=true. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with the list of questions for the attempt summary. + * @param attemptId Attempt ID. + * @param preflightData Preflight required data (like password). + * @param offline Whether it should return cached data. Has priority over ignoreCache. + * @param ignoreCache Whether it should ignore cached data (it will always fail in offline or server down). + * @param loadLocal Whether it should load local state for each question. Only applicable if offline=true. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the list of questions for the attempt summary. */ getAttemptSummary(attemptId: number, preflightData: any, offline?: boolean, ignoreCache?: boolean, loadLocal?: boolean, siteId?: string): Promise { @@ -475,9 +475,9 @@ export class AddonModQuizProvider { /** * Get cache key for get combined review options WS calls. * - * @param {number} quizId Quiz ID. - * @param {number} userId User ID. - * @return {string} Cache key. + * @param quizId Quiz ID. + * @param userId User ID. + * @return Cache key. */ protected getCombinedReviewOptionsCacheKey(quizId: number, userId: number): string { return this.getCombinedReviewOptionsCommonCacheKey(quizId) + ':' + userId; @@ -486,8 +486,8 @@ export class AddonModQuizProvider { /** * Get common cache key for get combined review options WS calls. * - * @param {number} quizId Quiz ID. - * @return {string} Cache key. + * @param quizId Quiz ID. + * @return Cache key. */ protected getCombinedReviewOptionsCommonCacheKey(quizId: number): string { return this.ROOT_CACHE_KEY + 'combinedReviewOptions:' + quizId; @@ -496,11 +496,11 @@ export class AddonModQuizProvider { /** * Get a quiz combined review options. * - * @param {number} quizId Quiz ID. - * @param {boolean} [ignoreCache] Whether it should ignore cached data (it will always fail in offline or server down). - * @param {string} [siteId] Site ID. If not defined, current site. - * @param {number} [userId] User ID. If not defined use site's current user. - * @return {Promise}Promise resolved with the combined review options. + * @param quizId Quiz ID. + * @param ignoreCache Whether it should ignore cached data (it will always fail in offline or server down). + * @param siteId Site ID. If not defined, current site. + * @param userId User ID. If not defined use site's current user. + * @return Promise resolved with the combined review options. */ getCombinedReviewOptions(quizId: number, ignoreCache?: boolean, siteId?: string, userId?: number): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -536,9 +536,9 @@ export class AddonModQuizProvider { /** * Get cache key for get feedback for grade WS calls. * - * @param {number} quizId Quiz ID. - * @param {number} grade Grade. - * @return {string} Cache key. + * @param quizId Quiz ID. + * @param grade Grade. + * @return Cache key. */ protected getFeedbackForGradeCacheKey(quizId: number, grade: number): string { return this.getFeedbackForGradeCommonCacheKey(quizId) + ':' + grade; @@ -547,8 +547,8 @@ export class AddonModQuizProvider { /** * Get common cache key for get feedback for grade WS calls. * - * @param {number} quizId Quiz ID. - * @return {string} Cache key. + * @param quizId Quiz ID. + * @return Cache key. */ protected getFeedbackForGradeCommonCacheKey(quizId: number): string { return this.ROOT_CACHE_KEY + 'feedbackForGrade:' + quizId; @@ -557,11 +557,11 @@ export class AddonModQuizProvider { /** * Get the feedback for a certain grade. * - * @param {number} quizId Quiz ID. - * @param {number} grade Grade. - * @param {boolean} [ignoreCache] Whether it should ignore cached data (it will always fail in offline or server down). - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with the feedback. + * @param quizId Quiz ID. + * @param grade Grade. + * @param ignoreCache Whether it should ignore cached data (it will always fail in offline or server down). + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the feedback. */ getFeedbackForGrade(quizId: number, grade: number, ignoreCache?: boolean, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -587,8 +587,8 @@ export class AddonModQuizProvider { * Determine the correct number of decimal places required to format a grade. * Based on Moodle's quiz_get_grade_format. * - * @param {any} quiz Quiz. - * @return {number} Number of decimals. + * @param quiz Quiz. + * @return Number of decimals. */ getGradeDecimals(quiz: any): number { if (typeof quiz.questiondecimalpoints == 'undefined') { @@ -605,12 +605,12 @@ export class AddonModQuizProvider { /** * Gets a quiz grade and feedback from the gradebook. * - * @param {number} courseId Course ID. - * @param {number} moduleId Quiz module ID. - * @param {boolean} [ignoreCache] Whether it should ignore cached data (it will always fail in offline or server down). - * @param {string} [siteId] Site ID. If not defined, current site. - * @param {number} [userId] User ID. If not defined use site's current user. - * @return {Promise} Promise resolved with an object containing the grade and the feedback. + * @param courseId Course ID. + * @param moduleId Quiz module ID. + * @param ignoreCache Whether it should ignore cached data (it will always fail in offline or server down). + * @param siteId Site ID. If not defined, current site. + * @param userId User ID. If not defined use site's current user. + * @return Promise resolved with an object containing the grade and the feedback. */ getGradeFromGradebook(courseId: number, moduleId: number, ignoreCache?: boolean, siteId?: string, userId?: number) : Promise { @@ -623,8 +623,8 @@ export class AddonModQuizProvider { /** * Given a list of attempts, returns the last finished attempt. * - * @param {any[]} attempts Attempts. - * @return {any} Last finished attempt. + * @param attempts Attempts. + * @return Last finished attempt. */ getLastFinishedAttemptFromList(attempts: any[]): any { if (attempts && attempts.length) { @@ -642,8 +642,8 @@ export class AddonModQuizProvider { * Given a list of questions, check if the quiz can be submitted. * Will return an array with the messages to prevent the submit. Empty array if quiz can be submitted. * - * @param {any[]} questions Questions. - * @return {string[]} List of prevent submit messages. Empty array if quiz can be submitted. + * @param questions Questions. + * @return List of prevent submit messages. Empty array if quiz can be submitted. */ getPreventSubmitMessages(questions: any[]): string[] { const messages = []; @@ -670,8 +670,8 @@ export class AddonModQuizProvider { /** * Get cache key for quiz data WS calls. * - * @param {number} courseId Course ID. - * @return {string} Cache key. + * @param courseId Course ID. + * @return Cache key. */ protected getQuizDataCacheKey(courseId: number): string { return this.ROOT_CACHE_KEY + 'quiz:' + courseId; @@ -680,13 +680,13 @@ export class AddonModQuizProvider { /** * Get a Quiz with key=value. If more than one is found, only the first will be returned. * - * @param {number} courseId Course ID. - * @param {string} key Name of the property to check. - * @param {any} value Value to search. - * @param {boolean} [forceCache] Whether it should always return cached data. - * @param {boolean} [ignoreCache] Whether it should ignore cached data (it will always fail in offline or server down). - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the Quiz is retrieved. + * @param courseId Course ID. + * @param key Name of the property to check. + * @param value Value to search. + * @param forceCache Whether it should always return cached data. + * @param ignoreCache Whether it should ignore cached data (it will always fail in offline or server down). + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the Quiz is retrieved. */ protected getQuizByField(courseId: number, key: string, value: any, forceCache?: boolean, ignoreCache?: boolean, siteId?: string): Promise { @@ -726,12 +726,12 @@ export class AddonModQuizProvider { /** * Get a quiz by module ID. * - * @param {number} courseId Course ID. - * @param {number} cmId Course module ID. - * @param {boolean} [forceCache] Whether it should always return cached data. - * @param {boolean} [ignoreCache] Whether it should ignore cached data (it will always fail in offline or server down). - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the quiz is retrieved. + * @param courseId Course ID. + * @param cmId Course module ID. + * @param forceCache Whether it should always return cached data. + * @param ignoreCache Whether it should ignore cached data (it will always fail in offline or server down). + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the quiz is retrieved. */ getQuiz(courseId: number, cmId: number, forceCache?: boolean, ignoreCache?: boolean, siteId?: string): Promise { return this.getQuizByField(courseId, 'coursemodule', cmId, forceCache, ignoreCache, siteId); @@ -740,12 +740,12 @@ export class AddonModQuizProvider { /** * Get a quiz by quiz ID. * - * @param {number} courseId Course ID. - * @param {number} id Quiz ID. - * @param {boolean} [forceCache] Whether it should always return cached data. - * @param {boolean} [ignoreCache] Whether it should ignore cached data (it will always fail in offline or server down). - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the quiz is retrieved. + * @param courseId Course ID. + * @param id Quiz ID. + * @param forceCache Whether it should always return cached data. + * @param ignoreCache Whether it should ignore cached data (it will always fail in offline or server down). + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the quiz is retrieved. */ getQuizById(courseId: number, id: number, forceCache?: boolean, ignoreCache?: boolean, siteId?: string): Promise { return this.getQuizByField(courseId, 'id', id, forceCache, ignoreCache, siteId); @@ -754,8 +754,8 @@ export class AddonModQuizProvider { /** * Get cache key for get quiz access information WS calls. * - * @param {number} quizId Quiz ID. - * @return {string} Cache key. + * @param quizId Quiz ID. + * @return Cache key. */ protected getQuizAccessInformationCacheKey(quizId: number): string { return this.ROOT_CACHE_KEY + 'quizAccessInformation:' + quizId; @@ -764,11 +764,11 @@ export class AddonModQuizProvider { /** * Get access information for an attempt. * - * @param {number} quizId Quiz ID. - * @param {boolean} [offline] Whether it should return cached data. Has priority over ignoreCache. - * @param {boolean} [ignoreCache] Whether it should ignore cached data (it will always fail in offline or server down). - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with the access information. + * @param quizId Quiz ID. + * @param offline Whether it should return cached data. Has priority over ignoreCache. + * @param ignoreCache Whether it should ignore cached data (it will always fail in offline or server down). + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the access information. */ getQuizAccessInformation(quizId: number, offline?: boolean, ignoreCache?: boolean, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -793,8 +793,8 @@ export class AddonModQuizProvider { /** * Get a readable Quiz grade method. * - * @param {number|string} method Grading method. - * @return {string} Readable grading method. + * @param method Grading method. + * @return Readable grading method. */ getQuizGradeMethod(method: number | string): string { if (typeof method == 'string') { @@ -818,8 +818,8 @@ export class AddonModQuizProvider { /** * Get cache key for get quiz required qtypes WS calls. * - * @param {number} quizId Quiz ID. - * @return {string} Cache key. + * @param quizId Quiz ID. + * @return Cache key. */ protected getQuizRequiredQtypesCacheKey(quizId: number): string { return this.ROOT_CACHE_KEY + 'quizRequiredQtypes:' + quizId; @@ -828,10 +828,10 @@ export class AddonModQuizProvider { /** * Get the potential question types that would be required for a given quiz. * - * @param {number} quizId Quiz ID. - * @param {boolean} [ignoreCache] Whether it should ignore cached data (it will always fail in offline or server down). - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with the access information. + * @param quizId Quiz ID. + * @param ignoreCache Whether it should ignore cached data (it will always fail in offline or server down). + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the access information. */ getQuizRequiredQtypes(quizId: number, ignoreCache?: boolean, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -861,8 +861,8 @@ export class AddonModQuizProvider { /** * Given an attempt's layout, return the list of pages. * - * @param {string} layout Attempt's layout. - * @return {number[]} Pages. + * @param layout Attempt's layout. + * @return Pages. * @description * An attempt's layout is a string with the question numbers separated by commas. A 0 indicates a change of page. * Example: 1,2,3,0,4,5,6,0 @@ -889,9 +889,9 @@ export class AddonModQuizProvider { * Given an attempt's layout and a list of questions identified by question slot, * return the list of pages that have at least 1 of the questions. * - * @param {string} layout Attempt's layout. - * @param {any} questions List of questions. It needs to be an object where the keys are question slot. - * @return {number[]} Pages. + * @param layout Attempt's layout. + * @param questions List of questions. It needs to be an object where the keys are question slot. + * @return Pages. * @description * An attempt's layout is a string with the question numbers separated by commas. A 0 indicates a change of page. * Example: 1,2,3,0,4,5,6,0 @@ -923,8 +923,8 @@ export class AddonModQuizProvider { /** * Given a list of question types, returns the types that aren't supported. * - * @param {string[]} questionTypes Question types to check. - * @return {string[]} Not supported question types. + * @param questionTypes Question types to check. + * @return Not supported question types. */ getUnsupportedQuestions(questionTypes: string[]): string[] { const notSupported = []; @@ -941,8 +941,8 @@ export class AddonModQuizProvider { /** * Given a list of access rules names, returns the rules that aren't supported. * - * @param {string[]} rulesNames Rules to check. - * @return {string[]} Not supported rules names. + * @param rulesNames Rules to check. + * @return Not supported rules names. */ getUnsupportedRules(rulesNames: string[]): string[] { const notSupported = []; @@ -959,9 +959,9 @@ export class AddonModQuizProvider { /** * Get cache key for get user attempts WS calls. * - * @param {number} quizId Quiz ID. - * @param {number} userId User ID. - * @return {string} Cache key. + * @param quizId Quiz ID. + * @param userId User ID. + * @return Cache key. */ protected getUserAttemptsCacheKey(quizId: number, userId: number): string { return this.getUserAttemptsCommonCacheKey(quizId) + ':' + userId; @@ -970,8 +970,8 @@ export class AddonModQuizProvider { /** * Get common cache key for get user attempts WS calls. * - * @param {number} quizId Quiz ID. - * @return {string} Cache key. + * @param quizId Quiz ID. + * @return Cache key. */ protected getUserAttemptsCommonCacheKey(quizId: number): string { return this.ROOT_CACHE_KEY + 'userAttempts:' + quizId; @@ -980,14 +980,14 @@ export class AddonModQuizProvider { /** * Get quiz attempts for a certain user. * - * @param {number} quizId Quiz ID. - * @param {number} [status=all] Status of the attempts to get. By default, 'all'. - * @param {boolean} [includePreviews=true] Whether to include previews. Defaults to true. - * @param {boolean} [offline] Whether it should return cached data. Has priority over ignoreCache. - * @param {boolean} [ignoreCache] Whether it should ignore cached data (it will always fail in offline or server down). - * @param {string} [siteId] Site ID. If not defined, current site. - * @param {number} [userId] User ID. If not defined use site's current user. - * @return {Promise} Promise resolved with the attempts. + * @param quizId Quiz ID. + * @param status Status of the attempts to get. By default, 'all'. + * @param includePreviews Whether to include previews. Defaults to true. + * @param offline Whether it should return cached data. Has priority over ignoreCache. + * @param ignoreCache Whether it should ignore cached data (it will always fail in offline or server down). + * @param siteId Site ID. If not defined, current site. + * @param userId User ID. If not defined use site's current user. + * @return Promise resolved with the attempts. */ getUserAttempts(quizId: number, status: string = 'all', includePreviews: boolean = true, offline?: boolean, ignoreCache?: boolean, siteId?: string, userId?: number): Promise { @@ -1026,9 +1026,9 @@ export class AddonModQuizProvider { /** * Get cache key for get user best grade WS calls. * - * @param {number} quizId Quiz ID. - * @param {number} userId User ID. - * @return {string} Cache key. + * @param quizId Quiz ID. + * @param userId User ID. + * @return Cache key. */ protected getUserBestGradeCacheKey(quizId: number, userId: number): string { return this.getUserBestGradeCommonCacheKey(quizId) + ':' + userId; @@ -1037,8 +1037,8 @@ export class AddonModQuizProvider { /** * Get common cache key for get user best grade WS calls. * - * @param {number} quizId Quiz ID. - * @return {string} Cache key. + * @param quizId Quiz ID. + * @return Cache key. */ protected getUserBestGradeCommonCacheKey(quizId: number): string { return this.ROOT_CACHE_KEY + 'userBestGrade:' + quizId; @@ -1047,11 +1047,11 @@ export class AddonModQuizProvider { /** * Get best grade in a quiz for a certain user. * - * @param {number} quizId Quiz ID. - * @param {boolean} [ignoreCache] Whether it should ignore cached data (it will always fail in offline or server down). - * @param {string} [siteId] Site ID. If not defined, current site. - * @param {number} [userId] User ID. If not defined use site's current user. - * @return {Promise} Promise resolved with the best grade data. + * @param quizId Quiz ID. + * @param ignoreCache Whether it should ignore cached data (it will always fail in offline or server down). + * @param siteId Site ID. If not defined, current site. + * @param userId User ID. If not defined use site's current user. + * @return Promise resolved with the best grade data. */ getUserBestGrade(quizId: number, ignoreCache?: boolean, siteId?: string, userId?: number): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -1077,12 +1077,12 @@ export class AddonModQuizProvider { /** * Invalidates all the data related to a certain quiz. * - * @param {number} quizId Quiz ID. - * @param {number} [courseId] Course ID. - * @param {number} [attemptId] Attempt ID to invalidate some WS calls. - * @param {string} [siteId] Site ID. If not defined, current site. - * @param {number} [userId] User ID. If not defined use site's current user. - * @return {Promise} Promise resolved when the data is invalidated. + * @param quizId Quiz ID. + * @param courseId Course ID. + * @param attemptId Attempt ID to invalidate some WS calls. + * @param siteId Site ID. If not defined, current site. + * @param userId User ID. If not defined use site's current user. + * @return Promise resolved when the data is invalidated. */ invalidateAllQuizData(quizId: number, courseId?: number, attemptId?: number, siteId?: string, userId?: number): Promise { siteId = siteId || this.sitesProvider.getCurrentSiteId(); @@ -1113,9 +1113,9 @@ export class AddonModQuizProvider { /** * Invalidates attempt access information for all attempts in a quiz. * - * @param {number} quizId Quiz ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the data is invalidated. + * @param quizId Quiz ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the data is invalidated. */ invalidateAttemptAccessInformation(quizId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -1126,10 +1126,10 @@ export class AddonModQuizProvider { /** * Invalidates attempt access information for an attempt. * - * @param {number} quizId Quiz ID. - * @param {number} attemptId Attempt ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the data is invalidated. + * @param quizId Quiz ID. + * @param attemptId Attempt ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the data is invalidated. */ invalidateAttemptAccessInformationForAttempt(quizId: number, attemptId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -1140,9 +1140,9 @@ export class AddonModQuizProvider { /** * Invalidates attempt data for all pages. * - * @param {number} attemptId Attempt ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the data is invalidated. + * @param attemptId Attempt ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the data is invalidated. */ invalidateAttemptData(attemptId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -1153,10 +1153,10 @@ export class AddonModQuizProvider { /** * Invalidates attempt data for a certain page. * - * @param {number} attemptId Attempt ID. - * @param {number} page Page. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the data is invalidated. + * @param attemptId Attempt ID. + * @param page Page. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the data is invalidated. */ invalidateAttemptDataForPage(attemptId: number, page: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -1167,9 +1167,9 @@ export class AddonModQuizProvider { /** * Invalidates attempt review for all pages. * - * @param {number} attemptId Attempt ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the data is invalidated. + * @param attemptId Attempt ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the data is invalidated. */ invalidateAttemptReview(attemptId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -1180,10 +1180,10 @@ export class AddonModQuizProvider { /** * Invalidates attempt review for a certain page. * - * @param {number} attemptId Attempt ID. - * @param {number} page Page. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the data is invalidated. + * @param attemptId Attempt ID. + * @param page Page. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the data is invalidated. */ invalidateAttemptReviewForPage(attemptId: number, page: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -1194,9 +1194,9 @@ export class AddonModQuizProvider { /** * Invalidates attempt summary. * - * @param {number} attemptId Attempt ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the data is invalidated. + * @param attemptId Attempt ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the data is invalidated. */ invalidateAttemptSummary(attemptId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -1207,9 +1207,9 @@ export class AddonModQuizProvider { /** * Invalidates combined review options for all users. * - * @param {number} quizId Quiz ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the data is invalidated. + * @param quizId Quiz ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the data is invalidated. */ invalidateCombinedReviewOptions(quizId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -1220,10 +1220,10 @@ export class AddonModQuizProvider { /** * Invalidates combined review options for a certain user. * - * @param {number} quizId Quiz ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @param {number} [userId] User ID. If not defined use site's current user. - * @return {Promise} Promise resolved when the data is invalidated. + * @param quizId Quiz ID. + * @param siteId Site ID. If not defined, current site. + * @param userId User ID. If not defined use site's current user. + * @return Promise resolved when the data is invalidated. */ invalidateCombinedReviewOptionsForUser(quizId: number, siteId?: string, userId?: number): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -1237,10 +1237,10 @@ export class AddonModQuizProvider { * Invalidate the prefetched content except files. * To invalidate files, use AddonModQuizProvider.invalidateFiles. * - * @param {number} moduleId The module ID. - * @param {number} courseId Course ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the data is invalidated. + * @param moduleId The module ID. + * @param courseId Course ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the data is invalidated. */ invalidateContent(moduleId: number, courseId: number, siteId?: string): Promise { siteId = siteId || this.sitesProvider.getCurrentSiteId(); @@ -1259,9 +1259,9 @@ export class AddonModQuizProvider { /** * Invalidates feedback for all grades of a quiz. * - * @param {number} quizId Quiz ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the data is invalidated. + * @param quizId Quiz ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the data is invalidated. */ invalidateFeedback(quizId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -1272,10 +1272,10 @@ export class AddonModQuizProvider { /** * Invalidates feedback for a certain grade. * - * @param {number} quizId Quiz ID. - * @param {number} grade Grade. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the data is invalidated. + * @param quizId Quiz ID. + * @param grade Grade. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the data is invalidated. */ invalidateFeedbackForGrade(quizId: number, grade: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -1286,8 +1286,8 @@ export class AddonModQuizProvider { /** * Invalidate the prefetched files. * - * @param {number} moduleId The module ID. - * @return {Promise} Promise resolved when the files are invalidated. + * @param moduleId The module ID. + * @return Promise resolved when the files are invalidated. */ invalidateFiles(moduleId: number): Promise { return this.filepoolProvider.invalidateFilesByComponent(this.sitesProvider.getCurrentSiteId(), @@ -1297,10 +1297,10 @@ export class AddonModQuizProvider { /** * Invalidates grade from gradebook for a certain user. * - * @param {number} courseId Course ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @param {number} [userId] User ID. If not defined use site's current user. - * @return {Promise} Promise resolved when the data is invalidated. + * @param courseId Course ID. + * @param siteId Site ID. If not defined, current site. + * @param userId User ID. If not defined use site's current user. + * @return Promise resolved when the data is invalidated. */ invalidateGradeFromGradebook(courseId: number, siteId?: string, userId?: number): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -1313,9 +1313,9 @@ export class AddonModQuizProvider { /** * Invalidates quiz access information for a quiz. * - * @param {number} quizId Quiz ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the data is invalidated. + * @param quizId Quiz ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the data is invalidated. */ invalidateQuizAccessInformation(quizId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -1326,9 +1326,9 @@ export class AddonModQuizProvider { /** * Invalidates required qtypes for a quiz. * - * @param {number} quizId Quiz ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the data is invalidated. + * @param quizId Quiz ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the data is invalidated. */ invalidateQuizRequiredQtypes(quizId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -1339,9 +1339,9 @@ export class AddonModQuizProvider { /** * Invalidates user attempts for all users. * - * @param {number} quizId Quiz ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the data is invalidated. + * @param quizId Quiz ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the data is invalidated. */ invalidateUserAttempts(quizId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -1352,10 +1352,10 @@ export class AddonModQuizProvider { /** * Invalidates user attempts for a certain user. * - * @param {number} quizId Quiz ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @param {number} [userId] User ID. If not defined use site's current user. - * @return {Promise} Promise resolved when the data is invalidated. + * @param quizId Quiz ID. + * @param siteId Site ID. If not defined, current site. + * @param userId User ID. If not defined use site's current user. + * @return Promise resolved when the data is invalidated. */ invalidateUserAttemptsForUser(quizId: number, siteId?: string, userId?: number): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -1368,9 +1368,9 @@ export class AddonModQuizProvider { /** * Invalidates user best grade for all users. * - * @param {number} quizId Quiz ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the data is invalidated. + * @param quizId Quiz ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the data is invalidated. */ invalidateUserBestGrade(quizId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -1381,10 +1381,10 @@ export class AddonModQuizProvider { /** * Invalidates user best grade for a certain user. * - * @param {number} quizId Quiz ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @param {number} [userId] User ID. If not defined use site's current user. - * @return {Promise} Promise resolved when the data is invalidated. + * @param quizId Quiz ID. + * @param siteId Site ID. If not defined, current site. + * @param userId User ID. If not defined use site's current user. + * @return Promise resolved when the data is invalidated. */ invalidateUserBestGradeForUser(quizId: number, siteId?: string, userId?: number): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -1397,9 +1397,9 @@ export class AddonModQuizProvider { /** * Invalidates quiz data. * - * @param {number} courseId Course ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the data is invalidated. + * @param courseId Course ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the data is invalidated. */ invalidateQuizData(courseId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -1410,8 +1410,8 @@ export class AddonModQuizProvider { /** * Check if an attempt is finished based on its state. * - * @param {string} state Attempt's state. - * @return {boolean} Whether it's finished. + * @param state Attempt's state. + * @return Whether it's finished. */ isAttemptFinished(state: string): boolean { return state == AddonModQuizProvider.ATTEMPT_FINISHED || state == AddonModQuizProvider.ATTEMPT_ABANDONED; @@ -1420,9 +1420,9 @@ export class AddonModQuizProvider { /** * Check if an attempt is finished in offline but not synced. * - * @param {number} attemptId Attempt ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with boolean: true if finished in offline but not synced, false otherwise. + * @param attemptId Attempt ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with boolean: true if finished in offline but not synced, false otherwise. */ isAttemptFinishedOffline(attemptId: number, siteId?: string): Promise { return this.quizOfflineProvider.getAttemptById(attemptId, siteId).then((attempt) => { @@ -1438,9 +1438,9 @@ export class AddonModQuizProvider { * OR * - It finished before autosaveperiod passes. * - * @param {any} quiz Quiz. - * @param {any} attempt Attempt. - * @return {boolean} Whether it's nearly over or over. + * @param quiz Quiz. + * @param attempt Attempt. + * @return Whether it's nearly over or over. */ isAttemptTimeNearlyOver(quiz: any, attempt: any): boolean { if (attempt.state != AddonModQuizProvider.ATTEMPT_IN_PROGRESS) { @@ -1461,10 +1461,10 @@ export class AddonModQuizProvider { /** * Check if last attempt is offline and unfinished. * - * @param {number} attemptId Attempt ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @param {number} [userId] User ID. If not defined, user current site's user. - * @return {Promise} Promise resolved with boolean: true if last offline attempt is unfinished, false otherwise. + * @param attemptId Attempt ID. + * @param siteId Site ID. If not defined, current site. + * @param userId User ID. If not defined, user current site's user. + * @return Promise resolved with boolean: true if last offline attempt is unfinished, false otherwise. */ isLastAttemptOfflineUnfinished(quiz: any, siteId?: string, userId?: number): Promise { return this.quizOfflineProvider.getQuizAttempts(quiz.id, siteId, userId).then((attempts) => { @@ -1479,8 +1479,8 @@ export class AddonModQuizProvider { /** * Check if a quiz navigation is sequential. * - * @param {any} quiz Quiz. - * @return {boolean} Whether navigation is sequential. + * @param quiz Quiz. + * @return Whether navigation is sequential. */ isNavigationSequential(quiz: any): boolean { return quiz.navmethod == 'sequential'; @@ -1489,8 +1489,8 @@ export class AddonModQuizProvider { /** * Return whether or not the plugin is enabled in a certain site. Plugin is enabled if the quiz WS are available. * - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {boolean} Whether the plugin is enabled. + * @param siteId Site ID. If not defined, current site. + * @return Whether the plugin is enabled. */ isPluginEnabled(siteId?: string): boolean { // Quiz WebServices were introduced in 3.1, it will always be enabled. @@ -1500,8 +1500,8 @@ export class AddonModQuizProvider { /** * Check if a question is blocked. * - * @param {any} question Question. - * @return {boolean} Whether it's blocked. + * @param question Question. + * @return Whether it's blocked. */ isQuestionBlocked(question: any): boolean { const element = this.domUtils.convertToElement(question.html); @@ -1512,8 +1512,8 @@ export class AddonModQuizProvider { /** * Check if a quiz is enabled to be used in offline. * - * @param {any} quiz Quiz. - * @return {boolean} Whether offline is enabled. + * @param quiz Quiz. + * @return Whether offline is enabled. */ isQuizOffline(quiz: any): boolean { // Don't allow downloading the quiz if offline is disabled to prevent wasting a lot of data when opening it. @@ -1523,9 +1523,9 @@ export class AddonModQuizProvider { /** * Given a list of attempts, add finishedOffline=true to those attempts that are finished in offline but not synced. * - * @param {any[]} attempts List of attempts. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when done. + * @param attempts List of attempts. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when done. */ loadFinishedOfflineData(attempts: any[], siteId?: string): Promise { if (attempts.length) { @@ -1543,13 +1543,13 @@ export class AddonModQuizProvider { /** * Report an attempt as being viewed. It did not store logs offline because order of the log is important. * - * @param {number} attemptId Attempt ID. - * @param {number} [page=0] Page number. - * @param {any} [preflightData] Preflight required data (like password). - * @param {boolean} [offline] Whether attempt is offline. - * @param {string} [quiz] Quiz instance. If set, a Firebase event will be stored. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the WS call is successful. + * @param attemptId Attempt ID. + * @param page Page number. + * @param preflightData Preflight required data (like password). + * @param offline Whether attempt is offline. + * @param quiz Quiz instance. If set, a Firebase event will be stored. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the WS call is successful. */ logViewAttempt(attemptId: number, page: number = 0, preflightData: any = {}, offline?: boolean, quiz?: any, siteId?: string): Promise { @@ -1578,11 +1578,11 @@ export class AddonModQuizProvider { /** * Report an attempt's review as being viewed. * - * @param {number} attemptId Attempt ID. - * @param {number} quizId Quiz ID. - * @param {string} [name] Name of the quiz. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the WS call is successful. + * @param attemptId Attempt ID. + * @param quizId Quiz ID. + * @param name Name of the quiz. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the WS call is successful. */ logViewAttemptReview(attemptId: number, quizId: number, name?: string, siteId?: string): Promise { const params = { @@ -1596,12 +1596,12 @@ export class AddonModQuizProvider { /** * Report an attempt's summary as being viewed. * - * @param {number} attemptId Attempt ID. - * @param {any} preflightData Preflight required data (like password). - * @param {number} quizId Quiz ID. - * @param {string} [name] Name of the quiz. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the WS call is successful. + * @param attemptId Attempt ID. + * @param preflightData Preflight required data (like password). + * @param quizId Quiz ID. + * @param name Name of the quiz. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the WS call is successful. */ logViewAttemptSummary(attemptId: number, preflightData: any, quizId: number, name?: string, siteId?: string): Promise { const params = { @@ -1616,10 +1616,10 @@ export class AddonModQuizProvider { /** * Report a quiz as being viewed. * - * @param {number} id Module ID. - * @param {string} [name] Name of the quiz. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the WS call is successful. + * @param id Module ID. + * @param name Name of the quiz. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the WS call is successful. */ logViewQuiz(id: number, name?: string, siteId?: string): Promise { const params = { @@ -1633,15 +1633,15 @@ export class AddonModQuizProvider { /** * Process an attempt, saving its data. * - * @param {any} quiz Quiz. - * @param {any} attempt Attempt. - * @param {any} data Data to save. - * @param {any} preflightData Preflight required data (like password). - * @param {boolean} [finish] Whether to finish the quiz. - * @param {boolean} [timeUp] Whether the quiz time is up, false otherwise. - * @param {boolean} [offline] Whether the attempt is offline. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved in success, rejected otherwise. + * @param quiz Quiz. + * @param attempt Attempt. + * @param data Data to save. + * @param preflightData Preflight required data (like password). + * @param finish Whether to finish the quiz. + * @param timeUp Whether the quiz time is up, false otherwise. + * @param offline Whether the attempt is offline. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved in success, rejected otherwise. */ processAttempt(quiz: any, attempt: any, data: any, preflightData: any, finish?: boolean, timeUp?: boolean, offline?: boolean, siteId?: string): Promise { @@ -1655,13 +1655,13 @@ export class AddonModQuizProvider { /** * Process an online attempt, saving its data. * - * @param {number} attemptId Attempt ID. - * @param {any} data Data to save. - * @param {any} preflightData Preflight required data (like password). - * @param {boolean} [finish] Whether to finish the quiz. - * @param {boolean} [timeUp] Whether the quiz time is up, false otherwise. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved in success, rejected otherwise. + * @param attemptId Attempt ID. + * @param data Data to save. + * @param preflightData Preflight required data (like password). + * @param finish Whether to finish the quiz. + * @param timeUp Whether the quiz time is up, false otherwise. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved in success, rejected otherwise. */ protected processAttemptOnline(attemptId: number, data: any, preflightData: any, finish?: boolean, timeUp?: boolean, siteId?: string): Promise { @@ -1691,13 +1691,13 @@ export class AddonModQuizProvider { /** * Process an offline attempt, saving its data. * - * @param {any} quiz Quiz. - * @param {any} attempt Attempt. - * @param {any} data Data to save. - * @param {any} preflightData Preflight required data (like password). - * @param {boolean} [finish] Whether to finish the quiz. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved in success, rejected otherwise. + * @param quiz Quiz. + * @param attempt Attempt. + * @param data Data to save. + * @param preflightData Preflight required data (like password). + * @param finish Whether to finish the quiz. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved in success, rejected otherwise. */ protected processAttemptOffline(quiz: any, attempt: any, data: any, preflightData: any, finish?: boolean, siteId?: string) : Promise { @@ -1714,8 +1714,8 @@ export class AddonModQuizProvider { /** * Check if it's a graded quiz. Based on Moodle's quiz_has_grades. * - * @param {any} quiz Quiz. - * @return {boolean} Whether quiz is graded. + * @param quiz Quiz. + * @return Whether quiz is graded. */ quizHasGrades(quiz: any): boolean { return quiz.grade >= 0.000005 && quiz.sumgrades >= 0.000005; @@ -1725,11 +1725,11 @@ export class AddonModQuizProvider { * Convert the raw grade into a grade out of the maximum grade for this quiz. * Based on Moodle's quiz_rescale_grade. * - * @param {string} rawGrade The unadjusted grade, for example attempt.sumgrades. - * @param {any} quiz Quiz. - * @param {boolean|string} format True to format the results for display, 'question' to format a question grade - * (different number of decimal places), false to not format it. - * @return {string} Grade to display. + * @param rawGrade The unadjusted grade, for example attempt.sumgrades. + * @param quiz Quiz. + * @param format True to format the results for display, 'question' to format a question grade + * (different number of decimal places), false to not format it. + * @return Grade to display. */ rescaleGrade(rawGrade: string, quiz: any, format: boolean | string = true): string { let grade: number; @@ -1761,13 +1761,13 @@ export class AddonModQuizProvider { /** * Save an attempt data. * - * @param {any} quiz Quiz. - * @param {any} attempt Attempt. - * @param {any} data Data to save. - * @param {any} preflightData Preflight required data (like password). - * @param {boolean} [offline] Whether attempt is offline. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved in success, rejected otherwise. + * @param quiz Quiz. + * @param attempt Attempt. + * @param data Data to save. + * @param preflightData Preflight required data (like password). + * @param offline Whether attempt is offline. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved in success, rejected otherwise. */ saveAttempt(quiz: any, attempt: any, data: any, preflightData: any, offline?: boolean, siteId?: string): Promise { try { @@ -1786,11 +1786,11 @@ export class AddonModQuizProvider { /** * Save an attempt data. * - * @param {number} attemptId Attempt ID. - * @param {any} data Data to save. - * @param {any} preflightData Preflight required data (like password). - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved in success, rejected otherwise. + * @param attemptId Attempt ID. + * @param data Data to save. + * @param preflightData Preflight required data (like password). + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved in success, rejected otherwise. */ protected saveAttemptOnline(attemptId: number, data: any, preflightData: any, siteId?: string): Promise { @@ -1815,10 +1815,10 @@ export class AddonModQuizProvider { /** * Check if time left should be shown. * - * @param {string[]} rules List of active rules names. - * @param {any} attempt Attempt. - * @param {number} endTime The attempt end time (in seconds). - * @return {boolean} Whether time left should be displayed. + * @param rules List of active rules names. + * @param attempt Attempt. + * @param endTime The attempt end time (in seconds). + * @return Whether time left should be displayed. */ shouldShowTimeLeft(rules: string[], attempt: any, endTime: number): boolean { const timeNow = this.timeUtils.timestamp(); @@ -1833,11 +1833,11 @@ export class AddonModQuizProvider { /** * Start an attempt. * - * @param {number} quizId Quiz ID. - * @param {any} preflightData Preflight required data (like password). - * @param {boolean} [forceNew] Whether to force a new attempt or not. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with the attempt data. + * @param quizId Quiz ID. + * @param preflightData Preflight required data (like password). + * @param forceNew Whether to force a new attempt or not. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the attempt data. */ startAttempt(quizId: number, preflightData: any, forceNew?: boolean, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { diff --git a/src/addon/mod/quiz/providers/review-link-handler.ts b/src/addon/mod/quiz/providers/review-link-handler.ts index 516499fd0..8fd018ca9 100644 --- a/src/addon/mod/quiz/providers/review-link-handler.ts +++ b/src/addon/mod/quiz/providers/review-link-handler.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -34,12 +34,12 @@ export class AddonModQuizReviewLinkHandler extends CoreContentLinksHandlerBase { /** * Get the list of actions for a link (url). * - * @param {string[]} siteIds List of sites the URL belongs to. - * @param {string} url The URL to treat. - * @param {any} params The params of the URL. E.g. 'mysite.com?id=1' -> {id: 1} - * @param {number} [courseId] Course ID related to the URL. Optional but recommended. - * @param {any} [data] Extra data to handle the URL. - * @return {CoreContentLinksAction[]|Promise} List of (or promise resolved with list of) actions. + * @param siteIds List of sites the URL belongs to. + * @param url The URL to treat. + * @param params The params of the URL. E.g. 'mysite.com?id=1' -> {id: 1} + * @param courseId Course ID related to the URL. Optional but recommended. + * @param data Extra data to handle the URL. + * @return List of (or promise resolved with list of) actions. */ getActions(siteIds: string[], url: string, params: any, courseId?: number, data?: any): CoreContentLinksAction[] | Promise { @@ -62,11 +62,11 @@ export class AddonModQuizReviewLinkHandler extends CoreContentLinksHandlerBase { * Check if the handler is enabled for a certain site (site + user) and a URL. * If not defined, defaults to true. * - * @param {string} siteId The site ID. - * @param {string} url The URL to treat. - * @param {any} params The params of the URL. E.g. 'mysite.com?id=1' -> {id: 1} - * @param {number} [courseId] Course ID related to the URL. Optional but recommended. - * @return {boolean|Promise} Whether the handler is enabled for the URL and site. + * @param siteId The site ID. + * @param url The URL to treat. + * @param params The params of the URL. E.g. 'mysite.com?id=1' -> {id: 1} + * @param courseId Course ID related to the URL. Optional but recommended. + * @return Whether the handler is enabled for the URL and site. */ isEnabled(siteId: string, url: string, params: any, courseId?: number): boolean | Promise { return this.quizProvider.isPluginEnabled(); diff --git a/src/addon/mod/quiz/providers/sync-cron-handler.ts b/src/addon/mod/quiz/providers/sync-cron-handler.ts index 2c3c1bcbe..622f556f6 100644 --- a/src/addon/mod/quiz/providers/sync-cron-handler.ts +++ b/src/addon/mod/quiz/providers/sync-cron-handler.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -29,9 +29,9 @@ export class AddonModQuizSyncCronHandler implements CoreCronHandler { * Execute the process. * Receives the ID of the site affected, undefined for all sites. * - * @param {string} [siteId] ID of the site affected, undefined for all sites. - * @param {boolean} [force] Wether the execution is forced (manual sync). - * @return {Promise} Promise resolved when done, rejected if failure. + * @param siteId ID of the site affected, undefined for all sites. + * @param force Wether the execution is forced (manual sync). + * @return Promise resolved when done, rejected if failure. */ execute(siteId?: string, force?: boolean): Promise { return this.quizSync.syncAllQuizzes(siteId, force); @@ -40,7 +40,7 @@ export class AddonModQuizSyncCronHandler implements CoreCronHandler { /** * Get the time between consecutive executions. * - * @return {number} Time between consecutive executions (in ms). + * @return Time between consecutive executions (in ms). */ getInterval(): number { return this.quizSync.syncInterval; diff --git a/src/addon/mod/quiz/quiz.module.ts b/src/addon/mod/quiz/quiz.module.ts index 93cc7ae2f..e77749f5f 100644 --- a/src/addon/mod/quiz/quiz.module.ts +++ b/src/addon/mod/quiz/quiz.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/addon/mod/resource/components/components.module.ts b/src/addon/mod/resource/components/components.module.ts index 1f2cdda66..cb608dcb2 100644 --- a/src/addon/mod/resource/components/components.module.ts +++ b/src/addon/mod/resource/components/components.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/addon/mod/resource/components/index/addon-mod-resource-index.html b/src/addon/mod/resource/components/index/addon-mod-resource-index.html index 45056cdcd..7d2e36a94 100644 --- a/src/addon/mod/resource/components/index/addon-mod-resource-index.html +++ b/src/addon/mod/resource/components/index/addon-mod-resource-index.html @@ -6,21 +6,21 @@ - + - + - +
- +
diff --git a/src/addon/mod/resource/components/index/index.ts b/src/addon/mod/resource/components/index/index.ts index ab8ccffac..12913fe40 100644 --- a/src/addon/mod/resource/components/index/index.ts +++ b/src/addon/mod/resource/components/index/index.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -36,6 +36,7 @@ export class AddonModResourceIndexComponent extends CoreCourseModuleMainResource mode: string; src: string; contentText: string; + displayDescription = true; constructor(injector: Injector, private resourceProvider: AddonModResourceProvider, private courseProvider: CoreCourseProvider, private appProvider: CoreAppProvider, private prefetchHandler: AddonModResourcePrefetchHandler, @@ -64,7 +65,7 @@ export class AddonModResourceIndexComponent extends CoreCourseModuleMainResource /** * Perform the invalidate content function. * - * @return {Promise} Resolved when done. + * @return Resolved when done. */ protected invalidateContent(): Promise { return this.resourceProvider.invalidateContent(this.module.id, this.courseId); @@ -73,8 +74,8 @@ export class AddonModResourceIndexComponent extends CoreCourseModuleMainResource /** * Download resource contents. * - * @param {boolean} [refresh] Whether we're refreshing data. - * @return {Promise} Promise resolved when done. + * @param refresh Whether we're refreshing data. + * @return Promise resolved when done. */ protected fetchContent(refresh?: boolean): Promise { // Load module contents if needed. Passing refresh is needed to force reloading contents. @@ -96,6 +97,8 @@ export class AddonModResourceIndexComponent extends CoreCourseModuleMainResource }).then((resource) => { if (resource) { this.description = resource.intro || resource.description; + const options = this.textUtils.unserialize(resource.displayoptions) || {}; + this.displayDescription = typeof options.printintro == 'undefined' || !!options.printintro; this.dataRetrieved.emit(resource); } @@ -131,12 +134,13 @@ export class AddonModResourceIndexComponent extends CoreCourseModuleMainResource return this.resourceHelper.getEmbeddedHtml(this.module, this.courseId).then((html) => { this.contentText = html; + + this.mode = this.contentText.length > 0 ? 'embedded' : 'external'; }); } else { this.mode = 'external'; } - }).then(() => { - // All data obtained, now fill the context menu. + }).finally(() => { this.fillContextMenu(refresh); }); } diff --git a/src/addon/mod/resource/pages/index/index.html b/src/addon/mod/resource/pages/index/index.html index bae9e1278..d52cc3f30 100644 --- a/src/addon/mod/resource/pages/index/index.html +++ b/src/addon/mod/resource/pages/index/index.html @@ -1,6 +1,6 @@ - + diff --git a/src/addon/mod/resource/pages/index/index.module.ts b/src/addon/mod/resource/pages/index/index.module.ts index 6d6dc347d..1a207614d 100644 --- a/src/addon/mod/resource/pages/index/index.module.ts +++ b/src/addon/mod/resource/pages/index/index.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/addon/mod/resource/pages/index/index.ts b/src/addon/mod/resource/pages/index/index.ts index 0bc1de345..331a1566d 100644 --- a/src/addon/mod/resource/pages/index/index.ts +++ b/src/addon/mod/resource/pages/index/index.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -40,7 +40,7 @@ export class AddonModResourceIndexPage { /** * Update some data based on the resource instance. * - * @param {any} resource Resource instance. + * @param resource Resource instance. */ updateData(resource: any): void { this.title = resource.name || this.title; diff --git a/src/addon/mod/resource/providers/helper.ts b/src/addon/mod/resource/providers/helper.ts index 0f216e4aa..401783103 100644 --- a/src/addon/mod/resource/providers/helper.ts +++ b/src/addon/mod/resource/providers/helper.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -46,38 +46,22 @@ export class AddonModResourceHelperProvider { /** * Get the HTML to display an embedded resource. * - * @param {any} module The module object. - * @param {number} courseId The course ID. - * @return {Promise} Promise resolved with the HTML. + * @param module The module object. + * @param courseId The course ID. + * @return Promise resolved with the HTML. */ getEmbeddedHtml(module: any, courseId: number): Promise { return this.courseHelper.downloadModuleWithMainFileIfNeeded(module, courseId, AddonModResourceProvider.COMPONENT, module.id, module.contents).then((result) => { - const file = module.contents[0], - ext = this.mimetypeUtils.getFileExtension(file.filename), - type = this.mimetypeUtils.getExtensionType(ext), - mimeType = this.mimetypeUtils.getMimeType(ext); - - if (type == 'image') { - return ''; - } - - if (type == 'audio' || type == 'video') { - return '<' + type + ' controls title="' + file.filename + '"" src="' + result.path + '">' + - '' + - ''; - } - - // Shouldn't reach here, the user should have called CoreMimetypeUtilsProvider#canBeEmbedded. - return ''; + return this.mimetypeUtils.getEmbeddedHtml(module.contents[0], result.path); }); } /** * Download all the files needed and returns the src of the iframe. * - * @param {any} module The module object. - * @return {Promise} Promise resolved with the iframe src. + * @param module The module object. + * @return Promise resolved with the iframe src. */ getIframeSrc(module: any): Promise { if (!module.contents.length) { @@ -98,7 +82,7 @@ export class AddonModResourceHelperProvider { // Error getting directory, there was an error downloading or we're in browser. Return online URL. if (this.appProvider.isOnline() && mainFile.fileurl) { // This URL is going to be injected in an iframe, we need this to make it work. - return Promise.resolve(this.sitesProvider.getCurrentSite().fixPluginfileURL(mainFile.fileurl)); + return this.sitesProvider.getCurrentSite().checkAndFixPluginfileURL(mainFile.fileurl); } return Promise.reject(null); @@ -108,9 +92,9 @@ export class AddonModResourceHelperProvider { /** * Whether the resource has to be displayed embedded. * - * @param {any} module The module object. - * @param {number} [display] The display mode (if available). - * @return {boolean} Whether the resource should be displayed embeded. + * @param module The module object. + * @param display The display mode (if available). + * @return Whether the resource should be displayed embeded. */ isDisplayedEmbedded(module: any, display: number): boolean { if ((!module.contents.length && !module.contentsinfo) || !this.fileProvider.isAvailable() || @@ -132,8 +116,8 @@ export class AddonModResourceHelperProvider { /** * Whether the resource has to be displayed in an iframe. * - * @param {any} module The module object. - * @return {boolean} Whether the resource should be displayed in an iframe. + * @param module The module object. + * @return Whether the resource should be displayed in an iframe. */ isDisplayedInIframe(module: any): boolean { if ((!module.contents.length && !module.contentsinfo) || !this.fileProvider.isAvailable()) { @@ -155,8 +139,8 @@ export class AddonModResourceHelperProvider { /** * Check if the resource is a Nextcloud file. * - * @param {any} module Module to check. - * @return {boolean} Whether it's a Nextcloud file. + * @param module Module to check. + * @return Whether it's a Nextcloud file. */ isNextcloudFile(module: any): boolean { if (module.contentsinfo) { @@ -169,9 +153,9 @@ export class AddonModResourceHelperProvider { /** * Opens a file of the resource activity. * - * @param {any} module Module where to get the contents. - * @param {number} courseId Course Id, used for completion purposes. - * @return {Promise} Resolved when done. + * @param module Module where to get the contents. + * @param courseId Course Id, used for completion purposes. + * @return Resolved when done. */ openModuleFile(module: any, courseId: number): Promise { const modal = this.domUtils.showModalLoading(); diff --git a/src/addon/mod/resource/providers/link-handler.ts b/src/addon/mod/resource/providers/link-handler.ts index 2a84bbf49..a9cc54f1a 100644 --- a/src/addon/mod/resource/providers/link-handler.ts +++ b/src/addon/mod/resource/providers/link-handler.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -15,6 +15,7 @@ import { Injectable } from '@angular/core'; import { CoreContentLinksModuleIndexHandler } from '@core/contentlinks/classes/module-index-handler'; import { CoreCourseHelperProvider } from '@core/course/providers/helper'; +import { AddonModResourceProvider } from './resource'; /** * Handler to treat links to resource. @@ -23,7 +24,21 @@ import { CoreCourseHelperProvider } from '@core/course/providers/helper'; export class AddonModResourceLinkHandler extends CoreContentLinksModuleIndexHandler { name = 'AddonModResourceLinkHandler'; - constructor(courseHelper: CoreCourseHelperProvider) { - super(courseHelper, 'AddonModResource', 'resource'); + constructor(courseHelper: CoreCourseHelperProvider, + protected resourceProvider: AddonModResourceProvider) { + super(courseHelper, 'AddonModResource', 'resource', 'r'); + } + + /** + * Check if the handler is enabled for a certain site (site + user) and a URL. + * + * @param siteId The site ID. + * @param url The URL to treat. + * @param params The params of the URL. E.g. 'mysite.com?id=1' -> {id: 1} + * @param courseId Course ID related to the URL. Optional but recommended. + * @return Whether the handler is enabled for the URL and site. + */ + isEnabled(siteId: string, url: string, params: any, courseId?: number): boolean | Promise { + return this.resourceProvider.isPluginEnabled(); } } diff --git a/src/addon/mod/resource/providers/list-link-handler.ts b/src/addon/mod/resource/providers/list-link-handler.ts index 8acc2d435..5101685ad 100644 --- a/src/addon/mod/resource/providers/list-link-handler.ts +++ b/src/addon/mod/resource/providers/list-link-handler.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -33,7 +33,7 @@ export class AddonModResourceListLinkHandler extends CoreContentLinksModuleListH /** * Check if the handler is enabled on a site level. * - * @return {boolean|Promise} Whether or not the handler is enabled on a site level. + * @return Whether or not the handler is enabled on a site level. */ isEnabled(): boolean | Promise { return this.resourceProvider.isPluginEnabled(); diff --git a/src/addon/mod/resource/providers/module-handler.ts b/src/addon/mod/resource/providers/module-handler.ts index 573546982..735bcae53 100644 --- a/src/addon/mod/resource/providers/module-handler.ts +++ b/src/addon/mod/resource/providers/module-handler.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -25,6 +25,7 @@ import { CoreTextUtilsProvider } from '@providers/utils/text'; import { CoreTimeUtilsProvider } from '@providers/utils/time'; import { CoreCourseModulePrefetchDelegate } from '@core/course/providers/module-prefetch-delegate'; import { CoreConstants } from '@core/constants'; +import { CoreWSExternalFile } from '@providers/ws'; /** * Handler to support resource modules. @@ -57,7 +58,7 @@ export class AddonModResourceModuleHandler implements CoreCourseModuleHandler { /** * Check if the handler is enabled on a site level. * - * @return {boolean|Promise} Whether or not the handler is enabled on a site level. + * @return Whether or not the handler is enabled on a site level. */ isEnabled(): boolean | Promise { return this.resourceProvider.isPluginEnabled(); @@ -66,10 +67,10 @@ export class AddonModResourceModuleHandler implements CoreCourseModuleHandler { /** * Get the data required to display the module in the course contents view. * - * @param {any} module The module object. - * @param {number} courseId The course ID. - * @param {number} sectionId The section ID. - * @return {CoreCourseModuleHandlerData} Data to render the module. + * @param module The module object. + * @param courseId The course ID. + * @param sectionId The section ID. + * @return Data to render the module. */ getData(module: any, courseId: number, sectionId: number): CoreCourseModuleHandlerData { const updateStatus = (status: string): void => { @@ -116,9 +117,9 @@ export class AddonModResourceModuleHandler implements CoreCourseModuleHandler { /** * Returns if contents are loaded to show open button. * - * @param {any} module The module object. - * @param {number} courseId The course ID. - * @return {Promise} Resolved when done. + * @param module The module object. + * @param courseId The course ID. + * @return Resolved when done. */ protected hideOpenButton(module: any, courseId: number): Promise { let promise; @@ -140,13 +141,13 @@ export class AddonModResourceModuleHandler implements CoreCourseModuleHandler { /** * Returns the activity icon and data. * - * @param {any} module The module object. - * @param {number} courseId The course ID. - * @return {Promise} Resource data. + * @param module The module object. + * @param courseId The course ID. + * @return Resource data. */ protected getResourceData(module: any, courseId: number, handlerData: CoreCourseModuleHandlerData): Promise { const promises = []; - let infoFiles = [], + let infoFiles: CoreWSExternalFile[] = [], options: any = {}; // Check if the button needs to be shown or not. @@ -221,12 +222,11 @@ export class AddonModResourceModuleHandler implements CoreCourseModuleHandler { } } - if (resourceData.icon == '') { - resourceData.icon = this.courseProvider.getModuleIconSrc(this.modName, module.modicon); - } resourceData.extra += extra.join(' '); - } else { - // No files, just set the icon. + } + + // No previously set, just set the icon. + if (resourceData.icon == '') { resourceData.icon = this.courseProvider.getModuleIconSrc(this.modName, module.modicon); } @@ -238,9 +238,9 @@ export class AddonModResourceModuleHandler implements CoreCourseModuleHandler { * Get the component to render the module. This is needed to support singleactivity course format. * The component returned must implement CoreCourseModuleMainComponent. * - * @param {any} course The course object. - * @param {any} module The module object. - * @return {any} The component to use, undefined if not found. + * @param course The course object. + * @param module The module object. + * @return The component to use, undefined if not found. */ getMainComponent(course: any, module: any): any { return AddonModResourceIndexComponent; diff --git a/src/addon/mod/resource/providers/pluginfile-handler.ts b/src/addon/mod/resource/providers/pluginfile-handler.ts index ecd7154f1..f9179a955 100644 --- a/src/addon/mod/resource/providers/pluginfile-handler.ts +++ b/src/addon/mod/resource/providers/pluginfile-handler.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -26,8 +26,8 @@ export class AddonModResourcePluginFileHandler implements CorePluginFileHandler /** * Return the RegExp to match the revision on pluginfile URLs. * - * @param {string[]} args Arguments of the pluginfile URL defining component and filearea at least. - * @return {RegExp} RegExp to match the revision on pluginfile URLs. + * @param args Arguments of the pluginfile URL defining component and filearea at least. + * @return RegExp to match the revision on pluginfile URLs. */ getComponentRevisionRegExp(args: string[]): RegExp { // Check filearea. @@ -40,11 +40,20 @@ export class AddonModResourcePluginFileHandler implements CorePluginFileHandler /** * Should return the string to remove the revision on pluginfile url. * - * @param {string[]} args Arguments of the pluginfile URL defining component and filearea at least. - * @return {string} String to remove the revision on pluginfile url. + * @param args Arguments of the pluginfile URL defining component and filearea at least. + * @return String to remove the revision on pluginfile url. */ getComponentRevisionReplace(args: string[]): string { // Component + Filearea + Revision return '/mod_resource/content/0/'; } + + /** + * Whether or not the handler is enabled on a site level. + * + * @return Whether or not the handler is enabled on a site level. + */ + isEnabled(): boolean | Promise { + return true; + } } diff --git a/src/addon/mod/resource/providers/prefetch-handler.ts b/src/addon/mod/resource/providers/prefetch-handler.ts index 7c3e7be65..27c52989d 100644 --- a/src/addon/mod/resource/providers/prefetch-handler.ts +++ b/src/addon/mod/resource/providers/prefetch-handler.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -24,6 +24,8 @@ import { CoreCourseResourcePrefetchHandlerBase } from '@core/course/classes/reso import { AddonModResourceProvider } from './resource'; import { AddonModResourceHelperProvider } from './helper'; import { CoreConstants } from '@core/constants'; +import { CoreFilterHelperProvider } from '@core/filter/providers/helper'; +import { CorePluginFileDelegate } from '@providers/plugin-file-delegate'; /** * Handler to prefetch resources. @@ -34,21 +36,29 @@ export class AddonModResourcePrefetchHandler extends CoreCourseResourcePrefetchH modName = 'resource'; component = AddonModResourceProvider.COMPONENT; - constructor(translate: TranslateService, appProvider: CoreAppProvider, utils: CoreUtilsProvider, - courseProvider: CoreCourseProvider, filepoolProvider: CoreFilepoolProvider, sitesProvider: CoreSitesProvider, - domUtils: CoreDomUtilsProvider, protected resourceProvider: AddonModResourceProvider, + constructor(translate: TranslateService, + appProvider: CoreAppProvider, + utils: CoreUtilsProvider, + courseProvider: CoreCourseProvider, + filepoolProvider: CoreFilepoolProvider, + sitesProvider: CoreSitesProvider, + domUtils: CoreDomUtilsProvider, + filterHelper: CoreFilterHelperProvider, + pluginFileDelegate: CorePluginFileDelegate, + protected resourceProvider: AddonModResourceProvider, protected resourceHelper: AddonModResourceHelperProvider) { - super(translate, appProvider, utils, courseProvider, filepoolProvider, sitesProvider, domUtils); + super(translate, appProvider, utils, courseProvider, filepoolProvider, sitesProvider, domUtils, filterHelper, + pluginFileDelegate); } /** * Return the status to show based on current status. * - * @param {any} module Module. - * @param {string} status The current status. - * @param {boolean} canCheck Whether the site allows checking for updates. - * @return {string} Status to display. + * @param module Module. + * @param status The current status. + * @param canCheck Whether the site allows checking for updates. + * @return Status to display. */ determineStatus(module: any, status: string, canCheck: boolean): string { if (status == CoreConstants.DOWNLOADED && module) { @@ -72,13 +82,13 @@ export class AddonModResourcePrefetchHandler extends CoreCourseResourcePrefetchH /** * Download or prefetch the content. * - * @param {any} module The module object returned by WS. - * @param {number} courseId Course ID. - * @param {boolean} [prefetch] True to prefetch, false to download right away. - * @param {string} [dirPath] Path of the directory where to store all the content files. This is to keep the files - * relative paths and make the package work in an iframe. Undefined to download the files - * in the filepool root folder. - * @return {Promise} Promise resolved when all content is downloaded. Data returned is not reliable. + * @param module The module object returned by WS. + * @param courseId Course ID. + * @param prefetch True to prefetch, false to download right away. + * @param dirPath Path of the directory where to store all the content files. This is to keep the files + * relative paths and make the package work in an iframe. Undefined to download the files + * in the filepool root folder. + * @return Promise resolved when all content is downloaded. Data returned is not reliable. */ downloadOrPrefetch(module: any, courseId: number, prefetch?: boolean, dirPath?: string): Promise { let promise; @@ -105,9 +115,9 @@ export class AddonModResourcePrefetchHandler extends CoreCourseResourcePrefetchH /** * Invalidate the prefetched content. * - * @param {number} moduleId The module ID. - * @param {number} courseId Course ID the module belongs to. - * @return {Promise} Promise resolved when the data is invalidated. + * @param moduleId The module ID. + * @param courseId Course ID the module belongs to. + * @return Promise resolved when the data is invalidated. */ invalidateContent(moduleId: number, courseId: number): Promise { return this.resourceProvider.invalidateContent(moduleId, courseId); @@ -116,9 +126,9 @@ export class AddonModResourcePrefetchHandler extends CoreCourseResourcePrefetchH /** * Invalidate WS calls needed to determine module status. * - * @param {any} module Module. - * @param {number} courseId Course ID the module belongs to. - * @return {Promise} Promise resolved when invalidated. + * @param module Module. + * @param courseId Course ID the module belongs to. + * @return Promise resolved when invalidated. */ invalidateModule(module: any, courseId: number): Promise { const promises = []; @@ -132,9 +142,9 @@ export class AddonModResourcePrefetchHandler extends CoreCourseResourcePrefetchH /** * Check if a resource is downloadable. * - * @param {any} module Module to check. - * @param {number} courseId Course ID the module belongs to. - * @return {Promise} Promise resolved with true if downloadable, resolved with false otherwise. + * @param module Module to check. + * @param courseId Course ID the module belongs to. + * @return Promise resolved with true if downloadable, resolved with false otherwise. */ isDownloadable(module: any, courseId: number): Promise { if (this.sitesProvider.getCurrentSite().isVersionGreaterEqualThan('3.7')) { @@ -151,7 +161,7 @@ export class AddonModResourcePrefetchHandler extends CoreCourseResourcePrefetchH /** * Whether or not the handler is enabled on a site level. * - * @return {boolean|Promise} A boolean, or a promise resolved with a boolean, indicating if the handler is enabled. + * @return A boolean, or a promise resolved with a boolean, indicating if the handler is enabled. */ isEnabled(): boolean | Promise { return this.resourceProvider.isPluginEnabled(); diff --git a/src/addon/mod/resource/providers/resource.ts b/src/addon/mod/resource/providers/resource.ts index 3de250b9b..5445ec3b1 100644 --- a/src/addon/mod/resource/providers/resource.ts +++ b/src/addon/mod/resource/providers/resource.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -20,6 +20,7 @@ import { CoreCourseProvider } from '@core/course/providers/course'; import { CoreCourseLogHelperProvider } from '@core/course/providers/log-helper'; import { CoreFilepoolProvider } from '@providers/filepool'; import { CoreSite } from '@classes/site'; +import { CoreWSExternalWarning, CoreWSExternalFile } from '@providers/ws'; /** * Service that provides some features for resources. @@ -40,8 +41,8 @@ export class AddonModResourceProvider { /** * Get cache key for resource data WS calls. * - * @param {number} courseId Course ID. - * @return {string} Cache key. + * @param courseId Course ID. + * @return Cache key. */ protected getResourceCacheKey(courseId: number): string { return this.ROOT_CACHE_KEY + 'resource:' + courseId; @@ -50,13 +51,13 @@ export class AddonModResourceProvider { /** * Get a resource data. * - * @param {number} courseId Course ID. - * @param {string} key Name of the property to check. - * @param {any} value Value to search. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the resource is retrieved. + * @param courseId Course ID. + * @param key Name of the property to check. + * @param value Value to search. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the resource is retrieved. */ - protected getResourceDataByKey(courseId: number, key: string, value: any, siteId?: string): Promise { + protected getResourceDataByKey(courseId: number, key: string, value: any, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { const params = { courseids: [courseId] @@ -66,7 +67,9 @@ export class AddonModResourceProvider { updateFrequency: CoreSite.FREQUENCY_RARELY }; - return site.read('mod_resource_get_resources_by_courses', params, preSets).then((response) => { + return site.read('mod_resource_get_resources_by_courses', params, preSets) + .then((response: AddonModResourceGetResourcesByCoursesResult): any => { + if (response && response.resources) { const currentResource = response.resources.find((resource) => { return resource[key] == value; @@ -84,22 +87,22 @@ export class AddonModResourceProvider { /** * Get a resource by course module ID. * - * @param {number} courseId Course ID. - * @param {number} cmId Course module ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the resource is retrieved. + * @param courseId Course ID. + * @param cmId Course module ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the resource is retrieved. */ - getResourceData(courseId: number, cmId: number, siteId?: string): Promise { + getResourceData(courseId: number, cmId: number, siteId?: string): Promise { return this.getResourceDataByKey(courseId, 'coursemodule', cmId, siteId); } /** * Invalidate the prefetched content. * - * @param {number} moduleId The module ID. - * @param {number} courseId Course ID of the module. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the data is invalidated. + * @param moduleId The module ID. + * @param courseId Course ID of the module. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the data is invalidated. */ invalidateContent(moduleId: number, courseId: number, siteId?: string): Promise { siteId = siteId || this.sitesProvider.getCurrentSiteId(); @@ -116,9 +119,9 @@ export class AddonModResourceProvider { /** * Invalidates resource data. * - * @param {number} courseid Course ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the data is invalidated. + * @param courseid Course ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the data is invalidated. */ invalidateResourceData(courseId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -129,7 +132,7 @@ export class AddonModResourceProvider { /** * Returns whether or not getResource WS available or not. * - * @return {boolean} If WS is abalaible. + * @return If WS is abalaible. * @since 3.3 */ isGetResourceWSAvailable(): boolean { @@ -139,8 +142,8 @@ export class AddonModResourceProvider { /** * Return whether or not the plugin is enabled. * - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with true if plugin is enabled, rejected or resolved with false otherwise. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with true if plugin is enabled, rejected or resolved with false otherwise. */ isPluginEnabled(siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -151,10 +154,10 @@ export class AddonModResourceProvider { /** * Report the resource as being viewed. * - * @param {number} id Module ID. - * @param {string} [name] Name of the resource. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the WS call is successful. + * @param id Module ID. + * @param name Name of the resource. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the WS call is successful. */ logView(id: number, name?: string, siteId?: string): Promise { const params = { @@ -165,3 +168,37 @@ export class AddonModResourceProvider { 'resource', {}, siteId); } } + +/** + * Resource returned by mod_resource_get_resources_by_courses. + */ +export type AddonModResourceResource = { + id: number; // Module id. + coursemodule: number; // Course module id. + course: number; // Course id. + name: string; // Page name. + intro: string; // Summary. + introformat: number; // Intro format (1 = HTML, 0 = MOODLE, 2 = PLAIN or 4 = MARKDOWN). + introfiles: CoreWSExternalFile[]; + contentfiles: CoreWSExternalFile[]; + tobemigrated: number; // Whether this resource was migrated. + legacyfiles: number; // Legacy files flag. + legacyfileslast: number; // Legacy files last control flag. + display: number; // How to display the resource. + displayoptions: string; // Display options (width, height). + filterfiles: number; // If filters should be applied to the resource content. + revision: number; // Incremented when after each file changes, to avoid cache. + timemodified: number; // Last time the resource was modified. + section: number; // Course section id. + visible: number; // Module visibility. + groupmode: number; // Group mode. + groupingid: number; // Grouping id. +}; + +/** + * Result of WS mod_resource_get_resources_by_courses. + */ +export type AddonModResourceGetResourcesByCoursesResult = { + resources: AddonModResourceResource[]; + warnings?: CoreWSExternalWarning[]; +}; diff --git a/src/addon/mod/resource/resource.module.ts b/src/addon/mod/resource/resource.module.ts index 7d0726dca..d2d2be4e9 100644 --- a/src/addon/mod/resource/resource.module.ts +++ b/src/addon/mod/resource/resource.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/addon/mod/scorm/classes/data-model-12.ts b/src/addon/mod/scorm/classes/data-model-12.ts index 196d36fda..ef615fede 100644 --- a/src/addon/mod/scorm/classes/data-model-12.ts +++ b/src/addon/mod/scorm/classes/data-model-12.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -86,14 +86,14 @@ export class AddonModScormDataModel12 { /** * Constructor. * - * @param {CoreEventsProvider} eventsProvider Events provider instance. - * @param {AddonModScormProvider} scormProvider SCORM provider instance. - * @param {any} scorm SCORM. - * @param {number} scoId Current SCO ID. - * @param {number} attempt Attempt number. - * @param {any} userData The user default data. - * @param {string} [mode] Mode being played. By default, MODENORMAL. - * @param {boolean} offline Whether the attempt is offline. + * @param eventsProvider Events provider instance. + * @param scormProvider SCORM provider instance. + * @param scorm SCORM. + * @param scoId Current SCO ID. + * @param attempt Attempt number. + * @param userData The user default data. + * @param mode Mode being played. By default, MODENORMAL. + * @param offline Whether the attempt is offline. */ constructor(protected eventsProvider: CoreEventsProvider, protected scormProvider: AddonModScormProvider, protected siteId: string, protected scorm: any, protected scoId: number, protected attempt: number, @@ -108,9 +108,9 @@ export class AddonModScormDataModel12 { /** * Utility function for adding two times in format hh:mm:ss. * - * @param {string} first First time. - * @param {string} second Second time. - * @return {string} Total time. + * @param first First time. + * @param second Second time. + * @return Total time. */ protected addTime(first: string, second: string): string { const sFirst = first.split(':'), @@ -165,8 +165,8 @@ export class AddonModScormDataModel12 { /** * Utility function for cloning an object * - * @param {any} obj The object to be cloned - * @return {any} The object cloned + * @param obj The object to be cloned + * @return The object cloned */ protected cloneObj(obj: any): any { if (obj == null || typeof(obj) != 'object') { @@ -184,7 +184,7 @@ export class AddonModScormDataModel12 { /** * Collect all the user tracking data that must be persisted in the system, this is usually called by LMSCommit(). * - * @return {any[]} Collected data. + * @return Collected data. */ protected collectData(): any[] { const data = []; @@ -250,8 +250,8 @@ export class AddonModScormDataModel12 { /** * Get the value of the given element from the non-persistent (current) user data. * - * @param {string} el The element - * @return {any} The element value + * @param el The element + * @return The element value */ protected getEl(el: string): any { if (typeof this.currentUserData[this.scoId] != 'undefined' && typeof this.currentUserData[this.scoId][el] != 'undefined') { @@ -264,7 +264,7 @@ export class AddonModScormDataModel12 { /** * Initialize the model. * - * @param {any} userData The user default data. + * @param userData The user default data. */ protected init(userData: any): void { // Prepare the definition array containing the default values. @@ -463,8 +463,8 @@ export class AddonModScormDataModel12 { /** * Commit the changes. * - * @param {string} param Param. - * @return {string} "true" if success, "false" otherwise. + * @param param Param. + * @return "true" if success, "false" otherwise. */ LMSCommit(param: string): string { if (this.timeout) { @@ -497,8 +497,8 @@ export class AddonModScormDataModel12 { /** * Finish the data model. * - * @param {string} param Param. - * @return {string} "true" if success, "false" otherwise. + * @param param Param. + * @return "true" if success, "false" otherwise. */ LMSFinish(param: string): string { this.errorCode = '0'; @@ -540,8 +540,8 @@ export class AddonModScormDataModel12 { /** * Get diagnostic. * - * @param {string} param Param. - * @return {string} Result. + * @param param Param. + * @return Result. */ LMSGetDiagnostic(param: string): string { if (param == '') { @@ -554,8 +554,8 @@ export class AddonModScormDataModel12 { /** * Get the error message for a certain code. * - * @param {string} param Error code. - * @return {string} Error message. + * @param param Error code. + * @return Error message. */ LMSGetErrorString(param: string): string { if (param != '') { @@ -568,7 +568,7 @@ export class AddonModScormDataModel12 { /** * Get the last error code. * - * @return {string} Last error code. + * @return Last error code. */ LMSGetLastError(): string { return this.errorCode; @@ -577,8 +577,8 @@ export class AddonModScormDataModel12 { /** * Get the value of a certain element. * - * @param {string} element Name of the element to get. - * @return {string} Value. + * @param element Name of the element to get. + * @return Value. */ LMSGetValue(element: string): string { this.errorCode = '0'; @@ -633,8 +633,8 @@ export class AddonModScormDataModel12 { /** * Initialize the data model. * - * @param {string} param Param. - * @return {string} "true" if initialized, "false" otherwise. + * @param param Param. + * @return "true" if initialized, "false" otherwise. */ LMSInitialize(param: string): string { this.errorCode = '0'; @@ -658,9 +658,9 @@ export class AddonModScormDataModel12 { /** * Set the value of a certain element. * - * @param {string} element Name of the element to set. - * @param {any} value Value to set. - * @return {string} "true" if success, "false" otherwise. + * @param element Name of the element to set. + * @param value Value to set. + * @return "true" if success, "false" otherwise. */ LMSSetValue(element: string, value: any): string { this.errorCode = '0'; @@ -794,7 +794,7 @@ export class AddonModScormDataModel12 { * The scoId is like a pointer to be able to retrieve the SCO default values and set the new ones in the overall SCORM * data structure. * - * @param {number} scoId The new SCO id. + * @param scoId The new SCO id. */ loadSco(scoId: number): void { this.scoId = scoId; @@ -803,8 +803,8 @@ export class AddonModScormDataModel12 { /** * Set the value of the given element in the non-persistent (current) user data. * - * @param {string} el The element. - * @param {any} value The value. + * @param el The element. + * @param value The value. */ protected setEl(el: string, value: any): void { if (typeof this.currentUserData[this.scoId] == 'undefined') { @@ -817,7 +817,7 @@ export class AddonModScormDataModel12 { /** * Set offline mode to true or false. * - * @param {boolean} offline True if offline, false otherwise. + * @param offline True if offline, false otherwise. */ setOffline(offline: boolean): void { this.offline = offline; @@ -826,8 +826,8 @@ export class AddonModScormDataModel12 { /** * Persist the current user data (this is usually called by LMSCommit). * - * @param {boolean} storeTotalTime If true, we need to calculate the total time too. - * @return {boolean} True if success, false otherwise. + * @param storeTotalTime If true, we need to calculate the total time too. + * @return True if success, false otherwise. */ protected storeData(storeTotalTime?: boolean): boolean { let tracks; @@ -882,7 +882,7 @@ export class AddonModScormDataModel12 { /** * Utility function for calculating the total time spent in the SCO. * - * @return {any} Total time element. + * @return Total time element. */ protected totalTime(): any { const totalTime = this.addTime(this.getEl('cmi.core.total_time'), this.getEl('cmi.core.session_time')); @@ -893,7 +893,7 @@ export class AddonModScormDataModel12 { /** * Convenience function to trigger events. * - * @param {string} name Name of the event to trigger. + * @param name Name of the event to trigger. */ protected triggerEvent(name: string): void { this.eventsProvider.trigger(name, { diff --git a/src/addon/mod/scorm/components/components.module.ts b/src/addon/mod/scorm/components/components.module.ts index 2a9c6569b..41b0313f8 100644 --- a/src/addon/mod/scorm/components/components.module.ts +++ b/src/addon/mod/scorm/components/components.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/addon/mod/scorm/components/index/addon-mod-scorm-index.html b/src/addon/mod/scorm/components/index/addon-mod-scorm-index.html index 5aeb8dec5..c7f421705 100644 --- a/src/addon/mod/scorm/components/index/addon-mod-scorm-index.html +++ b/src/addon/mod/scorm/components/index/addon-mod-scorm-index.html @@ -7,14 +7,14 @@ - + - +
@@ -26,7 +26,9 @@ -

{{ 'addon.mod_scorm.attempts' | translate }}

+
+

{{ 'addon.mod_scorm.attempts' | translate }}

+
@@ -49,8 +51,8 @@

{{ 'addon.mod_scorm.gradeforattempt' | translate }} {{attempt.number}}

{{ attempt.grade }}

{{ 'addon.mod_scorm.cannotcalculategrade' | translate }}

-

{{ 'addon.mod_scorm.offlineattemptnote' | translate }}

-

{{ 'addon.mod_scorm.offlineattemptovermax' | translate }}

+

{{ 'addon.mod_scorm.offlineattemptnote' | translate }}

+

{{ 'addon.mod_scorm.offlineattemptovermax' | translate }}

{{ 'addon.mod_scorm.grademethod' | translate }}

@@ -93,11 +95,12 @@

{{ 'addon.mod_scorm.dataattemptshown' | translate:{number: attemptToContinue} }}

{{ currentOrganization.title }}

-
+

- - {{ sco.title }} - {{ sco.title }} + + + + ({{ 'addon.mod_scorm.score' | translate }}: {{sco.score_raw}})

diff --git a/src/addon/mod/scorm/components/index/index.scss b/src/addon/mod/scorm/components/index/index.scss index 67f01dcb6..763ae6067 100644 --- a/src/addon/mod/scorm/components/index/index.scss +++ b/src/addon/mod/scorm/components/index/index.scss @@ -1,9 +1,13 @@ -ion-app.app-root addon-mod-scorm-index { - +ion-app.app-root addon-mod-scorm-index, +ion-app.app-root page-addon-mod-scorm-toc { .addon-mod_scorm-toc { - img { - width: auto; - display: inline; + // Hide all non sco icons using css to maintain padding. + ion-icon { + opacity: 0; + } + + .addon-mod_scorm-type-sco ion-icon { + opacity: 1 } } } diff --git a/src/addon/mod/scorm/components/index/index.ts b/src/addon/mod/scorm/components/index/index.ts index 6672548b7..f2fd0a9a2 100644 --- a/src/addon/mod/scorm/components/index/index.ts +++ b/src/addon/mod/scorm/components/index/index.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -107,7 +107,7 @@ export class AddonModScormIndexComponent extends CoreCourseModuleMainActivityCom /** * Download a SCORM package or restores an ongoing download. * - * @return {Promise} Promise resolved when done. + * @return Promise resolved when done. */ protected downloadScormPackage(): Promise { this.downloading = true; @@ -143,10 +143,10 @@ export class AddonModScormIndexComponent extends CoreCourseModuleMainActivityCom /** * Get the SCORM data. * - * @param {boolean} [refresh=false] If it's refreshing content. - * @param {boolean} [sync=false] If it should try to sync. - * @param {boolean} [showErrors=false] If show errors to the user of hide them. - * @return {Promise} Promise resolved when done. + * @param refresh If it's refreshing content. + * @param sync If it should try to sync. + * @param showErrors If show errors to the user of hide them. + * @return Promise resolved when done. */ protected fetchContent(refresh: boolean = false, sync: boolean = false, showErrors: boolean = false): Promise { @@ -254,8 +254,7 @@ export class AddonModScormIndexComponent extends CoreCourseModuleMainActivityCom } }); }); - }).then(() => { - // All data obtained, now fill the context menu. + }).finally(() => { this.fillContextMenu(refresh); }); } @@ -263,7 +262,7 @@ export class AddonModScormIndexComponent extends CoreCourseModuleMainActivityCom /** * Fetch the structure of the SCORM (TOC). * - * @return {Promise} Promise resolved when done. + * @return Promise resolved when done. */ protected fetchStructure(): Promise { return this.scormProvider.getOrganizations(this.scorm.id).then((organizations) => { @@ -285,10 +284,10 @@ export class AddonModScormIndexComponent extends CoreCourseModuleMainActivityCom /** * Get the grade of an attempt and add it to the scorm attempts list. * - * @param {number} attempt The attempt number. - * @param {boolean} offline Whether it's an offline attempt. - * @param {any} attempts Object where to add the attempt. - * @return {Promise} Promise resolved when done. + * @param attempt The attempt number. + * @param offline Whether it's an offline attempt. + * @param attempts Object where to add the attempt. + * @return Promise resolved when done. */ protected getAttemptGrade(attempt: number, offline: boolean, attempts: any): Promise { return this.scormProvider.getAttemptGrade(this.scorm, attempt, offline).then((grade) => { @@ -302,7 +301,7 @@ export class AddonModScormIndexComponent extends CoreCourseModuleMainActivityCom /** * Get the grades of each attempt and the grade of the SCORM. * - * @return {Promise} Promise resolved when done. + * @return Promise resolved when done. */ protected getReportedGrades(): Promise { const promises = [], @@ -351,8 +350,8 @@ export class AddonModScormIndexComponent extends CoreCourseModuleMainActivityCom /** * Checks if sync has succeed from result sync data. * - * @param {any} result Data returned on the sync function. - * @return {boolean} If suceed or not. + * @param result Data returned on the sync function. + * @return If suceed or not. */ protected hasSyncSucceed(result: any): boolean { if (result.updated || this.dataSent) { @@ -412,7 +411,7 @@ export class AddonModScormIndexComponent extends CoreCourseModuleMainActivityCom /** * Perform the invalidate content function. * - * @return {Promise} Resolved when done. + * @return Resolved when done. */ protected invalidateContent(): Promise { const promises = []; @@ -429,8 +428,8 @@ export class AddonModScormIndexComponent extends CoreCourseModuleMainActivityCom /** * Compares sync event data with current data to check if refresh content is needed. * - * @param {any} syncEventData Data receiven on sync observer. - * @return {boolean} True if refresh is needed, false otherwise. + * @param syncEventData Data receiven on sync observer. + * @return True if refresh is needed, false otherwise. */ protected isRefreshSyncNeeded(syncEventData: any): boolean { if (syncEventData.updated && this.scorm && syncEventData.scormId == this.scorm.id) { @@ -455,8 +454,8 @@ export class AddonModScormIndexComponent extends CoreCourseModuleMainActivityCom /** * Load the TOC of a certain organization. * - * @param {string} organizationId The organization id. - * @return {Promise} Promise resolved when done. + * @param organizationId The organization id. + * @return Promise resolved when done. */ protected loadOrganizationToc(organizationId: string): Promise { if (!this.scorm.displaycoursestructure) { @@ -490,8 +489,8 @@ export class AddonModScormIndexComponent extends CoreCourseModuleMainActivityCom /** * Open a SCORM. It will download the SCORM package if it's not downloaded or it has changed. * - * @param {Event} [event] Event. - * @param {string} [scoId] SCO that needs to be loaded when the SCORM is opened. If not defined, load first SCO. + * @param event Event. + * @param scoId SCO that needs to be loaded when the SCORM is opened. If not defined, load first SCO. */ open(event?: Event, scoId?: number): void { if (event) { @@ -534,7 +533,7 @@ export class AddonModScormIndexComponent extends CoreCourseModuleMainActivityCom /** * Open a SCORM package. * - * @param {number} scoId SCO ID. + * @param scoId SCO ID. */ protected openScorm(scoId: number): void { this.navCtrl.push('AddonModScormPlayerPage', { @@ -549,8 +548,8 @@ export class AddonModScormIndexComponent extends CoreCourseModuleMainActivityCom /** * Displays some data based on the current status. * - * @param {string} status The current status. - * @param {string} [previousStatus] The previous status. If not defined, there is no previous status. + * @param status The current status. + * @param previousStatus The previous status. If not defined, there is no previous status. */ protected showStatus(status: string, previousStatus?: string): void { @@ -574,7 +573,7 @@ export class AddonModScormIndexComponent extends CoreCourseModuleMainActivityCom /** * Performs the sync of the activity. * - * @return {Promise} Promise resolved when done. + * @return Promise resolved when done. */ protected sync(): Promise { return this.scormSync.syncScorm(this.scorm).then((result) => { diff --git a/src/addon/mod/scorm/lang/en.json b/src/addon/mod/scorm/lang/en.json index c5b06c51a..e7d6e5cfb 100644 --- a/src/addon/mod/scorm/lang/en.json +++ b/src/addon/mod/scorm/lang/en.json @@ -44,6 +44,7 @@ "organizations": "Organisations", "passed": "Passed", "reviewmode": "Review mode", + "score": "Score", "scormstatusnotdownloaded": "This SCORM package is not downloaded. It will be automatically downloaded when you open it.", "scormstatusoutdated": "This SCORM package has been modified since the last download. It will be automatically downloaded when you open it.", "suspended": "Suspended", diff --git a/src/addon/mod/scorm/pages/index/index.html b/src/addon/mod/scorm/pages/index/index.html index f8a5f7d42..9a0b2d855 100644 --- a/src/addon/mod/scorm/pages/index/index.html +++ b/src/addon/mod/scorm/pages/index/index.html @@ -1,6 +1,6 @@ - + diff --git a/src/addon/mod/scorm/pages/index/index.module.ts b/src/addon/mod/scorm/pages/index/index.module.ts index 68bd2e47b..7f2fad09d 100644 --- a/src/addon/mod/scorm/pages/index/index.module.ts +++ b/src/addon/mod/scorm/pages/index/index.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/addon/mod/scorm/pages/index/index.ts b/src/addon/mod/scorm/pages/index/index.ts index 9179fc6d1..0a840bfd9 100644 --- a/src/addon/mod/scorm/pages/index/index.ts +++ b/src/addon/mod/scorm/pages/index/index.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -40,7 +40,7 @@ export class AddonModScormIndexPage { /** * Update some data based on the SCORM instance. * - * @param {any} scorm SCORM instance. + * @param scorm SCORM instance. */ updateData(scorm: any): void { this.title = scorm.name || this.title; diff --git a/src/addon/mod/scorm/pages/player/player.html b/src/addon/mod/scorm/pages/player/player.html index bdf47bd53..6d7874bad 100644 --- a/src/addon/mod/scorm/pages/player/player.html +++ b/src/addon/mod/scorm/pages/player/player.html @@ -1,6 +1,6 @@ - + - - @@ -19,64 +18,39 @@ - + +
+ - - - - -
- + +
+ + {{ 'core.hasdatatosync' | translate:{$a: pageStr} }} + {{ 'core.hasdatatosync' | translate:{$a: moduleName} }} +
- -
- - {{ 'core.hasdatatosync' | translate:{$a: pageStr} }} - {{ 'core.hasdatatosync' | translate:{$a: moduleName} }} -
- - -
- - {{ pageWarning }} -
- -
- - -
- -
- {{ 'core.tag.tags' | translate }}: - -
-
-
-
- - - - - - - - {{ letter.label }} - - - {{ page.title }} - {{ 'core.offline' | translate }} - - - - - -
+ +
+ + {{ pageWarning }} +
+
+
+
+ + +
+
+ {{ 'core.tag.tags' | translate }}: + +
+
diff --git a/src/addon/mod/wiki/components/index/index.scss b/src/addon/mod/wiki/components/index/index.scss index 49867d614..a19821b4f 100644 --- a/src/addon/mod/wiki/components/index/index.scss +++ b/src/addon/mod/wiki/components/index/index.scss @@ -4,22 +4,52 @@ $addon-mod-wiki-toc-title-color: $gray-darker !default; $addon-mod-wiki-toc-border-color: $gray-dark !default; $addon-mod-wiki-toc-background-color: $gray-light !default; +$addon-mod-wiki-dark-toc-title-color: $white !default; +$addon-mod-wiki-dark-toc-border-color: $gray-dark !default; +$addon-mod-wiki-dark-toc-background-color: $gray-darker !default; +$addon-mod-wiki-dark-newentry-link-color: $red-light !default; + ion-app.app-root addon-mod-wiki-index { background-color: $white; + @include darkmode() { + background-color: $black; + } .addon-mod_wiki-page-content { background-color: $white; + border-top: 1px solid $gray; + @include safe-area-padding-horizontal(10px, 10px); + @include darkmode() { + background-color: $black; + } } .wiki-toc { border: 1px solid $addon-mod-wiki-toc-border-color; background: $addon-mod-wiki-toc-background-color; + p { + color: $text-color !important; + } + a { + color: $link-color; + } + + @include darkmode() { + border: 1px solid $addon-mod-wiki-dark-toc-border-color; + background: $addon-mod-wiki-dark-toc-background-color; + p { + color: $core-dark-text-color !important; + } + a { + color: $core-dark-link-color; + } + } margin: 16px; padding: 8px; } .wiki-toc-title { - color: $addon-mod-wiki-toc-title-color; + color: $addon-mod-wiki-toc-title-color !important; font-size: 1.1em; font-variant: small-caps; text-align: center; @@ -41,6 +71,9 @@ ion-app.app-root addon-mod-wiki-index { .wiki_newentry { color: $addon-mod-wiki-newentry-link-color; font-style: italic; + @include darkmode() { + color: $addon-mod-wiki-dark-newentry-link-color !important; + } } /* Hide edit section links */ diff --git a/src/addon/mod/wiki/components/index/index.ts b/src/addon/mod/wiki/components/index/index.ts index cb91ef072..f72447642 100644 --- a/src/addon/mod/wiki/components/index/index.ts +++ b/src/addon/mod/wiki/components/index/index.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -12,8 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { Component, Optional, Injector, Input, ViewChild } from '@angular/core'; -import { Content, NavController, PopoverController, ViewController } from 'ionic-angular'; +import { Component, Optional, Injector, Input } from '@angular/core'; +import { Content, NavController, PopoverController, ViewController, ModalController } from 'ionic-angular'; import { CoreGroupsProvider } from '@providers/groups'; import { CoreUtilsProvider } from '@providers/utils/utils'; import { CoreCourseModuleMainActivityComponent } from '@core/course/classes/main-activity-component'; @@ -21,7 +21,6 @@ import { CoreUserProvider } from '@core/user/providers/user'; import { AddonModWikiProvider, AddonModWikiSubwikiListData } from '../../providers/wiki'; import { AddonModWikiOfflineProvider } from '../../providers/wiki-offline'; import { AddonModWikiSyncProvider } from '../../providers/wiki-sync'; -import { CoreTabsComponent } from '@components/tabs/tabs'; import { AddonModWikiSubwikiPickerComponent } from '../../components/subwiki-picker/subwiki-picker'; import { CoreTagProvider } from '@core/tag/providers/tag'; @@ -33,7 +32,6 @@ import { CoreTagProvider } from '@core/tag/providers/tag'; templateUrl: 'addon-mod-wiki-index.html', }) export class AddonModWikiIndexComponent extends CoreCourseModuleMainActivityComponent { - @ViewChild(CoreTabsComponent) tabs: CoreTabsComponent; @Input() action: string; @Input() pageId: number; @@ -55,9 +53,6 @@ export class AddonModWikiIndexComponent extends CoreCourseModuleMainActivityComp loadedSubwikis: any[] = []; // The loaded subwikis. pageIsOffline: boolean; // Whether the loaded page is an offline page. pageContent: string; // Page content to display. - showHomeButton: boolean; // Whether to display the home button. - selectedTab = 0; // Tab to select at start. - map: any[] = []; // Map of pages, categorized by letter. subwikiData: AddonModWikiSubwikiListData = { // Data for the subwiki selector. subwikiSelected: 0, userSelected: 0, @@ -66,25 +61,23 @@ export class AddonModWikiIndexComponent extends CoreCourseModuleMainActivityComp count: 0 }; tagsEnabled: boolean; + currentPageObj: any; // Object of the current loaded page. protected syncEventName = AddonModWikiSyncProvider.AUTO_SYNCED; protected currentSubwiki: any; // Current selected subwiki. protected currentPage: number; // Current loaded page ID. - protected currentPageObj: any; // Object of the current loaded page. protected subwikiPages: any[]; // List of subwiki pages. protected newPageObserver: any; // Observer to check for new pages. protected ignoreManualSyncEvent: boolean; // Whether manual sync event should be ignored. protected manualSyncObserver: any; // An observer to watch for manual sync events. protected currentUserId: number; // Current user ID. protected hasEdited = false; // Whether the user has opened the edit page. - protected mapInitialized = false; // Whether the map was initialized. - protected initHomeButton = true; // Whether the init home button must be initialized. constructor(injector: Injector, protected wikiProvider: AddonModWikiProvider, @Optional() protected content: Content, protected wikiOffline: AddonModWikiOfflineProvider, protected wikiSync: AddonModWikiSyncProvider, protected navCtrl: NavController, protected utils: CoreUtilsProvider, protected groupsProvider: CoreGroupsProvider, protected userProvider: CoreUserProvider, private popoverCtrl: PopoverController, - private tagProvider: CoreTagProvider) { + private tagProvider: CoreTagProvider, protected modalCtrl: ModalController) { super(injector, content); this.pageStr = this.translate.instant('addon.mod_wiki.wikipage'); @@ -100,24 +93,27 @@ export class AddonModWikiIndexComponent extends CoreCourseModuleMainActivityComp this.currentUserId = this.sitesProvider.getCurrentSiteUserId(); this.isMainPage = !this.pageId && !this.pageTitle; this.currentPage = this.pageId; - this.selectedTab = this.action == 'map' ? 1 : 0; this.loadContent(false, true).then(() => { if (!this.wiki) { return; } - if (this.isMainPage) { + if (!this.pageId) { this.wikiProvider.logView(this.wiki.id, this.wiki.name).then(() => { this.courseProvider.checkModuleCompletion(this.courseId, this.module.completiondata); }).catch((error) => { // Ignore errors. }); } else { - this.wikiProvider.logPageView(this.pageId, this.wiki.id, this.wiki.name).catch(() => { + this.wikiProvider.logPageView(this.pageId, this.wiki.id, this.wiki.name).catch((error) => { // Ignore errors. }); } + }).finally(() => { + if (this.action == 'map') { + this.openMap(); + } }); // Listen for manual sync events. @@ -144,82 +140,37 @@ export class AddonModWikiIndexComponent extends CoreCourseModuleMainActivityComp /** * Check if the current page was created or discarded. * - * @param {any} data Data about created and deleted pages. + * @param data Data about created and deleted pages. */ protected checkPageCreatedOrDiscarded(data: any): void { if (!this.currentPage && data) { // This is an offline page. Check if the page was created. - let pageId; - - for (let i = 0, len = data.created.length; i < len; i++) { - const page = data.created[i]; - if (page.title == this.pageTitle) { - pageId = page.pageId; - break; - } - } - - if (pageId) { + const page = data.created.find((page) => page.title == this.pageTitle); + if (page) { // Page was created, set the ID so it's retrieved from server. - this.currentPage = pageId; + this.currentPage = page.pageId; this.pageIsOffline = false; } else { // Page not found in created list, check if it was discarded. - for (let i = 0, len = data.discarded.length; i < len; i++) { - const page = data.discarded[i]; - if (page.title == this.pageTitle) { - // Page discarded, show warning. - this.pageWarning = page.warning; - this.pageContent = ''; - this.pageIsOffline = false; - this.hasOffline = false; - } + const page = data.discarded.find((page) => page.title == this.pageTitle); + if (page) { + // Page discarded, show warning. + this.pageWarning = page.warning; + this.pageContent = ''; + this.pageIsOffline = false; + this.hasOffline = false; } } } } - /** - * Construct the map of pages. - * - * @param {any[]} subwikiPages List of pages. - */ - constructMap(subwikiPages: any[]): void { - let letter, - initialLetter; - - this.map = []; - this.mapInitialized = true; - subwikiPages.sort((a, b) => { - const compareA = a.title.toLowerCase().trim(), - compareB = b.title.toLowerCase().trim(); - - return compareA.localeCompare(compareB); - }); - - subwikiPages.forEach((page) => { - const letterCandidate = page.title.charAt(0).toLocaleUpperCase(); - - // Should we create a new grouping? - if (letterCandidate !== initialLetter) { - initialLetter = letterCandidate; - letter = {label: letterCandidate, pages: []}; - - this.map.push(letter); - } - - // Add the subwiki to the currently active grouping. - letter.pages.push(page); - }); - } - /** * Get the wiki data. * - * @param {boolean} [refresh=false] If it's refreshing content. - * @param {boolean} [sync=false] If it should try to sync. - * @param {boolean} [showErrors=false] If show errors to the user of hide them. - * @return {Promise} Promise resolved when done. + * @param refresh If it's refreshing content. + * @param sync If it should try to sync. + * @param showErrors If show errors to the user of hide them. + * @return Promise resolved when done. */ protected fetchContent(refresh: boolean = false, sync: boolean = false, showErrors: boolean = false): Promise { @@ -248,11 +199,6 @@ export class AddonModWikiIndexComponent extends CoreCourseModuleMainActivityComp return Promise.reject(null); } - if (this.isCurrentView || this.initHomeButton) { - this.initHomeButton = false; - this.showHomeButton = !!this.getWikiHomeView(); - } - // Get module instance if it's empty. let promise; if (!this.module.id) { @@ -299,8 +245,7 @@ export class AddonModWikiIndexComponent extends CoreCourseModuleMainActivityComp return this.fetchWikiPage(); }); }); - }).then(() => { - // All data obtained, now fill the context menu. + }).finally(() => { this.fillContextMenu(refresh); }).catch((error) => { if (this.pageWarning) { @@ -315,8 +260,8 @@ export class AddonModWikiIndexComponent extends CoreCourseModuleMainActivityComp /** * Get wiki page contents. * - * @param {number} pageId Page to get. - * @return {Promise} Promise resolved with the page data. + * @param pageId Page to get. + * @return Promise resolved with the page data. */ protected fetchPageContents(pageId: number): Promise { if (!pageId) { @@ -335,9 +280,11 @@ export class AddonModWikiIndexComponent extends CoreCourseModuleMainActivityComp this.currentPage = data.pageId; this.showLoadingAndFetch(true, false).then(() => { - this.wikiProvider.logPageView(this.currentPage, this.wiki.id, this.wiki.name).catch(() => { - // Ignore errors. - }); + if (this.currentPage) { + this.wikiProvider.logPageView(this.currentPage, this.wiki.id, this.wiki.name).catch(() => { + // Ignore errors. + }); + } }); // Stop listening for new page events. @@ -361,7 +308,7 @@ export class AddonModWikiIndexComponent extends CoreCourseModuleMainActivityComp /** * Fetch the list of pages of a subwiki. * - * @param {any} subwiki Subwiki. + * @param subwiki Subwiki. */ protected fetchSubwikiPages(subwiki: any): Promise { let subwikiPages; @@ -371,12 +318,9 @@ export class AddonModWikiIndexComponent extends CoreCourseModuleMainActivityComp // If no page specified, search first page. if (!this.currentPage && !this.pageTitle) { - for (const i in subwikiPages) { - const page = subwikiPages[i]; - if (page.firstpage) { - this.currentPage = page.id; - break; - } + const firstPage = subwikiPages.find((page) => page.firstpage ); + if (firstPage) { + this.currentPage = firstPage.id; } } @@ -397,7 +341,6 @@ export class AddonModWikiIndexComponent extends CoreCourseModuleMainActivityComp } this.subwikiPages = this.wikiProvider.sortPagesByTitle(subwikiPages.concat(offlinePages)); - this.constructMap(this.subwikiPages); // Reject if no currentPage selected from the subwikis given (if no subwikis available, do not reject). if (!this.currentPage && !this.pageTitle && this.subwikiPages.length > 0) { @@ -409,7 +352,7 @@ export class AddonModWikiIndexComponent extends CoreCourseModuleMainActivityComp /** * Get the subwikis. * - * @param {number} wikiId Wiki ID. + * @param wikiId Wiki ID. */ protected fetchSubwikis(wikiId: number): Promise { return this.wikiProvider.getSubwikis(wikiId).then((subwikis) => { @@ -424,7 +367,7 @@ export class AddonModWikiIndexComponent extends CoreCourseModuleMainActivityComp /** * Fetch the page to be shown. * - * @return {Promise} [description] + * @return [description] */ protected fetchWikiPage(): Promise { // Search the current Subwiki. @@ -458,7 +401,7 @@ export class AddonModWikiIndexComponent extends CoreCourseModuleMainActivityComp /** * Get the wiki home view. If cannot determine or it's current view, return undefined. * - * @return {ViewController} The view controller of the home view + * @return The view controller of the home view */ protected getWikiHomeView(): ViewController { @@ -492,17 +435,6 @@ export class AddonModWikiIndexComponent extends CoreCourseModuleMainActivityComp } } - /** - * Go back to the initial page of the wiki. - */ - goToWikiHome(): void { - const homeView = this.getWikiHomeView(); - - if (homeView) { - this.navCtrl.popTo(homeView); - } - } - /** * Open the view to create the first page of the wiki. */ @@ -580,9 +512,9 @@ export class AddonModWikiIndexComponent extends CoreCourseModuleMainActivityComp /** * Go to view a certain page. * - * @param {any} page Page to view. + * @param page Page to view. */ - goToPage(page: any): void { + protected goToPage(page: any): void { if (!page.id) { // It's an offline page. Check if we are already in the same offline page. if (this.currentPage || !this.pageTitle || page.title != this.pageTitle) { @@ -591,8 +523,7 @@ export class AddonModWikiIndexComponent extends CoreCourseModuleMainActivityComp courseId: this.courseId, pageTitle: page.title, wikiId: this.wiki.id, - subwikiId: page.subwikiid, - action: 'page' + subwikiId: page.subwikiid }); return; @@ -606,25 +537,56 @@ export class AddonModWikiIndexComponent extends CoreCourseModuleMainActivityComp pageTitle: page.title, pageId: page.id, wikiId: page.wikiid, - subwikiId: page.subwikiid, - action: 'page' + subwikiId: page.subwikiid }); }); return; } + } - // No changes done. - this.tabs.selectTab(0); + /** + * Show the map. + * + * @param event Event. + */ + openMap(event?: MouseEvent): void { + const modal = this.modalCtrl.create('AddonModWikiMapPage', { + pages: this.subwikiPages, + selected: this.currentPageObj && this.currentPageObj.id, + homeView: this.getWikiHomeView(), + moduleId: this.module.id, + courseId: this.courseId + }, { cssClass: 'core-modal-lateral', + showBackdrop: true, + enableBackdropDismiss: true, + enterAnimation: 'core-modal-lateral-transition', + leaveAnimation: 'core-modal-lateral-transition' }); + + // If the modal sends back a page, load it. + modal.onDidDismiss((page) => { + if (page) { + if (page.type == 'home') { + // Go back to the initial page of the wiki. + this.navCtrl.popTo(page.goto); + } else { + this.goToPage(page.goto); + } + } + }); + + modal.present({ + ev: event + }); } /** * Go to the page to view a certain subwiki. * - * @param {number} subwikiId Subwiki ID. - * @param {number} userId User ID of the subwiki. - * @param {number} groupId Group ID of the subwiki. - * @param {boolean} canEdit Whether the subwiki can be edited. + * @param subwikiId Subwiki ID. + * @param userId User ID of the subwiki. + * @param groupId Group ID of the subwiki. + * @param canEdit Whether the subwiki can be edited. */ goToSubwiki(subwikiId: number, userId: number, groupId: number, canEdit: boolean): void { // Check if the subwiki is disabled. @@ -638,8 +600,7 @@ export class AddonModWikiIndexComponent extends CoreCourseModuleMainActivityComp wikiId: this.wiki.id, subwikiId: subwikiId, userId: userId, - groupId: groupId, - action: this.tabs.selected == 0 ? 'page' : 'map' + groupId: groupId }); } } @@ -648,7 +609,7 @@ export class AddonModWikiIndexComponent extends CoreCourseModuleMainActivityComp /** * Checks if there is any subwiki selected. * - * @return {boolean} Whether there is any subwiki selected. + * @return Whether there is any subwiki selected. */ protected isAnySubwikiSelected(): boolean { return this.subwikiData.subwikiSelected > 0 || this.subwikiData.userSelected > 0 || this.subwikiData.groupSelected > 0; @@ -657,8 +618,8 @@ export class AddonModWikiIndexComponent extends CoreCourseModuleMainActivityComp /** * Checks if the given subwiki is the one picked on the subwiki picker. * - * @param {any} subwiki Subwiki to check. - * @return {boolean} Whether it's the selected subwiki. + * @param subwiki Subwiki to check. + * @return Whether it's the selected subwiki. */ protected isSubwikiSelected(subwiki: any): boolean { const subwikiId = parseInt(subwiki.id, 10) || 0; @@ -676,8 +637,8 @@ export class AddonModWikiIndexComponent extends CoreCourseModuleMainActivityComp /** * Replace edit links to have full url. * - * @param {string} content Content to treat. - * @return {string} Treated content. + * @param content Content to treat. + * @return Treated content. */ protected replaceEditLinks(content: string): string { content = content.trim(); @@ -693,9 +654,9 @@ export class AddonModWikiIndexComponent extends CoreCourseModuleMainActivityComp /** * Sets the selected subwiki for the subwiki picker. * - * @param {number} subwikiId Subwiki ID to select. - * @param {number} userId User ID of the subwiki to select. - * @param {number} groupId Group ID of the subwiki to select. + * @param subwikiId Subwiki ID to select. + * @param userId User ID of the subwiki to select. + * @param groupId Group ID of the subwiki to select. */ protected setSelectedWiki(subwikiId: number, userId: number, groupId: number): void { this.subwikiData.subwikiSelected = this.wikiOffline.convertToPositiveNumber(subwikiId); @@ -706,8 +667,8 @@ export class AddonModWikiIndexComponent extends CoreCourseModuleMainActivityComp /** * Checks if sync has succeed from result sync data. * - * @param {any} result Data returned on the sync function. - * @return {boolean} If suceed or not. + * @param result Data returned on the sync function. + * @return If suceed or not. */ protected hasSyncSucceed(result: any): boolean { result.wikiId = this.wiki.id; @@ -731,8 +692,6 @@ export class AddonModWikiIndexComponent extends CoreCourseModuleMainActivityComp ionViewDidEnter(): void { super.ionViewDidEnter(); - this.tabs && this.tabs.ionViewDidEnter(); - if (this.hasEdited) { this.hasEdited = false; this.showLoadingAndRefresh(true, false); @@ -745,8 +704,6 @@ export class AddonModWikiIndexComponent extends CoreCourseModuleMainActivityComp ionViewDidLeave(): void { super.ionViewDidLeave(); - this.tabs && this.tabs.ionViewDidLeave(); - if (this.navCtrl.getActive().component.name == 'AddonModWikiEditPage') { this.hasEdited = true; } @@ -755,7 +712,7 @@ export class AddonModWikiIndexComponent extends CoreCourseModuleMainActivityComp /** * Perform the invalidate content function. * - * @return {Promise} Resolved when done. + * @return Resolved when done. */ protected invalidateContent(): Promise { const promises = []; @@ -783,8 +740,8 @@ export class AddonModWikiIndexComponent extends CoreCourseModuleMainActivityComp /** * Compares sync event data with current data to check if refresh content is needed. * - * @param {any} syncEventData Data receiven on sync observer. - * @return {boolean} True if refresh is needed, false otherwise. + * @param syncEventData Data receiven on sync observer. + * @return True if refresh is needed, false otherwise. */ protected isRefreshSyncNeeded(syncEventData: any): boolean { if (this.currentSubwiki && syncEventData.subwikiId == this.currentSubwiki.id && @@ -806,7 +763,7 @@ export class AddonModWikiIndexComponent extends CoreCourseModuleMainActivityComp /** * Show the TOC. * - * @param {MouseEvent} event Event. + * @param event Event. */ showSubwikiPicker(event: MouseEvent): void { const popover = this.popoverCtrl.create(AddonModWikiSubwikiPickerComponent, { @@ -828,7 +785,7 @@ export class AddonModWikiIndexComponent extends CoreCourseModuleMainActivityComp /** * Performs the sync of the activity. * - * @return {Promise} Promise resolved when done. + * @return Promise resolved when done. */ protected sync(): Promise { return this.wikiSync.syncWiki(this.wiki.id, this.courseId, this.wiki.coursemodule); @@ -847,8 +804,8 @@ export class AddonModWikiIndexComponent extends CoreCourseModuleMainActivityComp /** * Create the subwiki list for the selector and store it in the cache. * - * @param {any[]} userGroups Groups. - * @return {Promise} Promise resolved when done. + * @param userGroups Groups. + * @return Promise resolved when done. */ protected createSubwikiList(userGroups: any[]): Promise { const subwikiList = [], @@ -938,9 +895,9 @@ export class AddonModWikiIndexComponent extends CoreCourseModuleMainActivityComp /** * Fill the subwiki data. * - * @param {any[]} subwikiList List of subwikis. - * @param {boolean} showMyGroupsLabel Whether subwikis should be grouped in "My groups" and "Other groups". - * @param {boolean} multiLevelList Whether it's a multi level list. + * @param subwikiList List of subwikis. + * @param showMyGroupsLabel Whether subwikis should be grouped in "My groups" and "Other groups". + * @param multiLevelList Whether it's a multi level list. */ protected fillSubwikiData(subwikiList: any[], showMyGroupsLabel: boolean, multiLevelList: boolean): void { let groupValue = -1, @@ -1014,9 +971,7 @@ export class AddonModWikiIndexComponent extends CoreCourseModuleMainActivityComp if (multiLevelList) { // As we loop over each subwiki, add it to the current group - for (const i in subwikiList) { - const subwiki = subwikiList[i]; - + subwikiList.forEach((subwiki) => { // Should we create a new grouping? if (subwiki.groupid !== groupValue) { grouping = {label: subwiki.groupLabel, subwikis: []}; @@ -1027,16 +982,14 @@ export class AddonModWikiIndexComponent extends CoreCourseModuleMainActivityComp // Add the subwiki to the currently active grouping. grouping.subwikis.push(subwiki); - } + }); } else if (showMyGroupsLabel) { const noGrouping = {label: '', subwikis: []}, myGroupsGrouping = {label: this.translate.instant('core.mygroups'), subwikis: []}, otherGroupsGrouping = {label: this.translate.instant('core.othergroups'), subwikis: []}; // As we loop over each subwiki, add it to the current group - for (const i in subwikiList) { - const subwiki = subwikiList[i]; - + subwikiList.forEach((subwiki) => { // Add the subwiki to the currently active grouping. if (typeof subwiki.canedit == 'undefined') { noGrouping.subwikis.push(subwiki); @@ -1045,7 +998,7 @@ export class AddonModWikiIndexComponent extends CoreCourseModuleMainActivityComp } else { otherGroupsGrouping.subwikis.push(subwiki); } - } + }); // Add each grouping to the subwikis if (noGrouping.subwikis.length > 0) { diff --git a/src/addon/mod/wiki/components/subwiki-picker/subwiki-picker.ts b/src/addon/mod/wiki/components/subwiki-picker/subwiki-picker.ts index 56a149bd6..942ddb71b 100644 --- a/src/addon/mod/wiki/components/subwiki-picker/subwiki-picker.ts +++ b/src/addon/mod/wiki/components/subwiki-picker/subwiki-picker.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -34,8 +34,8 @@ export class AddonModWikiSubwikiPickerComponent { /** * Checks if the given subwiki is the one currently selected. * - * @param {any} subwiki Subwiki to check. - * @return {boolean} Whether it's the selected subwiki. + * @param subwiki Subwiki to check. + * @return Whether it's the selected subwiki. */ protected isSubwikiSelected(subwiki: any): boolean { const subwikiId = parseInt(subwiki.id, 10) || 0; @@ -53,7 +53,7 @@ export class AddonModWikiSubwikiPickerComponent { /** * Function called when a subwiki is clicked. * - * @param {any} subwiki The subwiki to open. + * @param subwiki The subwiki to open. */ openSubwiki(subwiki: any): void { // Check if the subwiki is disabled. diff --git a/src/addon/mod/wiki/pages/edit/edit.html b/src/addon/mod/wiki/pages/edit/edit.html index f31cfc9b5..c9f61f896 100644 --- a/src/addon/mod/wiki/pages/edit/edit.html +++ b/src/addon/mod/wiki/pages/edit/edit.html @@ -1,6 +1,6 @@ - + + + + + + + diff --git a/src/addon/mod/wiki/pages/map/map.module.ts b/src/addon/mod/wiki/pages/map/map.module.ts new file mode 100644 index 000000000..75177b8dd --- /dev/null +++ b/src/addon/mod/wiki/pages/map/map.module.ts @@ -0,0 +1,33 @@ +// (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 { NgModule } from '@angular/core'; +import { IonicPageModule } from 'ionic-angular'; +import { TranslateModule } from '@ngx-translate/core'; +import { CoreComponentsModule } from '@components/components.module'; +import { CoreDirectivesModule } from '@directives/directives.module'; +import { AddonModWikiMapPage } from './map'; + +@NgModule({ + declarations: [ + AddonModWikiMapPage, + ], + imports: [ + CoreComponentsModule, + CoreDirectivesModule, + IonicPageModule.forChild(AddonModWikiMapPage), + TranslateModule.forChild() + ], +}) +export class AddonModWikiMapPageModule {} diff --git a/src/addon/mod/wiki/pages/map/map.ts b/src/addon/mod/wiki/pages/map/map.ts new file mode 100644 index 000000000..9d5991e64 --- /dev/null +++ b/src/addon/mod/wiki/pages/map/map.ts @@ -0,0 +1,97 @@ +// (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 { Component } from '@angular/core'; +import { IonicPage, NavParams, ViewController } from 'ionic-angular'; + +/** + * Modal to display the map of a Wiki. + */ +@IonicPage({ segment: 'addon-mod-wiki-map' }) +@Component({ + selector: 'page-addon-mod-wiki-map', + templateUrl: 'map.html', +}) +export class AddonModWikiMapPage { + map: any[] = []; // Map of pages, categorized by letter. + selected: number; + moduleId: number; + courseId: number; + homeView: ViewController; + + constructor(navParams: NavParams, protected viewCtrl: ViewController) { + this.constructMap(navParams.get('pages') || []); + + this.selected = navParams.get('selected'); + this.homeView = navParams.get('homeView'); + this.moduleId = navParams.get('moduleId'); + this.courseId = navParams.get('courseId'); + } + + /** + * Function called when a page is clicked. + * + * @param page Clicked page. + */ + goToPage(page: any): void { + this.viewCtrl.dismiss({type: 'page', goto: page}); + } + + /** + * Go back to the initial page of the wiki. + */ + goToWikiHome(): void { + this.viewCtrl.dismiss({type: 'home', goto: this.homeView}); + } + + /** + * Construct the map of pages. + * + * @param pages List of pages. + */ + protected constructMap(pages: any[]): void { + let letter, + initialLetter; + + this.map = []; + pages.sort((a, b) => { + const compareA = a.title.toLowerCase().trim(), + compareB = b.title.toLowerCase().trim(); + + return compareA.localeCompare(compareB); + }); + + pages.forEach((page) => { + const letterCandidate = page.title.charAt(0).toLocaleUpperCase(); + + // Should we create a new grouping? + if (letterCandidate !== initialLetter) { + initialLetter = letterCandidate; + letter = {label: letterCandidate, pages: []}; + + this.map.push(letter); + } + + // Add the subwiki to the currently active grouping. + letter.pages.push(page); + }); + } + + /** + * Close modal. + */ + closeModal(): void { + this.viewCtrl.dismiss(); + } +} diff --git a/src/addon/mod/wiki/providers/create-link-handler.ts b/src/addon/mod/wiki/providers/create-link-handler.ts index 7eb5287ab..f56b8ee8e 100644 --- a/src/addon/mod/wiki/providers/create-link-handler.ts +++ b/src/addon/mod/wiki/providers/create-link-handler.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -40,10 +40,10 @@ export class AddonModWikiCreateLinkHandler extends CoreContentLinksHandlerBase { /** * Check if the current view is a wiki page of the same wiki. * - * @param {ViewController} activeView Active view. - * @param {number} subwikiId Subwiki ID to check. - * @param {string} siteId Site ID. - * @return {Promise} Promise resolved with boolean: whether current view belongs to the same wiki. + * @param activeView Active view. + * @param subwikiId Subwiki ID to check. + * @param siteId Site ID. + * @return Promise resolved with boolean: whether current view belongs to the same wiki. */ protected currentStateIsSameWiki(activeView: ViewController, subwikiId: number, siteId: string): Promise { @@ -86,11 +86,11 @@ export class AddonModWikiCreateLinkHandler extends CoreContentLinksHandlerBase { /** * Get the list of actions for a link (url). * - * @param {string[]} siteIds List of sites the URL belongs to. - * @param {string} url The URL to treat. - * @param {any} params The params of the URL. E.g. 'mysite.com?id=1' -> {id: 1} - * @param {number} [courseId] Course ID related to the URL. Optional but recommended. - * @return {CoreContentLinksAction[]|Promise} List of (or promise resolved with list of) actions. + * @param siteIds List of sites the URL belongs to. + * @param url The URL to treat. + * @param params The params of the URL. E.g. 'mysite.com?id=1' -> {id: 1} + * @param courseId Course ID related to the URL. Optional but recommended. + * @return List of (or promise resolved with list of) actions. */ getActions(siteIds: string[], url: string, params: any, courseId?: number): CoreContentLinksAction[] | Promise { diff --git a/src/addon/mod/wiki/providers/edit-link-handler.ts b/src/addon/mod/wiki/providers/edit-link-handler.ts index 512c8c65f..b6442157a 100644 --- a/src/addon/mod/wiki/providers/edit-link-handler.ts +++ b/src/addon/mod/wiki/providers/edit-link-handler.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -34,11 +34,11 @@ export class AddonModWikiEditLinkHandler extends CoreContentLinksHandlerBase { /** * Get the list of actions for a link (url). * - * @param {string[]} siteIds List of sites the URL belongs to. - * @param {string} url The URL to treat. - * @param {any} params The params of the URL. E.g. 'mysite.com?id=1' -> {id: 1} - * @param {number} [courseId] Course ID related to the URL. Optional but recommended. - * @return {CoreContentLinksAction[]|Promise} List of (or promise resolved with list of) actions. + * @param siteIds List of sites the URL belongs to. + * @param url The URL to treat. + * @param params The params of the URL. E.g. 'mysite.com?id=1' -> {id: 1} + * @param courseId Course ID related to the URL. Optional but recommended. + * @return List of (or promise resolved with list of) actions. */ getActions(siteIds: string[], url: string, params: any, courseId?: number): CoreContentLinksAction[] | Promise { diff --git a/src/addon/mod/wiki/providers/index-link-handler.ts b/src/addon/mod/wiki/providers/index-link-handler.ts index 212cbaa64..8b734e980 100644 --- a/src/addon/mod/wiki/providers/index-link-handler.ts +++ b/src/addon/mod/wiki/providers/index-link-handler.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -24,6 +24,6 @@ export class AddonModWikiIndexLinkHandler extends CoreContentLinksModuleIndexHan name = 'AddonModWikiIndexLinkHandler'; constructor(courseHelper: CoreCourseHelperProvider) { - super(courseHelper, 'AddonModWiki', 'wiki'); + super(courseHelper, 'AddonModWiki', 'wiki', 'wid'); } } diff --git a/src/addon/mod/wiki/providers/list-link-handler.ts b/src/addon/mod/wiki/providers/list-link-handler.ts index 1be40c6ea..3645d1be3 100644 --- a/src/addon/mod/wiki/providers/list-link-handler.ts +++ b/src/addon/mod/wiki/providers/list-link-handler.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/addon/mod/wiki/providers/module-handler.ts b/src/addon/mod/wiki/providers/module-handler.ts index 20a94af39..85e40225d 100644 --- a/src/addon/mod/wiki/providers/module-handler.ts +++ b/src/addon/mod/wiki/providers/module-handler.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -45,7 +45,7 @@ export class AddonModWikiModuleHandler implements CoreCourseModuleHandler { /** * Check if the handler is enabled on a site level. * - * @return {boolean} Whether or not the handler is enabled on a site level. + * @return Whether or not the handler is enabled on a site level. */ isEnabled(): boolean { return true; @@ -54,10 +54,10 @@ export class AddonModWikiModuleHandler implements CoreCourseModuleHandler { /** * Get the data required to display the module in the course contents view. * - * @param {any} module The module object. - * @param {number} courseId The course ID. - * @param {number} sectionId The section ID. - * @return {CoreCourseModuleHandlerData} Data to render the module. + * @param module The module object. + * @param courseId The course ID. + * @param sectionId The section ID. + * @return Data to render the module. */ getData(module: any, courseId: number, sectionId: number): CoreCourseModuleHandlerData { return { @@ -79,9 +79,9 @@ export class AddonModWikiModuleHandler implements CoreCourseModuleHandler { * Get the component to render the module. This is needed to support singleactivity course format. * The component returned must implement CoreCourseModuleMainComponent. * - * @param {any} course The course object. - * @param {any} module The module object. - * @return {any} The component to use, undefined if not found. + * @param course The course object. + * @param module The module object. + * @return The component to use, undefined if not found. */ getMainComponent(course: any, module: any): any { return AddonModWikiIndexComponent; diff --git a/src/addon/mod/wiki/providers/page-or-map-link-handler.ts b/src/addon/mod/wiki/providers/page-or-map-link-handler.ts index 8a5bf9e23..cad304c40 100644 --- a/src/addon/mod/wiki/providers/page-or-map-link-handler.ts +++ b/src/addon/mod/wiki/providers/page-or-map-link-handler.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -37,11 +37,11 @@ export class AddonModWikiPageOrMapLinkHandler extends CoreContentLinksHandlerBas /** * Get the list of actions for a link (url). * - * @param {string[]} siteIds List of sites the URL belongs to. - * @param {string} url The URL to treat. - * @param {any} params The params of the URL. E.g. 'mysite.com?id=1' -> {id: 1} - * @param {number} [courseId] Course ID related to the URL. Optional but recommended. - * @return {CoreContentLinksAction[]|Promise} List of (or promise resolved with list of) actions. + * @param siteIds List of sites the URL belongs to. + * @param url The URL to treat. + * @param params The params of the URL. E.g. 'mysite.com?id=1' -> {id: 1} + * @param courseId Course ID related to the URL. Optional but recommended. + * @return List of (or promise resolved with list of) actions. */ getActions(siteIds: string[], url: string, params: any, courseId?: number): CoreContentLinksAction[] | Promise { @@ -89,11 +89,11 @@ export class AddonModWikiPageOrMapLinkHandler extends CoreContentLinksHandlerBas * Check if the handler is enabled for a certain site (site + user) and a URL. * If not defined, defaults to true. * - * @param {string} siteId The site ID. - * @param {string} url The URL to treat. - * @param {any} params The params of the URL. E.g. 'mysite.com?id=1' -> {id: 1} - * @param {number} [courseId] Course ID related to the URL. Optional but recommended. - * @return {boolean|Promise} Whether the handler is enabled for the URL and site. + * @param siteId The site ID. + * @param url The URL to treat. + * @param params The params of the URL. E.g. 'mysite.com?id=1' -> {id: 1} + * @param courseId Course ID related to the URL. Optional but recommended. + * @return Whether the handler is enabled for the URL and site. */ isEnabled(siteId: string, url: string, params: any, courseId?: number): boolean | Promise { const isMap = url.indexOf('mod/wiki/map.php') != -1; diff --git a/src/addon/mod/wiki/providers/prefetch-handler.ts b/src/addon/mod/wiki/providers/prefetch-handler.ts index 5acebea4c..cfec49818 100644 --- a/src/addon/mod/wiki/providers/prefetch-handler.ts +++ b/src/addon/mod/wiki/providers/prefetch-handler.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -28,6 +28,8 @@ import { CoreGradesHelperProvider } from '@core/grades/providers/helper'; import { CoreUserProvider } from '@core/user/providers/user'; import { AddonModWikiProvider } from './wiki'; import { AddonModWikiSyncProvider } from './wiki-sync'; +import { CoreFilterHelperProvider } from '@core/filter/providers/helper'; +import { CorePluginFileDelegate } from '@providers/plugin-file-delegate'; /** * Handler to prefetch wikis. @@ -39,25 +41,36 @@ export class AddonModWikiPrefetchHandler extends CoreCourseActivityPrefetchHandl component = AddonModWikiProvider.COMPONENT; updatesNames = /^.*files$|^pages$/; - constructor(translate: TranslateService, appProvider: CoreAppProvider, utils: CoreUtilsProvider, - courseProvider: CoreCourseProvider, filepoolProvider: CoreFilepoolProvider, sitesProvider: CoreSitesProvider, - domUtils: CoreDomUtilsProvider, protected wikiProvider: AddonModWikiProvider, protected userProvider: CoreUserProvider, - protected textUtils: CoreTextUtilsProvider, protected courseHelper: CoreCourseHelperProvider, - protected groupsProvider: CoreGroupsProvider, protected gradesHelper: CoreGradesHelperProvider, + constructor(translate: TranslateService, + appProvider: CoreAppProvider, + utils: CoreUtilsProvider, + courseProvider: CoreCourseProvider, + filepoolProvider: CoreFilepoolProvider, + sitesProvider: CoreSitesProvider, + domUtils: CoreDomUtilsProvider, + filterHelper: CoreFilterHelperProvider, + pluginFileDelegate: CorePluginFileDelegate, + protected wikiProvider: AddonModWikiProvider, + protected userProvider: CoreUserProvider, + protected textUtils: CoreTextUtilsProvider, + protected courseHelper: CoreCourseHelperProvider, + protected groupsProvider: CoreGroupsProvider, + protected gradesHelper: CoreGradesHelperProvider, protected syncProvider: AddonModWikiSyncProvider) { - super(translate, appProvider, utils, courseProvider, filepoolProvider, sitesProvider, domUtils); + super(translate, appProvider, utils, courseProvider, filepoolProvider, sitesProvider, domUtils, filterHelper, + pluginFileDelegate); } /** * Returns a list of pages that can be downloaded. * - * @param {any} module The module object returned by WS. - * @param {number} courseId The course ID. - * @param {boolean} [offline] Whether it should return cached data. Has priority over ignoreCache. - * @param {boolean} [ignoreCache] Whether it should ignore cached data (it will always fail in offline or server down). - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} List of pages. + * @param module The module object returned by WS. + * @param courseId The course ID. + * @param offline Whether it should return cached data. Has priority over ignoreCache. + * @param ignoreCache Whether it should ignore cached data (it will always fail in offline or server down). + * @param siteId Site ID. If not defined, current site. + * @return List of pages. */ protected getAllPages(module: any, courseId: number, offline?: boolean, ignoreCache?: boolean, siteId?: string) : Promise { @@ -75,18 +88,18 @@ export class AddonModWikiPrefetchHandler extends CoreCourseActivityPrefetchHandl /** * Get the download size of a module. * - * @param {any} module Module. - * @param {Number} courseId Course ID the module belongs to. - * @param {boolean} [single] True if we're downloading a single module, false if we're downloading a whole section. - * @return {Promise<{size: number, total: boolean}>} Promise resolved with the size and a boolean indicating if it was able - * to calculate the total size. + * @param module Module. + * @param courseId Course ID the module belongs to. + * @param single True if we're downloading a single module, false if we're downloading a whole section. + * @return Promise resolved with the size and a boolean indicating if it was able + * to calculate the total size. */ getDownloadSize(module: any, courseId: number, single?: boolean): Promise<{ size: number, total: boolean }> { const promises = [], siteId = this.sitesProvider.getCurrentSiteId(); promises.push(this.getFiles(module, courseId, single, siteId).then((files) => { - return this.utils.sumFileSizes(files); + return this.pluginFileDelegate.getFilesSize(files); })); promises.push(this.getAllPages(module, courseId, false, true, siteId).then((pages) => { @@ -112,11 +125,11 @@ export class AddonModWikiPrefetchHandler extends CoreCourseActivityPrefetchHandl /** * Get list of files. If not defined, we'll assume they're in module.contents. * - * @param {any} module Module. - * @param {number} courseId Course ID the module belongs to. - * @param {boolean} [single] True if we're downloading a single module, false if we're downloading a whole section. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with the list of files. + * @param module Module. + * @param courseId Course ID the module belongs to. + * @param single True if we're downloading a single module, false if we're downloading a whole section. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the list of files. */ getFiles(module: any, courseId: number, single?: boolean, siteId?: string): Promise { @@ -137,9 +150,9 @@ export class AddonModWikiPrefetchHandler extends CoreCourseActivityPrefetchHandl /** * Invalidate the prefetched content. * - * @param {number} moduleId The module ID. - * @param {number} courseId The course ID the module belongs to. - * @return {Promise} Promise resolved when the data is invalidated. + * @param moduleId The module ID. + * @param courseId The course ID the module belongs to. + * @return Promise resolved when the data is invalidated. */ invalidateContent(moduleId: number, courseId: number): Promise { return this.wikiProvider.invalidateContent(moduleId, courseId); @@ -148,11 +161,11 @@ export class AddonModWikiPrefetchHandler extends CoreCourseActivityPrefetchHandl /** * Prefetch a module. * - * @param {any} module Module. - * @param {number} courseId Course ID the module belongs to. - * @param {boolean} [single] True if we're downloading a single module, false if we're downloading a whole section. - * @param {string} [dirPath] Path of the directory where to store all the content files. - * @return {Promise} Promise resolved when done. + * @param module Module. + * @param courseId Course ID the module belongs to. + * @param single True if we're downloading a single module, false if we're downloading a whole section. + * @param dirPath Path of the directory where to store all the content files. + * @return Promise resolved when done. */ prefetch(module: any, courseId?: number, single?: boolean, dirPath?: string): Promise { // Get the download time of the package before starting the download (otherwise we'd always get current time). @@ -170,12 +183,12 @@ export class AddonModWikiPrefetchHandler extends CoreCourseActivityPrefetchHandl /** * Prefetch a wiki. * - * @param {any} module Module. - * @param {number} courseId Course ID the module belongs to. - * @param {boolean} single True if we're downloading a single module, false if we're downloading a whole section. - * @param {string} siteId Site ID. - * @param {number} downloadTime The previous download time, 0 if no previous download. - * @return {Promise} Promise resolved when done. + * @param module Module. + * @param courseId Course ID the module belongs to. + * @param single True if we're downloading a single module, false if we're downloading a whole section. + * @param siteId Site ID. + * @param downloadTime The previous download time, 0 if no previous download. + * @return Promise resolved when done. */ protected prefetchWiki(module: any, courseId: number, single: boolean, siteId: string, downloadTime: number): Promise { const userId = this.sitesProvider.getCurrentSiteUserId(); @@ -211,10 +224,10 @@ export class AddonModWikiPrefetchHandler extends CoreCourseActivityPrefetchHandl /** * Sync a module. * - * @param {any} module Module. - * @param {number} courseId Course ID the module belongs to - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when done. + * @param module Module. + * @param courseId Course ID the module belongs to + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when done. */ sync(module: any, courseId: number, siteId?: any): Promise { return this.syncProvider.syncWiki(module.instance, module.course, module.id, siteId); diff --git a/src/addon/mod/wiki/providers/sync-cron-handler.ts b/src/addon/mod/wiki/providers/sync-cron-handler.ts index c74a439fe..fe854f560 100644 --- a/src/addon/mod/wiki/providers/sync-cron-handler.ts +++ b/src/addon/mod/wiki/providers/sync-cron-handler.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -29,9 +29,9 @@ export class AddonModWikiSyncCronHandler implements CoreCronHandler { * Execute the process. * Receives the ID of the site affected, undefined for all sites. * - * @param {string} [siteId] ID of the site affected, undefined for all sites. - * @param {boolean} [force] Wether the execution is forced (manual sync). - * @return {Promise} Promise resolved when done, rejected if failure. + * @param siteId ID of the site affected, undefined for all sites. + * @param force Wether the execution is forced (manual sync). + * @return Promise resolved when done, rejected if failure. */ execute(siteId?: string, force?: boolean): Promise { return this.wikiSync.syncAllWikis(siteId, force); @@ -40,7 +40,7 @@ export class AddonModWikiSyncCronHandler implements CoreCronHandler { /** * Get the time between consecutive executions. * - * @return {number} Time between consecutive executions (in ms). + * @return Time between consecutive executions (in ms). */ getInterval(): number { return this.wikiSync.syncInterval; diff --git a/src/addon/mod/wiki/providers/tag-area-handler.ts b/src/addon/mod/wiki/providers/tag-area-handler.ts index 0f3cab521..a0fcaa301 100644 --- a/src/addon/mod/wiki/providers/tag-area-handler.ts +++ b/src/addon/mod/wiki/providers/tag-area-handler.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -29,7 +29,7 @@ export class AddonModWikiTagAreaHandler implements CoreTagAreaHandler { /** * Whether or not the handler is enabled on a site level. - * @return {boolean|Promise} Whether or not the handler is enabled on a site level. + * @return Whether or not the handler is enabled on a site level. */ isEnabled(): boolean | Promise { return true; @@ -38,8 +38,8 @@ export class AddonModWikiTagAreaHandler implements CoreTagAreaHandler { /** * Parses the rendered content of a tag index and returns the items. * - * @param {string} content Rendered content. - * @return {any[]|Promise} Area items (or promise resolved with the items). + * @param content Rendered content. + * @return Area items (or promise resolved with the items). */ parseContent(content: string): any[] | Promise { return this.tagHelper.parseFeedContent(content); @@ -48,8 +48,8 @@ export class AddonModWikiTagAreaHandler implements CoreTagAreaHandler { /** * Get the component to use to display items. * - * @param {Injector} injector Injector. - * @return {any|Promise} The component (or promise resolved with component) to use, undefined if not found. + * @param injector Injector. + * @return The component (or promise resolved with component) to use, undefined if not found. */ getComponent(injector: Injector): any | Promise { return CoreTagFeedComponent; diff --git a/src/addon/mod/wiki/providers/wiki-offline.ts b/src/addon/mod/wiki/providers/wiki-offline.ts index 003fa3b7a..7b98dc034 100644 --- a/src/addon/mod/wiki/providers/wiki-offline.ts +++ b/src/addon/mod/wiki/providers/wiki-offline.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -91,8 +91,8 @@ export class AddonModWikiOfflineProvider { /** * Convert a value to a positive number. If not a number or less than 0, 0 will be returned. * - * @param {any} value Value to convert. - * @return {number} Converted value. + * @param value Value to convert. + * @return Converted value. */ convertToPositiveNumber(value: any): number { value = parseInt(value, 10); @@ -103,13 +103,13 @@ export class AddonModWikiOfflineProvider { /** * Delete a new page. * - * @param {string} title Title of the page. - * @param {number} [subwikiId] Subwiki ID. If not defined, wikiId, userId and groupId should be defined. - * @param {number} [wikiId] Wiki ID. Optional, will be used create subwiki if not informed. - * @param {number} [userId] User ID. Optional, will be used create subwiki if not informed. - * @param {number} [groupId] Group ID. Optional, will be used create subwiki if not informed. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved if deleted, rejected if failure. + * @param title Title of the page. + * @param subwikiId Subwiki ID. If not defined, wikiId, userId and groupId should be defined. + * @param wikiId Wiki ID. Optional, will be used create subwiki if not informed. + * @param userId User ID. Optional, will be used create subwiki if not informed. + * @param groupId Group ID. Optional, will be used create subwiki if not informed. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved if deleted, rejected if failure. */ deleteNewPage(title: string, subwikiId?: number, wikiId?: number, userId?: number, groupId?: number, siteId?: string) : Promise { @@ -134,8 +134,8 @@ export class AddonModWikiOfflineProvider { /** * Get all the stored new pages from all the wikis. * - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with pages. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with pages. */ getAllNewPages(siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -146,13 +146,13 @@ export class AddonModWikiOfflineProvider { /** * Get a stored new page. * - * @param {string} title Title of the page. - * @param {number} [subwikiId] Subwiki ID. If not defined, wikiId, userId and groupId should be defined. - * @param {number} [wikiId] Wiki ID. Optional, will be used create subwiki if not informed. - * @param {number} [userId] User ID. Optional, will be used create subwiki if not informed. - * @param {number} [groupId] Group ID. Optional, will be used create subwiki if not informed. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with page. + * @param title Title of the page. + * @param subwikiId Subwiki ID. If not defined, wikiId, userId and groupId should be defined. + * @param wikiId Wiki ID. Optional, will be used create subwiki if not informed. + * @param userId User ID. Optional, will be used create subwiki if not informed. + * @param groupId Group ID. Optional, will be used create subwiki if not informed. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with page. */ getNewPage(title: string, subwikiId?: number, wikiId?: number, userId?: number, groupId?: number, siteId?: string) : Promise { @@ -177,12 +177,12 @@ export class AddonModWikiOfflineProvider { /** * Get all the stored new pages from a certain subwiki. * - * @param {number} [subwikiId] Subwiki ID. If not defined, wikiId, userId and groupId should be defined. - * @param {number} [wikiId] Wiki ID. Optional, will be used create subwiki if not informed. - * @param {number} [userId] User ID. Optional, will be used create subwiki if not informed. - * @param {number} [groupId] Group ID. Optional, will be used create subwiki if not informed. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with pages. + * @param subwikiId Subwiki ID. If not defined, wikiId, userId and groupId should be defined. + * @param wikiId Wiki ID. Optional, will be used create subwiki if not informed. + * @param userId User ID. Optional, will be used create subwiki if not informed. + * @param groupId Group ID. Optional, will be used create subwiki if not informed. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with pages. */ getSubwikiNewPages(subwikiId?: number, wikiId?: number, userId?: number, groupId?: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -204,9 +204,9 @@ export class AddonModWikiOfflineProvider { /** * Get all the stored new pages from a list of subwikis. * - * @param {any[]} subwikis List of subwiki. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with pages. + * @param subwikis List of subwiki. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with pages. */ getSubwikisNewPages(subwikis: any[], siteId?: string): Promise { const promises = []; @@ -227,14 +227,14 @@ export class AddonModWikiOfflineProvider { /** * Save a new page to be sent later. * - * @param {string} title Title of the page. - * @param {string} content Content of the page. - * @param {number} [subwikiId] Subwiki ID. If not defined, wikiId, userId and groupId should be defined. - * @param {number} [wikiId] Wiki ID. Optional, will be used create subwiki if not informed. - * @param {number} [userId] User ID. Optional, will be used create subwiki if not informed. - * @param {number} [groupId] Group ID. Optional, will be used create subwiki if not informed. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved if stored, rejected if failure. + * @param title Title of the page. + * @param content Content of the page. + * @param subwikiId Subwiki ID. If not defined, wikiId, userId and groupId should be defined. + * @param wikiId Wiki ID. Optional, will be used create subwiki if not informed. + * @param userId User ID. Optional, will be used create subwiki if not informed. + * @param groupId Group ID. Optional, will be used create subwiki if not informed. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved if stored, rejected if failure. */ saveNewPage(title: string, content: string, subwikiId?: number, wikiId?: number, userId?: number, groupId?: number, siteId?: string): Promise { @@ -261,9 +261,9 @@ export class AddonModWikiOfflineProvider { /** * Check if a list of subwikis have offline data stored. * - * @param {any[]} subwikis List of subwikis. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return{Promise} Promise resolved with boolean: whether it has offline data. + * @param subwikis List of subwikis. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with boolean: whether it has offline data. */ subwikisHaveOfflineData(subwikis: any[], siteId?: string): Promise { return this.getSubwikisNewPages(subwikis, siteId).then((pages) => { diff --git a/src/addon/mod/wiki/providers/wiki-sync.ts b/src/addon/mod/wiki/providers/wiki-sync.ts index 9fa0200c9..174fedcf6 100644 --- a/src/addon/mod/wiki/providers/wiki-sync.ts +++ b/src/addon/mod/wiki/providers/wiki-sync.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -35,25 +35,21 @@ import { AddonModWikiOfflineProvider } from './wiki-offline'; export interface AddonModWikiSyncSubwikiResult { /** * List of warnings. - * @type {string[]} */ warnings: string[]; /** * Whether data was updated in the site. - * @type {boolean} */ updated: boolean; /** * List of created pages. - * @type {{pageId: number, title: string}} */ created: {pageId: number, title: string}[]; /** * List of discarded pages. - * @type {{title: string, warning: string}} */ discarded: {title: string, warning: string}[]; } @@ -64,19 +60,16 @@ export interface AddonModWikiSyncSubwikiResult { export interface AddonModWikiSyncWikiResult { /** * List of warnings. - * @type {string[]} */ warnings: string[]; /** * Whether data was updated in the site. - * @type {boolean} */ updated: boolean; /** * List of subwikis. - * @type {{[subwikiId: number]: {created: {pageId: number, title: string}, discarded: {title: string, warning: string}}}} */ subwikis: {[subwikiId: number]: { created: {pageId: number, title: string}[], @@ -85,7 +78,6 @@ export interface AddonModWikiSyncWikiResult { /** * Site ID. - * @type {string} */ siteId: string; } @@ -117,11 +109,11 @@ export class AddonModWikiSyncProvider extends CoreSyncBaseProvider { /** * Get a string to identify a subwiki. If it doesn't have a subwiki ID it will be identified by wiki ID, user ID and group ID. * - * @param {number} subwikiId Subwiki ID. If not defined, wikiId, userId and groupId should be defined. - * @param {number} [wikiId] Wiki ID. Optional, will be used to create the subwiki if subwiki ID not provided. - * @param {number} [userId] User ID. Optional, will be used to create the subwiki if subwiki ID not provided. - * @param {number} [groupId] Group ID. Optional, will be used to create the subwiki if subwiki ID not provided. - * @return {string} Identifier. + * @param subwikiId Subwiki ID. If not defined, wikiId, userId and groupId should be defined. + * @param wikiId Wiki ID. Optional, will be used to create the subwiki if subwiki ID not provided. + * @param userId User ID. Optional, will be used to create the subwiki if subwiki ID not provided. + * @param groupId Group ID. Optional, will be used to create the subwiki if subwiki ID not provided. + * @return Identifier. */ getSubwikiBlockId(subwikiId: number, wikiId?: number, userId?: number, groupId?: number): string { subwikiId = this.wikiOfflineProvider.convertToPositiveNumber(subwikiId); @@ -142,9 +134,9 @@ export class AddonModWikiSyncProvider extends CoreSyncBaseProvider { /** * Try to synchronize all the wikis in a certain site or in all sites. * - * @param {string} [siteId] Site ID to sync. If not defined, sync all sites. - * @param {boolean} [force] Wether to force sync not depending on last execution. - * @return {Promise} Promise resolved if sync is successful, rejected if sync fails. + * @param siteId Site ID to sync. If not defined, sync all sites. + * @param force Wether to force sync not depending on last execution. + * @return Promise resolved if sync is successful, rejected if sync fails. */ syncAllWikis(siteId?: string, force?: boolean): Promise { return this.syncOnSites('all wikis', this.syncAllWikisFunc.bind(this), [force], siteId); @@ -153,9 +145,9 @@ export class AddonModWikiSyncProvider extends CoreSyncBaseProvider { /** * Sync all wikis on a site. * - * @param {string} siteId Site ID to sync. - * @param {boolean} [force] Wether to force sync not depending on last execution. - * @param {Promise} Promise resolved if sync is successful, rejected if sync fails. + * @param siteId Site ID to sync. + * @param force Wether to force sync not depending on last execution. + * @param Promise resolved if sync is successful, rejected if sync fails. */ protected syncAllWikisFunc(siteId: string, force?: boolean): Promise { // Get all the pages created in offline. @@ -201,12 +193,12 @@ export class AddonModWikiSyncProvider extends CoreSyncBaseProvider { /** * Sync a subwiki only if a certain time has passed since the last time. * - * @param {number} subwikiId Subwiki ID. If not defined, wikiId, userId and groupId should be defined. - * @param {number} [wikiId] Wiki ID. Optional, will be used to create the subwiki if subwiki ID not provided. - * @param {number} [userId] User ID. Optional, will be used to create the subwiki if subwiki ID not provided. - * @param {number} [groupId] Group ID. Optional, will be used to create the subwiki if subwiki ID not provided. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when subwiki is synced or doesn't need to be synced. + * @param subwikiId Subwiki ID. If not defined, wikiId, userId and groupId should be defined. + * @param wikiId Wiki ID. Optional, will be used to create the subwiki if subwiki ID not provided. + * @param userId User ID. Optional, will be used to create the subwiki if subwiki ID not provided. + * @param groupId Group ID. Optional, will be used to create the subwiki if subwiki ID not provided. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when subwiki is synced or doesn't need to be synced. */ syncSubwikiIfNeeded(subwikiId: number, wikiId?: number, userId?: number, groupId?: number, siteId?: string) : Promise { @@ -223,12 +215,12 @@ export class AddonModWikiSyncProvider extends CoreSyncBaseProvider { /** * Synchronize a subwiki. * - * @param {number} subwikiId Subwiki ID. If not defined, wikiId, userId and groupId should be defined. - * @param {number} [wikiId] Wiki ID. Optional, will be used to create the subwiki if subwiki ID not provided. - * @param {number} [userId] User ID. Optional, will be used to create the subwiki if subwiki ID not provided. - * @param {number} [groupId] Group ID. Optional, will be used to create the subwiki if subwiki ID not provided. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved if sync is successful, rejected otherwise. + * @param subwikiId Subwiki ID. If not defined, wikiId, userId and groupId should be defined. + * @param wikiId Wiki ID. Optional, will be used to create the subwiki if subwiki ID not provided. + * @param userId User ID. Optional, will be used to create the subwiki if subwiki ID not provided. + * @param groupId Group ID. Optional, will be used to create the subwiki if subwiki ID not provided. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved if sync is successful, rejected otherwise. */ syncSubwiki(subwikiId: number, wikiId?: number, userId?: number, groupId?: number, siteId?: string) : Promise { @@ -333,11 +325,11 @@ export class AddonModWikiSyncProvider extends CoreSyncBaseProvider { /** * Tries to synchronize a wiki. * - * @param {number} wikiId Wiki ID. - * @param {number} [courseId] Course ID. - * @param {number} [cmId] Wiki course module ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved if sync is successful, rejected otherwise. + * @param wikiId Wiki ID. + * @param courseId Course ID. + * @param cmId Wiki course module ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved if sync is successful, rejected otherwise. */ syncWiki(wikiId: number, courseId?: number, cmId?: number, siteId?: string): Promise { siteId = siteId || this.sitesProvider.getCurrentSiteId(); diff --git a/src/addon/mod/wiki/providers/wiki.ts b/src/addon/mod/wiki/providers/wiki.ts index 4523f541b..ee9414c54 100644 --- a/src/addon/mod/wiki/providers/wiki.ts +++ b/src/addon/mod/wiki/providers/wiki.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -27,31 +27,26 @@ import { CoreSite, CoreSiteWSPreSets } from '@classes/site'; export interface AddonModWikiSubwikiListData { /** * Number of subwikis. - * @type {number} */ count: number; /** * Subwiki ID currently selected. - * @type {number} */ subwikiSelected: number; /** * User of the subwiki currently selected. - * @type {number} */ userSelected: number; /** * Group of the subwiki currently selected. - * @type {number} */ groupSelected: number; /** * List of subwikis. - * @type {any[]} */ subwikis: any[]; } @@ -84,7 +79,7 @@ export class AddonModWikiProvider { /** * Clear subwiki list cache for a certain wiki or all of them. * - * @param {number} [wikiId] wiki Id, if not provided all will be cleared. + * @param wikiId wiki Id, if not provided all will be cleared. */ clearSubwikiList(wikiId?: number): void { if (typeof wikiId == 'undefined') { @@ -97,10 +92,10 @@ export class AddonModWikiProvider { /** * Save wiki contents on a page or section. * - * @param {number} pageId Page ID. - * @param {string} content content to be saved. - * @param {string} [section] section to get. - * @return {Promise} Promise resolved with the page ID. + * @param pageId Page ID. + * @param content content to be saved. + * @param section section to get. + * @return Promise resolved with the page ID. */ editPage(pageId: number, content: string, section?: string, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -122,11 +117,11 @@ export class AddonModWikiProvider { /** * Get a wiki page contents. * - * @param {number} pageId Page ID. - * @param {boolean} [offline] Whether it should return cached data. Has priority over ignoreCache. - * @param {boolean} [ignoreCache] Whether it should ignore cached data (it will always fail in offline or server down). - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with the page data. + * @param pageId Page ID. + * @param offline Whether it should return cached data. Has priority over ignoreCache. + * @param ignoreCache Whether it should ignore cached data (it will always fail in offline or server down). + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the page data. */ getPageContents(pageId: number, offline?: boolean, ignoreCache?: boolean, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -154,8 +149,8 @@ export class AddonModWikiProvider { /** * Get cache key for wiki Pages Contents WS calls. * - * @param {number} pageId Wiki Page ID. - * @return {string} Cache key. + * @param pageId Wiki Page ID. + * @return Cache key. */ protected getPageContentsCacheKey(pageId: number): string { return this.ROOT_CACHE_KEY + 'page:' + pageId; @@ -164,11 +159,11 @@ export class AddonModWikiProvider { /** * Get a wiki page contents for editing. It does not cache calls. * - * @param {number} pageId Page ID. - * @param {string} [section] Section to get. - * @param {boolean} [lockOnly] Just renew lock and not return content. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with page contents. + * @param pageId Page ID. + * @param section Section to get. + * @param lockOnly Just renew lock and not return content. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with page contents. */ getPageForEditing(pageId: number, section?: string, lockOnly?: boolean, siteId?: string): Promise { @@ -195,13 +190,13 @@ export class AddonModWikiProvider { /** * Gets the list of files from a specific subwiki. * - * @param {number} wikiId Wiki ID. - * @param {number} [groupId] Group to get files from. - * @param {number} [userId] User to get files from. - * @param {boolean} [offline] Whether it should return cached data. Has priority over ignoreCache. - * @param {boolean} [ignoreCache] Whether it should ignore cached data (it will always fail in offline or server down). - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with subwiki files. + * @param wikiId Wiki ID. + * @param groupId Group to get files from. + * @param userId User to get files from. + * @param offline Whether it should return cached data. Has priority over ignoreCache. + * @param ignoreCache Whether it should ignore cached data (it will always fail in offline or server down). + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with subwiki files. */ getSubwikiFiles(wikiId: number, groupId?: number, userId?: number, offline?: boolean, ignoreCache?: boolean, siteId?: string) : Promise { @@ -236,10 +231,10 @@ export class AddonModWikiProvider { /** * Get cache key for wiki Subwiki Files WS calls. * - * @param {number} wikiId Wiki ID. - * @param {number} groupId Group ID. - * @param {number} userId User ID. - * @return {string} Cache key. + * @param wikiId Wiki ID. + * @param groupId Group ID. + * @param userId User ID. + * @return Cache key. */ protected getSubwikiFilesCacheKey(wikiId: number, groupId: number, userId: number): string { return this.getSubwikiFilesCacheKeyPrefix(wikiId) + ':' + groupId + ':' + userId; @@ -248,8 +243,8 @@ export class AddonModWikiProvider { /** * Get cache key for all wiki Subwiki Files WS calls. * - * @param {number} wikiId Wiki ID. - * @return {string} Cache key. + * @param wikiId Wiki ID. + * @return Cache key. */ protected getSubwikiFilesCacheKeyPrefix(wikiId: number): string { return this.ROOT_CACHE_KEY + 'subwikifiles:' + wikiId; @@ -258,8 +253,8 @@ export class AddonModWikiProvider { /** * Get a list of subwikis and related data for a certain wiki from the cache. * - * @param {number} wikiId wiki Id - * @return {AddonModWikiSubwikiListData} Subwiki list and related data. + * @param wikiId wiki Id + * @return Subwiki list and related data. */ getSubwikiList(wikiId: number): AddonModWikiSubwikiListData { return this.subwikiListsCache[wikiId]; @@ -268,16 +263,16 @@ export class AddonModWikiProvider { /** * Get the list of Pages of a SubWiki. * - * @param {number} wikiId Wiki ID. - * @param {number} [groupId] Group to get pages from. - * @param {number} [userId] User to get pages from. - * @param {string} [sortBy=title] The attribute to sort the returned list. - * @param {string} [sortDirection=ASC] Direction to sort the returned list (ASC | DESC). - * @param {boolean} [includeContent] Whether the pages have to include its content. Default: false. - * @param {boolean} [offline] Whether it should return cached data. Has priority over ignoreCache. - * @param {boolean} [ignoreCache] Whether it should ignore cached data (it will always fail in offline or server down). - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with wiki subwiki pages. + * @param wikiId Wiki ID. + * @param groupId Group to get pages from. + * @param userId User to get pages from. + * @param sortBy The attribute to sort the returned list. + * @param sortDirection Direction to sort the returned list (ASC | DESC). + * @param includeContent Whether the pages have to include its content. Default: false. + * @param offline Whether it should return cached data. Has priority over ignoreCache. + * @param ignoreCache Whether it should ignore cached data (it will always fail in offline or server down). + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with wiki subwiki pages. */ getSubwikiPages(wikiId: number, groupId?: number, userId?: number, sortBy: string = 'title', sortDirection: string = 'ASC', includeContent?: boolean, offline?: boolean, ignoreCache?: boolean, siteId?: string): Promise { @@ -321,10 +316,10 @@ export class AddonModWikiProvider { /** * Get cache key for wiki Subwiki Pages WS calls. * - * @param {number} wikiId Wiki ID. - * @param {number} groupId Group ID. - * @param {number} userId User ID. - * @return {string} Cache key. + * @param wikiId Wiki ID. + * @param groupId Group ID. + * @param userId User ID. + * @return Cache key. */ protected getSubwikiPagesCacheKey(wikiId: number, groupId: number, userId: number): string { return this.getSubwikiPagesCacheKeyPrefix(wikiId) + ':' + groupId + ':' + userId; @@ -333,8 +328,8 @@ export class AddonModWikiProvider { /** * Get cache key for all wiki Subwiki Pages WS calls. * - * @param {number} wikiId Wiki ID. - * @return {string} Cache key. + * @param wikiId Wiki ID. + * @return Cache key. */ protected getSubwikiPagesCacheKeyPrefix(wikiId: number): string { return this.ROOT_CACHE_KEY + 'subwikipages:' + wikiId; @@ -343,11 +338,11 @@ export class AddonModWikiProvider { /** * Get all the subwikis of a wiki. * - * @param {number} wikiId Wiki ID. - * @param {boolean} [offline] Whether it should return cached data. Has priority over ignoreCache. - * @param {boolean} [ignoreCache] Whether it should ignore cached data (it will always fail in offline or server down). - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with subwikis. + * @param wikiId Wiki ID. + * @param offline Whether it should return cached data. Has priority over ignoreCache. + * @param ignoreCache Whether it should ignore cached data (it will always fail in offline or server down). + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with subwikis. */ getSubwikis(wikiId: number, offline?: boolean, ignoreCache?: boolean, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -375,8 +370,8 @@ export class AddonModWikiProvider { /** * Get cache key for get wiki subWikis WS calls. * - * @param {number} wikiId Wiki ID. - * @return {string} Cache key. + * @param wikiId Wiki ID. + * @return Cache key. */ protected getSubwikisCacheKey(wikiId: number): string { return this.ROOT_CACHE_KEY + 'subwikis:' + wikiId; @@ -385,11 +380,11 @@ export class AddonModWikiProvider { /** * Get a wiki by module ID. * - * @param {number} courseId Course ID. - * @param {number} cmId Course module ID. - * @param {boolean} [forceCache] Whether it should always return cached data. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the wiki is retrieved. + * @param courseId Course ID. + * @param cmId Course module ID. + * @param forceCache Whether it should always return cached data. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the wiki is retrieved. */ getWiki(courseId: number, cmId: number, forceCache?: boolean, siteId?: string): Promise { return this.getWikiByField(courseId, 'coursemodule', cmId, forceCache, siteId); @@ -398,12 +393,12 @@ export class AddonModWikiProvider { /** * Get a wiki with key=value. If more than one is found, only the first will be returned. * - * @param {number} courseId Course ID. - * @param {string} key Name of the property to check. - * @param {any} value Value to search. - * @param {boolean} [forceCache] Whether it should always return cached data. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the wiki is retrieved. + * @param courseId Course ID. + * @param key Name of the property to check. + * @param value Value to search. + * @param forceCache Whether it should always return cached data. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the wiki is retrieved. */ protected getWikiByField(courseId: number, key: string, value: any, forceCache?: boolean, siteId?: string): Promise { @@ -435,11 +430,11 @@ export class AddonModWikiProvider { /** * Get a wiki by wiki ID. * - * @param {number} courseId Course ID. - * @param {number} id Wiki ID. - * @param {boolean} [forceCache] Whether it should always return cached data. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the wiki is retrieved. + * @param courseId Course ID. + * @param id Wiki ID. + * @param forceCache Whether it should always return cached data. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the wiki is retrieved. */ getWikiById(courseId: number, id: number, forceCache?: boolean, siteId?: string): Promise { return this.getWikiByField(courseId, 'id', id, forceCache, siteId); @@ -448,8 +443,8 @@ export class AddonModWikiProvider { /** * Get cache key for wiki data WS calls. * - * @param {number} courseId Course ID. - * @return {string} Cache key. + * @param courseId Course ID. + * @return Cache key. */ protected getWikiDataCacheKey(courseId: number): string { return this.ROOT_CACHE_KEY + 'wiki:' + courseId; @@ -458,11 +453,11 @@ export class AddonModWikiProvider { /** * Gets a list of files to download for a wiki, using a format similar to module.contents from get_course_contents. * - * @param {any} wiki Wiki. - * @param {boolean} [offline] Whether it should return cached data. Has priority over ignoreCache. - * @param {boolean} [ignoreCache] Whether it should ignore cached data (it will always fail in offline or server down). - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with the list of files. + * @param wiki Wiki. + * @param offline Whether it should return cached data. Has priority over ignoreCache. + * @param ignoreCache Whether it should ignore cached data (it will always fail in offline or server down). + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the list of files. */ getWikiFileList(wiki: any, offline?: boolean, ignoreCache?: boolean, siteId?: string): Promise { siteId = siteId || this.sitesProvider.getCurrentSiteId(); @@ -488,11 +483,11 @@ export class AddonModWikiProvider { /** * Gets a list of all pages for a Wiki. * - * @param {any} wiki Wiki. - * @param {boolean} [offline] Whether it should return cached data. Has priority over ignoreCache. - * @param {boolean} [ignoreCache] Whether it should ignore cached data (it will always fail in offline or server down). - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Page list. + * @param wiki Wiki. + * @param offline Whether it should return cached data. Has priority over ignoreCache. + * @param ignoreCache Whether it should ignore cached data (it will always fail in offline or server down). + * @param siteId Site ID. If not defined, current site. + * @return Page list. */ getWikiPageList(wiki: any, offline?: boolean, ignoreCache?: boolean, siteId?: string): Promise { siteId = siteId || this.sitesProvider.getCurrentSiteId(); @@ -519,10 +514,10 @@ export class AddonModWikiProvider { * Invalidate the prefetched content except files. * To invalidate files, use invalidateFiles. * - * @param {number} moduleId The module ID. - * @param {number} courseId Course ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when done. + * @param moduleId The module ID. + * @param courseId Course ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when done. */ invalidateContent(moduleId: number, courseId: number, siteId?: string): Promise { siteId = siteId || this.sitesProvider.getCurrentSiteId(); @@ -542,9 +537,9 @@ export class AddonModWikiProvider { /** * Invalidate the prefetched files. * - * @param {number} moduleId The module ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the files are invalidated. + * @param moduleId The module ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the files are invalidated. */ invalidateFiles(moduleId: number, siteId?: string): Promise { return this.filepoolProvider.invalidateFilesByComponent(siteId, AddonModWikiProvider.COMPONENT, moduleId); @@ -553,9 +548,9 @@ export class AddonModWikiProvider { /** * Invalidates page content WS call for a certain page. * - * @param {number} pageId Wiki Page ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the data is invalidated. + * @param pageId Wiki Page ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the data is invalidated. */ invalidatePage(pageId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -566,9 +561,9 @@ export class AddonModWikiProvider { /** * Invalidates all the subwiki files WS calls for a certain wiki. * - * @param {number} wikiId Wiki ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the data is invalidated. + * @param wikiId Wiki ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the data is invalidated. */ invalidateSubwikiFiles(wikiId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -579,9 +574,9 @@ export class AddonModWikiProvider { /** * Invalidates all the subwiki pages WS calls for a certain wiki. * - * @param {Number} wikiId Wiki ID. - * @param {String} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the data is invalidated. + * @param wikiId Wiki ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the data is invalidated. */ invalidateSubwikiPages(wikiId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -592,9 +587,9 @@ export class AddonModWikiProvider { /** * Invalidates all the get subwikis WS calls for a certain wiki. * - * @param {number} wikiId Wiki ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the data is invalidated. + * @param wikiId Wiki ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the data is invalidated. */ invalidateSubwikis(wikiId: number, siteId?: string): Promise { this.clearSubwikiList(wikiId); @@ -607,9 +602,9 @@ export class AddonModWikiProvider { /** * Invalidates wiki data. * - * @param {Number} courseId Course ID. - * @param {String} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the data is invalidated. + * @param courseId Course ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the data is invalidated. */ invalidateWikiData(courseId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -620,13 +615,13 @@ export class AddonModWikiProvider { /** * Check if a page title is already used. * - * @param {number} wikiId Wiki ID. - * @param {number} subwikiId Subwiki ID. - * @param {string} title Page title. - * @param {boolean} [offline] Whether it should return cached data. Has priority over ignoreCache. - * @param {boolean} [ignoreCache] Whether it should ignore cached data (it will always fail in offline or server down). - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with true if used, resolved with false if not used or cannot determine. + * @param wikiId Wiki ID. + * @param subwikiId Subwiki ID. + * @param title Page title. + * @param offline Whether it should return cached data. Has priority over ignoreCache. + * @param ignoreCache Whether it should ignore cached data (it will always fail in offline or server down). + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with true if used, resolved with false if not used or cannot determine. */ isTitleUsed(wikiId: number, subwikiId: number, title: string, offline?: boolean, ignoreCache?: boolean, siteId?: string) : Promise { @@ -658,11 +653,11 @@ export class AddonModWikiProvider { /** * Report a wiki page as being viewed. * - * @param {number} id Page ID. - * @param {number} wikiId Wiki ID. - * @param {string} [name] Name of the wiki. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the WS call is successful. + * @param id Page ID. + * @param wikiId Wiki ID. + * @param name Name of the wiki. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the WS call is successful. */ logPageView(id: number, wikiId: number, name?: string, siteId?: string): Promise { const params = { @@ -676,10 +671,10 @@ export class AddonModWikiProvider { /** * Report the wiki as being viewed. * - * @param {number} id Wiki ID. - * @param {string} [name] Name of the wiki. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the WS call is successful. + * @param id Wiki ID. + * @param name Name of the wiki. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the WS call is successful. */ logView(id: number, name?: string, siteId?: string): Promise { const params = { @@ -693,14 +688,14 @@ export class AddonModWikiProvider { /** * Create a new page on a subwiki. * - * @param {string} title Title to create the page. - * @param {string} content Content to save on the page. - * @param {number} [subwikiId] Subwiki ID. If not defined, wikiId, userId and groupId should be defined. - * @param {number} [wikiId] Wiki ID. Optional, will be used to create a new subwiki if subwikiId not supplied. - * @param {number} [userId] User ID. Optional, will be used to create a new subwiki if subwikiId not supplied. - * @param {number} [groupId] Group ID. Optional, will be used to create a new subwiki if subwikiId not supplied. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with page ID if page was created in server, -1 if stored in device. + * @param title Title to create the page. + * @param content Content to save on the page. + * @param subwikiId Subwiki ID. If not defined, wikiId, userId and groupId should be defined. + * @param wikiId Wiki ID. Optional, will be used to create a new subwiki if subwikiId not supplied. + * @param userId User ID. Optional, will be used to create a new subwiki if subwikiId not supplied. + * @param groupId Group ID. Optional, will be used to create a new subwiki if subwikiId not supplied. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with page ID if page was created in server, -1 if stored in device. */ newPage(title: string, content: string, subwikiId?: number, wikiId?: number, userId?: number, groupId?: number, siteId?: string): Promise { @@ -755,14 +750,14 @@ export class AddonModWikiProvider { /** * Create a new page on a subwiki. It will fail if offline or cannot connect. * - * @param {string} title Title to create the page. - * @param {string} content Content to save on the page. - * @param {number} [subwikiId] Subwiki ID. If not defined, wikiId, userId and groupId should be defined. - * @param {number} [wikiId] Wiki ID. Optional, will be used create subwiki if not informed. - * @param {number} [userId] User ID. Optional, will be used create subwiki if not informed. - * @param {number} [groupId] Group ID. Optional, will be used create subwiki if not informed. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with the page ID if created, rejected otherwise. + * @param title Title to create the page. + * @param content Content to save on the page. + * @param subwikiId Subwiki ID. If not defined, wikiId, userId and groupId should be defined. + * @param wikiId Wiki ID. Optional, will be used create subwiki if not informed. + * @param userId User ID. Optional, will be used create subwiki if not informed. + * @param groupId Group ID. Optional, will be used create subwiki if not informed. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the page ID if created, rejected otherwise. */ newPageOnline(title: string, content: string, subwikiId?: number, wikiId?: number, userId?: number, groupId?: number, siteId?: string): Promise { @@ -794,12 +789,12 @@ export class AddonModWikiProvider { /** * Save subwiki list for a wiki to the cache. * - * @param {number} wikiId Wiki Id. - * @param {any[]} subwikis List of subwikis. - * @param {number} count Number of subwikis in the subwikis list. - * @param {number} subwikiId Subwiki Id currently selected. - * @param {number} userId User Id currently selected. - * @param {number} groupId Group Id currently selected. + * @param wikiId Wiki Id. + * @param subwikis List of subwikis. + * @param count Number of subwikis in the subwikis list. + * @param subwikiId Subwiki Id currently selected. + * @param userId User Id currently selected. + * @param groupId Group Id currently selected. */ setSubwikiList(wikiId: number, subwikis: any[], count: number, subwikiId: number, userId: number, groupId: number): void { this.subwikiListsCache[wikiId] = { @@ -814,9 +809,9 @@ export class AddonModWikiProvider { /** * Sort an array of wiki pages by title. * - * @param {any[]} pages Pages to sort. - * @param {boolean} [desc] True to sort in descendent order, false to sort in ascendent order. Defaults to false. - * @return {any[]} Sorted pages. + * @param pages Pages to sort. + * @param desc True to sort in descendent order, false to sort in ascendent order. Defaults to false. + * @return Sorted pages. */ sortPagesByTitle(pages: any[], desc?: boolean): any[] { return pages.sort((a, b) => { @@ -833,12 +828,12 @@ export class AddonModWikiProvider { /** * Check if a wiki has a certain subwiki. * - * @param {number} wikiId Wiki ID. - * @param {number} subwikiId Subwiki ID to search. - * @param {boolean} [offline] Whether it should return cached data. Has priority over ignoreCache. - * @param {boolean} [ignoreCache] Whether it should ignore cached data (it will always fail in offline or server down). - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with true if it has subwiki, resolved with false otherwise. + * @param wikiId Wiki ID. + * @param subwikiId Subwiki ID to search. + * @param offline Whether it should return cached data. Has priority over ignoreCache. + * @param ignoreCache Whether it should ignore cached data (it will always fail in offline or server down). + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with true if it has subwiki, resolved with false otherwise. */ wikiHasSubwiki(wikiId: number, subwikiId: number, offline?: boolean, ignoreCache?: boolean, siteId?: string): Promise { // Get the subwikis to check if any of them matches the one passed as param. diff --git a/src/addon/mod/wiki/wiki.module.ts b/src/addon/mod/wiki/wiki.module.ts index 6396cac74..9d2c85269 100644 --- a/src/addon/mod/wiki/wiki.module.ts +++ b/src/addon/mod/wiki/wiki.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/addon/mod/workshop/assessment/accumulative/accumulative.module.ts b/src/addon/mod/workshop/assessment/accumulative/accumulative.module.ts index f20ce1fec..79091aeeb 100644 --- a/src/addon/mod/workshop/assessment/accumulative/accumulative.module.ts +++ b/src/addon/mod/workshop/assessment/accumulative/accumulative.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/addon/mod/workshop/assessment/accumulative/component/accumulative.ts b/src/addon/mod/workshop/assessment/accumulative/component/accumulative.ts index eabb8bf09..0e7aefb26 100644 --- a/src/addon/mod/workshop/assessment/accumulative/component/accumulative.ts +++ b/src/addon/mod/workshop/assessment/accumulative/component/accumulative.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/addon/mod/workshop/assessment/accumulative/component/addon-mod-workshop-assessment-strategy-accumulative.html b/src/addon/mod/workshop/assessment/accumulative/component/addon-mod-workshop-assessment-strategy-accumulative.html index 80195fbb5..229a181d3 100644 --- a/src/addon/mod/workshop/assessment/accumulative/component/addon-mod-workshop-assessment-strategy-accumulative.html +++ b/src/addon/mod/workshop/assessment/accumulative/component/addon-mod-workshop-assessment-strategy-accumulative.html @@ -2,7 +2,7 @@

{{ field.dimtitle }}

- +
{{ 'addon.mod_workshop_assessment_accumulative.dimensiongradefor' | translate : {'$a': field.dimtitle } }} @@ -23,7 +23,7 @@

{{ 'addon.mod_workshop_assessment_accumulative.dimensioncommentfor' | translate : {'$a': field.dimtitle } }}

-

+

diff --git a/src/addon/mod/workshop/assessment/accumulative/providers/handler.ts b/src/addon/mod/workshop/assessment/accumulative/providers/handler.ts index 40505af47..a2fa5142c 100644 --- a/src/addon/mod/workshop/assessment/accumulative/providers/handler.ts +++ b/src/addon/mod/workshop/assessment/accumulative/providers/handler.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -30,7 +30,7 @@ export class AddonModWorkshopAssessmentStrategyAccumulativeHandler implements Ad /** * Whether or not the handler is enabled on a site level. - * @return {boolean|Promise} Whether or not the handler is enabled on a site level. + * @return Whether or not the handler is enabled on a site level. */ isEnabled(): boolean | Promise { return true; @@ -40,8 +40,8 @@ export class AddonModWorkshopAssessmentStrategyAccumulativeHandler implements Ad * Return the Component to render the plugin. * It's recommended to return the class of the component, but you can also return an instance of the component. * - * @param {Injector} injector Injector. - * @return {any|Promise} The component (or promise resolved with component) to use, undefined if not found. + * @param injector Injector. + * @return The component (or promise resolved with component) to use, undefined if not found. */ getComponent(injector: Injector): any | Promise { return AddonModWorkshopAssessmentStrategyAccumulativeComponent; @@ -50,9 +50,9 @@ export class AddonModWorkshopAssessmentStrategyAccumulativeHandler implements Ad /** * Prepare original values to be shown and compared. * - * @param {any} form Original data of the form. - * @param {number} workshopId WorkShop Id - * @return {Promise} Promise resolved with original values sorted. + * @param form Original data of the form. + * @param workshopId WorkShop Id + * @return Promise resolved with original values sorted. */ getOriginalValues(form: any, workshopId: number): Promise { const defaultGrade = this.translate.instant('core.choosedots'), @@ -92,9 +92,9 @@ export class AddonModWorkshopAssessmentStrategyAccumulativeHandler implements Ad /** * Check if the assessment data has changed for a certain submission and workshop for a this strategy plugin. * - * @param {any[]} originalValues Original values of the form. - * @param {any[]} currentValues Current values of the form. - * @return {boolean} True if data has changed, false otherwise. + * @param originalValues Original values of the form. + * @param currentValues Current values of the form. + * @return True if data has changed, false otherwise. */ hasDataChanged(originalValues: any[], currentValues: any[]): boolean { for (const x in originalValues) { @@ -112,9 +112,9 @@ export class AddonModWorkshopAssessmentStrategyAccumulativeHandler implements Ad /** * Prepare assessment data to be sent to the server depending on the strategy selected. * - * @param {any{}} currentValues Current values of the form. - * @param {any} form Assessment form data. - * @return {Promise} Promise resolved with the data to be sent. Or rejected with the input errors object. + * @param currentValues Current values of the form. + * @param form Assessment form data. + * @return Promise resolved with the data to be sent. Or rejected with the input errors object. */ prepareAssessmentData(currentValues: any[], form: any): Promise { const data = {}; diff --git a/src/addon/mod/workshop/assessment/assessment.module.ts b/src/addon/mod/workshop/assessment/assessment.module.ts index 3a77823ad..10855c8fb 100644 --- a/src/addon/mod/workshop/assessment/assessment.module.ts +++ b/src/addon/mod/workshop/assessment/assessment.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/addon/mod/workshop/assessment/comments/comments.module.ts b/src/addon/mod/workshop/assessment/comments/comments.module.ts index c8f91b558..1feb5958a 100644 --- a/src/addon/mod/workshop/assessment/comments/comments.module.ts +++ b/src/addon/mod/workshop/assessment/comments/comments.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/addon/mod/workshop/assessment/comments/component/addon-mod-workshop-assessment-strategy-comments.html b/src/addon/mod/workshop/assessment/comments/component/addon-mod-workshop-assessment-strategy-comments.html index 0eb9b23cb..7b2a324d4 100644 --- a/src/addon/mod/workshop/assessment/comments/component/addon-mod-workshop-assessment-strategy-comments.html +++ b/src/addon/mod/workshop/assessment/comments/component/addon-mod-workshop-assessment-strategy-comments.html @@ -2,7 +2,7 @@

{{ field.dimtitle }}

- +
{{ 'addon.mod_workshop_assessment_comments.dimensioncommentfor' | translate : {'$a': field.dimtitle } }} @@ -11,7 +11,7 @@

{{ 'addon.mod_workshop_assessment_comments.dimensioncommentfor' | translate : {'$a': field.dimtitle } }}

-

+

diff --git a/src/addon/mod/workshop/assessment/comments/component/comments.ts b/src/addon/mod/workshop/assessment/comments/component/comments.ts index 61a187536..da83af810 100644 --- a/src/addon/mod/workshop/assessment/comments/component/comments.ts +++ b/src/addon/mod/workshop/assessment/comments/component/comments.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/addon/mod/workshop/assessment/comments/providers/handler.ts b/src/addon/mod/workshop/assessment/comments/providers/handler.ts index 132e0f38b..592a84480 100644 --- a/src/addon/mod/workshop/assessment/comments/providers/handler.ts +++ b/src/addon/mod/workshop/assessment/comments/providers/handler.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -29,7 +29,7 @@ export class AddonModWorkshopAssessmentStrategyCommentsHandler implements AddonW /** * Whether or not the handler is enabled on a site level. - * @return {boolean|Promise} Whether or not the handler is enabled on a site level. + * @return Whether or not the handler is enabled on a site level. */ isEnabled(): boolean | Promise { return true; @@ -39,8 +39,8 @@ export class AddonModWorkshopAssessmentStrategyCommentsHandler implements AddonW * Return the Component to render the plugin. * It's recommended to return the class of the component, but you can also return an instance of the component. * - * @param {Injector} injector Injector. - * @return {any|Promise} The component (or promise resolved with component) to use, undefined if not found. + * @param injector Injector. + * @return The component (or promise resolved with component) to use, undefined if not found. */ getComponent(injector: Injector): any | Promise { return AddonModWorkshopAssessmentStrategyCommentsComponent; @@ -49,9 +49,9 @@ export class AddonModWorkshopAssessmentStrategyCommentsHandler implements AddonW /** * Prepare original values to be shown and compared. * - * @param {any} form Original data of the form. - * @param {number} workshopId Workshop Id - * @return {Promise} Promise resolved with original values sorted. + * @param form Original data of the form. + * @param workshopId Workshop Id + * @return Promise resolved with original values sorted. */ getOriginalValues(form: any, workshopId: number): Promise { const originalValues = []; @@ -75,9 +75,9 @@ export class AddonModWorkshopAssessmentStrategyCommentsHandler implements AddonW /** * Check if the assessment data has changed for a certain submission and workshop for a this strategy plugin. * - * @param {any[]} originalValues Original values of the form. - * @param {any[]} currentValues Current values of the form. - * @return {boolean} True if data has changed, false otherwise. + * @param originalValues Original values of the form. + * @param currentValues Current values of the form. + * @return True if data has changed, false otherwise. */ hasDataChanged(originalValues: any[], currentValues: any[]): boolean { for (const x in originalValues) { @@ -92,9 +92,9 @@ export class AddonModWorkshopAssessmentStrategyCommentsHandler implements AddonW /** * Prepare assessment data to be sent to the server depending on the strategy selected. * - * @param {any{}} currentValues Current values of the form. - * @param {any} form Assessment form data. - * @return {Promise} Promise resolved with the data to be sent. Or rejected with the input errors object. + * @param currentValues Current values of the form. + * @param form Assessment form data. + * @return Promise resolved with the data to be sent. Or rejected with the input errors object. */ prepareAssessmentData(currentValues: any[], form: any): Promise { const data = {}; diff --git a/src/addon/mod/workshop/assessment/numerrors/component/addon-mod-workshop-assessment-strategy-numerrors.html b/src/addon/mod/workshop/assessment/numerrors/component/addon-mod-workshop-assessment-strategy-numerrors.html index 925db72d1..0b322a738 100644 --- a/src/addon/mod/workshop/assessment/numerrors/component/addon-mod-workshop-assessment-strategy-numerrors.html +++ b/src/addon/mod/workshop/assessment/numerrors/component/addon-mod-workshop-assessment-strategy-numerrors.html @@ -2,7 +2,7 @@

{{ field.dimtitle }}

- +
@@ -10,11 +10,11 @@ - + - + @@ -24,7 +24,7 @@

{{ 'addon.mod_workshop_assessment_numerrors.dimensioncommentfor' | translate : {'$a': field.dimtitle } }}

-

+

diff --git a/src/addon/mod/workshop/assessment/numerrors/component/numerrors.ts b/src/addon/mod/workshop/assessment/numerrors/component/numerrors.ts index cffa93995..c3fd4ba83 100644 --- a/src/addon/mod/workshop/assessment/numerrors/component/numerrors.ts +++ b/src/addon/mod/workshop/assessment/numerrors/component/numerrors.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/addon/mod/workshop/assessment/numerrors/numerrors.module.ts b/src/addon/mod/workshop/assessment/numerrors/numerrors.module.ts index d066236cb..fff4115e7 100644 --- a/src/addon/mod/workshop/assessment/numerrors/numerrors.module.ts +++ b/src/addon/mod/workshop/assessment/numerrors/numerrors.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/addon/mod/workshop/assessment/numerrors/providers/handler.ts b/src/addon/mod/workshop/assessment/numerrors/providers/handler.ts index 5e77dc7e0..11729491d 100644 --- a/src/addon/mod/workshop/assessment/numerrors/providers/handler.ts +++ b/src/addon/mod/workshop/assessment/numerrors/providers/handler.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -29,7 +29,7 @@ export class AddonModWorkshopAssessmentStrategyNumErrorsHandler implements Addon /** * Whether or not the handler is enabled on a site level. - * @return {boolean|Promise} Whether or not the handler is enabled on a site level. + * @return Whether or not the handler is enabled on a site level. */ isEnabled(): boolean | Promise { return true; @@ -39,8 +39,8 @@ export class AddonModWorkshopAssessmentStrategyNumErrorsHandler implements Addon * Return the Component to render the plugin. * It's recommended to return the class of the component, but you can also return an instance of the component. * - * @param {Injector} injector Injector. - * @return {any|Promise} The component (or promise resolved with component) to use, undefined if not found. + * @param injector Injector. + * @return The component (or promise resolved with component) to use, undefined if not found. */ getComponent(injector: Injector): any | Promise { return AddonModWorkshopAssessmentStrategyNumErrorsComponent; @@ -49,9 +49,9 @@ export class AddonModWorkshopAssessmentStrategyNumErrorsHandler implements Addon /** * Prepare original values to be shown and compared. * - * @param {any} form Original data of the form. - * @param {number} workshopId Workshop Id - * @return {Promise} Promise resolved with original values sorted. + * @param form Original data of the form. + * @param workshopId Workshop Id + * @return Promise resolved with original values sorted. */ getOriginalValues(form: any, workshopId: number): Promise { const originalValues = []; @@ -76,9 +76,9 @@ export class AddonModWorkshopAssessmentStrategyNumErrorsHandler implements Addon /** * Check if the assessment data has changed for a certain submission and workshop for a this strategy plugin. * - * @param {any[]} originalValues Original values of the form. - * @param {any[]} currentValues Current values of the form. - * @return {boolean} True if data has changed, false otherwise. + * @param originalValues Original values of the form. + * @param currentValues Current values of the form. + * @return True if data has changed, false otherwise. */ hasDataChanged(originalValues: any[], currentValues: any[]): boolean { for (const x in originalValues) { @@ -96,9 +96,9 @@ export class AddonModWorkshopAssessmentStrategyNumErrorsHandler implements Addon /** * Prepare assessment data to be sent to the server depending on the strategy selected. * - * @param {any{}} currentValues Current values of the form. - * @param {any} form Assessment form data. - * @return {Promise} Promise resolved with the data to be sent. Or rejected with the input errors object. + * @param currentValues Current values of the form. + * @param form Assessment form data. + * @return Promise resolved with the data to be sent. Or rejected with the input errors object. */ prepareAssessmentData(currentValues: any[], form: any): Promise { const data = {}; diff --git a/src/addon/mod/workshop/assessment/rubric/component/addon-mod-workshop-assessment-strategy-rubric.html b/src/addon/mod/workshop/assessment/rubric/component/addon-mod-workshop-assessment-strategy-rubric.html index 2d1f0be61..fad4f86b5 100644 --- a/src/addon/mod/workshop/assessment/rubric/component/addon-mod-workshop-assessment-strategy-rubric.html +++ b/src/addon/mod/workshop/assessment/rubric/component/addon-mod-workshop-assessment-strategy-rubric.html @@ -2,12 +2,12 @@

{{ field.dimtitle }}

- +
-

+

diff --git a/src/addon/mod/workshop/assessment/rubric/component/rubric.ts b/src/addon/mod/workshop/assessment/rubric/component/rubric.ts index fda33b4d3..a4ee181d5 100644 --- a/src/addon/mod/workshop/assessment/rubric/component/rubric.ts +++ b/src/addon/mod/workshop/assessment/rubric/component/rubric.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/addon/mod/workshop/assessment/rubric/providers/handler.ts b/src/addon/mod/workshop/assessment/rubric/providers/handler.ts index d9dc5a38b..ab4fb62c8 100644 --- a/src/addon/mod/workshop/assessment/rubric/providers/handler.ts +++ b/src/addon/mod/workshop/assessment/rubric/providers/handler.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -29,7 +29,7 @@ export class AddonModWorkshopAssessmentStrategyRubricHandler implements AddonWor /** * Whether or not the handler is enabled on a site level. - * @return {boolean|Promise} Whether or not the handler is enabled on a site level. + * @return Whether or not the handler is enabled on a site level. */ isEnabled(): boolean | Promise { return true; @@ -39,8 +39,8 @@ export class AddonModWorkshopAssessmentStrategyRubricHandler implements AddonWor * Return the Component to render the plugin. * It's recommended to return the class of the component, but you can also return an instance of the component. * - * @param {Injector} injector Injector. - * @return {any|Promise} The component (or promise resolved with component) to use, undefined if not found. + * @param injector Injector. + * @return The component (or promise resolved with component) to use, undefined if not found. */ getComponent(injector: Injector): any | Promise { return AddonModWorkshopAssessmentStrategyRubricComponent; @@ -49,9 +49,9 @@ export class AddonModWorkshopAssessmentStrategyRubricHandler implements AddonWor /** * Prepare original values to be shown and compared. * - * @param {any} form Original data of the form. - * @param {number} workshopId Workshop Id - * @return {Promise} Promise resolved with original values sorted. + * @param form Original data of the form. + * @param workshopId Workshop Id + * @return Promise resolved with original values sorted. */ getOriginalValues(form: any, workshopId: number): Promise { const originalValues = []; @@ -75,9 +75,9 @@ export class AddonModWorkshopAssessmentStrategyRubricHandler implements AddonWor /** * Check if the assessment data has changed for a certain submission and workshop for a this strategy plugin. * - * @param {any[]} originalValues Original values of the form. - * @param {any[]} currentValues Current values of the form. - * @return {boolean} True if data has changed, false otherwise. + * @param originalValues Original values of the form. + * @param currentValues Current values of the form. + * @return True if data has changed, false otherwise. */ hasDataChanged(originalValues: any[], currentValues: any[]): boolean { for (const x in originalValues) { @@ -92,9 +92,9 @@ export class AddonModWorkshopAssessmentStrategyRubricHandler implements AddonWor /** * Prepare assessment data to be sent to the server depending on the strategy selected. * - * @param {any{}} currentValues Current values of the form. - * @param {any} form Assessment form data. - * @return {Promise} Promise resolved with the data to be sent. Or rejected with the input errors object. + * @param currentValues Current values of the form. + * @param form Assessment form data. + * @return Promise resolved with the data to be sent. Or rejected with the input errors object. */ prepareAssessmentData(currentValues: any[], form: any): Promise { const data = {}; diff --git a/src/addon/mod/workshop/assessment/rubric/rubric.module.ts b/src/addon/mod/workshop/assessment/rubric/rubric.module.ts index 2b5392f31..c8a71406e 100644 --- a/src/addon/mod/workshop/assessment/rubric/rubric.module.ts +++ b/src/addon/mod/workshop/assessment/rubric/rubric.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/addon/mod/workshop/classes/assessment-strategy-component.ts b/src/addon/mod/workshop/classes/assessment-strategy-component.ts index 6c49dbb99..f45376472 100644 --- a/src/addon/mod/workshop/classes/assessment-strategy-component.ts +++ b/src/addon/mod/workshop/classes/assessment-strategy-component.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -24,6 +24,8 @@ export class AddonModWorkshopAssessmentStrategyComponentBase { @Input() selectedValues: any[]; @Input() fieldErrors: any; @Input() strategy: string; + @Input() moduleId: number; + @Input() courseId: number; constructor() { // Nothing to do. diff --git a/src/addon/mod/workshop/components/assessment-strategy/addon-mod-workshop-assessment-strategy.html b/src/addon/mod/workshop/components/assessment-strategy/addon-mod-workshop-assessment-strategy.html index e74d57ded..7f3b2bba7 100644 --- a/src/addon/mod/workshop/components/assessment-strategy/addon-mod-workshop-assessment-strategy.html +++ b/src/addon/mod/workshop/components/assessment-strategy/addon-mod-workshop-assessment-strategy.html @@ -28,7 +28,7 @@ - +
diff --git a/src/addon/mod/workshop/components/assessment-strategy/assessment-strategy.ts b/src/addon/mod/workshop/components/assessment-strategy/assessment-strategy.ts index 1516b9c60..e92c1e998 100644 --- a/src/addon/mod/workshop/components/assessment-strategy/assessment-strategy.ts +++ b/src/addon/mod/workshop/components/assessment-strategy/assessment-strategy.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -51,7 +51,9 @@ export class AddonModWorkshopAssessmentStrategyComponent implements OnInit { edit: false, selectedValues: [], fieldErrors: {}, - strategy: '' + strategy: '', + moduleId: 0, + courseId: null }; assessmentStrategyLoaded = false; notSupported = false; @@ -101,6 +103,8 @@ export class AddonModWorkshopAssessmentStrategyComponent implements OnInit { this.data.workshopId = this.workshop.id; this.data.edit = this.edit; this.data.strategy = this.strategy; + this.data.moduleId = this.workshop.coursemodule; + this.data.courseId = this.workshop.course; this.componentClass = this.strategyDelegate.getComponentForPlugin(this.injector, this.strategy); if (this.componentClass) { @@ -138,7 +142,7 @@ export class AddonModWorkshopAssessmentStrategyComponent implements OnInit { /** * Convenience function to load the assessment data. * - * @return {Promise} Promised resvoled when data is loaded. + * @return Promised resvoled when data is loaded. */ protected load(): Promise { return this.workshopHelper.getReviewerAssessmentById(this.workshop.id, this.assessmentId, this.userId) @@ -224,7 +228,7 @@ export class AddonModWorkshopAssessmentStrategyComponent implements OnInit { /** * Check if data has changed. * - * @return {boolean} True if data has changed. + * @return True if data has changed. */ hasDataChanged(): boolean { if (!this.assessmentStrategyLoaded) { @@ -254,7 +258,7 @@ export class AddonModWorkshopAssessmentStrategyComponent implements OnInit { /** * Save the assessment. * - * @return {Promise} Promise resolved when done, rejected if assessment could not be saved. + * @return Promise resolved when done, rejected if assessment could not be saved. */ saveAssessment(): Promise { const files = this.fileSessionProvider.getFiles(AddonModWorkshopProvider.COMPONENT, @@ -332,7 +336,7 @@ export class AddonModWorkshopAssessmentStrategyComponent implements OnInit { /** * Feedback text changed. * - * @param {string} text The new text. + * @param text The new text. */ onFeedbackChange(text: string): void { this.feedbackText = text; diff --git a/src/addon/mod/workshop/components/assessment/assessment.ts b/src/addon/mod/workshop/components/assessment/assessment.ts index 6df7556f9..a565bf5a6 100644 --- a/src/addon/mod/workshop/components/assessment/assessment.ts +++ b/src/addon/mod/workshop/components/assessment/assessment.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/addon/mod/workshop/components/components.module.ts b/src/addon/mod/workshop/components/components.module.ts index f8e20d0ad..775490396 100644 --- a/src/addon/mod/workshop/components/components.module.ts +++ b/src/addon/mod/workshop/components/components.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/addon/mod/workshop/components/index/addon-mod-workshop-index.html b/src/addon/mod/workshop/components/index/addon-mod-workshop-index.html index c90bb99ae..3ce7e2c32 100644 --- a/src/addon/mod/workshop/components/index/addon-mod-workshop-index.html +++ b/src/addon/mod/workshop/components/index/addon-mod-workshop-index.html @@ -7,27 +7,25 @@ - + - -

{{ phases[workshop.phase].title }}

- +

{{task.title}}

-

+

@@ -39,13 +37,21 @@ {{ 'core.hasdatatosync' | translate: {$a: moduleName} }}
+ + + +

{{ 'core.description' | translate }}

+ +
+
+

{{ 'addon.mod_workshop.conclusion' | translate }}

- +
@@ -55,11 +61,11 @@

{{ 'addon.mod_workshop.submissiongrade' | translate }}

- + {{ userGrades.submissionlongstrgrade }}

{{ 'addon.mod_workshop.gradinggrade' | translate }}

- + {{ userGrades.assessmentlongstrgrade }}
@@ -68,7 +74,7 @@

{{ 'addon.mod_workshop.areainstructauthors' | translate }}

- +
@@ -88,7 +94,7 @@ - + - - - + diff --git a/src/addon/notes/components/list/list.ts b/src/addon/notes/components/list/list.ts index cf6f54afa..f242bcc31 100644 --- a/src/addon/notes/components/list/list.ts +++ b/src/addon/notes/components/list/list.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -14,14 +14,13 @@ import { Component, Input, OnDestroy, OnInit, ViewChild } from '@angular/core'; import { Content, ModalController } from 'ionic-angular'; -import { TranslateService } from '@ngx-translate/core'; import { CoreDomUtilsProvider } from '@providers/utils/dom'; import { CoreEventsProvider } from '@providers/events'; import { CoreSitesProvider } from '@providers/sites'; import { CoreTextUtilsProvider } from '@providers/utils/text'; import { CoreUserProvider } from '@core/user/providers/user'; import { coreSlideInOut } from '@classes/animations'; -import { AddonNotesProvider } from '../../providers/notes'; +import { AddonNotesProvider, AddonNotesNoteFormatted } from '../../providers/notes'; import { AddonNotesOfflineProvider } from '../../providers/notes-offline'; import { AddonNotesSyncProvider } from '../../providers/notes-sync'; @@ -44,7 +43,7 @@ export class AddonNotesListComponent implements OnInit, OnDestroy { type = 'course'; refreshIcon = 'spinner'; syncIcon = 'spinner'; - notes: any[]; + notes: AddonNotesNoteFormatted[]; hasOffline = false; notesLoaded = false; user: any; @@ -55,8 +54,7 @@ export class AddonNotesListComponent implements OnInit, OnDestroy { constructor(private domUtils: CoreDomUtilsProvider, private textUtils: CoreTextUtilsProvider, sitesProvider: CoreSitesProvider, eventsProvider: CoreEventsProvider, private modalCtrl: ModalController, private notesProvider: AddonNotesProvider, private notesSync: AddonNotesSyncProvider, - private userProvider: CoreUserProvider, private translate: TranslateService, - private notesOffline: AddonNotesOfflineProvider) { + private userProvider: CoreUserProvider, private notesOffline: AddonNotesOfflineProvider) { // Refresh data if notes are synchronized automatically. this.syncObserver = eventsProvider.on(AddonNotesSyncProvider.AUTO_SYNCED, (data) => { if (data.courseId == this.courseId) { @@ -90,9 +88,9 @@ export class AddonNotesListComponent implements OnInit, OnDestroy { /** * Fetch notes. * - * @param {boolean} sync When to resync notes. - * @param {boolean} [showErrors] When to display errors or not. - * @return {Promise} Promise with the notes. + * @param sync When to resync notes. + * @param showErrors When to display errors or not. + * @return Promise with the notes. */ private fetchNotes(sync: boolean, showErrors?: boolean): Promise { const promise = sync ? this.syncNotes(showErrors) : Promise.resolve(); @@ -101,21 +99,25 @@ export class AddonNotesListComponent implements OnInit, OnDestroy { // Ignore errors. }).then(() => { return this.notesProvider.getNotes(this.courseId, this.userId).then((notes) => { - notes = notes[this.type + 'notes'] || []; + const notesList: AddonNotesNoteFormatted[] = notes[this.type + 'notes'] || []; - return this.notesProvider.setOfflineDeletedNotes(notes, this.courseId).then((notes) => { + notesList.forEach((note) => { + note.content = this.textUtils.decodeHTML(note.content); + }); - this.hasOffline = notes.some((note) => note.offline || note.deleted); + return this.notesProvider.setOfflineDeletedNotes(notesList, this.courseId).then((notesList) => { + + this.hasOffline = notesList.some((note) => note.offline || note.deleted); if (this.userId) { - this.notes = notes; + this.notes = notesList; // Get the user profile to retrieve the user image. return this.userProvider.getProfile(this.userId, this.courseId, true).then((user) => { this.user = user; }); } else { - return this.notesProvider.getNotesUserData(notes, this.courseId).then((notes) => { + return this.notesProvider.getNotesUserData(notesList, this.courseId).then((notes) => { this.notes = notes; }); } @@ -126,7 +128,7 @@ export class AddonNotesListComponent implements OnInit, OnDestroy { }).finally(() => { let canDelete = this.notes && this.notes.length > 0; if (canDelete && this.type == 'personal') { - canDelete = this.notes.find((note) => { + canDelete = !!this.notes.find((note) => { return note.usermodified == this.currentUserId; }); } @@ -141,8 +143,8 @@ export class AddonNotesListComponent implements OnInit, OnDestroy { /** * Refresh notes on PTR. * - * @param {boolean} showErrors Whether to display errors or not. - * @param {any} refresher Refresher instance. + * @param showErrors Whether to display errors or not. + * @param refresher Refresher instance. */ refreshNotes(showErrors: boolean, refresher?: any): void { this.refreshIcon = 'spinner'; @@ -173,11 +175,12 @@ export class AddonNotesListComponent implements OnInit, OnDestroy { /** * Add a new Note to user and course. * - * @param {Event} e Event. + * @param e Event. */ addNote(e: Event): void { e.preventDefault(); e.stopPropagation(); + const modal = this.modalCtrl.create('AddonNotesAddPage', { userId: this.userId, courseId: this.courseId, type: this.type }); modal.onDidDismiss((data) => { if (data && data.sent && data.type) { @@ -192,20 +195,21 @@ export class AddonNotesListComponent implements OnInit, OnDestroy { this.typeChanged(); } }); + modal.present(); } /** * Delete a note. * - * @param {Event} e Click event. - * @param {any} note Note to delete. + * @param e Click event. + * @param note Note to delete. */ - deleteNote(e: Event, note: any): void { + deleteNote(e: Event, note: AddonNotesNoteFormatted): void { e.preventDefault(); e.stopPropagation(); - this.domUtils.showConfirm(this.translate.instant('addon.notes.deleteconfirm')).then(() => { + this.domUtils.showDeleteConfirm('addon.notes.deleteconfirm').then(() => { this.notesProvider.deleteNote(note, this.courseId).then(() => { this.showDelete = false; @@ -223,10 +227,10 @@ export class AddonNotesListComponent implements OnInit, OnDestroy { /** * Restore a note. * - * @param {Event} e Click event. - * @param {any} note Note to delete. + * @param e Click event. + * @param note Note to delete. */ - undoDeleteNote(e: Event, note: any): void { + undoDeleteNote(e: Event, note: AddonNotesNoteFormatted): void { e.preventDefault(); e.stopPropagation(); @@ -245,8 +249,8 @@ export class AddonNotesListComponent implements OnInit, OnDestroy { /** * Tries to synchronize course notes. * - * @param {boolean} showErrors Whether to display errors or not. - * @return {Promise} Promise resolved if sync is successful, rejected otherwise. + * @param showErrors Whether to display errors or not. + * @return Promise resolved if sync is successful, rejected otherwise. */ private syncNotes(showErrors: boolean): Promise { return this.notesSync.syncNotes(this.courseId).then((warnings) => { @@ -263,7 +267,7 @@ export class AddonNotesListComponent implements OnInit, OnDestroy { /** * Show sync warnings if any. * - * @param {string[]} warnings the warnings + * @param warnings the warnings */ private showSyncWarnings(warnings: string[]): void { const message = this.textUtils.buildMessage(warnings); diff --git a/src/addon/notes/notes.module.ts b/src/addon/notes/notes.module.ts index 002f60177..40b418b7d 100644 --- a/src/addon/notes/notes.module.ts +++ b/src/addon/notes/notes.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/addon/notes/pages/add/add.module.ts b/src/addon/notes/pages/add/add.module.ts index 404012014..bc4e43e23 100644 --- a/src/addon/notes/pages/add/add.module.ts +++ b/src/addon/notes/pages/add/add.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/addon/notes/pages/add/add.ts b/src/addon/notes/pages/add/add.ts index 98c770c8f..15fd9da0a 100644 --- a/src/addon/notes/pages/add/add.ts +++ b/src/addon/notes/pages/add/add.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -43,7 +43,7 @@ export class AddonNotesAddPage { /** * Send the note or store it offline. * - * @param {Event} e Event. + * @param e Event. */ addNote(e: Event): void { e.preventDefault(); diff --git a/src/addon/notes/pages/list/list.module.ts b/src/addon/notes/pages/list/list.module.ts index c983a9395..6178c4838 100644 --- a/src/addon/notes/pages/list/list.module.ts +++ b/src/addon/notes/pages/list/list.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/addon/notes/pages/list/list.ts b/src/addon/notes/pages/list/list.ts index 499ca2304..db6fb0703 100644 --- a/src/addon/notes/pages/list/list.ts +++ b/src/addon/notes/pages/list/list.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/addon/notes/providers/course-option-handler.ts b/src/addon/notes/providers/course-option-handler.ts index f00761ae8..4bd746cdc 100644 --- a/src/addon/notes/providers/course-option-handler.ts +++ b/src/addon/notes/providers/course-option-handler.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -31,7 +31,7 @@ export class AddonNotesCourseOptionHandler implements CoreCourseOptionsHandler { /** * Whether or not the handler is enabled on a site level. - * @return {boolean|Promise} Whether or not the handler is enabled on a site level. + * @return Whether or not the handler is enabled on a site level. */ isEnabled(): boolean | Promise { return this.notesProvider.isPluginEnabled(); @@ -40,11 +40,11 @@ export class AddonNotesCourseOptionHandler implements CoreCourseOptionsHandler { /** * Whether or not the handler is enabled for a certain course. * - * @param {number} courseId The course ID. - * @param {any} accessData Access type and data. Default, guest, ... - * @param {any} [navOptions] Course navigation options for current user. See CoreCoursesProvider.getUserNavigationOptions. - * @param {any} [admOptions] Course admin options for current user. See CoreCoursesProvider.getUserAdministrationOptions. - * @return {boolean|Promise} True or promise resolved with true if enabled. + * @param courseId The course ID. + * @param accessData Access type and data. Default, guest, ... + * @param navOptions Course navigation options for current user. See CoreCoursesProvider.getUserNavigationOptions. + * @param admOptions Course admin options for current user. See CoreCoursesProvider.getUserAdministrationOptions. + * @return True or promise resolved with true if enabled. */ isEnabledForCourse(courseId: number, accessData: any, navOptions?: any, admOptions?: any): boolean | Promise { if (accessData && accessData.type == CoreCourseProvider.ACCESS_GUEST) { @@ -61,8 +61,8 @@ export class AddonNotesCourseOptionHandler implements CoreCourseOptionsHandler { /** * Returns the data needed to render the handler. * - * @param {number} courseId The course ID. - * @return {CoreCourseOptionsHandlerData} Data. + * @param courseId The course ID. + * @return Data. */ getDisplayData?(injector: Injector, courseId: number): CoreCourseOptionsHandlerData { return { @@ -75,8 +75,8 @@ export class AddonNotesCourseOptionHandler implements CoreCourseOptionsHandler { /** * Called when a course is downloaded. It should prefetch all the data to be able to see the addon in offline. * - * @param {any} course The course. - * @return {Promise} Promise resolved when done. + * @param course The course. + * @return Promise resolved when done. */ prefetch(course: any): Promise { return this.notesProvider.getNotes(course.id, undefined, true); diff --git a/src/addon/notes/providers/notes-offline.ts b/src/addon/notes/providers/notes-offline.ts index 28bae97bf..828ee24aa 100644 --- a/src/addon/notes/providers/notes-offline.ts +++ b/src/addon/notes/providers/notes-offline.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -94,11 +94,11 @@ export class AddonNotesOfflineProvider { /** * Delete an offline note. * - * @param {number} userId User ID the note is about. - * @param {string} content The note content. - * @param {number} timecreated The time the note was created. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved if deleted, rejected if failure. + * @param userId User ID the note is about. + * @param content The note content. + * @param timecreated The time the note was created. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved if deleted, rejected if failure. */ deleteOfflineNote(userId: number, content: string, timecreated: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -113,8 +113,8 @@ export class AddonNotesOfflineProvider { /** * Get all offline deleted notes. * - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with notes. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with notes. */ getAllDeletedNotes(siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -125,9 +125,9 @@ export class AddonNotesOfflineProvider { /** * Get course offline deleted notes. * - * @param {number} courseId Course ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with notes. + * @param courseId Course ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with notes. */ getCourseDeletedNotes(courseId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -138,8 +138,8 @@ export class AddonNotesOfflineProvider { /** * Get all offline notes. * - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with notes. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with notes. */ getAllNotes(siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -150,11 +150,11 @@ export class AddonNotesOfflineProvider { /** * Get an offline note. * - * @param {number} userId User ID the note is about. - * @param {string} content The note content. - * @param {number} timecreated The time the note was created. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with the notes. + * @param userId User ID the note is about. + * @param content The note content. + * @param timecreated The time the note was created. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the notes. */ getNote(userId: number, content: string, timecreated: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -169,10 +169,10 @@ export class AddonNotesOfflineProvider { /** * Get offline notes for a certain course and user. * - * @param {number} courseId Course ID. - * @param {number} [userId] User ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with notes. + * @param courseId Course ID. + * @param userId User ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with notes. */ getNotesForCourseAndUser(courseId: number, userId?: number, siteId?: string): Promise { if (!userId) { @@ -187,9 +187,9 @@ export class AddonNotesOfflineProvider { /** * Get offline notes for a certain course. * - * @param {number} courseId Course ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with notes. + * @param courseId Course ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with notes. */ getNotesForCourse(courseId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -200,9 +200,9 @@ export class AddonNotesOfflineProvider { /** * Get offline notes for a certain user. * - * @param {number} userId User ID the notes are about. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with notes. + * @param userId User ID the notes are about. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with notes. */ getNotesForUser(userId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -213,9 +213,9 @@ export class AddonNotesOfflineProvider { /** * Get offline notes with a certain publish state (Personal, Site or Course). * - * @param {string} state Publish state ('personal', 'site' or 'course'). - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with notes. + * @param state Publish state ('personal', 'site' or 'course'). + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with notes. */ getNotesWithPublishState(state: string, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -226,9 +226,9 @@ export class AddonNotesOfflineProvider { /** * Check if there are offline notes for a certain course. * - * @param {number} courseId Course ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with boolean: true if has offline notes, false otherwise. + * @param courseId Course ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with boolean: true if has offline notes, false otherwise. */ hasNotesForCourse(courseId: number, siteId?: string): Promise { return this.getNotesForCourse(courseId, siteId).then((notes) => { @@ -239,9 +239,9 @@ export class AddonNotesOfflineProvider { /** * Check if there are offline notes for a certain user. * - * @param {number} userId User ID the notes are about. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with boolean: true if has offline notes, false otherwise. + * @param userId User ID the notes are about. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with boolean: true if has offline notes, false otherwise. */ hasNotesForUser(userId: number, siteId?: string): Promise { return this.getNotesForUser(userId, siteId).then((notes) => { @@ -252,9 +252,9 @@ export class AddonNotesOfflineProvider { /** * Check if there are offline notes with a certain publish state (Personal, Site or Course). * - * @param {string} state Publish state ('personal', 'site' or 'course'). - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with boolean: true if has offline notes, false otherwise. + * @param state Publish state ('personal', 'site' or 'course'). + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with boolean: true if has offline notes, false otherwise. */ hasNotesWithPublishState(state: string, siteId?: string): Promise { return this.getNotesWithPublishState(state, siteId).then((notes) => { @@ -265,12 +265,12 @@ export class AddonNotesOfflineProvider { /** * Save a note to be sent later. * - * @param {number} userId User ID the note is about. - * @param {number} courseId Course ID. - * @param {string} state Publish state ('personal', 'site' or 'course'). - * @param {string} content The note content. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved if stored, rejected if failure. + * @param userId User ID the note is about. + * @param courseId Course ID. + * @param state Publish state ('personal', 'site' or 'course'). + * @param content The note content. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved if stored, rejected if failure. */ saveNote(userId: number, courseId: number, state: string, content: string, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -294,10 +294,10 @@ export class AddonNotesOfflineProvider { /** * Delete a note offline to be sent later. * - * @param {number} noteId Note ID. - * @param {number} courseId Course ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved if stored, rejected if failure. + * @param noteId Note ID. + * @param courseId Course ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved if stored, rejected if failure. */ deleteNote(noteId: number, courseId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -317,9 +317,9 @@ export class AddonNotesOfflineProvider { /** * Undo delete a note. * - * @param {number} noteId Note ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved if deleted, rejected if failure. + * @param noteId Note ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved if deleted, rejected if failure. */ undoDeleteNote(noteId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { diff --git a/src/addon/notes/providers/notes-sync.ts b/src/addon/notes/providers/notes-sync.ts index cebf92d73..0ca1ec84a 100644 --- a/src/addon/notes/providers/notes-sync.ts +++ b/src/addon/notes/providers/notes-sync.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -47,9 +47,9 @@ export class AddonNotesSyncProvider extends CoreSyncBaseProvider { /** * Try to synchronize all the notes in a certain site or in all sites. * - * @param {string} [siteId] Site ID to sync. If not defined, sync all sites. - * @param {boolean} [force] Wether to force sync not depending on last execution. - * @return {Promise} Promise resolved if sync is successful, rejected if sync fails. + * @param siteId Site ID to sync. If not defined, sync all sites. + * @param force Wether to force sync not depending on last execution. + * @return Promise resolved if sync is successful, rejected if sync fails. */ syncAllNotes(siteId?: string, force?: boolean): Promise { return this.syncOnSites('all notes', this.syncAllNotesFunc.bind(this), [force], siteId); @@ -58,9 +58,9 @@ export class AddonNotesSyncProvider extends CoreSyncBaseProvider { /** * Synchronize all the notes in a certain site * - * @param {string} siteId Site ID to sync. - * @param {boolean} force Wether to force sync not depending on last execution. - * @return {Promise} Promise resolved if sync is successful, rejected if sync fails. + * @param siteId Site ID to sync. + * @param force Wether to force sync not depending on last execution. + * @return Promise resolved if sync is successful, rejected if sync fails. */ private syncAllNotesFunc(siteId: string, force: boolean): Promise { const proms = []; @@ -100,9 +100,9 @@ export class AddonNotesSyncProvider extends CoreSyncBaseProvider { /** * Sync course notes only if a certain time has passed since the last time. * - * @param {number} courseId Course ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the notes are synced or if they don't need to be synced. + * @param courseId Course ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the notes are synced or if they don't need to be synced. */ private syncNotesIfNeeded(courseId: number, siteId?: string): Promise { return this.isSyncNeeded(courseId, siteId).then((needed) => { @@ -115,9 +115,9 @@ export class AddonNotesSyncProvider extends CoreSyncBaseProvider { /** * Synchronize notes of a course. * - * @param {number} courseId Course ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved if sync is successful, rejected otherwise. + * @param courseId Course ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved if sync is successful, rejected otherwise. */ syncNotes(courseId: number, siteId?: string): Promise { siteId = siteId || this.sitesProvider.getCurrentSiteId(); diff --git a/src/addon/notes/providers/notes.ts b/src/addon/notes/providers/notes.ts index 82f095e41..c0a2a85fb 100644 --- a/src/addon/notes/providers/notes.ts +++ b/src/addon/notes/providers/notes.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -22,6 +22,7 @@ import { TranslateService } from '@ngx-translate/core'; import { CoreUserProvider } from '@core/user/providers/user'; import { AddonNotesOfflineProvider } from './notes-offline'; import { CorePushNotificationsProvider } from '@core/pushnotifications/providers/pushnotifications'; +import { CoreWSExternalWarning } from '@providers/ws'; /** * Service to handle notes. @@ -41,12 +42,12 @@ export class AddonNotesProvider { /** * Add a note. * - * @param {number} userId User ID of the person to add the note. - * @param {number} courseId Course ID where the note belongs. - * @param {string} publishState Personal, Site or Course. - * @param {string} noteText The note text. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with boolean: true if note was sent to server, false if stored in device. + * @param userId User ID of the person to add the note. + * @param courseId Course ID where the note belongs. + * @param publishState Personal, Site or Course. + * @param noteText The note text. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with boolean: true if note was sent to server, false if stored in device. */ addNote(userId: number, courseId: number, publishState: string, noteText: string, siteId?: string): Promise { siteId = siteId || this.sitesProvider.getCurrentSiteId(); @@ -80,12 +81,12 @@ export class AddonNotesProvider { /** * Add a note. It will fail if offline or cannot connect. * - * @param {number} userId User ID of the person to add the note. - * @param {number} courseId Course ID where the note belongs. - * @param {string} publishState Personal, Site or Course. - * @param {string} noteText The note text. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when added, rejected otherwise. + * @param userId User ID of the person to add the note. + * @param courseId Course ID where the note belongs. + * @param publishState Personal, Site or Course. + * @param noteText The note text. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when added, rejected otherwise. */ addNoteOnline(userId: number, courseId: number, publishState: string, noteText: string, siteId?: string): Promise { const notes = [ @@ -114,14 +115,14 @@ export class AddonNotesProvider { /** * Add several notes. It will fail if offline or cannot connect. * - * @param {any[]} notes Notes to save. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when added, rejected otherwise. Promise resolved doesn't mean that notes - * have been added, the resolve param can contain errors for notes not sent. + * @param notes Notes to save. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when added, rejected otherwise. Promise resolved doesn't mean that notes + * have been added, the resolve param can contain errors for notes not sent. */ - addNotesOnline(notes: any[], siteId?: string): Promise { + addNotesOnline(notes: any[], siteId?: string): Promise { if (!notes || !notes.length) { - return Promise.resolve(); + return Promise.resolve([]); } return this.sitesProvider.getSite(siteId).then((site) => { @@ -136,13 +137,13 @@ export class AddonNotesProvider { /** * Delete a note. * - * @param {any} note Note object to delete. - * @param {number} courseId Course ID where the note belongs. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when deleted, rejected otherwise. Promise resolved doesn't mean that notes - * have been deleted, the resolve param can contain errors for notes not deleted. + * @param note Note object to delete. + * @param courseId Course ID where the note belongs. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when deleted, rejected otherwise. Promise resolved doesn't mean that notes + * have been deleted, the resolve param can contain errors for notes not deleted. */ - deleteNote(note: any, courseId: number, siteId?: string): Promise { + deleteNote(note: AddonNotesNoteFormatted, courseId: number, siteId?: string): Promise { siteId = siteId || this.sitesProvider.getCurrentSiteId(); if (note.offline) { @@ -178,11 +179,11 @@ export class AddonNotesProvider { /** * Delete a note. It will fail if offline or cannot connect. * - * @param {number[]} noteIds Note IDs to delete. - * @param {number} courseId Course ID where the note belongs. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when deleted, rejected otherwise. Promise resolved doesn't mean that notes - * have been deleted, the resolve param can contain errors for notes not deleted. + * @param noteIds Note IDs to delete. + * @param courseId Course ID where the note belongs. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when deleted, rejected otherwise. Promise resolved doesn't mean that notes + * have been deleted, the resolve param can contain errors for notes not deleted. */ deleteNotesOnline(noteIds: number[], courseId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -190,7 +191,7 @@ export class AddonNotesProvider { notes: noteIds }; - return site.write('core_notes_delete_notes', data).then((response) => { + return site.write('core_notes_delete_notes', data).then((response: CoreWSExternalWarning[]) => { // A note was deleted, invalidate the course notes. return this.invalidateNotes(courseId, undefined, siteId).catch(() => { // Ignore errors. @@ -205,8 +206,8 @@ export class AddonNotesProvider { * This method is called quite often and thus should only perform a quick * check, we should not be calling WS from here. * - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with true if enabled, resolved with false or rejected otherwise. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with true if enabled, resolved with false or rejected otherwise. */ isPluginEnabled(siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -217,9 +218,9 @@ export class AddonNotesProvider { /** * Returns whether or not the add note plugin is enabled for a certain course. * - * @param {number} courseId ID of the course. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with true if enabled, resolved with false or rejected otherwise. + * @param courseId ID of the course. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with true if enabled, resolved with false or rejected otherwise. */ isPluginAddNoteEnabledForCourse(courseId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -249,9 +250,9 @@ export class AddonNotesProvider { /** * Returns whether or not the read notes plugin is enabled for a certain course. * - * @param {number} courseId ID of the course. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with true if enabled, resolved with false or rejected otherwise. + * @param courseId ID of the course. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with true if enabled, resolved with false or rejected otherwise. */ isPluginViewNotesEnabledForCourse(courseId: number, siteId?: string): Promise { return this.utils.promiseWorks(this.getNotes(courseId, undefined, false, true, siteId)); @@ -260,8 +261,8 @@ export class AddonNotesProvider { /** * Get prefix cache key for course notes. * - * @param {number} courseId ID of the course to get the notes from. - * @return {string} Cache key. + * @param courseId ID of the course to get the notes from. + * @return Cache key. */ getNotesPrefixCacheKey(courseId: number): string { return this.ROOT_CACHE_KEY + 'notes:' + courseId + ':'; @@ -270,9 +271,9 @@ export class AddonNotesProvider { /** * Get the cache key for the get notes call. * - * @param {number} courseId ID of the course to get the notes from. - * @param {number} [userId] ID of the user to get the notes from if requested. - * @return {string} Cache key. + * @param courseId ID of the course to get the notes from. + * @param userId ID of the user to get the notes from if requested. + * @return Cache key. */ getNotesCacheKey(courseId: number, userId?: number): string { return this.getNotesPrefixCacheKey(courseId) + (userId ? userId : ''); @@ -281,14 +282,16 @@ export class AddonNotesProvider { /** * Get users notes for a certain site, course and personal notes. * - * @param {number} courseId ID of the course to get the notes from. - * @param {number} [userId] ID of the user to get the notes from if requested. - * @param {boolean} [ignoreCache] True when we should not get the value from the cache. - * @param {boolean} [onlyOnline] True to return only online notes, false to return both online and offline. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise to be resolved when the notes are retrieved. + * @param courseId ID of the course to get the notes from. + * @param userId ID of the user to get the notes from if requested. + * @param ignoreCache True when we should not get the value from the cache. + * @param onlyOnline True to return only online notes, false to return both online and offline. + * @param siteId Site ID. If not defined, current site. + * @return Promise to be resolved when the notes are retrieved. */ - getNotes(courseId: number, userId?: number, ignoreCache?: boolean, onlyOnline?: boolean, siteId?: string): Promise { + getNotes(courseId: number, userId?: number, ignoreCache?: boolean, onlyOnline?: boolean, siteId?: string) + : Promise { + this.logger.debug('Get notes for course ' + courseId); return this.sitesProvider.getSite(siteId).then((site) => { @@ -310,7 +313,7 @@ export class AddonNotesProvider { preSets.emergencyCache = false; } - return site.read('core_notes_get_course_notes', data, preSets).then((notes) => { + return site.read('core_notes_get_course_notes', data, preSets).then((notes: AddonNotesGetCourseNotesResult) => { if (onlyOnline) { return notes; } @@ -336,12 +339,14 @@ export class AddonNotesProvider { /** * Get offline deleted notes and set the state. * - * @param {any[]} notes Array of notes. - * @param {number} courseId ID of the course the notes belong to. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} [description] + * @param notes Array of notes. + * @param courseId ID of the course the notes belong to. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when done. */ - setOfflineDeletedNotes(notes: any[], courseId: number, siteId?: string): Promise { + setOfflineDeletedNotes(notes: AddonNotesNoteFormatted[], courseId: number, siteId?: string) + : Promise { + return this.notesOffline.getCourseDeletedNotes(courseId, siteId).then((deletedNotes) => { notes.forEach((note) => { note.deleted = deletedNotes.some((n) => n.noteid == note.id); @@ -354,11 +359,11 @@ export class AddonNotesProvider { /** * Get user data for notes since they only have userid. * - * @param {any[]} notes Notes to get the data for. - * @param {number} courseId ID of the course the notes belong to. - * @return {Promise} Promise always resolved. Resolve param is the formatted notes. + * @param notes Notes to get the data for. + * @param courseId ID of the course the notes belong to. + * @return Promise always resolved. Resolve param is the formatted notes. */ - getNotesUserData(notes: any[], courseId: number): Promise { + getNotesUserData(notes: AddonNotesNoteFormatted[], courseId: number): Promise { const promises = notes.map((note) => { // Get the user profile to retrieve the user image. return this.userProvider.getProfile(note.userid, note.courseid, true).then((user) => { @@ -377,10 +382,10 @@ export class AddonNotesProvider { /** * Invalidate get notes WS call. * - * @param {number} courseId Course ID. - * @param {number} [userId] User ID if needed. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when data is invalidated. + * @param courseId Course ID. + * @param userId User ID if needed. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when data is invalidated. */ invalidateNotes(courseId: number, userId?: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -395,12 +400,12 @@ export class AddonNotesProvider { /** * Report notes as being viewed. * - * @param {number} courseId ID of the course. - * @param {number} [userId] User ID if needed. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the WS call is successful. + * @param courseId ID of the course. + * @param userId User ID if needed. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the WS call is successful. */ - logView(courseId: number, userId?: number, siteId?: string): Promise { + logView(courseId: number, userId?: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { const params = { courseid: courseId, @@ -413,3 +418,57 @@ export class AddonNotesProvider { }); } } + +/** + * Note data returned by core_notes_get_course_notes. + */ +export type AddonNotesNote = { + id: number; // Id of this note. + courseid: number; // Id of the course. + userid: number; // User id. + content: string; // The content text formated. + format: number; // Content format (1 = HTML, 0 = MOODLE, 2 = PLAIN or 4 = MARKDOWN). + created: number; // Time created (timestamp). + lastmodified: number; // Time of last modification (timestamp). + usermodified: number; // User id of the creator of this note. + publishstate: string; // State of the note (i.e. draft, public, site). +}; + +/** + * Result of WS core_notes_get_course_notes. + */ +export type AddonNotesGetCourseNotesResult = { + sitenotes?: AddonNotesNote[]; // Site notes. + coursenotes?: AddonNotesNote[]; // Couse notes. + personalnotes?: AddonNotesNote[]; // Personal notes. + canmanagesystemnotes?: boolean; // @since 3.7. Whether the user can manage notes at system level. + canmanagecoursenotes?: boolean; // @since 3.7. Whether the user can manage notes at the given course. + warnings?: CoreWSExternalWarning[]; +}; + +/** + * Note returned by WS core_notes_create_notes. + */ +export type AddonNotesCreateNotesNote = { + clientnoteid?: string; // Your own id for the note. + noteid: number; // ID of the created note when successful, -1 when failed. + errormessage?: string; // Error message - if failed. +}; + +/** + * Result of WS core_notes_view_notes. + */ +export type AddonNotesViewNotesResult = { + status: boolean; // Status: true if success. + warnings?: CoreWSExternalWarning[]; +}; + +/** + * Notes with some calculated data. + */ +export type AddonNotesNoteFormatted = AddonNotesNote & { + offline?: boolean; // Calculated in the app. Whether it's an offline note. + deleted?: boolean; // Calculated in the app. Whether the note was deleted in offline. + userfullname?: string; // Calculated in the app. Full name of the user the note refers to. + userprofileimageurl?: string; // Calculated in the app. Avatar url of the user the note refers to. +}; diff --git a/src/addon/notes/providers/sync-cron-handler.ts b/src/addon/notes/providers/sync-cron-handler.ts index dcd1ad5d6..25518cda3 100644 --- a/src/addon/notes/providers/sync-cron-handler.ts +++ b/src/addon/notes/providers/sync-cron-handler.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -29,9 +29,9 @@ export class AddonNotesSyncCronHandler implements CoreCronHandler { * Execute the process. * Receives the ID of the site affected, undefined for all sites. * - * @param {string} [siteId] ID of the site affected, undefined for all sites. - * @param {boolean} [force] Wether the execution is forced (manual sync). - * @return {Promise} Promise resolved when done, rejected if failure. + * @param siteId ID of the site affected, undefined for all sites. + * @param force Wether the execution is forced (manual sync). + * @return Promise resolved when done, rejected if failure. */ execute(siteId?: string, force?: boolean): Promise { return this.notesSync.syncAllNotes(siteId, force); @@ -40,7 +40,7 @@ export class AddonNotesSyncCronHandler implements CoreCronHandler { /** * Get the time between consecutive executions. * - * @return {number} Time between consecutive executions (in ms). + * @return Time between consecutive executions (in ms). */ getInterval(): number { return 300000; // 5 minutes. diff --git a/src/addon/notes/providers/user-handler.ts b/src/addon/notes/providers/user-handler.ts index cd9ce8015..62e2eadb0 100644 --- a/src/addon/notes/providers/user-handler.ts +++ b/src/addon/notes/providers/user-handler.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -42,7 +42,7 @@ export class AddonNotesUserHandler implements CoreUserProfileHandler { * Clear note cache. * If a courseId is specified, it will only delete the entry for that course. * - * @param {number} [courseId] Course ID. + * @param courseId Course ID. */ private clearNoteCache(courseId?: number): void { if (courseId) { @@ -54,7 +54,7 @@ export class AddonNotesUserHandler implements CoreUserProfileHandler { /** * Whether or not the handler is enabled on a site level. - * @return {boolean|Promise} Whether or not the handler is enabled on a site level. + * @return Whether or not the handler is enabled on a site level. */ isEnabled(): boolean | Promise { return this.notesProvider.isPluginEnabled(); @@ -63,11 +63,11 @@ export class AddonNotesUserHandler implements CoreUserProfileHandler { /** * Check if handler is enabled for this user in this context. * - * @param {any} user User to check. - * @param {number} courseId Course ID. - * @param {any} [navOptions] Course navigation options for current user. See CoreCoursesProvider.getUserNavigationOptions. - * @param {any} [admOptions] Course admin options for current user. See CoreCoursesProvider.getUserAdministrationOptions. - * @return {boolean|Promise} Promise resolved with true if enabled, resolved with false otherwise. + * @param user User to check. + * @param courseId Course ID. + * @param navOptions Course navigation options for current user. See CoreCoursesProvider.getUserNavigationOptions. + * @param admOptions Course admin options for current user. See CoreCoursesProvider.getUserAdministrationOptions. + * @return Promise resolved with true if enabled, resolved with false otherwise. */ isEnabledForUser(user: any, courseId: number, navOptions?: any, admOptions?: any): boolean | Promise { // Active course required. @@ -89,7 +89,7 @@ export class AddonNotesUserHandler implements CoreUserProfileHandler { /** * Returns the data needed to render the handler. * - * @return {CoreUserProfileHandlerData} Data needed to render the handler. + * @return Data needed to render the handler. */ getDisplayData(user: any, courseId: number): CoreUserProfileHandlerData { return { diff --git a/src/addon/notifications/components/actions/actions.ts b/src/addon/notifications/components/actions/actions.ts index c2a6f23bb..4ff7368f7 100644 --- a/src/addon/notifications/components/actions/actions.ts +++ b/src/addon/notifications/components/actions/actions.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -77,8 +77,8 @@ export class AddonNotificationsActionsComponent implements OnInit { /** * Default action. Open in browser. * - * @param {string} siteId Site ID to use. - * @param {NavController} [navCtrl] NavController. + * @param siteId Site ID to use. + * @param navCtrl NavController. */ protected defaultAction(siteId: string, navCtrl?: NavController): void { const url = (this.data && this.data.appurl) || this.contextUrl; diff --git a/src/addon/notifications/components/components.module.ts b/src/addon/notifications/components/components.module.ts index a1eaa049a..76b933080 100644 --- a/src/addon/notifications/components/components.module.ts +++ b/src/addon/notifications/components/components.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/addon/notifications/notifications.module.ts b/src/addon/notifications/notifications.module.ts index 2a2b94021..863927401 100644 --- a/src/addon/notifications/notifications.module.ts +++ b/src/addon/notifications/notifications.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/addon/notifications/pages/list/list.html b/src/addon/notifications/pages/list/list.html index bd523d6ba..951c63659 100644 --- a/src/addon/notifications/pages/list/list.html +++ b/src/addon/notifications/pages/list/list.html @@ -21,16 +21,16 @@ -

+

{{ notification.subject }}

{{notification.timecreated | coreDateDayOrTime}}

-

+

{{ notification.userfromfullname }}

-

+

diff --git a/src/addon/notifications/pages/list/list.module.ts b/src/addon/notifications/pages/list/list.module.ts index 56369935d..4aaf30d11 100644 --- a/src/addon/notifications/pages/list/list.module.ts +++ b/src/addon/notifications/pages/list/list.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/addon/notifications/pages/list/list.ts b/src/addon/notifications/pages/list/list.ts index 1ab97b2be..2046b00bc 100644 --- a/src/addon/notifications/pages/list/list.ts +++ b/src/addon/notifications/pages/list/list.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -20,7 +20,7 @@ import { CoreTextUtilsProvider } from '@providers/utils/text'; import { CoreEventsProvider, CoreEventObserver } from '@providers/events'; import { CoreSitesProvider } from '@providers/sites'; import { CoreUtilsProvider } from '@providers/utils/utils'; -import { AddonNotificationsProvider } from '../../providers/notifications'; +import { AddonNotificationsProvider, AddonNotificationsAnyNotification } from '../../providers/notifications'; import { AddonNotificationsHelperProvider } from '../../providers/helper'; import { CorePushNotificationsDelegate } from '@core/pushnotifications/providers/delegate'; @@ -34,7 +34,7 @@ import { CorePushNotificationsDelegate } from '@core/pushnotifications/providers }) export class AddonNotificationsListPage { - notifications = []; + notifications: AddonNotificationsAnyNotification[] = []; notificationsLoaded = false; canLoadMore = false; loadMoreError = false; @@ -82,8 +82,8 @@ export class AddonNotificationsListPage { /** * Convenience function to get notifications. Gets unread notifications first. * - * @param {boolean} refreh Whether we're refreshing data. - * @return {Promise} Resolved when done. + * @param refreh Whether we're refreshing data. + * @return Resolved when done. */ protected fetchNotifications(refresh?: boolean): Promise { this.loadMoreError = false; @@ -128,13 +128,14 @@ export class AddonNotificationsListPage { /** * Mark notifications as read. * - * @param {any[]} notifications Array of notification objects. + * @param notifications Array of notification objects. */ - protected markNotificationsAsRead(notifications: any[]): void { + protected markNotificationsAsRead(notifications: AddonNotificationsAnyNotification[]): void { + let promise; if (notifications.length > 0) { - const promises = notifications.map((notification) => { + const promises: Promise[] = notifications.map((notification) => { if (notification.read) { // Already read, don't mark it. return Promise.resolve(); @@ -173,7 +174,7 @@ export class AddonNotificationsListPage { /** * Refresh notifications. * - * @param {any} [refresher] Refresher. + * @param refresher Refresher. * @return Promise Promise resolved when done. */ refreshNotifications(refresher?: any): Promise { @@ -189,7 +190,7 @@ export class AddonNotificationsListPage { /** * Load more results. * - * @param {any} [infiniteComplete] Infinite scroll complete function. Only used from core-infinite-loading. + * @param infiniteComplete Infinite scroll complete function. Only used from core-infinite-loading. */ loadMoreNotifications(infiniteComplete?: any): void { this.fetchNotifications().finally(() => { @@ -200,9 +201,9 @@ export class AddonNotificationsListPage { /** * Formats the text of a notification. * - * @param {any} notification The notification object. + * @param notification The notification object. */ - protected formatText(notification: any): void { + protected formatText(notification: AddonNotificationsAnyNotification): void { const text = notification.mobiletext.replace(/-{4,}/ig, ''); notification.mobiletext = this.textUtils.replaceNewLines(text, '
'); } diff --git a/src/addon/notifications/pages/settings/settings.html b/src/addon/notifications/pages/settings/settings.html index b9f76c243..b6502c2c0 100644 --- a/src/addon/notifications/pages/settings/settings.html +++ b/src/addon/notifications/pages/settings/settings.html @@ -24,8 +24,8 @@ - {{ 'core.settings.disableall' | translate }} - + {{ 'addon.notifications.notifications' | translate }} + {{ 'addon.notifications.playsound' | translate }} @@ -53,25 +53,25 @@ {{ notification.displayname }} - - - + + + -
{{'core.settings.locked' | translate }}
+
{{'core.settings.locked' | translate }}
- {{ 'core.settings.disabled' | translate }} + {{ 'core.settings.disabled' | translate }}
{{ notification.displayname }} - + {{ 'core.settings.' + state | translate }} - - + + - {{'core.settings.locked' | translate }} - {{ 'core.settings.disabled' | translate }} + {{'core.settings.locked' | translate }} + {{ 'core.settings.disabled' | translate }}
diff --git a/src/addon/notifications/pages/settings/settings.module.ts b/src/addon/notifications/pages/settings/settings.module.ts index 2b6e9e500..9a4bbb57e 100644 --- a/src/addon/notifications/pages/settings/settings.module.ts +++ b/src/addon/notifications/pages/settings/settings.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/addon/notifications/pages/settings/settings.ts b/src/addon/notifications/pages/settings/settings.ts index 58ab72395..0431e91c5 100644 --- a/src/addon/notifications/pages/settings/settings.ts +++ b/src/addon/notifications/pages/settings/settings.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -14,7 +14,11 @@ import { Component, OnDestroy, Optional } from '@angular/core'; import { IonicPage, NavController } from 'ionic-angular'; -import { AddonNotificationsProvider } from '../../providers/notifications'; +import { + AddonNotificationsProvider, AddonNotificationsNotificationPreferences, AddonNotificationsNotificationPreferencesProcessor, + AddonNotificationsNotificationPreferencesComponent, AddonNotificationsNotificationPreferencesNotification, + AddonNotificationsNotificationPreferencesNotificationProcessorState +} from '../../providers/notifications'; import { CoreUserProvider } from '@core/user/providers/user'; import { CoreDomUtilsProvider } from '@providers/utils/dom'; import { CoreSettingsHelper } from '@core/settings/providers/helper'; @@ -38,10 +42,10 @@ import { CoreSplitViewComponent } from '@components/split-view/split-view'; export class AddonNotificationsSettingsPage implements OnDestroy { protected updateTimeout: any; - components: any[]; - preferences: any; + components: AddonNotificationsNotificationPreferencesComponent[]; + preferences: AddonNotificationsNotificationPreferences; preferencesLoaded: boolean; - currentProcessor: any; + currentProcessor: AddonNotificationsNotificationPreferencesProcessorFormatted; notifPrefsEnabled: boolean; canChangeSound: boolean; notificationSound: boolean; @@ -78,7 +82,7 @@ export class AddonNotificationsSettingsPage implements OnDestroy { /** * Fetches preference data. * - * @return {Promise} Resolved when done. + * @return Resolved when done. */ protected fetchPreferences(): Promise { return this.notificationsProvider.getNotificationPreferences().then((preferences) => { @@ -92,14 +96,14 @@ export class AddonNotificationsSettingsPage implements OnDestroy { return Promise.reject('No processor found'); } - preferences.disableall = !!preferences.disableall; // Convert to boolean. + preferences.enableall = !preferences.disableall; this.preferences = preferences; this.loadProcessor(this.currentProcessor); // Get display data of message output handlers (thery are displayed in the context menu), this.processorHandlers = []; if (preferences.processors) { - preferences.processors.forEach((processor) => { + preferences.processors.forEach((processor: AddonNotificationsNotificationPreferencesProcessorFormatted) => { processor.supported = this.messageOutputDelegate.hasHandler(processor.name, true); if (processor.hassettings && processor.supported) { this.processorHandlers.push(this.messageOutputDelegate.getDisplayData(processor)); @@ -116,9 +120,9 @@ export class AddonNotificationsSettingsPage implements OnDestroy { /** * Load a processor. * - * @param {any} processor Processor object. + * @param processor Processor object. */ - protected loadProcessor(processor: any): void { + protected loadProcessor(processor: AddonNotificationsNotificationPreferencesProcessorFormatted): void { if (!processor) { return; } @@ -151,7 +155,7 @@ export class AddonNotificationsSettingsPage implements OnDestroy { /** * The selected processor was changed. * - * @param {string} name Name of the selected processor. + * @param name Name of the selected processor. */ changeProcessor(name: string): void { this.preferences.processors.forEach((processor) => { @@ -164,7 +168,7 @@ export class AddonNotificationsSettingsPage implements OnDestroy { /** * Refresh the list of preferences. * - * @param {any} [refresher] Refresher. + * @param refresher Refresher. */ refreshPreferences(refresher?: any): void { this.notificationsProvider.invalidateNotificationPreferences().finally(() => { @@ -177,7 +181,7 @@ export class AddonNotificationsSettingsPage implements OnDestroy { /** * Open extra preferences. * - * @param {AddonMessageOutputHandlerData} handlerData + * @param handlerData */ openExtraPreferences(handlerData: AddonMessageOutputHandlerData): void { // Decide which navCtrl to use. If this page is inside a split view, use the split view's master nav. @@ -188,11 +192,12 @@ export class AddonNotificationsSettingsPage implements OnDestroy { /** * Change the value of a certain preference. * - * @param {any} notification Notification object. - * @param {string} state State name, ['loggedin', 'loggedoff']. + * @param notification Notification object. + * @param state State name, ['loggedin', 'loggedoff']. */ - changePreference(notification: any, state: string): void { - const processorState = notification.currentProcessor[state]; + changePreference(notification: AddonNotificationsNotificationPreferencesNotificationFormatted, state: string): void { + const processorState: AddonNotificationsNotificationPreferencesNotificationProcessorStateFormatted = + notification.currentProcessor[state]; const preferenceName = notification.preferencekey + '_' + processorState.name; let value; @@ -211,6 +216,7 @@ export class AddonNotificationsSettingsPage implements OnDestroy { } processorState.updating = true; + this.userProvider.updateUserPreference(preferenceName, value).then(() => { // Update the preferences since they were modified. this.updatePreferencesAfterDelay(); @@ -224,17 +230,17 @@ export class AddonNotificationsSettingsPage implements OnDestroy { } /** - * Disable all notifications changed. + * Enable all notifications changed. */ - disableAll(disable: boolean): void { + enableAll(enable: boolean): void { const modal = this.domUtils.showModalLoading('core.sending', true); - this.userProvider.updateUserPreferences([], disable).then(() => { + this.userProvider.updateUserPreferences([], !enable).then(() => { // Update the preferences since they were modified. this.updatePreferencesAfterDelay(); }).catch((message) => { // Show error and revert change. this.domUtils.showErrorModal(message); - this.preferences.disableall = !this.preferences.disableall; + this.preferences.enableall = !this.preferences.enableall; }).finally(() => { modal.dismiss(); }); @@ -243,7 +249,7 @@ export class AddonNotificationsSettingsPage implements OnDestroy { /** * Change the notification sound setting. * - * @param {enabled} enabled True to enable the notification sound, false to disable it. + * @param enabled True to enable the notification sound, false to disable it. */ changeNotificationSound(enabled: boolean): void { this.configProvider.set(CoreConstants.SETTINGS_NOTIFICATION_SOUND, enabled ? 1 : 0).finally(() => { @@ -264,3 +270,25 @@ export class AddonNotificationsSettingsPage implements OnDestroy { } } } + +/** + * Notification preferences notification with some calculated data. + */ +type AddonNotificationsNotificationPreferencesNotificationFormatted = AddonNotificationsNotificationPreferencesNotification & { + currentProcessor?: AddonNotificationsNotificationPreferencesProcessorFormatted; // Calculated in the app. Current processor. +}; + +/** + * Notification preferences processor with some calculated data. + */ +type AddonNotificationsNotificationPreferencesProcessorFormatted = AddonNotificationsNotificationPreferencesProcessor & { + supported?: boolean; // Calculated in the app. Whether the processor is supported in the app. +}; + +/** + * State in notification processor in notification preferences component with some calculated data. + */ +type AddonNotificationsNotificationPreferencesNotificationProcessorStateFormatted = + AddonNotificationsNotificationPreferencesNotificationProcessorState & { + updating?: boolean; // Calculated in the app. Whether the state is being updated. +}; diff --git a/src/addon/notifications/providers/cron-handler.ts b/src/addon/notifications/providers/cron-handler.ts index e7ae59f17..cd3a5c4a1 100644 --- a/src/addon/notifications/providers/cron-handler.ts +++ b/src/addon/notifications/providers/cron-handler.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -38,7 +38,7 @@ export class AddonNotificationsCronHandler implements CoreCronHandler { /** * Get the time between consecutive executions. * - * @return {number} Time between consecutive executions (in ms). + * @return Time between consecutive executions (in ms). */ getInterval(): number { return this.appProvider.isDesktop() ? 60000 : 600000; // 1 or 10 minutes. @@ -47,7 +47,7 @@ export class AddonNotificationsCronHandler implements CoreCronHandler { /** * Check whether it's a synchronization process or not. True if not defined. * - * @return {boolean} Whether it's a synchronization process or not. + * @return Whether it's a synchronization process or not. */ isSync(): boolean { // This is done to use only wifi if using the fallback function. @@ -58,7 +58,7 @@ export class AddonNotificationsCronHandler implements CoreCronHandler { /** * Check whether the sync can be executed manually. Call isSync if not defined. * - * @return {boolean} Whether the sync can be executed manually. + * @return Whether the sync can be executed manually. */ canManualSync(): boolean { return true; @@ -68,10 +68,10 @@ export class AddonNotificationsCronHandler implements CoreCronHandler { * Execute the process. * Receives the ID of the site affected, undefined for all sites. * - * @param {string} [siteId] ID of the site affected, undefined for all sites. - * @param {boolean} [force] Wether the execution is forced (manual sync). - * @return {Promise} Promise resolved when done, rejected if failure. If the promise is rejected, this function - * will be called again often, it shouldn't be abused. + * @param siteId ID of the site affected, undefined for all sites. + * @param force Wether the execution is forced (manual sync). + * @return Promise resolved when done, rejected if failure. If the promise is rejected, this function + * will be called again often, it shouldn't be abused. */ execute(siteId?: string, force?: boolean): Promise { if (this.sitesProvider.isCurrentSite(siteId)) { @@ -90,8 +90,8 @@ export class AddonNotificationsCronHandler implements CoreCronHandler { /** * Get the latest unread notifications from a site. * - * @param {string} siteId Site ID. - * @return {Promise} Promise resolved with the notifications. + * @param siteId Site ID. + * @return Promise resolved with the notifications. */ protected fetchNotifications(siteId: string): Promise { return this.notificationsHelper.getNotifications([], undefined, true, false, true, siteId).then((result) => { @@ -102,8 +102,8 @@ export class AddonNotificationsCronHandler implements CoreCronHandler { /** * Given a notification, return the title and the text for the notification. * - * @param {any} notification Notification. - * @return {Promise} Promise resvoled with an object with title and text. + * @param notification Notification. + * @return Promise resvoled with an object with title and text. */ protected getTitleAndText(notification: any): Promise { const data = { diff --git a/src/addon/notifications/providers/helper.ts b/src/addon/notifications/providers/helper.ts index f9c9e80bf..13446781b 100644 --- a/src/addon/notifications/providers/helper.ts +++ b/src/addon/notifications/providers/helper.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -14,7 +14,9 @@ import { Injectable } from '@angular/core'; import { CoreSitesProvider } from '@providers/sites'; -import { AddonNotificationsProvider } from './notifications'; +import { + AddonNotificationsProvider, AddonNotificationsAnyNotification, AddonNotificationsGetMessagesMessage +} from './notifications'; /** * Service that provides some helper functions for notifications. @@ -28,16 +30,16 @@ export class AddonNotificationsHelperProvider { /** * Get some notifications. It will try to use the new WS if available. * - * @param {any[]} notifications Current list of loaded notifications. It's used to calculate the offset. - * @param {number} [limit] Number of notifications to get. Defaults to LIST_LIMIT. - * @param {boolean} [toDisplay=true] True if notifications will be displayed to the user, either in view or in a notification. - * @param {boolean} [forceCache] True if it should return cached data. Has priority over ignoreCache. - * @param {boolean} [ignoreCache] True if it should ignore cached data (it will always fail in offline or server down). - * @param {string} [siteId] Site ID. If not defined, use current site. - * @return {Promise<{notifications: any[], canLoadMore: boolean}>} Promise resolved with notifications and if can load more. + * @param notifications Current list of loaded notifications. It's used to calculate the offset. + * @param limit Number of notifications to get. Defaults to LIST_LIMIT. + * @param toDisplay True if notifications will be displayed to the user, either in view or in a notification. + * @param forceCache True if it should return cached data. Has priority over ignoreCache. + * @param ignoreCache True if it should ignore cached data (it will always fail in offline or server down). + * @param siteId Site ID. If not defined, use current site. + * @return Promise resolved with notifications and if can load more. */ getNotifications(notifications: any[], limit?: number, toDisplay: boolean = true, forceCache?: boolean, ignoreCache?: boolean, - siteId?: string): Promise<{notifications: any[], canLoadMore: boolean}> { + siteId?: string): Promise<{notifications: AddonNotificationsAnyNotification[], canLoadMore: boolean}> { notifications = notifications || []; limit = limit || AddonNotificationsProvider.LIST_LIMIT; @@ -80,7 +82,7 @@ export class AddonNotificationsHelperProvider { promise = Promise.resolve(unread); } - return promise.then((notifications) => { + return promise.then((notifications: AddonNotificationsGetMessagesMessage[]) => { return { notifications: notifications, canLoadMore: notifications.length >= limit diff --git a/src/addon/notifications/providers/mainmenu-handler.ts b/src/addon/notifications/providers/mainmenu-handler.ts index a73fd6065..be415fbb2 100644 --- a/src/addon/notifications/providers/mainmenu-handler.ts +++ b/src/addon/notifications/providers/mainmenu-handler.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -73,7 +73,7 @@ export class AddonNotificationsMainMenuHandler implements CoreMainMenuHandler { /** * Check if the handler is enabled on a site level. * - * @return {boolean} Whether or not the handler is enabled on a site level. + * @return Whether or not the handler is enabled on a site level. */ isEnabled(): boolean | Promise { return true; @@ -82,7 +82,7 @@ export class AddonNotificationsMainMenuHandler implements CoreMainMenuHandler { /** * Returns the data needed to render the handler. * - * @return {CoreMainMenuHandlerData} Data needed to render the handler. + * @return Data needed to render the handler. */ getDisplayData(): CoreMainMenuHandlerData { if (this.handler.loading) { @@ -95,7 +95,7 @@ export class AddonNotificationsMainMenuHandler implements CoreMainMenuHandler { /** * Triggers an update for the badge number and loading status. Mandatory if showBadge is enabled. * - * @param {string} [siteId] Site ID or current Site if undefined. + * @param siteId Site ID or current Site if undefined. */ updateBadge(siteId?: string): void { siteId = siteId || this.sitesProvider.getCurrentSiteId(); diff --git a/src/addon/notifications/providers/notifications.ts b/src/addon/notifications/providers/notifications.ts index db8b5b62b..3a2234ada 100644 --- a/src/addon/notifications/providers/notifications.ts +++ b/src/addon/notifications/providers/notifications.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -20,8 +20,9 @@ import { CoreTextUtilsProvider } from '@providers/utils/text'; import { CoreTimeUtilsProvider } from '@providers/utils/time'; import { CoreUserProvider } from '@core/user/providers/user'; import { CoreEmulatorHelperProvider } from '@core/emulator/providers/helper'; -import { AddonMessagesProvider } from '@addon/messages/providers/messages'; +import { AddonMessagesProvider, AddonMessagesMarkMessageReadResult } from '@addon/messages/providers/messages'; import { CoreSite } from '@classes/site'; +import { CoreWSExternalWarning } from '@providers/ws'; /** * Service to handle notifications. @@ -47,18 +48,17 @@ export class AddonNotificationsProvider { /** * Function to format notification data. * - * @param {any[]} notifications List of notifications. - * @param {boolean} [read] Whether the notifications are read or unread. - * @return {Promise} Promise resolved with notifications. + * @param notifications List of notifications. + * @param read Whether the notifications are read or unread. + * @return Promise resolved with notifications. */ - protected formatNotificationsData(notifications: any[], read?: boolean): Promise { + protected formatNotificationsData(notifications: AddonNotificationsAnyNotification[], read?: boolean): Promise { + const promises = notifications.map((notification) => { // Set message to show. if (notification.component && notification.component == 'mod_forum') { notification.mobiletext = notification.smallmessage; - } else if (notification.component && notification.component == 'moodle' && notification.name == 'insights') { - notification.mobiletext = notification.fullmessagehtml; } else { notification.mobiletext = notification.fullmessage; } @@ -105,7 +105,7 @@ export class AddonNotificationsProvider { /** * Get the cache key for the get notification preferences call. * - * @return {string} Cache key. + * @return Cache key. */ protected getNotificationPreferencesCacheKey(): string { return this.ROOT_CACHE_KEY + 'notificationPreferences'; @@ -114,10 +114,10 @@ export class AddonNotificationsProvider { /** * Get notification preferences. * - * @param {string} [siteId] Site ID. If not defined, use current site. - * @return {Promise} Promise resolved with the notification preferences. + * @param siteId Site ID. If not defined, use current site. + * @return Promise resolved with the notification preferences. */ - getNotificationPreferences(siteId?: string): Promise { + getNotificationPreferences(siteId?: string): Promise { this.logger.debug('Get notification preferences'); return this.sitesProvider.getSite(siteId).then((site) => { @@ -126,7 +126,9 @@ export class AddonNotificationsProvider { updateFrequency: CoreSite.FREQUENCY_SOMETIMES }; - return site.read('core_message_get_user_notification_preferences', {}, preSets).then((data) => { + return site.read('core_message_get_user_notification_preferences', {}, preSets) + .then((data: AddonNotificationsGetUserNotificationPreferencesResult) => { + return data.preferences; }); }); @@ -135,7 +137,7 @@ export class AddonNotificationsProvider { /** * Get cache key for notification list WS calls. * - * @return {string} Cache key. + * @return Cache key. */ protected getNotificationsCacheKey(): string { return this.ROOT_CACHE_KEY + 'list'; @@ -144,17 +146,17 @@ export class AddonNotificationsProvider { /** * Get notifications from site. * - * @param {boolean} read True if should get read notifications, false otherwise. - * @param {number} limitFrom Position of the first notification to get. - * @param {number} limitNumber Number of notifications to get or 0 to use the default limit. - * @param {boolean} [toDisplay=true] True if notifications will be displayed to the user, either in view or in a notification. - * @param {boolean} [forceCache] True if it should return cached data. Has priority over ignoreCache. - * @param {boolean} [ignoreCache] True if it should ignore cached data (it will always fail in offline or server down). - * @param {string} [siteId] Site ID. If not defined, use current site. - * @return {Promise} Promise resolved with notifications. + * @param read True if should get read notifications, false otherwise. + * @param limitFrom Position of the first notification to get. + * @param limitNumber Number of notifications to get or 0 to use the default limit. + * @param toDisplay True if notifications will be displayed to the user, either in view or in a notification. + * @param forceCache True if it should return cached data. Has priority over ignoreCache. + * @param ignoreCache True if it should ignore cached data (it will always fail in offline or server down). + * @param siteId Site ID. If not defined, use current site. + * @return Promise resolved with notifications. */ getNotifications(read: boolean, limitFrom: number, limitNumber: number = 0, toDisplay: boolean = true, - forceCache?: boolean, ignoreCache?: boolean, siteId?: string): Promise { + forceCache?: boolean, ignoreCache?: boolean, siteId?: string): Promise { limitNumber = limitNumber || AddonNotificationsProvider.LIST_LIMIT; this.logger.debug('Get ' + (read ? 'read' : 'unread') + ' notifications from ' + limitFrom + '. Limit: ' + limitNumber); @@ -176,7 +178,7 @@ export class AddonNotificationsProvider { }; // Get unread notifications. - return site.read('core_message_get_messages', data, preSets).then((response) => { + return site.read('core_message_get_messages', data, preSets).then((response: AddonNotificationsGetMessagesResult) => { if (response.messages) { const notifications = response.messages; @@ -199,17 +201,17 @@ export class AddonNotificationsProvider { /** * Get notifications from site using the new WebService. * - * @param {number} offset Position of the first notification to get. - * @param {number} [limit] Number of notifications to get. Defaults to LIST_LIMIT. - * @param {boolean} [toDisplay=true] True if notifications will be displayed to the user, either in view or in a notification. - * @param {boolean} [forceCache] True if it should return cached data. Has priority over ignoreCache. - * @param {boolean} [ignoreCache] True if it should ignore cached data (it will always fail in offline or server down). - * @param {string} [siteId] Site ID. If not defined, use current site. - * @return {Promise<{notifications: any[], canLoadMore: boolean}>} Promise resolved with notifications and if can load more. + * @param offset Position of the first notification to get. + * @param limit Number of notifications to get. Defaults to LIST_LIMIT. + * @param toDisplay True if notifications will be displayed to the user, either in view or in a notification. + * @param forceCache True if it should return cached data. Has priority over ignoreCache. + * @param ignoreCache True if it should ignore cached data (it will always fail in offline or server down). + * @param siteId Site ID. If not defined, use current site. + * @return Promise resolved with notifications and if can load more. * @since 3.2 */ getPopupNotifications(offset: number, limit?: number, toDisplay: boolean = true, forceCache?: boolean, ignoreCache?: boolean, - siteId?: string): Promise<{notifications: any[], canLoadMore: boolean}> { + siteId?: string): Promise<{notifications: AddonNotificationsPopupNotificationFormatted[], canLoadMore: boolean}> { limit = limit || AddonNotificationsProvider.LIST_LIMIT; @@ -230,17 +232,17 @@ export class AddonNotificationsProvider { }; // Get notifications. - return site.read('message_popup_get_popup_notifications', data, preSets).then((response) => { + return site.read('message_popup_get_popup_notifications', data, preSets) + .then((response: AddonNotificationsGetPopupNotificationsResult) => { + if (response.notifications) { - const result: any = { - canLoadMore: response.notifications.length > limit - }, - notifications = response.notifications.slice(0, limit); + const result = { + canLoadMore: response.notifications.length > limit, + notifications: response.notifications.slice(0, limit) + }; - result.notifications = notifications; - - return this.formatNotificationsData(notifications).then(() => { - const first = notifications[0]; + return this.formatNotificationsData(result.notifications).then(() => { + const first = result.notifications[0]; if (this.appProvider.isDesktop() && toDisplay && offset === 0 && first && !first.read) { // Store the last received notification. Don't block the user for this. @@ -260,41 +262,41 @@ export class AddonNotificationsProvider { /** * Get read notifications from site. * - * @param {number} limitFrom Position of the first notification to get. - * @param {number} limitNumber Number of notifications to get. - * @param {boolean} [toDisplay=true] True if notifications will be displayed to the user, either in view or in a notification. - * @param {boolean} [forceCache] True if it should return cached data. Has priority over ignoreCache. - * @param {boolean} [ignoreCache] True if it should ignore cached data (it will always fail in offline or server down). - * @param {string} [siteId] Site ID. If not defined, use current site. - * @return {Promise} Promise resolved with notifications. + * @param limitFrom Position of the first notification to get. + * @param limitNumber Number of notifications to get. + * @param toDisplay True if notifications will be displayed to the user, either in view or in a notification. + * @param forceCache True if it should return cached data. Has priority over ignoreCache. + * @param ignoreCache True if it should ignore cached data (it will always fail in offline or server down). + * @param siteId Site ID. If not defined, use current site. + * @return Promise resolved with notifications. */ getReadNotifications(limitFrom: number, limitNumber: number, toDisplay: boolean = true, - forceCache?: boolean, ignoreCache?: boolean, siteId?: string): Promise { + forceCache?: boolean, ignoreCache?: boolean, siteId?: string): Promise { return this.getNotifications(true, limitFrom, limitNumber, toDisplay, forceCache, ignoreCache, siteId); } /** * Get unread notifications from site. * - * @param {number} limitFrom Position of the first notification to get. - * @param {number} limitNumber Number of notifications to get. - * @param {boolean} [toDisplay=true] True if notifications will be displayed to the user, either in view or in a notification. - * @param {boolean} [forceCache] True if it should return cached data. Has priority over ignoreCache. - * @param {boolean} [ignoreCache] True if it should ignore cached data (it will always fail in offline or server down). - * @param {string} [siteId] Site ID. If not defined, use current site. - * @return {Promise} Promise resolved with notifications. + * @param limitFrom Position of the first notification to get. + * @param limitNumber Number of notifications to get. + * @param toDisplay True if notifications will be displayed to the user, either in view or in a notification. + * @param forceCache True if it should return cached data. Has priority over ignoreCache. + * @param ignoreCache True if it should ignore cached data (it will always fail in offline or server down). + * @param siteId Site ID. If not defined, use current site. + * @return Promise resolved with notifications. */ getUnreadNotifications(limitFrom: number, limitNumber: number, toDisplay: boolean = true, - forceCache?: boolean, ignoreCache?: boolean, siteId?: string): Promise { + forceCache?: boolean, ignoreCache?: boolean, siteId?: string): Promise { return this.getNotifications(false, limitFrom, limitNumber, toDisplay, forceCache, ignoreCache, siteId); } /** * Get unread notifications count. Do not cache calls. * - * @param {number} [userId] The user id who received the notification. If not defined, use current user. - * @param {string} [siteId] Site ID. If not defined, use current site. - * @return {Promise} Promise resolved with the message notifications count. + * @param userId The user id who received the notification. If not defined, use current user. + * @param siteId Site ID. If not defined, use current site. + * @return Promise resolved with the message notifications count. */ getUnreadNotificationsCount(userId?: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -334,8 +336,8 @@ export class AddonNotificationsProvider { /** * Returns whether or not popup WS is available for a certain site. * - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with true if available, resolved with false or rejected otherwise. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with true if available, resolved with false or rejected otherwise. */ isPopupAvailable(siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -346,10 +348,10 @@ export class AddonNotificationsProvider { /** * Mark all message notification as read. * - * @returns {Promise} Resolved when done. + * @return Resolved when done. * @since 3.2 */ - markAllNotificationsAsRead(): Promise { + markAllNotificationsAsRead(): Promise { const params = { useridto: this.sitesProvider.getCurrentSiteUserId() }; @@ -360,12 +362,14 @@ export class AddonNotificationsProvider { /** * Mark a single notification as read. * - * @param {number} notificationId ID of notification to mark as read - * @param {string} [siteId] Site ID. If not defined, current site. - * @returns {Promise} Resolved when done. + * @param notificationId ID of notification to mark as read + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when done. * @since 3.5 */ - markNotificationRead(notificationId: number, siteId?: string): Promise { + markNotificationRead(notificationId: number, siteId?: string) + : Promise { + return this.sitesProvider.getSite(siteId).then((site) => { if (site.wsAvailable('core_message_mark_notification_read')) { @@ -385,8 +389,8 @@ export class AddonNotificationsProvider { /** * Invalidate get notification preferences. * - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when data is invalidated. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when data is invalidated. */ invalidateNotificationPreferences(siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -397,8 +401,8 @@ export class AddonNotificationsProvider { /** * Invalidates notifications list WS calls. * - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the list is invalidated. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the list is invalidated. */ invalidateNotificationsList(siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -409,7 +413,7 @@ export class AddonNotificationsProvider { /** * Returns whether or not we can mark all notifications as read. * - * @return {boolean} True if enabled, false otherwise. + * @return True if enabled, false otherwise. * @since 3.2 */ isMarkAllNotificationsAsReadEnabled(): boolean { @@ -419,7 +423,7 @@ export class AddonNotificationsProvider { /** * Returns whether or not we can count unread notifications precisely. * - * @return {boolean} True if enabled, false otherwise. + * @return True if enabled, false otherwise. * @since 3.2 */ isPreciseNotificationCountEnabled(): boolean { @@ -429,10 +433,187 @@ export class AddonNotificationsProvider { /** * Returns whether or not the notification preferences are enabled for the current site. * - * @return {boolean} True if enabled, false otherwise. + * @return True if enabled, false otherwise. * @since 3.2 */ isNotificationPreferencesEnabled(): boolean { return this.sitesProvider.wsAvailableInCurrentSite('core_message_get_user_notification_preferences'); } } + +/** + * Preferences returned by core_message_get_user_notification_preferences. + */ +export type AddonNotificationsNotificationPreferences = { + userid: number; // User id. + disableall: number | boolean; // Whether all the preferences are disabled. + processors: AddonNotificationsNotificationPreferencesProcessor[]; // Config form values. + components: AddonNotificationsNotificationPreferencesComponent[]; // Available components. + enableall?: boolean; // Calculated in the app. Whether all the preferences are enabled. +}; + +/** + * Processor in notification preferences. + */ +export type AddonNotificationsNotificationPreferencesProcessor = { + displayname: string; // Display name. + name: string; // Processor name. + hassettings: boolean; // Whether has settings. + contextid: number; // Context id. + userconfigured: number; // Whether is configured by the user. +}; + +/** + * Component in notification preferences. + */ +export type AddonNotificationsNotificationPreferencesComponent = { + displayname: string; // Display name. + notifications: AddonNotificationsNotificationPreferencesNotification[]; // List of notificaitons for the component. +}; + +/** + * Notification processor in notification preferences component. + */ +export type AddonNotificationsNotificationPreferencesNotification = { + displayname: string; // Display name. + preferencekey: string; // Preference key. + processors: AddonNotificationsNotificationPreferencesNotificationProcessor[]; // Processors values for this notification. +}; + +/** + * Notification processor in notification preferences component. + */ +export type AddonNotificationsNotificationPreferencesNotificationProcessor = { + displayname: string; // Display name. + name: string; // Processor name. + locked: boolean; // Is locked by admin?. + lockedmessage?: string; // @since 3.6. Text to display if locked. + userconfigured: number; // Is configured?. + loggedin: AddonNotificationsNotificationPreferencesNotificationProcessorState; + loggedoff: AddonNotificationsNotificationPreferencesNotificationProcessorState; +}; + +/** + * State in notification processor in notification preferences component. + */ +export type AddonNotificationsNotificationPreferencesNotificationProcessorState = { + name: string; // Name. + displayname: string; // Display name. + checked: boolean; // Is checked?. +}; + +/** + * Result of WS core_message_get_messages. + */ +export type AddonNotificationsGetMessagesResult = { + messages: AddonNotificationsGetMessagesMessage[]; + warnings?: CoreWSExternalWarning[]; +}; + +/** + * Message data returned by core_message_get_messages. + */ +export type AddonNotificationsGetMessagesMessage = { + id: number; // Message id. + useridfrom: number; // User from id. + useridto: number; // User to id. + subject: string; // The message subject. + text: string; // The message text formated. + fullmessage: string; // The message. + fullmessageformat: number; // Fullmessage format (1 = HTML, 0 = MOODLE, 2 = PLAIN or 4 = MARKDOWN). + fullmessagehtml: string; // The message in html. + smallmessage: string; // The shorten message. + notification: number; // Is a notification?. + contexturl: string; // Context URL. + contexturlname: string; // Context URL link name. + timecreated: number; // Time created. + timeread: number; // Time read. + usertofullname: string; // User to full name. + userfromfullname: string; // User from full name. + component?: string; // @since 3.7. The component that generated the notification. + eventtype?: string; // @since 3.7. The type of notification. + customdata?: any; // @since 3.7. Custom data to be passed to the message processor. +}; + +/** + * Message data returned by core_message_get_messages with some calculated data. + */ +export type AddonNotificationsGetMessagesMessageFormatted = + AddonNotificationsGetMessagesMessage & AddonNotificationsNotificationCalculatedData; + +/** + * Result of WS message_popup_get_popup_notifications. + */ +export type AddonNotificationsGetPopupNotificationsResult = { + notifications: AddonNotificationsPopupNotification[]; + unreadcount: number; // The number of unread message for the given user. +}; + +/** + * Notification returned by message_popup_get_popup_notifications. + */ +export type AddonNotificationsPopupNotification = { + id: number; // Notification id (this is not guaranteed to be unique within this result set). + useridfrom: number; // User from id. + useridto: number; // User to id. + subject: string; // The notification subject. + shortenedsubject: string; // The notification subject shortened with ellipsis. + text: string; // The message text formated. + fullmessage: string; // The message. + fullmessageformat: number; // Fullmessage format (1 = HTML, 0 = MOODLE, 2 = PLAIN or 4 = MARKDOWN). + fullmessagehtml: string; // The message in html. + smallmessage: string; // The shorten message. + contexturl: string; // Context URL. + contexturlname: string; // Context URL link name. + timecreated: number; // Time created. + timecreatedpretty: string; // Time created in a pretty format. + timeread: number; // Time read. + read: boolean; // Notification read status. + deleted: boolean; // Notification deletion status. + iconurl: string; // URL for notification icon. + component?: string; // The component that generated the notification. + eventtype?: string; // The type of notification. + customdata?: any; // @since 3.7. Custom data to be passed to the message processor. +}; + +/** + * Notification returned by message_popup_get_popup_notifications. + */ +export type AddonNotificationsPopupNotificationFormatted = + AddonNotificationsPopupNotification & AddonNotificationsNotificationCalculatedData; + +/** + * Any kind of notification that can be retrieved. + */ +export type AddonNotificationsAnyNotification = + AddonNotificationsPopupNotificationFormatted | AddonNotificationsGetMessagesMessageFormatted; + +/** + * Result of WS core_message_get_user_notification_preferences. + */ +export type AddonNotificationsGetUserNotificationPreferencesResult = { + preferences: AddonNotificationsNotificationPreferences; + warnings?: CoreWSExternalWarning[]; +}; + +/** + * Result of WS core_message_mark_notification_read. + */ +export type AddonNotificationsMarkNotificationReadResult = { + notificationid: number; // Id of the notification. + warnings?: CoreWSExternalWarning[]; +}; + +/** + * Calculated data for messages returned by core_message_get_messages. + */ +export type AddonNotificationsNotificationCalculatedData = { + mobiletext?: string; // Calculated in the app. Text to display for the notification. + moodlecomponent?: string; // Calculated in the app. Moodle's component. + notif?: number; // Calculated in the app. Whether it's a notification. + notification?: number; // Calculated in the app in some cases. Whether it's a notification. + read?: boolean; // Calculated in the app. Whether the notifications is read. + courseid?: number; // Calculated in the app. Course the notification belongs to. + profileimageurlfrom?: string; // Calculated in the app. Avatar of user that sent the notification. + userfromfullname?: string; // Calculated in the app in some cases. User from full name. +}; diff --git a/src/addon/notifications/providers/push-click-handler.ts b/src/addon/notifications/providers/push-click-handler.ts index ce39f5d17..425437ddb 100644 --- a/src/addon/notifications/providers/push-click-handler.ts +++ b/src/addon/notifications/providers/push-click-handler.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -34,8 +34,8 @@ export class AddonNotificationsPushClickHandler implements CorePushNotifications /** * Check if a notification click is handled by this handler. * - * @param {any} notification The notification to check. - * @return {boolean} Whether the notification click is handled by this handler + * @param notification The notification to check. + * @return Whether the notification click is handled by this handler */ handles(notification: any): boolean | Promise { if (this.utils.isTrueOrOne(notification.notif)) { @@ -57,8 +57,8 @@ export class AddonNotificationsPushClickHandler implements CorePushNotifications /** * Handle the notification click. * - * @param {any} notification The notification to check. - * @return {Promise} Promise resolved when done. + * @param notification The notification to check. + * @return Promise resolved when done. */ handleClick(notification: any): Promise { let promise; diff --git a/src/addon/notifications/providers/settings-handler.ts b/src/addon/notifications/providers/settings-handler.ts index 1bb8002f7..95036ef59 100644 --- a/src/addon/notifications/providers/settings-handler.ts +++ b/src/addon/notifications/providers/settings-handler.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -33,7 +33,7 @@ export class AddonNotificationsSettingsHandler implements CoreSettingsHandler { /** * Check if the handler is enabled on a site level. * - * @return {boolean | Promise} Whether or not the handler is enabled on a site level. + * @return Whether or not the handler is enabled on a site level. */ isEnabled(): boolean | Promise { // Preferences or notification sound setting available. @@ -44,7 +44,7 @@ export class AddonNotificationsSettingsHandler implements CoreSettingsHandler { /** * Returns the data needed to render the handler. * - * @return {CoreSettingsHandlerData} Data needed to render the handler. + * @return Data needed to render the handler. */ getDisplayData(): CoreSettingsHandlerData { return { diff --git a/src/addon/qbehaviour/adaptive/adaptive.module.ts b/src/addon/qbehaviour/adaptive/adaptive.module.ts index adbae973f..5779b56e4 100644 --- a/src/addon/qbehaviour/adaptive/adaptive.module.ts +++ b/src/addon/qbehaviour/adaptive/adaptive.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/addon/qbehaviour/adaptive/providers/handler.ts b/src/addon/qbehaviour/adaptive/providers/handler.ts index 90ad015b8..fcb42e26c 100644 --- a/src/addon/qbehaviour/adaptive/providers/handler.ts +++ b/src/addon/qbehaviour/adaptive/providers/handler.ts @@ -1,5 +1,5 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -34,10 +34,10 @@ export class AddonQbehaviourAdaptiveHandler implements CoreQuestionBehaviourHand * If the behaviour requires a submit button, it should add it to question.behaviourButtons. * If the behaviour requires to show some extra data, it should return the components to render it. * - * @param {Injector} injector Injector. - * @param {any} question The question. - * @return {any[]|Promise} Components (or promise resolved with components) to render some extra data in the question - * (e.g. certainty options). Don't return anything if no extra data is required. + * @param injector Injector. + * @param question The question. + * @return Components (or promise resolved with components) to render some extra data in the question + * (e.g. certainty options). Don't return anything if no extra data is required. */ handleQuestion(injector: Injector, question: any): any[] | Promise { // Just extract the button, it doesn't need any specific component. @@ -49,7 +49,7 @@ export class AddonQbehaviourAdaptiveHandler implements CoreQuestionBehaviourHand /** * Whether or not the handler is enabled on a site level. * - * @return {boolean|Promise} True or promise resolved with true if enabled. + * @return True or promise resolved with true if enabled. */ isEnabled(): boolean | Promise { return true; diff --git a/src/addon/qbehaviour/adaptivenopenalty/adaptivenopenalty.module.ts b/src/addon/qbehaviour/adaptivenopenalty/adaptivenopenalty.module.ts index 6fa4c5683..66bd7364b 100644 --- a/src/addon/qbehaviour/adaptivenopenalty/adaptivenopenalty.module.ts +++ b/src/addon/qbehaviour/adaptivenopenalty/adaptivenopenalty.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/addon/qbehaviour/adaptivenopenalty/providers/handler.ts b/src/addon/qbehaviour/adaptivenopenalty/providers/handler.ts index a8ebebb0b..d95f0610f 100644 --- a/src/addon/qbehaviour/adaptivenopenalty/providers/handler.ts +++ b/src/addon/qbehaviour/adaptivenopenalty/providers/handler.ts @@ -1,5 +1,5 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -34,10 +34,10 @@ export class AddonQbehaviourAdaptiveNoPenaltyHandler implements CoreQuestionBeha * If the behaviour requires a submit button, it should add it to question.behaviourButtons. * If the behaviour requires to show some extra data, it should return the components to render it. * - * @param {Injector} injector Injector. - * @param {any} question The question. - * @return {any[]|Promise} Components (or promise resolved with components) to render some extra data in the question - * (e.g. certainty options). Don't return anything if no extra data is required. + * @param injector Injector. + * @param question The question. + * @return Components (or promise resolved with components) to render some extra data in the question + * (e.g. certainty options). Don't return anything if no extra data is required. */ handleQuestion(injector: Injector, question: any): any[] | Promise { // Just extract the button, it doesn't need any specific component. @@ -49,7 +49,7 @@ export class AddonQbehaviourAdaptiveNoPenaltyHandler implements CoreQuestionBeha /** * Whether or not the handler is enabled on a site level. * - * @return {boolean|Promise} True or promise resolved with true if enabled. + * @return True or promise resolved with true if enabled. */ isEnabled(): boolean | Promise { return true; diff --git a/src/addon/qbehaviour/deferredcbm/component/addon-qbehaviour-deferredcbm.html b/src/addon/qbehaviour/deferredcbm/component/addon-qbehaviour-deferredcbm.html index 0975635c1..a141d0e03 100644 --- a/src/addon/qbehaviour/deferredcbm/component/addon-qbehaviour-deferredcbm.html +++ b/src/addon/qbehaviour/deferredcbm/component/addon-qbehaviour-deferredcbm.html @@ -4,9 +4,7 @@
- - - + {{ option.text }}
diff --git a/src/addon/qbehaviour/deferredcbm/component/deferredcbm.ts b/src/addon/qbehaviour/deferredcbm/component/deferredcbm.ts index 5aff68539..dcb4c1310 100644 --- a/src/addon/qbehaviour/deferredcbm/component/deferredcbm.ts +++ b/src/addon/qbehaviour/deferredcbm/component/deferredcbm.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -27,6 +27,8 @@ export class AddonQbehaviourDeferredCBMComponent { @Input() componentId: number; // ID of the component the question belongs to. @Input() attemptId: number; // Attempt ID. @Input() offlineEnabled?: boolean | string; // Whether the question can be answered in offline. + @Input() contextLevel?: string; // The context level. + @Input() contextInstanceId?: number; // The instance ID related to the context. @Output() buttonClicked: EventEmitter; // Should emit an event when a behaviour button is clicked. @Output() onAbort: EventEmitter; // Should emit an event if the question should be aborted. diff --git a/src/addon/qbehaviour/deferredcbm/deferredcbm.module.ts b/src/addon/qbehaviour/deferredcbm/deferredcbm.module.ts index 3948f8abe..361b138c4 100644 --- a/src/addon/qbehaviour/deferredcbm/deferredcbm.module.ts +++ b/src/addon/qbehaviour/deferredcbm/deferredcbm.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/addon/qbehaviour/deferredcbm/providers/handler.ts b/src/addon/qbehaviour/deferredcbm/providers/handler.ts index fb11cb208..a32634c1f 100644 --- a/src/addon/qbehaviour/deferredcbm/providers/handler.ts +++ b/src/addon/qbehaviour/deferredcbm/providers/handler.ts @@ -1,5 +1,5 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -37,11 +37,11 @@ export class AddonQbehaviourDeferredCBMHandler implements CoreQuestionBehaviourH /** * Determine a question new state based on its answer(s). * - * @param {string} component Component the question belongs to. - * @param {number} attemptId Attempt ID the question belongs to. - * @param {any} question The question. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {CoreQuestionState|Promise} New state (or promise resolved with state). + * @param component Component the question belongs to. + * @param attemptId Attempt ID the question belongs to. + * @param question The question. + * @param siteId Site ID. If not defined, current site. + * @return New state (or promise resolved with state). */ determineNewState(component: string, attemptId: number, question: any, siteId?: string) : CoreQuestionState | Promise { @@ -55,10 +55,10 @@ export class AddonQbehaviourDeferredCBMHandler implements CoreQuestionBehaviourH * If the behaviour requires a submit button, it should add it to question.behaviourButtons. * If the behaviour requires to show some extra data, it should return the components to render it. * - * @param {Injector} injector Injector. - * @param {any} question The question. - * @return {any[]|Promise} Components (or promise resolved with components) to render some extra data in the question - * (e.g. certainty options). Don't return anything if no extra data is required. + * @param injector Injector. + * @param question The question. + * @return Components (or promise resolved with components) to render some extra data in the question + * (e.g. certainty options). Don't return anything if no extra data is required. */ handleQuestion(injector: Injector, question: any): any[] | Promise { if (this.questionHelper.extractQbehaviourCBM(question)) { @@ -69,9 +69,9 @@ export class AddonQbehaviourDeferredCBMHandler implements CoreQuestionBehaviourH /** * Check if a response is complete. * - * @param {any} question The question. - * @param {any} answers Object with the question answers (without prefix). - * @return {number} 1 if complete, 0 if not complete, -1 if cannot determine. + * @param question The question. + * @param answers Object with the question answers (without prefix). + * @return 1 if complete, 0 if not complete, -1 if cannot determine. */ protected isCompleteResponse(question: any, answers: any): number { // First check if the question answer is complete. @@ -87,7 +87,7 @@ export class AddonQbehaviourDeferredCBMHandler implements CoreQuestionBehaviourH /** * Whether or not the handler is enabled on a site level. * - * @return {boolean|Promise} True or promise resolved with true if enabled. + * @return True or promise resolved with true if enabled. */ isEnabled(): boolean | Promise { return true; @@ -96,12 +96,12 @@ export class AddonQbehaviourDeferredCBMHandler implements CoreQuestionBehaviourH /** * Check if two responses are the same. * - * @param {any} question Question. - * @param {any} prevAnswers Object with the previous question answers. - * @param {any} prevBasicAnswers Object with the previous basic" answers (without sequencecheck, certainty, ...). - * @param {any} newAnswers Object with the new question answers. - * @param {any} newBasicAnswers Object with the previous basic" answers (without sequencecheck, certainty, ...). - * @return {boolean} Whether they're the same. + * @param question Question. + * @param prevAnswers Object with the previous question answers. + * @param prevBasicAnswers Object with the previous basic" answers (without sequencecheck, certainty, ...). + * @param newAnswers Object with the new question answers. + * @param newBasicAnswers Object with the previous basic" answers (without sequencecheck, certainty, ...). + * @return Whether they're the same. */ protected isSameResponse(question: any, prevAnswers: any, prevBasicAnswers: any, newAnswers: any, newBasicAnswers: any) : boolean { diff --git a/src/addon/qbehaviour/deferredfeedback/deferredfeedback.module.ts b/src/addon/qbehaviour/deferredfeedback/deferredfeedback.module.ts index 99f39f0c5..3611bc8a0 100644 --- a/src/addon/qbehaviour/deferredfeedback/deferredfeedback.module.ts +++ b/src/addon/qbehaviour/deferredfeedback/deferredfeedback.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/addon/qbehaviour/deferredfeedback/providers/handler.ts b/src/addon/qbehaviour/deferredfeedback/providers/handler.ts index da9052a14..0edf35d41 100644 --- a/src/addon/qbehaviour/deferredfeedback/providers/handler.ts +++ b/src/addon/qbehaviour/deferredfeedback/providers/handler.ts @@ -1,5 +1,5 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -21,21 +21,21 @@ import { CoreQuestionProvider, CoreQuestionState } from '@core/question/provider /** * Check if a response is complete. * - * @param {any} question The question. - * @param {any} answers Object with the question answers (without prefix). - * @return {number} 1 if complete, 0 if not complete, -1 if cannot determine. + * @param question The question. + * @param answers Object with the question answers (without prefix). + * @return 1 if complete, 0 if not complete, -1 if cannot determine. */ export type isCompleteResponseFunction = (question: any, answers: any) => number; /** * Check if two responses are the same. * - * @param {any} question Question. - * @param {any} prevAnswers Object with the previous question answers. - * @param {any} prevBasicAnswers Object with the previous basic" answers (without sequencecheck, certainty, ...). - * @param {any} newAnswers Object with the new question answers. - * @param {any} newBasicAnswers Object with the previous basic" answers (without sequencecheck, certainty, ...). - * @return {boolean} Whether they're the same. + * @param question Question. + * @param prevAnswers Object with the previous question answers. + * @param prevBasicAnswers Object with the previous basic" answers (without sequencecheck, certainty, ...). + * @param newAnswers Object with the new question answers. + * @param newBasicAnswers Object with the previous basic" answers (without sequencecheck, certainty, ...). + * @return Whether they're the same. */ export type isSameResponseFunction = (question: any, prevAnswers: any, prevBasicAnswers: any, newAnswers: any, newBasicAnswers: any) => boolean; @@ -55,11 +55,11 @@ export class AddonQbehaviourDeferredFeedbackHandler implements CoreQuestionBehav /** * Determine a question new state based on its answer(s). * - * @param {string} component Component the question belongs to. - * @param {number} attemptId Attempt ID the question belongs to. - * @param {any} question The question. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {CoreQuestionState|Promise} New state (or promise resolved with state). + * @param component Component the question belongs to. + * @param attemptId Attempt ID the question belongs to. + * @param question The question. + * @param siteId Site ID. If not defined, current site. + * @return New state (or promise resolved with state). */ determineNewState(component: string, attemptId: number, question: any, siteId?: string) : CoreQuestionState | Promise { @@ -69,13 +69,13 @@ export class AddonQbehaviourDeferredFeedbackHandler implements CoreQuestionBehav /** * Determine a question new state based on its answer(s) for deferred question behaviour. * - * @param {string} component Component the question belongs to. - * @param {number} attemptId Attempt ID the question belongs to. - * @param {any} question The question. - * @param {string} [siteId] Site ID. If not defined, current site. - * @param {isCompleteResponseFunction} [isCompleteFn] Function to override the default isCompleteResponse check. - * @param {isSameResponseFunction} [isSameFn] Function to override the default isSameResponse check. - * @return {Promise} Promise resolved with state. + * @param component Component the question belongs to. + * @param attemptId Attempt ID the question belongs to. + * @param question The question. + * @param siteId Site ID. If not defined, current site. + * @param isCompleteFn Function to override the default isCompleteResponse check. + * @param isSameFn Function to override the default isSameResponse check. + * @return Promise resolved with state. */ determineNewStateDeferred(component: string, attemptId: number, question: any, siteId?: string, isCompleteFn?: isCompleteResponseFunction, isSameFn?: isSameResponseFunction): Promise { @@ -146,7 +146,7 @@ export class AddonQbehaviourDeferredFeedbackHandler implements CoreQuestionBehav /** * Whether or not the handler is enabled on a site level. * - * @return {boolean|Promise} True or promise resolved with true if enabled. + * @return True or promise resolved with true if enabled. */ isEnabled(): boolean | Promise { return true; diff --git a/src/addon/qbehaviour/immediatecbm/immediatecbm.module.ts b/src/addon/qbehaviour/immediatecbm/immediatecbm.module.ts index a13e3e130..7a1a33cb3 100644 --- a/src/addon/qbehaviour/immediatecbm/immediatecbm.module.ts +++ b/src/addon/qbehaviour/immediatecbm/immediatecbm.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/addon/qbehaviour/immediatecbm/providers/handler.ts b/src/addon/qbehaviour/immediatecbm/providers/handler.ts index 4e1aa9ce4..f4fe6649b 100644 --- a/src/addon/qbehaviour/immediatecbm/providers/handler.ts +++ b/src/addon/qbehaviour/immediatecbm/providers/handler.ts @@ -1,5 +1,5 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -35,10 +35,10 @@ export class AddonQbehaviourImmediateCBMHandler implements CoreQuestionBehaviour * If the behaviour requires a submit button, it should add it to question.behaviourButtons. * If the behaviour requires to show some extra data, it should return the components to render it. * - * @param {Injector} injector Injector. - * @param {any} question The question. - * @return {any[]|Promise} Components (or promise resolved with components) to render some extra data in the question - * (e.g. certainty options). Don't return anything if no extra data is required. + * @param injector Injector. + * @param question The question. + * @return Components (or promise resolved with components) to render some extra data in the question + * (e.g. certainty options). Don't return anything if no extra data is required. */ handleQuestion(injector: Injector, question: any): any[] | Promise { // Just extract the button, it doesn't need any specific component. @@ -52,7 +52,7 @@ export class AddonQbehaviourImmediateCBMHandler implements CoreQuestionBehaviour /** * Whether or not the handler is enabled on a site level. * - * @return {boolean|Promise} True or promise resolved with true if enabled. + * @return True or promise resolved with true if enabled. */ isEnabled(): boolean | Promise { return true; diff --git a/src/addon/qbehaviour/immediatefeedback/immediatefeedback.module.ts b/src/addon/qbehaviour/immediatefeedback/immediatefeedback.module.ts index 3d5e5ed66..781733ffe 100644 --- a/src/addon/qbehaviour/immediatefeedback/immediatefeedback.module.ts +++ b/src/addon/qbehaviour/immediatefeedback/immediatefeedback.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/addon/qbehaviour/immediatefeedback/providers/handler.ts b/src/addon/qbehaviour/immediatefeedback/providers/handler.ts index 2103bba6c..4ec1fe768 100644 --- a/src/addon/qbehaviour/immediatefeedback/providers/handler.ts +++ b/src/addon/qbehaviour/immediatefeedback/providers/handler.ts @@ -1,5 +1,5 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -34,10 +34,10 @@ export class AddonQbehaviourImmediateFeedbackHandler implements CoreQuestionBeha * If the behaviour requires a submit button, it should add it to question.behaviourButtons. * If the behaviour requires to show some extra data, it should return the components to render it. * - * @param {Injector} injector Injector. - * @param {any} question The question. - * @return {any[]|Promise} Components (or promise resolved with components) to render some extra data in the question - * (e.g. certainty options). Don't return anything if no extra data is required. + * @param injector Injector. + * @param question The question. + * @return Components (or promise resolved with components) to render some extra data in the question + * (e.g. certainty options). Don't return anything if no extra data is required. */ handleQuestion(injector: Injector, question: any): any[] | Promise { // Just extract the button, it doesn't need any specific component. @@ -49,7 +49,7 @@ export class AddonQbehaviourImmediateFeedbackHandler implements CoreQuestionBeha /** * Whether or not the handler is enabled on a site level. * - * @return {boolean|Promise} True or promise resolved with true if enabled. + * @return True or promise resolved with true if enabled. */ isEnabled(): boolean | Promise { return true; diff --git a/src/addon/qbehaviour/informationitem/component/informationitem.ts b/src/addon/qbehaviour/informationitem/component/informationitem.ts index 057761bdb..aaa44569e 100644 --- a/src/addon/qbehaviour/informationitem/component/informationitem.ts +++ b/src/addon/qbehaviour/informationitem/component/informationitem.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -27,6 +27,8 @@ export class AddonQbehaviourInformationItemComponent { @Input() componentId: number; // ID of the component the question belongs to. @Input() attemptId: number; // Attempt ID. @Input() offlineEnabled?: boolean | string; // Whether the question can be answered in offline. + @Input() contextLevel?: string; // The context level. + @Input() contextInstanceId?: number; // The instance ID related to the context. @Output() buttonClicked: EventEmitter; // Should emit an event when a behaviour button is clicked. @Output() onAbort: EventEmitter; // Should emit an event if the question should be aborted. diff --git a/src/addon/qbehaviour/informationitem/informationitem.module.ts b/src/addon/qbehaviour/informationitem/informationitem.module.ts index 0f45881a0..8fcb958c2 100644 --- a/src/addon/qbehaviour/informationitem/informationitem.module.ts +++ b/src/addon/qbehaviour/informationitem/informationitem.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/addon/qbehaviour/informationitem/providers/handler.ts b/src/addon/qbehaviour/informationitem/providers/handler.ts index 21d7181ed..c30e2c800 100644 --- a/src/addon/qbehaviour/informationitem/providers/handler.ts +++ b/src/addon/qbehaviour/informationitem/providers/handler.ts @@ -1,5 +1,5 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -32,11 +32,11 @@ export class AddonQbehaviourInformationItemHandler implements CoreQuestionBehavi /** * Determine a question new state based on its answer(s). * - * @param {string} component Component the question belongs to. - * @param {number} attemptId Attempt ID the question belongs to. - * @param {any} question The question. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {CoreQuestionState|Promise} New state (or promise resolved with state). + * @param component Component the question belongs to. + * @param attemptId Attempt ID the question belongs to. + * @param question The question. + * @param siteId Site ID. If not defined, current site. + * @return New state (or promise resolved with state). */ determineNewState(component: string, attemptId: number, question: any, siteId?: string) : CoreQuestionState | Promise { @@ -52,10 +52,10 @@ export class AddonQbehaviourInformationItemHandler implements CoreQuestionBehavi * If the behaviour requires a submit button, it should add it to question.behaviourButtons. * If the behaviour requires to show some extra data, it should return the components to render it. * - * @param {Injector} injector Injector. - * @param {any} question The question. - * @return {any[]|Promise} Components (or promise resolved with components) to render some extra data in the question - * (e.g. certainty options). Don't return anything if no extra data is required. + * @param injector Injector. + * @param question The question. + * @return Components (or promise resolved with components) to render some extra data in the question + * (e.g. certainty options). Don't return anything if no extra data is required. */ handleQuestion(injector: Injector, question: any): any[] | Promise { if (this.questionHelper.extractQbehaviourSeenInput(question)) { @@ -66,7 +66,7 @@ export class AddonQbehaviourInformationItemHandler implements CoreQuestionBehavi /** * Whether or not the handler is enabled on a site level. * - * @return {boolean|Promise} True or promise resolved with true if enabled. + * @return True or promise resolved with true if enabled. */ isEnabled(): boolean | Promise { return true; diff --git a/src/addon/qbehaviour/interactive/interactive.module.ts b/src/addon/qbehaviour/interactive/interactive.module.ts index ab30d3e0a..f9bef6f89 100644 --- a/src/addon/qbehaviour/interactive/interactive.module.ts +++ b/src/addon/qbehaviour/interactive/interactive.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/addon/qbehaviour/interactive/providers/handler.ts b/src/addon/qbehaviour/interactive/providers/handler.ts index 5504da1cc..cf65a2d52 100644 --- a/src/addon/qbehaviour/interactive/providers/handler.ts +++ b/src/addon/qbehaviour/interactive/providers/handler.ts @@ -1,5 +1,5 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -34,10 +34,10 @@ export class AddonQbehaviourInteractiveHandler implements CoreQuestionBehaviourH * If the behaviour requires a submit button, it should add it to question.behaviourButtons. * If the behaviour requires to show some extra data, it should return the components to render it. * - * @param {Injector} injector Injector. - * @param {any} question The question. - * @return {any[]|Promise} Components (or promise resolved with components) to render some extra data in the question - * (e.g. certainty options). Don't return anything if no extra data is required. + * @param injector Injector. + * @param question The question. + * @return Components (or promise resolved with components) to render some extra data in the question + * (e.g. certainty options). Don't return anything if no extra data is required. */ handleQuestion(injector: Injector, question: any): any[] | Promise { // Just extract the button, it doesn't need any specific component. @@ -49,7 +49,7 @@ export class AddonQbehaviourInteractiveHandler implements CoreQuestionBehaviourH /** * Whether or not the handler is enabled on a site level. * - * @return {boolean|Promise} True or promise resolved with true if enabled. + * @return True or promise resolved with true if enabled. */ isEnabled(): boolean | Promise { return true; diff --git a/src/addon/qbehaviour/interactivecountback/interactivecountback.module.ts b/src/addon/qbehaviour/interactivecountback/interactivecountback.module.ts index bbe2dab24..d32d71246 100644 --- a/src/addon/qbehaviour/interactivecountback/interactivecountback.module.ts +++ b/src/addon/qbehaviour/interactivecountback/interactivecountback.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/addon/qbehaviour/interactivecountback/providers/handler.ts b/src/addon/qbehaviour/interactivecountback/providers/handler.ts index 54646e427..f8fa1d97e 100644 --- a/src/addon/qbehaviour/interactivecountback/providers/handler.ts +++ b/src/addon/qbehaviour/interactivecountback/providers/handler.ts @@ -1,5 +1,5 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -34,10 +34,10 @@ export class AddonQbehaviourInteractiveCountbackHandler implements CoreQuestionB * If the behaviour requires a submit button, it should add it to question.behaviourButtons. * If the behaviour requires to show some extra data, it should return the components to render it. * - * @param {Injector} injector Injector. - * @param {any} question The question. - * @return {any[]|Promise} Components (or promise resolved with components) to render some extra data in the question - * (e.g. certainty options). Don't return anything if no extra data is required. + * @param injector Injector. + * @param question The question. + * @return Components (or promise resolved with components) to render some extra data in the question + * (e.g. certainty options). Don't return anything if no extra data is required. */ handleQuestion(injector: Injector, question: any): any[] | Promise { // Just extract the button, it doesn't need any specific component. @@ -49,7 +49,7 @@ export class AddonQbehaviourInteractiveCountbackHandler implements CoreQuestionB /** * Whether or not the handler is enabled on a site level. * - * @return {boolean|Promise} True or promise resolved with true if enabled. + * @return True or promise resolved with true if enabled. */ isEnabled(): boolean | Promise { return true; diff --git a/src/addon/qbehaviour/manualgraded/manualgraded.module.ts b/src/addon/qbehaviour/manualgraded/manualgraded.module.ts index dbeaf1d34..eac6922b9 100644 --- a/src/addon/qbehaviour/manualgraded/manualgraded.module.ts +++ b/src/addon/qbehaviour/manualgraded/manualgraded.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/addon/qbehaviour/manualgraded/providers/handler.ts b/src/addon/qbehaviour/manualgraded/providers/handler.ts index 91d767af4..98da9c06d 100644 --- a/src/addon/qbehaviour/manualgraded/providers/handler.ts +++ b/src/addon/qbehaviour/manualgraded/providers/handler.ts @@ -1,5 +1,5 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -21,21 +21,21 @@ import { CoreQuestionProvider, CoreQuestionState } from '@core/question/provider /** * Check if a response is complete. * - * @param {any} question The question. - * @param {any} answers Object with the question answers (without prefix). - * @return {number} 1 if complete, 0 if not complete, -1 if cannot determine. + * @param question The question. + * @param answers Object with the question answers (without prefix). + * @return 1 if complete, 0 if not complete, -1 if cannot determine. */ export type isCompleteResponseFunction = (question: any, answers: any) => number; /** * Check if two responses are the same. * - * @param {any} question Question. - * @param {any} prevAnswers Object with the previous question answers. - * @param {any} prevBasicAnswers Object with the previous basic" answers (without sequencecheck, certainty, ...). - * @param {any} newAnswers Object with the new question answers. - * @param {any} newBasicAnswers Object with the previous basic" answers (without sequencecheck, certainty, ...). - * @return {boolean} Whether they're the same. + * @param question Question. + * @param prevAnswers Object with the previous question answers. + * @param prevBasicAnswers Object with the previous basic" answers (without sequencecheck, certainty, ...). + * @param newAnswers Object with the new question answers. + * @param newBasicAnswers Object with the previous basic" answers (without sequencecheck, certainty, ...). + * @return Whether they're the same. */ export type isSameResponseFunction = (question: any, prevAnswers: any, prevBasicAnswers: any, newAnswers: any, newBasicAnswers: any) => boolean; @@ -55,11 +55,11 @@ export class AddonQbehaviourManualGradedHandler implements CoreQuestionBehaviour /** * Determine a question new state based on its answer(s). * - * @param {string} component Component the question belongs to. - * @param {number} attemptId Attempt ID the question belongs to. - * @param {any} question The question. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {CoreQuestionState|Promise} New state (or promise resolved with state). + * @param component Component the question belongs to. + * @param attemptId Attempt ID the question belongs to. + * @param question The question. + * @param siteId Site ID. If not defined, current site. + * @return New state (or promise resolved with state). */ determineNewState(component: string, attemptId: number, question: any, siteId?: string) : CoreQuestionState | Promise { @@ -69,13 +69,13 @@ export class AddonQbehaviourManualGradedHandler implements CoreQuestionBehaviour /** * Determine a question new state based on its answer(s) for manual graded question behaviour. * - * @param {string} component Component the question belongs to. - * @param {number} attemptId Attempt ID the question belongs to. - * @param {any} question The question. - * @param {string} [siteId] Site ID. If not defined, current site. - * @param {isCompleteResponseFunction} [isCompleteFn] Function to override the default isCompleteResponse check. - * @param {isSameResponseFunction} [isSameFn] Function to override the default isSameResponse check. - * @return {Promise} Promise resolved with state. + * @param component Component the question belongs to. + * @param attemptId Attempt ID the question belongs to. + * @param question The question. + * @param siteId Site ID. If not defined, current site. + * @param isCompleteFn Function to override the default isCompleteResponse check. + * @param isSameFn Function to override the default isSameResponse check. + * @return Promise resolved with state. */ determineNewStateManualGraded(component: string, attemptId: number, question: any, siteId?: string, isCompleteFn?: isCompleteResponseFunction, isSameFn?: isSameResponseFunction): Promise { @@ -139,7 +139,7 @@ export class AddonQbehaviourManualGradedHandler implements CoreQuestionBehaviour /** * Whether or not the handler is enabled on a site level. * - * @return {boolean|Promise} True or promise resolved with true if enabled. + * @return True or promise resolved with true if enabled. */ isEnabled(): boolean | Promise { return true; diff --git a/src/addon/qbehaviour/qbehaviour.module.ts b/src/addon/qbehaviour/qbehaviour.module.ts index dbe1bf47c..d2bcdbba8 100644 --- a/src/addon/qbehaviour/qbehaviour.module.ts +++ b/src/addon/qbehaviour/qbehaviour.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/addon/qtype/calculated/calculated.module.ts b/src/addon/qtype/calculated/calculated.module.ts index a819b755e..08bda73d1 100644 --- a/src/addon/qtype/calculated/calculated.module.ts +++ b/src/addon/qtype/calculated/calculated.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/addon/qtype/calculated/component/addon-qtype-calculated.html b/src/addon/qtype/calculated/component/addon-qtype-calculated.html index e2ea63c89..e0096a827 100644 --- a/src/addon/qtype/calculated/component/addon-qtype-calculated.html +++ b/src/addon/qtype/calculated/component/addon-qtype-calculated.html @@ -1,6 +1,6 @@
-

+

@@ -55,9 +55,7 @@
- - - + {{ option.text }} diff --git a/src/addon/qtype/calculated/component/calculated.ts b/src/addon/qtype/calculated/component/calculated.ts index c44cbdd56..946526117 100644 --- a/src/addon/qtype/calculated/component/calculated.ts +++ b/src/addon/qtype/calculated/component/calculated.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/addon/qtype/calculated/providers/handler.ts b/src/addon/qtype/calculated/providers/handler.ts index 0874fbc82..c7832f91c 100644 --- a/src/addon/qtype/calculated/providers/handler.ts +++ b/src/addon/qtype/calculated/providers/handler.ts @@ -1,5 +1,5 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -33,9 +33,9 @@ export class AddonQtypeCalculatedHandler implements CoreQuestionHandler { * Return the Component to use to display the question. * It's recommended to return the class of the component, but you can also return an instance of the component. * - * @param {Injector} injector Injector. - * @param {any} question The question to render. - * @return {any|Promise} The component (or promise resolved with component) to use, undefined if not found. + * @param injector Injector. + * @param question The question to render. + * @return The component (or promise resolved with component) to use, undefined if not found. */ getComponent(injector: Injector, question: any): any | Promise { return AddonQtypeCalculatedComponent; @@ -44,9 +44,9 @@ export class AddonQtypeCalculatedHandler implements CoreQuestionHandler { /** * Check if a response is complete. * - * @param {any} question The question. - * @param {any} answers Object with the question answers (without prefix). - * @return {number} 1 if complete, 0 if not complete, -1 if cannot determine. + * @param question The question. + * @param answers Object with the question answers (without prefix). + * @return 1 if complete, 0 if not complete, -1 if cannot determine. */ isCompleteResponse(question: any, answers: any): number { if (this.isGradableResponse(question, answers) === 0 || !this.validateUnits(answers['answer'])) { @@ -63,7 +63,7 @@ export class AddonQtypeCalculatedHandler implements CoreQuestionHandler { /** * Whether or not the handler is enabled on a site level. * - * @return {boolean|Promise} True or promise resolved with true if enabled. + * @return True or promise resolved with true if enabled. */ isEnabled(): boolean | Promise { return true; @@ -73,9 +73,9 @@ export class AddonQtypeCalculatedHandler implements CoreQuestionHandler { * Check if a student has provided enough of an answer for the question to be graded automatically, * or whether it must be considered aborted. * - * @param {any} question The question. - * @param {any} answers Object with the question answers (without prefix). - * @return {number} 1 if gradable, 0 if not gradable, -1 if cannot determine. + * @param question The question. + * @param answers Object with the question answers (without prefix). + * @return 1 if gradable, 0 if not gradable, -1 if cannot determine. */ isGradableResponse(question: any, answers: any): number { let isGradable = this.isValidValue(answers['answer']); @@ -90,10 +90,10 @@ export class AddonQtypeCalculatedHandler implements CoreQuestionHandler { /** * Check if two responses are the same. * - * @param {any} question Question. - * @param {any} prevAnswers Object with the previous question answers. - * @param {any} newAnswers Object with the new question answers. - * @return {boolean} Whether they're the same. + * @param question Question. + * @param prevAnswers Object with the previous question answers. + * @param newAnswers Object with the new question answers. + * @return Whether they're the same. */ isSameResponse(question: any, prevAnswers: any, newAnswers: any): boolean { return this.utils.sameAtKeyMissingIsBlank(prevAnswers, newAnswers, 'answer') && @@ -103,8 +103,8 @@ export class AddonQtypeCalculatedHandler implements CoreQuestionHandler { /** * Check if a value is valid (not empty). * - * @param {string|number} value Value to check. - * @return {boolean} Whether the value is valid. + * @param value Value to check. + * @return Whether the value is valid. */ isValidValue(value: string | number): boolean { return !!value || value === '0' || value === 0; @@ -113,8 +113,8 @@ export class AddonQtypeCalculatedHandler implements CoreQuestionHandler { /** * Check if a question requires units in a separate input. * - * @param {any} question The question. - * @return {boolean} Whether the question requires units. + * @param question The question. + * @return Whether the question requires units. */ requiresUnits(question: any): boolean { const element = this.domUtils.convertToElement(question.html); @@ -126,8 +126,8 @@ export class AddonQtypeCalculatedHandler implements CoreQuestionHandler { * Validate a number with units. We don't have the list of valid units and conversions, so we can't perform * a full validation. If this function returns true it means we can't be sure it's valid. * - * @param {string} answer Answer. - * @return {boolean} False if answer isn't valid, true if we aren't sure if it's valid. + * @param answer Answer. + * @return False if answer isn't valid, true if we aren't sure if it's valid. */ validateUnits(answer: string): boolean { if (!answer) { diff --git a/src/addon/qtype/calculatedmulti/calculatedmulti.module.ts b/src/addon/qtype/calculatedmulti/calculatedmulti.module.ts index a5e7ee3eb..ad1357191 100644 --- a/src/addon/qtype/calculatedmulti/calculatedmulti.module.ts +++ b/src/addon/qtype/calculatedmulti/calculatedmulti.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/addon/qtype/calculatedmulti/providers/handler.ts b/src/addon/qtype/calculatedmulti/providers/handler.ts index 3cbe18057..63fcd7a3e 100644 --- a/src/addon/qtype/calculatedmulti/providers/handler.ts +++ b/src/addon/qtype/calculatedmulti/providers/handler.ts @@ -1,5 +1,5 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -32,9 +32,9 @@ export class AddonQtypeCalculatedMultiHandler implements CoreQuestionHandler { * Return the Component to use to display the question. * It's recommended to return the class of the component, but you can also return an instance of the component. * - * @param {Injector} injector Injector. - * @param {any} question The question to render. - * @return {any|Promise} The component (or promise resolved with component) to use, undefined if not found. + * @param injector Injector. + * @param question The question to render. + * @return The component (or promise resolved with component) to use, undefined if not found. */ getComponent(injector: Injector, question: any): any | Promise { // Calculated multi behaves like a multichoice, use the same component. @@ -44,9 +44,9 @@ export class AddonQtypeCalculatedMultiHandler implements CoreQuestionHandler { /** * Check if a response is complete. * - * @param {any} question The question. - * @param {any} answers Object with the question answers (without prefix). - * @return {number} 1 if complete, 0 if not complete, -1 if cannot determine. + * @param question The question. + * @param answers Object with the question answers (without prefix). + * @return 1 if complete, 0 if not complete, -1 if cannot determine. */ isCompleteResponse(question: any, answers: any): number { // This question type depends on multichoice. @@ -56,7 +56,7 @@ export class AddonQtypeCalculatedMultiHandler implements CoreQuestionHandler { /** * Whether or not the handler is enabled on a site level. * - * @return {boolean|Promise} True or promise resolved with true if enabled. + * @return True or promise resolved with true if enabled. */ isEnabled(): boolean | Promise { return true; @@ -66,9 +66,9 @@ export class AddonQtypeCalculatedMultiHandler implements CoreQuestionHandler { * Check if a student has provided enough of an answer for the question to be graded automatically, * or whether it must be considered aborted. * - * @param {any} question The question. - * @param {any} answers Object with the question answers (without prefix). - * @return {number} 1 if gradable, 0 if not gradable, -1 if cannot determine. + * @param question The question. + * @param answers Object with the question answers (without prefix). + * @return 1 if gradable, 0 if not gradable, -1 if cannot determine. */ isGradableResponse(question: any, answers: any): number { // This question type depends on multichoice. @@ -78,10 +78,10 @@ export class AddonQtypeCalculatedMultiHandler implements CoreQuestionHandler { /** * Check if two responses are the same. * - * @param {any} question Question. - * @param {any} prevAnswers Object with the previous question answers. - * @param {any} newAnswers Object with the new question answers. - * @return {boolean} Whether they're the same. + * @param question Question. + * @param prevAnswers Object with the previous question answers. + * @param newAnswers Object with the new question answers. + * @return Whether they're the same. */ isSameResponse(question: any, prevAnswers: any, newAnswers: any): boolean { // This question type depends on multichoice. diff --git a/src/addon/qtype/calculatedsimple/calculatedsimple.module.ts b/src/addon/qtype/calculatedsimple/calculatedsimple.module.ts index f4948e05a..8f7850e1d 100644 --- a/src/addon/qtype/calculatedsimple/calculatedsimple.module.ts +++ b/src/addon/qtype/calculatedsimple/calculatedsimple.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/addon/qtype/calculatedsimple/providers/handler.ts b/src/addon/qtype/calculatedsimple/providers/handler.ts index 2aaaf1c9c..e5f442b20 100644 --- a/src/addon/qtype/calculatedsimple/providers/handler.ts +++ b/src/addon/qtype/calculatedsimple/providers/handler.ts @@ -1,5 +1,5 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -32,9 +32,9 @@ export class AddonQtypeCalculatedSimpleHandler implements CoreQuestionHandler { * Return the Component to use to display the question. * It's recommended to return the class of the component, but you can also return an instance of the component. * - * @param {Injector} injector Injector. - * @param {any} question The question to render. - * @return {any|Promise} The component (or promise resolved with component) to use, undefined if not found. + * @param injector Injector. + * @param question The question to render. + * @return The component (or promise resolved with component) to use, undefined if not found. */ getComponent(injector: Injector, question: any): any | Promise { // Calculated simple behaves like a calculated, use the same component. @@ -44,9 +44,9 @@ export class AddonQtypeCalculatedSimpleHandler implements CoreQuestionHandler { /** * Check if a response is complete. * - * @param {any} question The question. - * @param {any} answers Object with the question answers (without prefix). - * @return {number} 1 if complete, 0 if not complete, -1 if cannot determine. + * @param question The question. + * @param answers Object with the question answers (without prefix). + * @return 1 if complete, 0 if not complete, -1 if cannot determine. */ isCompleteResponse(question: any, answers: any): number { // This question type depends on calculated. @@ -56,7 +56,7 @@ export class AddonQtypeCalculatedSimpleHandler implements CoreQuestionHandler { /** * Whether or not the handler is enabled on a site level. * - * @return {boolean|Promise} True or promise resolved with true if enabled. + * @return True or promise resolved with true if enabled. */ isEnabled(): boolean | Promise { return true; @@ -66,9 +66,9 @@ export class AddonQtypeCalculatedSimpleHandler implements CoreQuestionHandler { * Check if a student has provided enough of an answer for the question to be graded automatically, * or whether it must be considered aborted. * - * @param {any} question The question. - * @param {any} answers Object with the question answers (without prefix). - * @return {number} 1 if gradable, 0 if not gradable, -1 if cannot determine. + * @param question The question. + * @param answers Object with the question answers (without prefix). + * @return 1 if gradable, 0 if not gradable, -1 if cannot determine. */ isGradableResponse(question: any, answers: any): number { // This question type depends on calculated. @@ -78,10 +78,10 @@ export class AddonQtypeCalculatedSimpleHandler implements CoreQuestionHandler { /** * Check if two responses are the same. * - * @param {any} question Question. - * @param {any} prevAnswers Object with the previous question answers. - * @param {any} newAnswers Object with the new question answers. - * @return {boolean} Whether they're the same. + * @param question Question. + * @param prevAnswers Object with the previous question answers. + * @param newAnswers Object with the new question answers. + * @return Whether they're the same. */ isSameResponse(question: any, prevAnswers: any, newAnswers: any): boolean { // This question type depends on calculated. diff --git a/src/addon/qtype/ddimageortext/classes/ddimageortext.ts b/src/addon/qtype/ddimageortext/classes/ddimageortext.ts index 88f5c9e18..e3d1533bb 100644 --- a/src/addon/qtype/ddimageortext/classes/ddimageortext.ts +++ b/src/addon/qtype/ddimageortext/classes/ddimageortext.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -50,12 +50,12 @@ export class AddonQtypeDdImageOrTextQuestion { /** * Create the this. * - * @param {CoreLoggerProvider} logger Logger provider. - * @param {CoreDomUtilsProvider} domUtils Dom Utils provider. - * @param {HTMLElement} container The container HTMLElement of the question. - * @param {any} question The question this. - * @param {boolean} readOnly Whether it's read only. - * @param {any[]} drops The drop zones received in the init object of the question. + * @param logger Logger provider. + * @param domUtils Dom Utils provider. + * @param container The container HTMLElement of the question. + * @param question The question this. + * @param readOnly Whether it's read only. + * @param drops The drop zones received in the init object of the question. */ constructor(logger: CoreLoggerProvider, protected domUtils: CoreDomUtilsProvider, protected container: HTMLElement, protected question: any, protected readOnly: boolean, protected drops: any[]) { @@ -80,8 +80,8 @@ export class AddonQtypeDdImageOrTextQuestion { /** * Convert the X and Y position of the BG IMG to a position relative to the window. * - * @param {number[]} bgImgXY X and Y of the BG IMG relative position. - * @return {number[]} Position relative to the window. + * @param bgImgXY X and Y of the BG IMG relative position. + * @return Position relative to the window. */ convertToWindowXY(bgImgXY: number[]): number[] { const bgImg = this.doc.bgImg(), @@ -192,8 +192,8 @@ export class AddonQtypeDdImageOrTextQuestion { /** * Returns an object to encapsulate operations on dd area. * - * @param {number} slot The question slot. - * @return {AddonQtypeDdImageOrTextQuestionDocStructure} The object. + * @param slot The question slot. + * @return The object. */ docStructure(slot: number): AddonQtypeDdImageOrTextQuestionDocStructure { const topNode = this.container.querySelector('.addon-qtype-ddimageortext-container'), @@ -323,9 +323,9 @@ export class AddonQtypeDdImageOrTextQuestion { /** * Make an element draggable. * - * @param {HTMLElement} drag Element to make draggable. - * @param {number} group Group the element belongs to. - * @param {number} choice Choice the element belongs to. + * @param drag Element to make draggable. + * @param group Group the element belongs to. + * @param choice Choice the element belongs to. */ draggableForQuestion(drag: HTMLElement, group: number, choice: number): void { // Set attributes. @@ -348,7 +348,7 @@ export class AddonQtypeDdImageOrTextQuestion { /** * Function called when a drop zone is clicked. * - * @param {HTMLElement} dropNode Drop element. + * @param dropNode Drop element. */ dropClick(dropNode: HTMLElement): void { const drag = this.selected; @@ -368,9 +368,9 @@ export class AddonQtypeDdImageOrTextQuestion { /** * Get all the draggable elements for a choice and a drop zone. * - * @param {number} choice Choice number. - * @param {HTMLElement} drop Drop zone. - * @return {HTMLElement[]} Draggable elements. + * @param choice Choice number. + * @param drop Drop zone. + * @return Draggable elements. */ getChoicesForDrop(choice: number, drop: HTMLElement): HTMLElement[] { return Array.from(this.doc.topNode().querySelectorAll( @@ -380,9 +380,9 @@ export class AddonQtypeDdImageOrTextQuestion { /** * Get an unplaced draggable element that belongs to a certain choice and drop zone. * - * @param {number} choice Choice number. - * @param {HTMLElement} drop Drop zone. - * @return {HTMLElement} Unplaced draggable element. + * @param choice Choice number. + * @param drop Drop zone. + * @return Unplaced draggable element. */ getUnplacedChoiceForDrop(choice: number, drop: HTMLElement): HTMLElement { const dragItems = this.getChoicesForDrop(choice, drop); @@ -443,7 +443,7 @@ export class AddonQtypeDdImageOrTextQuestion { /** * Initialize the question. * - * @param {any} question Question. + * @param question Question. */ initializer(question: any): void { this.doc = this.docStructure(question.slot); @@ -517,8 +517,8 @@ export class AddonQtypeDdImageOrTextQuestion { /** * Place a draggable element into a certain drop zone. * - * @param {HTMLElement} drag Draggable element. - * @param {HTMLElement} drop Drop zone element. + * @param drag Draggable element. + * @param drop Drop zone element. */ placeDragInDrop(drag: HTMLElement, drop: HTMLElement): void { // Search the input related to the drop zone. @@ -568,7 +568,7 @@ export class AddonQtypeDdImageOrTextQuestion { /** * Remove a draggable element from the drop zone where it is. * - * @param {HTMLElement} drag Draggable element to remove. + * @param drag Draggable element to remove. */ removeDragFromDrop(drag: HTMLElement): void { // Check if the draggable element is assigned to an input. If so, empty the input's value. @@ -670,7 +670,7 @@ export class AddonQtypeDdImageOrTextQuestion { /** * Mark a draggable element as selected. * - * @param {HTMLElement} drag Element to select. + * @param drag Element to select. */ selectDrag(drag: HTMLElement): void { // Deselect previous ones. @@ -690,7 +690,7 @@ export class AddonQtypeDdImageOrTextQuestion { /** * Update the padding of all items in a group to make them all have the same width and height. * - * @param {number} groupNo The group number. + * @param groupNo The group number. */ updatePaddingSizeForGroup(groupNo: number): void { diff --git a/src/addon/qtype/ddimageortext/component/addon-qtype-ddimageortext.html b/src/addon/qtype/ddimageortext/component/addon-qtype-ddimageortext.html index 218b25b24..817b1db52 100644 --- a/src/addon/qtype/ddimageortext/component/addon-qtype-ddimageortext.html +++ b/src/addon/qtype/ddimageortext/component/addon-qtype-ddimageortext.html @@ -7,7 +7,7 @@ {{ 'core.question.howtodraganddrop' | translate }}

-

- +

+
diff --git a/src/addon/qtype/ddimageortext/component/ddimageortext.ts b/src/addon/qtype/ddimageortext/component/ddimageortext.ts index 38f65085a..1a752bdae 100644 --- a/src/addon/qtype/ddimageortext/component/ddimageortext.ts +++ b/src/addon/qtype/ddimageortext/component/ddimageortext.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -30,6 +30,8 @@ export class AddonQtypeDdImageOrTextComponent extends CoreQuestionBaseComponent protected questionInstance: AddonQtypeDdImageOrTextQuestion; protected drops: any[]; // The drop zones received in the init object of the question. protected destroyed = false; + protected textIsRendered = false; + protected ddAreaisRendered = false; constructor(protected loggerProvider: CoreLoggerProvider, injector: Injector, element: ElementRef) { super(loggerProvider, 'AddonQtypeDdImageOrTextComponent', injector); @@ -84,10 +86,30 @@ export class AddonQtypeDdImageOrTextComponent extends CoreQuestionBaseComponent this.question.loaded = false; } + /** + * The question ddArea has been rendered. + */ + ddAreaRendered(): void { + this.ddAreaisRendered = true; + if (this.textIsRendered) { + this.questionRendered(); + } + } + + /** + * The question text has been rendered. + */ + textRendered(): void { + this.textIsRendered = true; + if (this.ddAreaisRendered) { + this.questionRendered(); + } + } + /** * The question has been rendered. */ - questionRendered(): void { + protected questionRendered(): void { if (!this.destroyed) { // Create the instance. this.questionInstance = new AddonQtypeDdImageOrTextQuestion(this.loggerProvider, this.domUtils, this.element, diff --git a/src/addon/qtype/ddimageortext/ddimageortext.module.ts b/src/addon/qtype/ddimageortext/ddimageortext.module.ts index 82ec397bb..25507b299 100644 --- a/src/addon/qtype/ddimageortext/ddimageortext.module.ts +++ b/src/addon/qtype/ddimageortext/ddimageortext.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/addon/qtype/ddimageortext/providers/handler.ts b/src/addon/qtype/ddimageortext/providers/handler.ts index 6cdf63057..1774415fb 100644 --- a/src/addon/qtype/ddimageortext/providers/handler.ts +++ b/src/addon/qtype/ddimageortext/providers/handler.ts @@ -1,5 +1,5 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -32,9 +32,9 @@ export class AddonQtypeDdImageOrTextHandler implements CoreQuestionHandler { * Return the name of the behaviour to use for the question. * If the question should use the default behaviour you shouldn't implement this function. * - * @param {any} question The question. - * @param {string} behaviour The default behaviour. - * @return {string} The behaviour to use. + * @param question The question. + * @param behaviour The default behaviour. + * @return The behaviour to use. */ getBehaviour(question: any, behaviour: string): string { if (behaviour === 'interactive') { @@ -48,9 +48,9 @@ export class AddonQtypeDdImageOrTextHandler implements CoreQuestionHandler { * Return the Component to use to display the question. * It's recommended to return the class of the component, but you can also return an instance of the component. * - * @param {Injector} injector Injector. - * @param {any} question The question to render. - * @return {any|Promise} The component (or promise resolved with component) to use, undefined if not found. + * @param injector Injector. + * @param question The question to render. + * @return The component (or promise resolved with component) to use, undefined if not found. */ getComponent(injector: Injector, question: any): any | Promise { return AddonQtypeDdImageOrTextComponent; @@ -59,9 +59,9 @@ export class AddonQtypeDdImageOrTextHandler implements CoreQuestionHandler { /** * Check if a response is complete. * - * @param {any} question The question. - * @param {any} answers Object with the question answers (without prefix). - * @return {number} 1 if complete, 0 if not complete, -1 if cannot determine. + * @param question The question. + * @param answers Object with the question answers (without prefix). + * @return 1 if complete, 0 if not complete, -1 if cannot determine. */ isCompleteResponse(question: any, answers: any): number { // An answer is complete if all drop zones have an answer. @@ -79,7 +79,7 @@ export class AddonQtypeDdImageOrTextHandler implements CoreQuestionHandler { /** * Whether or not the handler is enabled on a site level. * - * @return {boolean|Promise} True or promise resolved with true if enabled. + * @return True or promise resolved with true if enabled. */ isEnabled(): boolean | Promise { return true; @@ -89,9 +89,9 @@ export class AddonQtypeDdImageOrTextHandler implements CoreQuestionHandler { * Check if a student has provided enough of an answer for the question to be graded automatically, * or whether it must be considered aborted. * - * @param {any} question The question. - * @param {any} answers Object with the question answers (without prefix). - * @return {number} 1 if gradable, 0 if not gradable, -1 if cannot determine. + * @param question The question. + * @param answers Object with the question answers (without prefix). + * @return 1 if gradable, 0 if not gradable, -1 if cannot determine. */ isGradableResponse(question: any, answers: any): number { for (const name in answers) { @@ -107,10 +107,10 @@ export class AddonQtypeDdImageOrTextHandler implements CoreQuestionHandler { /** * Check if two responses are the same. * - * @param {any} question Question. - * @param {any} prevAnswers Object with the previous question answers. - * @param {any} newAnswers Object with the new question answers. - * @return {boolean} Whether they're the same. + * @param question Question. + * @param prevAnswers Object with the previous question answers. + * @param newAnswers Object with the new question answers. + * @return Whether they're the same. */ isSameResponse(question: any, prevAnswers: any, newAnswers: any): boolean { return this.questionProvider.compareAllAnswers(prevAnswers, newAnswers); diff --git a/src/addon/qtype/ddmarker/classes/ddmarker.ts b/src/addon/qtype/ddmarker/classes/ddmarker.ts index f303d5c1b..d5199c656 100644 --- a/src/addon/qtype/ddmarker/classes/ddmarker.ts +++ b/src/addon/qtype/ddmarker/classes/ddmarker.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -58,14 +58,14 @@ export class AddonQtypeDdMarkerQuestion { /** * Create the instance. * - * @param {CoreLoggerProvider} logger Logger provider. - * @param {CoreDomUtilsProvider} domUtils Dom Utils provider. - * @param {CoreTextUtilsProvider} textUtils Text Utils provider. - * @param {HTMLElement} container The container HTMLElement of the question. - * @param {any} question The question instance. - * @param {boolean} readOnly Whether it's read only. - * @param {any[]} dropZones The drop zones received in the init object of the question. - * @param {string} [imgSrc] Background image source (3.6+ sites). + * @param logger Logger provider. + * @param domUtils Dom Utils provider. + * @param textUtils Text Utils provider. + * @param container The container HTMLElement of the question. + * @param question The question instance. + * @param readOnly Whether it's read only. + * @param dropZones The drop zones received in the init object of the question. + * @param imgSrc Background image source (3.6+ sites). */ constructor(logger: CoreLoggerProvider, protected domUtils: CoreDomUtilsProvider, protected textUtils: CoreTextUtilsProvider, protected container: HTMLElement, protected question: any, protected readOnly: boolean, protected dropZones: any[], @@ -93,9 +93,9 @@ export class AddonQtypeDdMarkerQuestion { /** * Create a new draggable element cloning a certain element. * - * @param {HTMLElement} dragHome The element to clone. - * @param {number} itemNo The number of the new item. - * @return {HTMLElement} The new element. + * @param dragHome The element to clone. + * @param itemNo The number of the new item. + * @return The new element. */ cloneNewDragItem(dragHome: HTMLElement, itemNo: number): HTMLElement { const marker = dragHome.querySelector('span.markertext'); @@ -119,8 +119,8 @@ export class AddonQtypeDdMarkerQuestion { /** * Convert the X and Y position of the BG IMG to a position relative to the window. * - * @param {number[]} bgImgXY X and Y of the BG IMG relative position. - * @return {number[]} Position relative to the window. + * @param bgImgXY X and Y of the BG IMG relative position. + * @return Position relative to the window. */ convertToWindowXY(bgImgXY: number[]): number[] { const bgImg = this.doc.bgImg(), @@ -136,8 +136,8 @@ export class AddonQtypeDdMarkerQuestion { /** * Check if some coordinates (X, Y) are inside the background image. * - * @param {number[]} coords Coordinates to check. - * @return {boolean} Whether they're inside the background image. + * @param coords Coordinates to check. + * @return Whether they're inside the background image. */ coordsInImg(coords: number[]): boolean { const bgImg = this.doc.bgImg(); @@ -168,8 +168,8 @@ export class AddonQtypeDdMarkerQuestion { /** * Returns an object to encapsulate operations on dd area. * - * @param {number} slot The question slot. - * @return {AddonQtypeDdMarkerQuestionDocStructure} The object. + * @param slot The question slot. + * @return The object. */ docStructure(slot: number): AddonQtypeDdMarkerQuestionDocStructure { const topNode = this.container.querySelector('.addon-qtype-ddmarker-container'), @@ -235,7 +235,7 @@ export class AddonQtypeDdMarkerQuestion { /** * Make an element "draggable". In the mobile app, items are "dragged" using tap and drop. * - * @param {HTMLElement} drag Element. + * @param drag Element. */ draggable(drag: HTMLElement): void { drag.addEventListener('click', (e) => { @@ -272,8 +272,8 @@ export class AddonQtypeDdMarkerQuestion { /** * Get the coordinates of the drag home of a certain choice. * - * @param {number} choiceNo Choice number. - * @return {number[]} Coordinates. + * @param choiceNo Choice number. + * @return Coordinates. */ dragHomeXY(choiceNo: number): number[] { const dragItemHome = this.doc.dragItemHome(choiceNo), @@ -285,11 +285,11 @@ export class AddonQtypeDdMarkerQuestion { /** * Draw a drop zone. * - * @param {number} dropZoneNo Number of the drop zone. - * @param {string} markerText The marker text to set. - * @param {string} shape Name of the shape of the drop zone (circle, rectangle, polygon). - * @param {string} coords Coordinates of the shape. - * @param {string} colour Colour of the shape. + * @param dropZoneNo Number of the drop zone. + * @param markerText The marker text to set. + * @param shape Name of the shape of the drop zone (circle, rectangle, polygon). + * @param coords Coordinates of the shape. + * @param colour Colour of the shape. */ drawDropZone(dropZoneNo: number, markerText: string, shape: string, coords: string, colour: string): void { let existingMarkerText: HTMLElement; @@ -363,10 +363,10 @@ export class AddonQtypeDdMarkerQuestion { /** * Draw a circle in a drop zone. * - * @param {number} dropZoneNo Number of the drop zone. - * @param {string} coords Coordinates of the circle. - * @param {string} colour Colour of the circle. - * @return {number[]} X and Y position of the center of the circle. + * @param dropZoneNo Number of the drop zone. + * @param coords Coordinates of the circle. + * @param colour Colour of the circle. + * @return X and Y position of the center of the circle. */ drawShapeCircle(dropZoneNo: number, coords: string, colour: string): number[] { // Extract the numbers in the coordinates. @@ -404,10 +404,10 @@ export class AddonQtypeDdMarkerQuestion { /** * Draw a rectangle in a drop zone. * - * @param {number} dropZoneNo Number of the drop zone. - * @param {string} coords Coordinates of the rectangle. - * @param {string} colour Colour of the rectangle. - * @return {number[]} X and Y position of the center of the rectangle. + * @param dropZoneNo Number of the drop zone. + * @param coords Coordinates of the rectangle. + * @param colour Colour of the rectangle. + * @return X and Y position of the center of the rectangle. */ drawShapeRectangle(dropZoneNo: number, coords: string, colour: string): number[] { // Extract the numbers in the coordinates. @@ -446,10 +446,10 @@ export class AddonQtypeDdMarkerQuestion { /** * Draw a polygon in a drop zone. * - * @param {number} dropZoneNo Number of the drop zone. - * @param {string} coords Coordinates of the polygon. - * @param {string} colour Colour of the polygon. - * @return {number[]} X and Y position of the center of the polygon. + * @param dropZoneNo Number of the drop zone. + * @param coords Coordinates of the polygon. + * @param colour Colour of the polygon. + * @return X and Y position of the center of the polygon. */ drawShapePolygon(dropZoneNo: number, coords: string, colour: string): number[] { const coordsParts = coords.split(';'), @@ -497,8 +497,8 @@ export class AddonQtypeDdMarkerQuestion { /** * Drop a drag element into a certain position. * - * @param {HTMLElement} drag The element to drop. - * @param {number[]} position Position to drop to (X, Y). + * @param drag The element to drop. + * @param position Position to drop to (X, Y). */ dropDrag(drag: HTMLElement, position: number[]): void { const choiceNo = this.getChoiceNoForNode(drag); @@ -522,8 +522,8 @@ export class AddonQtypeDdMarkerQuestion { * Determine which drag items need to be shown and return coords of all drag items except any that are currently being * dragged based on contents of hidden inputs and whether drags are 'infinite' or how many drags should be shown. * - * @param {HTMLElement} input The input element. - * @return {number[][]} List of coordinates. + * @param input The input element. + * @return List of coordinates. */ getCoords(input: HTMLElement): number[][] { const choiceNo = this.getChoiceNoForNode(input), @@ -557,8 +557,8 @@ export class AddonQtypeDdMarkerQuestion { /** * Get the choice number from an HTML element. * - * @param {HTMLElement} node Element to check. - * @return {number} Choice number. + * @param node Element to check. + * @return Choice number. */ getChoiceNoForNode(node: HTMLElement): number { return Number(this.doc.getClassnameNumericSuffix(node, 'choice')); @@ -567,8 +567,8 @@ export class AddonQtypeDdMarkerQuestion { /** * Get the coordinates (X, Y) of a draggable element. * - * @param {HTMLElement} dragItem The draggable item. - * @return {number[]} Coordinates. + * @param dragItem The draggable item. + * @return Coordinates. */ getDragXY(dragItem: HTMLElement): number[] { const position = this.domUtils.getElementXY(dragItem, null, 'ddarea'), @@ -593,8 +593,8 @@ export class AddonQtypeDdMarkerQuestion { /** * Get the item number from an HTML element. * - * @param {HTMLElement} node Element to check. - * @return {number} Choice number. + * @param node Element to check. + * @return Choice number. */ getItemNoForNode(node: HTMLElement): number { return Number(this.doc.getClassnameNumericSuffix(node, 'item')); @@ -603,7 +603,7 @@ export class AddonQtypeDdMarkerQuestion { /** * Get the next colour. * - * @return {string} Colour. + * @return Colour. */ getNextColour(): string { const colour = this.COLOURS[this.nextColourIndex]; @@ -620,8 +620,8 @@ export class AddonQtypeDdMarkerQuestion { /** * Get the number of drags from an HTML element. * - * @param {HTMLElement} node Element to check. - * @return {number} Choice number. + * @param node Element to check. + * @return Choice number. */ getNoOfDragsForNode(node: HTMLElement): number { return Number(this.doc.getClassnameNumericSuffix(node, 'noofdrags')); @@ -630,7 +630,7 @@ export class AddonQtypeDdMarkerQuestion { /** * Initialize the question. * - * @param {any} question Question. + * @param question Question. */ initializer(question: any): void { this.doc = this.docStructure(question.slot); @@ -800,7 +800,7 @@ export class AddonQtypeDdMarkerQuestion { /** * Reset the coordinates stored for a choice. * - * @param {number} choiceNo Choice number. + * @param choiceNo Choice number. */ resetDragXY(choiceNo: number): void { this.setFormValue(choiceNo, ''); @@ -816,9 +816,9 @@ export class AddonQtypeDdMarkerQuestion { /** * Save all the coordinates of a choice into the right input. * - * @param {number} choiceNo Number of the choice. - * @param {HTMLElement} dropped Element being dropped. - * @param {number[]} position Position where the element is dropped. + * @param choiceNo Number of the choice. + * @param dropped Element being dropped. + * @param position Position where the element is dropped. */ saveAllXYForChoice(choiceNo: number, dropped: HTMLElement, position: number[]): void { const coords = []; @@ -860,8 +860,8 @@ export class AddonQtypeDdMarkerQuestion { /** * Save a certain value in the input of a choice. * - * @param {number} choiceNo Choice number. - * @param {string} value The value to set. + * @param choiceNo Choice number. + * @param value The value to set. */ setFormValue(choiceNo: number, value: string): void { this.doc.inputForChoice(choiceNo).setAttribute('value', value); @@ -870,7 +870,7 @@ export class AddonQtypeDdMarkerQuestion { /** * Select a draggable element. * - * @param {HTMLElement} drag Element. + * @param drag Element. */ selectDrag(drag: HTMLElement): void { // Deselect previous drags. diff --git a/src/addon/qtype/ddmarker/classes/graphics_api.ts b/src/addon/qtype/ddmarker/classes/graphics_api.ts index a03df9a76..770031f3b 100644 --- a/src/addon/qtype/ddmarker/classes/graphics_api.ts +++ b/src/addon/qtype/ddmarker/classes/graphics_api.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -26,17 +26,17 @@ export class AddonQtypeDdMarkerGraphicsApi { /** * Create the instance. * - * @param {AddonQtypeDdMarkerQuestion} instance Question instance. - * @param {CoreDomUtilsProvider} domUtils Dom Utils provider. + * @param instance Question instance. + * @param domUtils Dom Utils provider. */ constructor(protected instance: AddonQtypeDdMarkerQuestion, protected domUtils: CoreDomUtilsProvider) { } /** * Add a shape. * - * @param {{type: string, color: string}} shapeAttribs Attributes for the shape: type and color. - * @param {{[name: string]: number|string} styles Object with the styles for the shape (name -> value). - * @return {Element} The new shape. + * @param shapeAttribs Attributes for the shape: type and color. + * @param styles Object with the styles for the shape (name -> value). + * @return The new shape. */ addShape(shapeAttribs: {type: string, color: string}, styles: {[name: string]: number | string}): Element { const shape = document.createElementNS(this.NS, shapeAttribs.type); diff --git a/src/addon/qtype/ddmarker/component/addon-qtype-ddmarker.html b/src/addon/qtype/ddmarker/component/addon-qtype-ddmarker.html index 199abc85d..60ec70218 100644 --- a/src/addon/qtype/ddmarker/component/addon-qtype-ddmarker.html +++ b/src/addon/qtype/ddmarker/component/addon-qtype-ddmarker.html @@ -7,7 +7,7 @@ {{ 'core.question.howtodraganddrop' | translate }}

-

- +

+ diff --git a/src/addon/qtype/ddmarker/component/ddmarker.scss b/src/addon/qtype/ddmarker/component/ddmarker.scss index d9197d122..cba59c4e7 100644 --- a/src/addon/qtype/ddmarker/component/ddmarker.scss +++ b/src/addon/qtype/ddmarker/component/ddmarker.scss @@ -69,6 +69,7 @@ addon-qtype-ddmarker { display: inline-block; zoom: 1; border-radius: 10px; + color: $text-color; } div.markertexts span.markertext { z-index: 3; diff --git a/src/addon/qtype/ddmarker/component/ddmarker.ts b/src/addon/qtype/ddmarker/component/ddmarker.ts index d21555a03..a7d30837c 100644 --- a/src/addon/qtype/ddmarker/component/ddmarker.ts +++ b/src/addon/qtype/ddmarker/component/ddmarker.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -35,6 +35,8 @@ export class AddonQtypeDdMarkerComponent extends CoreQuestionBaseComponent imple protected dropZones: any[]; // The drop zones received in the init object of the question. protected imgSrc: string; // Background image URL. protected destroyed = false; + protected textIsRendered = false; + protected ddAreaisRendered = false; constructor(protected loggerProvider: CoreLoggerProvider, injector: Injector, element: ElementRef, protected sitesProvider: CoreSitesProvider, protected urlUtils: CoreUrlUtilsProvider, @@ -101,10 +103,30 @@ export class AddonQtypeDdMarkerComponent extends CoreQuestionBaseComponent imple this.question.loaded = false; } + /** + * The question ddArea has been rendered. + */ + ddAreaRendered(): void { + this.ddAreaisRendered = true; + if (this.textIsRendered) { + this.questionRendered(); + } + } + + /** + * The question text has been rendered. + */ + textRendered(): void { + this.textIsRendered = true; + if (this.ddAreaisRendered) { + this.questionRendered(); + } + } + /** * The question has been rendered. */ - questionRendered(): void { + protected questionRendered(): void { if (!this.destroyed) { // Download background image (3.6+ sites). let promise = null; diff --git a/src/addon/qtype/ddmarker/ddmarker.module.ts b/src/addon/qtype/ddmarker/ddmarker.module.ts index 839fe9350..16ffc5612 100644 --- a/src/addon/qtype/ddmarker/ddmarker.module.ts +++ b/src/addon/qtype/ddmarker/ddmarker.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/addon/qtype/ddmarker/providers/handler.ts b/src/addon/qtype/ddmarker/providers/handler.ts index 38ecfd1ba..2e8195ff5 100644 --- a/src/addon/qtype/ddmarker/providers/handler.ts +++ b/src/addon/qtype/ddmarker/providers/handler.ts @@ -1,5 +1,5 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -33,9 +33,9 @@ export class AddonQtypeDdMarkerHandler implements CoreQuestionHandler { * Return the name of the behaviour to use for the question. * If the question should use the default behaviour you shouldn't implement this function. * - * @param {any} question The question. - * @param {string} behaviour The default behaviour. - * @return {string} The behaviour to use. + * @param question The question. + * @param behaviour The default behaviour. + * @return The behaviour to use. */ getBehaviour(question: any, behaviour: string): string { if (behaviour === 'interactive') { @@ -49,9 +49,9 @@ export class AddonQtypeDdMarkerHandler implements CoreQuestionHandler { * Return the Component to use to display the question. * It's recommended to return the class of the component, but you can also return an instance of the component. * - * @param {Injector} injector Injector. - * @param {any} question The question to render. - * @return {any|Promise} The component (or promise resolved with component) to use, undefined if not found. + * @param injector Injector. + * @param question The question to render. + * @return The component (or promise resolved with component) to use, undefined if not found. */ getComponent(injector: Injector, question: any): any | Promise { return AddonQtypeDdMarkerComponent; @@ -60,9 +60,9 @@ export class AddonQtypeDdMarkerHandler implements CoreQuestionHandler { /** * Check if a response is complete. * - * @param {any} question The question. - * @param {any} answers Object with the question answers (without prefix). - * @return {number} 1 if complete, 0 if not complete, -1 if cannot determine. + * @param question The question. + * @param answers Object with the question answers (without prefix). + * @return 1 if complete, 0 if not complete, -1 if cannot determine. */ isCompleteResponse(question: any, answers: any): number { // If 1 dragitem is set we assume the answer is complete (like Moodle does). @@ -78,7 +78,7 @@ export class AddonQtypeDdMarkerHandler implements CoreQuestionHandler { /** * Whether or not the handler is enabled on a site level. * - * @return {boolean|Promise} True or promise resolved with true if enabled. + * @return True or promise resolved with true if enabled. */ isEnabled(): boolean | Promise { return true; @@ -88,9 +88,9 @@ export class AddonQtypeDdMarkerHandler implements CoreQuestionHandler { * Check if a student has provided enough of an answer for the question to be graded automatically, * or whether it must be considered aborted. * - * @param {any} question The question. - * @param {any} answers Object with the question answers (without prefix). - * @return {number} 1 if gradable, 0 if not gradable, -1 if cannot determine. + * @param question The question. + * @param answers Object with the question answers (without prefix). + * @return 1 if gradable, 0 if not gradable, -1 if cannot determine. */ isGradableResponse(question: any, answers: any): number { return this.isCompleteResponse(question, answers); @@ -99,10 +99,10 @@ export class AddonQtypeDdMarkerHandler implements CoreQuestionHandler { /** * Check if two responses are the same. * - * @param {any} question Question. - * @param {any} prevAnswers Object with the previous question answers. - * @param {any} newAnswers Object with the new question answers. - * @return {boolean} Whether they're the same. + * @param question Question. + * @param prevAnswers Object with the previous question answers. + * @param newAnswers Object with the new question answers. + * @return Whether they're the same. */ isSameResponse(question: any, prevAnswers: any, newAnswers: any): boolean { return this.questionProvider.compareAllAnswers(prevAnswers, newAnswers); @@ -111,9 +111,9 @@ export class AddonQtypeDdMarkerHandler implements CoreQuestionHandler { /** * Get the list of files that needs to be downloaded in addition to the files embedded in the HTML. * - * @param {any} question Question. - * @param {number} usageId Usage ID. - * @return {string[]} List of URLs. + * @param question Question. + * @param usageId Usage ID. + * @return List of URLs. */ getAdditionalDownloadableFiles(question: any, usageId: number): string[] { this.questionHelper.extractQuestionScripts(question, usageId); diff --git a/src/addon/qtype/ddwtos/classes/ddwtos.ts b/src/addon/qtype/ddwtos/classes/ddwtos.ts index 14928b681..0e33cac95 100644 --- a/src/addon/qtype/ddwtos/classes/ddwtos.ts +++ b/src/addon/qtype/ddwtos/classes/ddwtos.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -52,12 +52,12 @@ export class AddonQtypeDdwtosQuestion { /** * Create the instance. * - * @param {CoreLoggerProvider} logger Logger provider. - * @param {CoreDomUtilsProvider} domUtils Dom Utils provider. - * @param {HTMLElement} container The container HTMLElement of the question. - * @param {any} question The question instance. - * @param {boolean} readOnly Whether it's read only. - * @param {string[]} inputIds Ids of the inputs of the question (where the answers will be stored). + * @param logger Logger provider. + * @param domUtils Dom Utils provider. + * @param container The container HTMLElement of the question. + * @param question The question instance. + * @param readOnly Whether it's read only. + * @param inputIds Ids of the inputs of the question (where the answers will be stored). */ constructor(logger: CoreLoggerProvider, protected domUtils: CoreDomUtilsProvider, protected container: HTMLElement, protected question: any, protected readOnly: boolean, protected inputIds: string[], @@ -70,7 +70,7 @@ export class AddonQtypeDdwtosQuestion { /** * Clone a drag item and add it to the drag container. * - * @param {HTMLElement} dragHome Item to clone + * @param dragHome Item to clone */ cloneDragItem(dragHome: HTMLElement): void { const drag = dragHome.cloneNode(true); @@ -106,7 +106,7 @@ export class AddonQtypeDdwtosQuestion { /** * Clone a certain 'drag home'. If it's an "infinite" drag, clone it several times. * - * @param {HTMLElement} dragHome Element to clone. + * @param dragHome Element to clone. */ cloneDragItemsForOneChoice(dragHome: HTMLElement): void { if (dragHome.classList.contains('infinite')) { @@ -124,8 +124,8 @@ export class AddonQtypeDdwtosQuestion { /** * Get an object with a set of functions to get the CSS selectors. * - * @param {number} slot Question slot. - * @return {AddonQtypeDdwtosQuestionCSSSelectors} Object with the functions to get the selectors. + * @param slot Question slot. + * @return Object with the functions to get the selectors. */ cssSelectors(slot: number): AddonQtypeDdwtosQuestionCSSSelectors { const topNode = '.addon-qtype-ddwtos-container', @@ -204,8 +204,8 @@ export class AddonQtypeDdwtosQuestion { /** * Get the choice number of an element. It is extracted from the classes. * - * @param {HTMLElement} node Element to check. - * @return {number} Choice number. + * @param node Element to check. + * @return Choice number. */ getChoice(node: HTMLElement): number { return this.getClassnameNumericSuffix(node, 'choice'); @@ -214,9 +214,9 @@ export class AddonQtypeDdwtosQuestion { /** * Get the number in a certain class name of an element. * - * @param {HTMLElement} node The element to check. - * @param {string} prefix Prefix of the class to check. - * @return {number} The number in the class. + * @param node The element to check. + * @param prefix Prefix of the class to check. + * @return The number in the class. */ getClassnameNumericSuffix(node: HTMLElement, prefix: string): number { if (node.classList && node.classList.length) { @@ -238,8 +238,8 @@ export class AddonQtypeDdwtosQuestion { /** * Get the group number of an element. It is extracted from the classes. * - * @param {HTMLElement} node Element to check. - * @return {number} Group number. + * @param node Element to check. + * @return Group number. */ getGroup(node: HTMLElement): number { return this.getClassnameNumericSuffix(node, 'group'); @@ -248,8 +248,8 @@ export class AddonQtypeDdwtosQuestion { /** * Get the number of an element ('no'). It is extracted from the classes. * - * @param {HTMLElement} node Element to check. - * @return {number} Number. + * @param node Element to check. + * @return Number. */ getNo(node: HTMLElement): number { return this.getClassnameNumericSuffix(node, 'no'); @@ -258,8 +258,8 @@ export class AddonQtypeDdwtosQuestion { /** * Get the place number of an element. It is extracted from the classes. * - * @param {HTMLElement} node Element to check. - * @return {number} Place number. + * @param node Element to check. + * @return Place number. */ getPlace(node: HTMLElement): number { return this.getClassnameNumericSuffix(node, 'place'); @@ -268,7 +268,7 @@ export class AddonQtypeDdwtosQuestion { /** * Initialize the question. * - * @param {any} question Question. + * @param question Question. */ initializer(question: any): void { this.selectors = this.cssSelectors(question.slot); @@ -326,7 +326,7 @@ export class AddonQtypeDdwtosQuestion { /** * Make an element "draggable". In the mobile app, items are "dragged" using tap and drop. * - * @param {HTMLElement} drag Element. + * @param drag Element. */ makeDraggable(drag: HTMLElement): void { drag.addEventListener('click', () => { @@ -341,7 +341,7 @@ export class AddonQtypeDdwtosQuestion { /** * Convert an element into a drop zone. * - * @param {HTMLElement} drop Element. + * @param drop Element. */ makeDropZone(drop: HTMLElement): void { drop.addEventListener('click', () => { @@ -401,9 +401,9 @@ export class AddonQtypeDdwtosQuestion { /** * Set the width and height of an element. * - * @param {HTMLElement} node Element. - * @param {number} width Width to set. - * @param {number} height Height to set. + * @param node Element. + * @param width Width to set. + * @param height Height to set. */ protected padToWidthHeight(node: HTMLElement, width: number, height: number): void { node.style.width = width + 'px'; @@ -414,8 +414,8 @@ export class AddonQtypeDdwtosQuestion { /** * Place a draggable element inside a drop zone. * - * @param {HTMLElement} drag Draggable element. - * @param {HTMLElement} drop Drop zone. + * @param drag Draggable element. + * @param drop Drop zone. */ placeDragInDrop(drag: HTMLElement, drop: HTMLElement): void { @@ -446,7 +446,7 @@ export class AddonQtypeDdwtosQuestion { /** * Position a drag element in the right drop zone or in the home zone. * - * @param {HTMLElement} drag Drag element. + * @param drag Drag element. */ positionDragItem(drag: HTMLElement): void { let position; @@ -485,7 +485,7 @@ export class AddonQtypeDdwtosQuestion { /** * Remove a draggable element from a drop zone. * - * @param {HTMLElement} drag The draggable element. + * @param drag The draggable element. */ removeDragFromDrop(drag: HTMLElement): void { const placeNo = this.placed[this.getNo(drag)], @@ -497,7 +497,7 @@ export class AddonQtypeDdwtosQuestion { /** * Select a certain element as being "dragged". * - * @param {HTMLElement} drag Element. + * @param drag Element. */ selectDrag(drag: HTMLElement): void { // Deselect previous drags, only 1 can be selected. @@ -519,7 +519,7 @@ export class AddonQtypeDdwtosQuestion { /** * Set the padding size for a certain group. * - * @param {number} groupNo Group number. + * @param groupNo Group number. */ setPaddingSizeForGroup(groupNo: number): void { const groupItems = Array.from(this.container.querySelectorAll(this.selectors.dragHomesGroup(groupNo))); diff --git a/src/addon/qtype/ddwtos/component/addon-qtype-ddwtos.html b/src/addon/qtype/ddwtos/component/addon-qtype-ddwtos.html index 67c15fdfd..b998f2fe0 100644 --- a/src/addon/qtype/ddwtos/component/addon-qtype-ddwtos.html +++ b/src/addon/qtype/ddwtos/component/addon-qtype-ddwtos.html @@ -7,8 +7,8 @@ {{ 'core.question.howtodraganddrop' | translate }}

-

- +

+
diff --git a/src/addon/qtype/ddwtos/component/ddwtos.scss b/src/addon/qtype/ddwtos/component/ddwtos.scss index a594f55fa..33666ece9 100644 --- a/src/addon/qtype/ddwtos/component/ddwtos.scss +++ b/src/addon/qtype/ddwtos/component/ddwtos.scss @@ -69,14 +69,21 @@ addon-qtype-ddwtos { span.incorrect { background-color: $red-light; + @include darkmode() { + background-color: $red-dark; + } } span.correct { background-color: $green-light; + @include darkmode() { + background-color: $green-dark; + } } @for $i from 0 to length($core-dd-question-colors) { .group#{$i + 1} { background: nth($core-dd-question-colors, $i + 1); + color: $text-color; } } diff --git a/src/addon/qtype/ddwtos/component/ddwtos.ts b/src/addon/qtype/ddwtos/component/ddwtos.ts index 710fbef3c..077677e27 100644 --- a/src/addon/qtype/ddwtos/component/ddwtos.ts +++ b/src/addon/qtype/ddwtos/component/ddwtos.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -31,6 +31,8 @@ export class AddonQtypeDdwtosComponent extends CoreQuestionBaseComponent impleme protected questionInstance: AddonQtypeDdwtosQuestion; protected inputIds: string[] = []; // Ids of the inputs of the question (where the answers will be stored). protected destroyed = false; + protected textIsRendered = false; + protected answerAreRendered = false; constructor(protected loggerProvider: CoreLoggerProvider, injector: Injector, element: ElementRef) { super(loggerProvider, 'AddonQtypeDdwtosComponent', injector); @@ -85,17 +87,38 @@ export class AddonQtypeDdwtosComponent extends CoreQuestionBaseComponent impleme this.question.loaded = false; } + /** + * The question answers have been rendered. + */ + answersRendered(): void { + this.answerAreRendered = true; + if (this.textIsRendered) { + this.questionRendered(); + } + } + + /** + * The question text has been rendered. + */ + textRendered(): void { + this.textIsRendered = true; + if (this.answerAreRendered) { + this.questionRendered(); + } + } + /** * The question has been rendered. */ - questionRendered(): void { + protected questionRendered(): void { if (!this.destroyed) { this.domUtils.waitForImages(this.questionTextEl.nativeElement).then(() => { // Create the instance. this.questionInstance = new AddonQtypeDdwtosQuestion(this.loggerProvider, this.domUtils, this.element, this.question, this.question.readOnly, this.inputIds, this.textUtils); - this.questionHelper.treatCorrectnessIconsClicks(this.element, this.component, this.componentId); + this.questionHelper.treatCorrectnessIconsClicks(this.element, this.component, this.componentId, this.contextLevel, + this.contextInstanceId, this.courseId); this.question.loaded = true; }); diff --git a/src/addon/qtype/ddwtos/ddwtos.module.ts b/src/addon/qtype/ddwtos/ddwtos.module.ts index 0508ddb5a..7f0044937 100644 --- a/src/addon/qtype/ddwtos/ddwtos.module.ts +++ b/src/addon/qtype/ddwtos/ddwtos.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/addon/qtype/ddwtos/providers/handler.ts b/src/addon/qtype/ddwtos/providers/handler.ts index 6a8a6eae4..d310e27b2 100644 --- a/src/addon/qtype/ddwtos/providers/handler.ts +++ b/src/addon/qtype/ddwtos/providers/handler.ts @@ -1,5 +1,5 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -32,9 +32,9 @@ export class AddonQtypeDdwtosHandler implements CoreQuestionHandler { * Return the name of the behaviour to use for the question. * If the question should use the default behaviour you shouldn't implement this function. * - * @param {any} question The question. - * @param {string} behaviour The default behaviour. - * @return {string} The behaviour to use. + * @param question The question. + * @param behaviour The default behaviour. + * @return The behaviour to use. */ getBehaviour(question: any, behaviour: string): string { if (behaviour === 'interactive') { @@ -48,9 +48,9 @@ export class AddonQtypeDdwtosHandler implements CoreQuestionHandler { * Return the Component to use to display the question. * It's recommended to return the class of the component, but you can also return an instance of the component. * - * @param {Injector} injector Injector. - * @param {any} question The question to render. - * @return {any|Promise} The component (or promise resolved with component) to use, undefined if not found. + * @param injector Injector. + * @param question The question to render. + * @return The component (or promise resolved with component) to use, undefined if not found. */ getComponent(injector: Injector, question: any): any | Promise { return AddonQtypeDdwtosComponent; @@ -59,9 +59,9 @@ export class AddonQtypeDdwtosHandler implements CoreQuestionHandler { /** * Check if a response is complete. * - * @param {any} question The question. - * @param {any} answers Object with the question answers (without prefix). - * @return {number} 1 if complete, 0 if not complete, -1 if cannot determine. + * @param question The question. + * @param answers Object with the question answers (without prefix). + * @return 1 if complete, 0 if not complete, -1 if cannot determine. */ isCompleteResponse(question: any, answers: any): number { for (const name in answers) { @@ -77,7 +77,7 @@ export class AddonQtypeDdwtosHandler implements CoreQuestionHandler { /** * Whether or not the handler is enabled on a site level. * - * @return {boolean|Promise} True or promise resolved with true if enabled. + * @return True or promise resolved with true if enabled. */ isEnabled(): boolean | Promise { return true; @@ -87,9 +87,9 @@ export class AddonQtypeDdwtosHandler implements CoreQuestionHandler { * Check if a student has provided enough of an answer for the question to be graded automatically, * or whether it must be considered aborted. * - * @param {any} question The question. - * @param {any} answers Object with the question answers (without prefix). - * @return {number} 1 if gradable, 0 if not gradable, -1 if cannot determine. + * @param question The question. + * @param answers Object with the question answers (without prefix). + * @return 1 if gradable, 0 if not gradable, -1 if cannot determine. */ isGradableResponse(question: any, answers: any): number { for (const name in answers) { @@ -105,10 +105,10 @@ export class AddonQtypeDdwtosHandler implements CoreQuestionHandler { /** * Check if two responses are the same. * - * @param {any} question Question. - * @param {any} prevAnswers Object with the previous question answers. - * @param {any} newAnswers Object with the new question answers. - * @return {boolean} Whether they're the same. + * @param question Question. + * @param prevAnswers Object with the previous question answers. + * @param newAnswers Object with the new question answers. + * @return Whether they're the same. */ isSameResponse(question: any, prevAnswers: any, newAnswers: any): boolean { return this.questionProvider.compareAllAnswers(prevAnswers, newAnswers); diff --git a/src/addon/qtype/description/component/addon-qtype-description.html b/src/addon/qtype/description/component/addon-qtype-description.html index 74d4f190d..1264d7c70 100644 --- a/src/addon/qtype/description/component/addon-qtype-description.html +++ b/src/addon/qtype/description/component/addon-qtype-description.html @@ -2,6 +2,6 @@ -

+

diff --git a/src/addon/qtype/description/component/description.ts b/src/addon/qtype/description/component/description.ts index 55c323b0c..1c9870d0b 100644 --- a/src/addon/qtype/description/component/description.ts +++ b/src/addon/qtype/description/component/description.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/addon/qtype/description/description.module.ts b/src/addon/qtype/description/description.module.ts index fa64bb94d..c5fcfca79 100644 --- a/src/addon/qtype/description/description.module.ts +++ b/src/addon/qtype/description/description.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/addon/qtype/description/providers/handler.ts b/src/addon/qtype/description/providers/handler.ts index 49bba134c..9fcb36ebf 100644 --- a/src/addon/qtype/description/providers/handler.ts +++ b/src/addon/qtype/description/providers/handler.ts @@ -1,5 +1,5 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -33,9 +33,9 @@ export class AddonQtypeDescriptionHandler implements CoreQuestionHandler { * Return the name of the behaviour to use for the question. * If the question should use the default behaviour you shouldn't implement this function. * - * @param {any} question The question. - * @param {string} behaviour The default behaviour. - * @return {string} The behaviour to use. + * @param question The question. + * @param behaviour The default behaviour. + * @return The behaviour to use. */ getBehaviour(question: any, behaviour: string): string { return 'informationitem'; @@ -45,9 +45,9 @@ export class AddonQtypeDescriptionHandler implements CoreQuestionHandler { * Return the Component to use to display the question. * It's recommended to return the class of the component, but you can also return an instance of the component. * - * @param {Injector} injector Injector. - * @param {any} question The question to render. - * @return {any|Promise} The component (or promise resolved with component) to use, undefined if not found. + * @param injector Injector. + * @param question The question to render. + * @return The component (or promise resolved with component) to use, undefined if not found. */ getComponent(injector: Injector, question: any): any | Promise { return AddonQtypeDescriptionComponent; @@ -56,7 +56,7 @@ export class AddonQtypeDescriptionHandler implements CoreQuestionHandler { /** * Whether or not the handler is enabled on a site level. * - * @return {boolean|Promise} True or promise resolved with true if enabled. + * @return True or promise resolved with true if enabled. */ isEnabled(): boolean | Promise { return true; @@ -66,9 +66,9 @@ export class AddonQtypeDescriptionHandler implements CoreQuestionHandler { * Validate if an offline sequencecheck is valid compared with the online one. * This function only needs to be implemented if a specific compare is required. * - * @param {any} question The question. - * @param {string} offlineSequenceCheck Sequence check stored in offline. - * @return {boolean} Whether sequencecheck is valid. + * @param question The question. + * @param offlineSequenceCheck Sequence check stored in offline. + * @return Whether sequencecheck is valid. */ validateSequenceCheck(question: any, offlineSequenceCheck: string): boolean { // Descriptions don't have any answer so we'll always treat them as valid. diff --git a/src/addon/qtype/essay/component/addon-qtype-essay.html b/src/addon/qtype/essay/component/addon-qtype-essay.html index ec4293251..5c171fedc 100644 --- a/src/addon/qtype/essay/component/addon-qtype-essay.html +++ b/src/addon/qtype/essay/component/addon-qtype-essay.html @@ -1,7 +1,7 @@
-

+

@@ -20,7 +20,7 @@

{{ 'core.question.errorinlinefilesnotsupported' | translate }}

-

+

@@ -31,7 +31,7 @@ -

+

diff --git a/src/addon/qtype/essay/component/essay.ts b/src/addon/qtype/essay/component/essay.ts index 3d68a0915..8cd380933 100644 --- a/src/addon/qtype/essay/component/essay.ts +++ b/src/addon/qtype/essay/component/essay.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/addon/qtype/essay/essay.module.ts b/src/addon/qtype/essay/essay.module.ts index 9c3d58ba6..da95a1363 100644 --- a/src/addon/qtype/essay/essay.module.ts +++ b/src/addon/qtype/essay/essay.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/addon/qtype/essay/providers/handler.ts b/src/addon/qtype/essay/providers/handler.ts index 3babbd6e1..9206b4e4e 100644 --- a/src/addon/qtype/essay/providers/handler.ts +++ b/src/addon/qtype/essay/providers/handler.ts @@ -1,5 +1,5 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -36,9 +36,9 @@ export class AddonQtypeEssayHandler implements CoreQuestionHandler { * Return the name of the behaviour to use for the question. * If the question should use the default behaviour you shouldn't implement this function. * - * @param {any} question The question. - * @param {string} behaviour The default behaviour. - * @return {string} The behaviour to use. + * @param question The question. + * @param behaviour The default behaviour. + * @return The behaviour to use. */ getBehaviour(question: any, behaviour: string): string { return 'manualgraded'; @@ -48,9 +48,9 @@ export class AddonQtypeEssayHandler implements CoreQuestionHandler { * Return the Component to use to display the question. * It's recommended to return the class of the component, but you can also return an instance of the component. * - * @param {Injector} injector Injector. - * @param {any} question The question to render. - * @return {any|Promise} The component (or promise resolved with component) to use, undefined if not found. + * @param injector Injector. + * @param question The question to render. + * @return The component (or promise resolved with component) to use, undefined if not found. */ getComponent(injector: Injector, question: any): any | Promise { return AddonQtypeEssayComponent; @@ -60,8 +60,8 @@ export class AddonQtypeEssayHandler implements CoreQuestionHandler { * Check if a question can be submitted. * If a question cannot be submitted it should return a message explaining why (translated or not). * - * @param {any} question The question. - * @return {string} Prevent submit message. Undefined or empty if can be submitted. + * @param question The question. + * @return Prevent submit message. Undefined or empty if can be submitted. */ getPreventSubmitMessage(question: any): string { const element = this.domUtils.convertToElement(question.html); @@ -79,9 +79,9 @@ export class AddonQtypeEssayHandler implements CoreQuestionHandler { /** * Check if a response is complete. * - * @param {any} question The question. - * @param {any} answers Object with the question answers (without prefix). - * @return {number} 1 if complete, 0 if not complete, -1 if cannot determine. + * @param question The question. + * @param answers Object with the question answers (without prefix). + * @return 1 if complete, 0 if not complete, -1 if cannot determine. */ isCompleteResponse(question: any, answers: any): number { const element = this.domUtils.convertToElement(question.html); @@ -100,7 +100,7 @@ export class AddonQtypeEssayHandler implements CoreQuestionHandler { /** * Whether or not the handler is enabled on a site level. * - * @return {boolean|Promise} True or promise resolved with true if enabled. + * @return True or promise resolved with true if enabled. */ isEnabled(): boolean | Promise { return true; @@ -110,9 +110,9 @@ export class AddonQtypeEssayHandler implements CoreQuestionHandler { * Check if a student has provided enough of an answer for the question to be graded automatically, * or whether it must be considered aborted. * - * @param {any} question The question. - * @param {any} answers Object with the question answers (without prefix). - * @return {number} 1 if gradable, 0 if not gradable, -1 if cannot determine. + * @param question The question. + * @param answers Object with the question answers (without prefix). + * @return 1 if gradable, 0 if not gradable, -1 if cannot determine. */ isGradableResponse(question: any, answers: any): number { return 0; @@ -121,10 +121,10 @@ export class AddonQtypeEssayHandler implements CoreQuestionHandler { /** * Check if two responses are the same. * - * @param {any} question Question. - * @param {any} prevAnswers Object with the previous question answers. - * @param {any} newAnswers Object with the new question answers. - * @return {boolean} Whether they're the same. + * @param question Question. + * @param prevAnswers Object with the previous question answers. + * @param newAnswers Object with the new question answers. + * @return Whether they're the same. */ isSameResponse(question: any, prevAnswers: any, newAnswers: any): boolean { return this.utils.sameAtKeyMissingIsBlank(prevAnswers, newAnswers, 'answer'); @@ -133,11 +133,11 @@ export class AddonQtypeEssayHandler implements CoreQuestionHandler { /** * Prepare and add to answers the data to send to server based in the input. Return promise if async. * - * @param {any} question Question. - * @param {any} answers The answers retrieved from the form. Prepared answers must be stored in this object. - * @param {boolean} [offline] Whether the data should be saved in offline. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {void|Promise} Return a promise resolved when done if async, void if sync. + * @param question Question. + * @param answers The answers retrieved from the form. Prepared answers must be stored in this object. + * @param offline Whether the data should be saved in offline. + * @param siteId Site ID. If not defined, current site. + * @return Return a promise resolved when done if async, void if sync. */ prepareAnswers(question: any, answers: any, offline: boolean, siteId?: string): void | Promise { const element = this.domUtils.convertToElement(question.html); diff --git a/src/addon/qtype/gapselect/component/addon-qtype-gapselect.html b/src/addon/qtype/gapselect/component/addon-qtype-gapselect.html index 70efb95b7..88b29f7fc 100644 --- a/src/addon/qtype/gapselect/component/addon-qtype-gapselect.html +++ b/src/addon/qtype/gapselect/component/addon-qtype-gapselect.html @@ -1,5 +1,5 @@
-

+

diff --git a/src/addon/qtype/gapselect/component/gapselect.scss b/src/addon/qtype/gapselect/component/gapselect.scss index 53ceeba30..4698805aa 100644 --- a/src/addon/qtype/gapselect/component/gapselect.scss +++ b/src/addon/qtype/gapselect/component/gapselect.scss @@ -15,5 +15,9 @@ ion-app.app-root addon-qtype-gapselect { border-radius: 4px; margin-bottom: 10px; background: $gray-lighter; + + @include darkmode() { + background: $gray-dark; + } } } diff --git a/src/addon/qtype/gapselect/component/gapselect.ts b/src/addon/qtype/gapselect/component/gapselect.ts index 0c8287511..7dd56017f 100644 --- a/src/addon/qtype/gapselect/component/gapselect.ts +++ b/src/addon/qtype/gapselect/component/gapselect.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -44,6 +44,7 @@ export class AddonQtypeGapSelectComponent extends CoreQuestionBaseComponent impl * The question has been rendered. */ questionRendered(): void { - this.questionHelper.treatCorrectnessIconsClicks(this.element, this.component, this.componentId); + this.questionHelper.treatCorrectnessIconsClicks(this.element, this.component, this.componentId, this.contextLevel, + this.contextInstanceId, this.courseId); } } diff --git a/src/addon/qtype/gapselect/gapselect.module.ts b/src/addon/qtype/gapselect/gapselect.module.ts index f95b7b790..25e2a3293 100644 --- a/src/addon/qtype/gapselect/gapselect.module.ts +++ b/src/addon/qtype/gapselect/gapselect.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/addon/qtype/gapselect/providers/handler.ts b/src/addon/qtype/gapselect/providers/handler.ts index e18b357cd..50b83068e 100644 --- a/src/addon/qtype/gapselect/providers/handler.ts +++ b/src/addon/qtype/gapselect/providers/handler.ts @@ -1,5 +1,5 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -32,9 +32,9 @@ export class AddonQtypeGapSelectHandler implements CoreQuestionHandler { * Return the name of the behaviour to use for the question. * If the question should use the default behaviour you shouldn't implement this function. * - * @param {any} question The question. - * @param {string} behaviour The default behaviour. - * @return {string} The behaviour to use. + * @param question The question. + * @param behaviour The default behaviour. + * @return The behaviour to use. */ getBehaviour(question: any, behaviour: string): string { if (behaviour === 'interactive') { @@ -48,9 +48,9 @@ export class AddonQtypeGapSelectHandler implements CoreQuestionHandler { * Return the Component to use to display the question. * It's recommended to return the class of the component, but you can also return an instance of the component. * - * @param {Injector} injector Injector. - * @param {any} question The question to render. - * @return {any|Promise} The component (or promise resolved with component) to use, undefined if not found. + * @param injector Injector. + * @param question The question to render. + * @return The component (or promise resolved with component) to use, undefined if not found. */ getComponent(injector: Injector, question: any): any | Promise { return AddonQtypeGapSelectComponent; @@ -59,9 +59,9 @@ export class AddonQtypeGapSelectHandler implements CoreQuestionHandler { /** * Check if a response is complete. * - * @param {any} question The question. - * @param {any} answers Object with the question answers (without prefix). - * @return {number} 1 if complete, 0 if not complete, -1 if cannot determine. + * @param question The question. + * @param answers Object with the question answers (without prefix). + * @return 1 if complete, 0 if not complete, -1 if cannot determine. */ isCompleteResponse(question: any, answers: any): number { // We should always get a value for each select so we can assume we receive all the possible answers. @@ -78,7 +78,7 @@ export class AddonQtypeGapSelectHandler implements CoreQuestionHandler { /** * Whether or not the handler is enabled on a site level. * - * @return {boolean|Promise} True or promise resolved with true if enabled. + * @return True or promise resolved with true if enabled. */ isEnabled(): boolean | Promise { return true; @@ -88,9 +88,9 @@ export class AddonQtypeGapSelectHandler implements CoreQuestionHandler { * Check if a student has provided enough of an answer for the question to be graded automatically, * or whether it must be considered aborted. * - * @param {any} question The question. - * @param {any} answers Object with the question answers (without prefix). - * @return {number} 1 if gradable, 0 if not gradable, -1 if cannot determine. + * @param question The question. + * @param answers Object with the question answers (without prefix). + * @return 1 if gradable, 0 if not gradable, -1 if cannot determine. */ isGradableResponse(question: any, answers: any): number { // We should always get a value for each select so we can assume we receive all the possible answers. @@ -107,10 +107,10 @@ export class AddonQtypeGapSelectHandler implements CoreQuestionHandler { /** * Check if two responses are the same. * - * @param {any} question Question. - * @param {any} prevAnswers Object with the previous question answers. - * @param {any} newAnswers Object with the new question answers. - * @return {boolean} Whether they're the same. + * @param question Question. + * @param prevAnswers Object with the previous question answers. + * @param newAnswers Object with the new question answers. + * @return Whether they're the same. */ isSameResponse(question: any, prevAnswers: any, newAnswers: any): boolean { return this.questionProvider.compareAllAnswers(prevAnswers, newAnswers); diff --git a/src/addon/qtype/match/component/addon-qtype-match.html b/src/addon/qtype/match/component/addon-qtype-match.html index 95bed5ddc..08bbe1274 100644 --- a/src/addon/qtype/match/component/addon-qtype-match.html +++ b/src/addon/qtype/match/component/addon-qtype-match.html @@ -1,12 +1,12 @@
- + -

+

diff --git a/src/addon/qtype/match/component/match.ts b/src/addon/qtype/match/component/match.ts index 936bc8a32..b986ac00c 100644 --- a/src/addon/qtype/match/component/match.ts +++ b/src/addon/qtype/match/component/match.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/addon/qtype/match/match.module.ts b/src/addon/qtype/match/match.module.ts index f140418a4..8d032e71e 100644 --- a/src/addon/qtype/match/match.module.ts +++ b/src/addon/qtype/match/match.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/addon/qtype/match/providers/handler.ts b/src/addon/qtype/match/providers/handler.ts index d9519ad04..90d87d1d1 100644 --- a/src/addon/qtype/match/providers/handler.ts +++ b/src/addon/qtype/match/providers/handler.ts @@ -1,5 +1,5 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -32,9 +32,9 @@ export class AddonQtypeMatchHandler implements CoreQuestionHandler { * Return the name of the behaviour to use for the question. * If the question should use the default behaviour you shouldn't implement this function. * - * @param {any} question The question. - * @param {string} behaviour The default behaviour. - * @return {string} The behaviour to use. + * @param question The question. + * @param behaviour The default behaviour. + * @return The behaviour to use. */ getBehaviour(question: any, behaviour: string): string { if (behaviour === 'interactive') { @@ -48,9 +48,9 @@ export class AddonQtypeMatchHandler implements CoreQuestionHandler { * Return the Component to use to display the question. * It's recommended to return the class of the component, but you can also return an instance of the component. * - * @param {Injector} injector Injector. - * @param {any} question The question to render. - * @return {any|Promise} The component (or promise resolved with component) to use, undefined if not found. + * @param injector Injector. + * @param question The question to render. + * @return The component (or promise resolved with component) to use, undefined if not found. */ getComponent(injector: Injector, question: any): any | Promise { return AddonQtypeMatchComponent; @@ -59,9 +59,9 @@ export class AddonQtypeMatchHandler implements CoreQuestionHandler { /** * Check if a response is complete. * - * @param {any} question The question. - * @param {any} answers Object with the question answers (without prefix). - * @return {number} 1 if complete, 0 if not complete, -1 if cannot determine. + * @param question The question. + * @param answers Object with the question answers (without prefix). + * @return 1 if complete, 0 if not complete, -1 if cannot determine. */ isCompleteResponse(question: any, answers: any): number { // We should always get a value for each select so we can assume we receive all the possible answers. @@ -78,7 +78,7 @@ export class AddonQtypeMatchHandler implements CoreQuestionHandler { /** * Whether or not the handler is enabled on a site level. * - * @return {boolean|Promise} True or promise resolved with true if enabled. + * @return True or promise resolved with true if enabled. */ isEnabled(): boolean | Promise { return true; @@ -88,9 +88,9 @@ export class AddonQtypeMatchHandler implements CoreQuestionHandler { * Check if a student has provided enough of an answer for the question to be graded automatically, * or whether it must be considered aborted. * - * @param {any} question The question. - * @param {any} answers Object with the question answers (without prefix). - * @return {number} 1 if gradable, 0 if not gradable, -1 if cannot determine. + * @param question The question. + * @param answers Object with the question answers (without prefix). + * @return 1 if gradable, 0 if not gradable, -1 if cannot determine. */ isGradableResponse(question: any, answers: any): number { // We should always get a value for each select so we can assume we receive all the possible answers. @@ -107,10 +107,10 @@ export class AddonQtypeMatchHandler implements CoreQuestionHandler { /** * Check if two responses are the same. * - * @param {any} question Question. - * @param {any} prevAnswers Object with the previous question answers. - * @param {any} newAnswers Object with the new question answers. - * @return {boolean} Whether they're the same. + * @param question Question. + * @param prevAnswers Object with the previous question answers. + * @param newAnswers Object with the new question answers. + * @return Whether they're the same. */ isSameResponse(question: any, prevAnswers: any, newAnswers: any): boolean { return this.questionProvider.compareAllAnswers(prevAnswers, newAnswers); diff --git a/src/addon/qtype/multianswer/component/addon-qtype-multianswer.html b/src/addon/qtype/multianswer/component/addon-qtype-multianswer.html index 74bc91da4..919da0149 100644 --- a/src/addon/qtype/multianswer/component/addon-qtype-multianswer.html +++ b/src/addon/qtype/multianswer/component/addon-qtype-multianswer.html @@ -1,5 +1,5 @@
- +
diff --git a/src/addon/qtype/multianswer/component/multianswer.ts b/src/addon/qtype/multianswer/component/multianswer.ts index 77ebea6f0..7c2257113 100644 --- a/src/addon/qtype/multianswer/component/multianswer.ts +++ b/src/addon/qtype/multianswer/component/multianswer.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -44,6 +44,7 @@ export class AddonQtypeMultiAnswerComponent extends CoreQuestionBaseComponent im * The question has been rendered. */ questionRendered(): void { - this.questionHelper.treatCorrectnessIconsClicks(this.element, this.component, this.componentId); + this.questionHelper.treatCorrectnessIconsClicks(this.element, this.component, this.componentId, this.contextLevel, + this.contextInstanceId, this.courseId); } } diff --git a/src/addon/qtype/multianswer/multianswer.module.ts b/src/addon/qtype/multianswer/multianswer.module.ts index b1c37c051..87458fd45 100644 --- a/src/addon/qtype/multianswer/multianswer.module.ts +++ b/src/addon/qtype/multianswer/multianswer.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/addon/qtype/multianswer/providers/handler.ts b/src/addon/qtype/multianswer/providers/handler.ts index b02a4eb70..798dcf572 100644 --- a/src/addon/qtype/multianswer/providers/handler.ts +++ b/src/addon/qtype/multianswer/providers/handler.ts @@ -1,5 +1,5 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -33,9 +33,9 @@ export class AddonQtypeMultiAnswerHandler implements CoreQuestionHandler { * Return the name of the behaviour to use for the question. * If the question should use the default behaviour you shouldn't implement this function. * - * @param {any} question The question. - * @param {string} behaviour The default behaviour. - * @return {string} The behaviour to use. + * @param question The question. + * @param behaviour The default behaviour. + * @return The behaviour to use. */ getBehaviour(question: any, behaviour: string): string { if (behaviour === 'interactive') { @@ -49,9 +49,9 @@ export class AddonQtypeMultiAnswerHandler implements CoreQuestionHandler { * Return the Component to use to display the question. * It's recommended to return the class of the component, but you can also return an instance of the component. * - * @param {Injector} injector Injector. - * @param {any} question The question to render. - * @return {any|Promise} The component (or promise resolved with component) to use, undefined if not found. + * @param injector Injector. + * @param question The question to render. + * @return The component (or promise resolved with component) to use, undefined if not found. */ getComponent(injector: Injector, question: any): any | Promise { return AddonQtypeMultiAnswerComponent; @@ -60,9 +60,9 @@ export class AddonQtypeMultiAnswerHandler implements CoreQuestionHandler { /** * Check if a response is complete. * - * @param {any} question The question. - * @param {any} answers Object with the question answers (without prefix). - * @return {number} 1 if complete, 0 if not complete, -1 if cannot determine. + * @param question The question. + * @param answers Object with the question answers (without prefix). + * @return 1 if complete, 0 if not complete, -1 if cannot determine. */ isCompleteResponse(question: any, answers: any): number { // Get all the inputs in the question to check if they've all been answered. @@ -80,7 +80,7 @@ export class AddonQtypeMultiAnswerHandler implements CoreQuestionHandler { /** * Whether or not the handler is enabled on a site level. * - * @return {boolean|Promise} True or promise resolved with true if enabled. + * @return True or promise resolved with true if enabled. */ isEnabled(): boolean | Promise { return true; @@ -90,9 +90,9 @@ export class AddonQtypeMultiAnswerHandler implements CoreQuestionHandler { * Check if a student has provided enough of an answer for the question to be graded automatically, * or whether it must be considered aborted. * - * @param {any} question The question. - * @param {any} answers Object with the question answers (without prefix). - * @return {number} 1 if gradable, 0 if not gradable, -1 if cannot determine. + * @param question The question. + * @param answers Object with the question answers (without prefix). + * @return 1 if gradable, 0 if not gradable, -1 if cannot determine. */ isGradableResponse(question: any, answers: any): number { // We should always get a value for each select so we can assume we receive all the possible answers. @@ -109,10 +109,10 @@ export class AddonQtypeMultiAnswerHandler implements CoreQuestionHandler { /** * Check if two responses are the same. * - * @param {any} question Question. - * @param {any} prevAnswers Object with the previous question answers. - * @param {any} newAnswers Object with the new question answers. - * @return {boolean} Whether they're the same. + * @param question Question. + * @param prevAnswers Object with the previous question answers. + * @param newAnswers Object with the new question answers. + * @return Whether they're the same. */ isSameResponse(question: any, prevAnswers: any, newAnswers: any): boolean { return this.questionProvider.compareAllAnswers(prevAnswers, newAnswers); @@ -122,9 +122,9 @@ export class AddonQtypeMultiAnswerHandler implements CoreQuestionHandler { * Validate if an offline sequencecheck is valid compared with the online one. * This function only needs to be implemented if a specific compare is required. * - * @param {any} question The question. - * @param {string} offlineSequenceCheck Sequence check stored in offline. - * @return {boolean} Whether sequencecheck is valid. + * @param question The question. + * @param offlineSequenceCheck Sequence check stored in offline. + * @return Whether sequencecheck is valid. */ validateSequenceCheck(question: any, offlineSequenceCheck: string): boolean { if (question.sequencecheck == offlineSequenceCheck) { diff --git a/src/addon/qtype/multichoice/component/addon-qtype-multichoice.html b/src/addon/qtype/multichoice/component/addon-qtype-multichoice.html index 56ac05cc3..32fc617f0 100644 --- a/src/addon/qtype/multichoice/component/addon-qtype-multichoice.html +++ b/src/addon/qtype/multichoice/component/addon-qtype-multichoice.html @@ -1,16 +1,16 @@
- - + + {{ question.prompt }} - -
+ +
@@ -25,8 +25,8 @@
- -
+ +
diff --git a/src/addon/qtype/multichoice/component/multichoice.ts b/src/addon/qtype/multichoice/component/multichoice.ts index e481c0f2f..ca2e46caa 100644 --- a/src/addon/qtype/multichoice/component/multichoice.ts +++ b/src/addon/qtype/multichoice/component/multichoice.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/addon/qtype/multichoice/multichoice.module.ts b/src/addon/qtype/multichoice/multichoice.module.ts index a0e36e755..5aea5339d 100644 --- a/src/addon/qtype/multichoice/multichoice.module.ts +++ b/src/addon/qtype/multichoice/multichoice.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/addon/qtype/multichoice/providers/handler.ts b/src/addon/qtype/multichoice/providers/handler.ts index 440ab80e8..92424fbd2 100644 --- a/src/addon/qtype/multichoice/providers/handler.ts +++ b/src/addon/qtype/multichoice/providers/handler.ts @@ -1,5 +1,5 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -32,9 +32,9 @@ export class AddonQtypeMultichoiceHandler implements CoreQuestionHandler { * Return the Component to use to display the question. * It's recommended to return the class of the component, but you can also return an instance of the component. * - * @param {Injector} injector Injector. - * @param {any} question The question to render. - * @return {any|Promise} The component (or promise resolved with component) to use, undefined if not found. + * @param injector Injector. + * @param question The question to render. + * @return The component (or promise resolved with component) to use, undefined if not found. */ getComponent(injector: Injector, question: any): any | Promise { return AddonQtypeMultichoiceComponent; @@ -43,9 +43,9 @@ export class AddonQtypeMultichoiceHandler implements CoreQuestionHandler { /** * Check if a response is complete. * - * @param {any} question The question. - * @param {any} answers Object with the question answers (without prefix). - * @return {number} 1 if complete, 0 if not complete, -1 if cannot determine. + * @param question The question. + * @param answers Object with the question answers (without prefix). + * @return 1 if complete, 0 if not complete, -1 if cannot determine. */ isCompleteResponse(question: any, answers: any): number { let isSingle = true, @@ -73,8 +73,8 @@ export class AddonQtypeMultichoiceHandler implements CoreQuestionHandler { /** * Check if a response is complete. Only for single answer. * - * @param {any} question The question.uestion answers (without prefix). - * @return {number} 1 if complete, 0 if not complete, -1 if cannot determine. + * @param question The question.uestion answers (without prefix). + * @return 1 if complete, 0 if not complete, -1 if cannot determine. */ isCompleteResponseSingle(answers: any): number { return (answers['answer'] && answers['answer'] !== '') ? 1 : 0; @@ -83,7 +83,7 @@ export class AddonQtypeMultichoiceHandler implements CoreQuestionHandler { /** * Whether or not the handler is enabled on a site level. * - * @return {boolean|Promise} True or promise resolved with true if enabled. + * @return True or promise resolved with true if enabled. */ isEnabled(): boolean | Promise { return true; @@ -93,9 +93,9 @@ export class AddonQtypeMultichoiceHandler implements CoreQuestionHandler { * Check if a student has provided enough of an answer for the question to be graded automatically, * or whether it must be considered aborted. * - * @param {any} question The question. - * @param {any} answers Object with the question answers (without prefix). - * @return {number} 1 if gradable, 0 if not gradable, -1 if cannot determine. + * @param question The question. + * @param answers Object with the question answers (without prefix). + * @return 1 if gradable, 0 if not gradable, -1 if cannot determine. */ isGradableResponse(question: any, answers: any): number { return this.isCompleteResponse(question, answers); @@ -105,8 +105,8 @@ export class AddonQtypeMultichoiceHandler implements CoreQuestionHandler { * Check if a student has provided enough of an answer for the question to be graded automatically, * or whether it must be considered aborted. Only for single answer. * - * @param {any} answers Object with the question answers (without prefix). - * @return {number} 1 if gradable, 0 if not gradable, -1 if cannot determine. + * @param answers Object with the question answers (without prefix). + * @return 1 if gradable, 0 if not gradable, -1 if cannot determine. */ isGradableResponseSingle(answers: any): number { return this.isCompleteResponseSingle(answers); @@ -115,10 +115,10 @@ export class AddonQtypeMultichoiceHandler implements CoreQuestionHandler { /** * Check if two responses are the same. * - * @param {any} question Question. - * @param {any} prevAnswers Object with the previous question answers. - * @param {any} newAnswers Object with the new question answers. - * @return {boolean} Whether they're the same. + * @param question Question. + * @param prevAnswers Object with the previous question answers. + * @param newAnswers Object with the new question answers. + * @return Whether they're the same. */ isSameResponse(question: any, prevAnswers: any, newAnswers: any): boolean { let isSingle = true, @@ -144,9 +144,9 @@ export class AddonQtypeMultichoiceHandler implements CoreQuestionHandler { /** * Check if two responses are the same. Only for single answer. * - * @param {any} prevAnswers Object with the previous question answers. - * @param {any} newAnswers Object with the new question answers. - * @return {boolean} Whether they're the same. + * @param prevAnswers Object with the previous question answers. + * @param newAnswers Object with the new question answers. + * @return Whether they're the same. */ isSameResponseSingle(prevAnswers: any, newAnswers: any): boolean { return this.utils.sameAtKeyMissingIsBlank(prevAnswers, newAnswers, 'answer'); @@ -155,11 +155,11 @@ export class AddonQtypeMultichoiceHandler implements CoreQuestionHandler { /** * Prepare and add to answers the data to send to server based in the input. Return promise if async. * - * @param {any} question Question. - * @param {any} answers The answers retrieved from the form. Prepared answers must be stored in this object. - * @param {boolean} [offline] Whether the data should be saved in offline. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {void|Promise} Return a promise resolved when done if async, void if sync. + * @param question Question. + * @param answers The answers retrieved from the form. Prepared answers must be stored in this object. + * @param offline Whether the data should be saved in offline. + * @param siteId Site ID. If not defined, current site. + * @return Return a promise resolved when done if async, void if sync. */ prepareAnswers(question: any, answers: any, offline: boolean, siteId?: string): void | Promise { if (question && !question.multi && typeof answers[question.optionsName] != 'undefined' && !answers[question.optionsName]) { diff --git a/src/addon/qtype/numerical/numerical.module.ts b/src/addon/qtype/numerical/numerical.module.ts index b3ed56bbf..ed4ce256e 100644 --- a/src/addon/qtype/numerical/numerical.module.ts +++ b/src/addon/qtype/numerical/numerical.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/addon/qtype/numerical/providers/handler.ts b/src/addon/qtype/numerical/providers/handler.ts index 5ecf9a947..b68cc15ac 100644 --- a/src/addon/qtype/numerical/providers/handler.ts +++ b/src/addon/qtype/numerical/providers/handler.ts @@ -1,5 +1,5 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/addon/qtype/qtype.module.ts b/src/addon/qtype/qtype.module.ts index 9959ae0ed..2b2c3bbd5 100644 --- a/src/addon/qtype/qtype.module.ts +++ b/src/addon/qtype/qtype.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/addon/qtype/randomsamatch/providers/handler.ts b/src/addon/qtype/randomsamatch/providers/handler.ts index eca943805..a0eede940 100644 --- a/src/addon/qtype/randomsamatch/providers/handler.ts +++ b/src/addon/qtype/randomsamatch/providers/handler.ts @@ -1,5 +1,5 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -32,9 +32,9 @@ export class AddonQtypeRandomSaMatchHandler implements CoreQuestionHandler { * Return the Component to use to display the question. * It's recommended to return the class of the component, but you can also return an instance of the component. * - * @param {Injector} injector Injector. - * @param {any} question The question to render. - * @return {any|Promise} The component (or promise resolved with component) to use, undefined if not found. + * @param injector Injector. + * @param question The question to render. + * @return The component (or promise resolved with component) to use, undefined if not found. */ getComponent(injector: Injector, question: any): any | Promise { // Random behaves like a match question, use the same component. @@ -44,9 +44,9 @@ export class AddonQtypeRandomSaMatchHandler implements CoreQuestionHandler { /** * Check if a response is complete. * - * @param {any} question The question. - * @param {any} answers Object with the question answers (without prefix). - * @return {number} 1 if complete, 0 if not complete, -1 if cannot determine. + * @param question The question. + * @param answers Object with the question answers (without prefix). + * @return 1 if complete, 0 if not complete, -1 if cannot determine. */ isCompleteResponse(question: any, answers: any): number { // This question behaves like a match question. @@ -56,7 +56,7 @@ export class AddonQtypeRandomSaMatchHandler implements CoreQuestionHandler { /** * Whether or not the handler is enabled on a site level. * - * @return {boolean|Promise} True or promise resolved with true if enabled. + * @return True or promise resolved with true if enabled. */ isEnabled(): boolean | Promise { return true; @@ -66,9 +66,9 @@ export class AddonQtypeRandomSaMatchHandler implements CoreQuestionHandler { * Check if a student has provided enough of an answer for the question to be graded automatically, * or whether it must be considered aborted. * - * @param {any} question The question. - * @param {any} answers Object with the question answers (without prefix). - * @return {number} 1 if gradable, 0 if not gradable, -1 if cannot determine. + * @param question The question. + * @param answers Object with the question answers (without prefix). + * @return 1 if gradable, 0 if not gradable, -1 if cannot determine. */ isGradableResponse(question: any, answers: any): number { // This question behaves like a match question. @@ -78,10 +78,10 @@ export class AddonQtypeRandomSaMatchHandler implements CoreQuestionHandler { /** * Check if two responses are the same. * - * @param {any} question Question. - * @param {any} prevAnswers Object with the previous question answers. - * @param {any} newAnswers Object with the new question answers. - * @return {boolean} Whether they're the same. + * @param question Question. + * @param prevAnswers Object with the previous question answers. + * @param newAnswers Object with the new question answers. + * @return Whether they're the same. */ isSameResponse(question: any, prevAnswers: any, newAnswers: any): boolean { // This question behaves like a match question. diff --git a/src/addon/qtype/randomsamatch/randomsamatch.module.ts b/src/addon/qtype/randomsamatch/randomsamatch.module.ts index 4920b0506..53e72cf1e 100644 --- a/src/addon/qtype/randomsamatch/randomsamatch.module.ts +++ b/src/addon/qtype/randomsamatch/randomsamatch.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/addon/qtype/shortanswer/component/addon-qtype-shortanswer.html b/src/addon/qtype/shortanswer/component/addon-qtype-shortanswer.html index 42febebf0..5cfaf0139 100644 --- a/src/addon/qtype/shortanswer/component/addon-qtype-shortanswer.html +++ b/src/addon/qtype/shortanswer/component/addon-qtype-shortanswer.html @@ -1,6 +1,6 @@
- + {{ 'addon.mod_quiz.answercolon' | translate }} diff --git a/src/addon/qtype/shortanswer/component/shortanswer.ts b/src/addon/qtype/shortanswer/component/shortanswer.ts index b737b280a..0cc904986 100644 --- a/src/addon/qtype/shortanswer/component/shortanswer.ts +++ b/src/addon/qtype/shortanswer/component/shortanswer.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/addon/qtype/shortanswer/providers/handler.ts b/src/addon/qtype/shortanswer/providers/handler.ts index abbb694dc..f9629fa75 100644 --- a/src/addon/qtype/shortanswer/providers/handler.ts +++ b/src/addon/qtype/shortanswer/providers/handler.ts @@ -1,5 +1,5 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -32,9 +32,9 @@ export class AddonQtypeShortAnswerHandler implements CoreQuestionHandler { * Return the Component to use to display the question. * It's recommended to return the class of the component, but you can also return an instance of the component. * - * @param {Injector} injector Injector. - * @param {any} question The question to render. - * @return {any|Promise} The component (or promise resolved with component) to use, undefined if not found. + * @param injector Injector. + * @param question The question to render. + * @return The component (or promise resolved with component) to use, undefined if not found. */ getComponent(injector: Injector, question: any): any | Promise { return AddonQtypeShortAnswerComponent; @@ -43,9 +43,9 @@ export class AddonQtypeShortAnswerHandler implements CoreQuestionHandler { /** * Check if a response is complete. * - * @param {any} question The question. - * @param {any} answers Object with the question answers (without prefix). - * @return {number} 1 if complete, 0 if not complete, -1 if cannot determine. + * @param question The question. + * @param answers Object with the question answers (without prefix). + * @return 1 if complete, 0 if not complete, -1 if cannot determine. */ isCompleteResponse(question: any, answers: any): number { return (answers['answer'] || answers['answer'] === 0) ? 1 : 0; @@ -54,7 +54,7 @@ export class AddonQtypeShortAnswerHandler implements CoreQuestionHandler { /** * Whether or not the handler is enabled on a site level. * - * @return {boolean|Promise} True or promise resolved with true if enabled. + * @return True or promise resolved with true if enabled. */ isEnabled(): boolean | Promise { return true; @@ -64,9 +64,9 @@ export class AddonQtypeShortAnswerHandler implements CoreQuestionHandler { * Check if a student has provided enough of an answer for the question to be graded automatically, * or whether it must be considered aborted. * - * @param {any} question The question. - * @param {any} answers Object with the question answers (without prefix). - * @return {number} 1 if gradable, 0 if not gradable, -1 if cannot determine. + * @param question The question. + * @param answers Object with the question answers (without prefix). + * @return 1 if gradable, 0 if not gradable, -1 if cannot determine. */ isGradableResponse(question: any, answers: any): number { return this.isCompleteResponse(question, answers); @@ -75,10 +75,10 @@ export class AddonQtypeShortAnswerHandler implements CoreQuestionHandler { /** * Check if two responses are the same. * - * @param {any} question Question. - * @param {any} prevAnswers Object with the previous question answers. - * @param {any} newAnswers Object with the new question answers. - * @return {boolean} Whether they're the same. + * @param question Question. + * @param prevAnswers Object with the previous question answers. + * @param newAnswers Object with the new question answers. + * @return Whether they're the same. */ isSameResponse(question: any, prevAnswers: any, newAnswers: any): boolean { return this.utils.sameAtKeyMissingIsBlank(prevAnswers, newAnswers, 'answer'); diff --git a/src/addon/qtype/shortanswer/shortanswer.module.ts b/src/addon/qtype/shortanswer/shortanswer.module.ts index 313a23b75..bb16c81dc 100644 --- a/src/addon/qtype/shortanswer/shortanswer.module.ts +++ b/src/addon/qtype/shortanswer/shortanswer.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/addon/qtype/truefalse/providers/handler.ts b/src/addon/qtype/truefalse/providers/handler.ts index 3be03abde..f811dfa65 100644 --- a/src/addon/qtype/truefalse/providers/handler.ts +++ b/src/addon/qtype/truefalse/providers/handler.ts @@ -1,5 +1,5 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -32,9 +32,9 @@ export class AddonQtypeTrueFalseHandler implements CoreQuestionHandler { * Return the Component to use to display the question. * It's recommended to return the class of the component, but you can also return an instance of the component. * - * @param {Injector} injector Injector. - * @param {any} question The question to render. - * @return {any|Promise} The component (or promise resolved with component) to use, undefined if not found. + * @param injector Injector. + * @param question The question to render. + * @return The component (or promise resolved with component) to use, undefined if not found. */ getComponent(injector: Injector, question: any): any | Promise { // True/false behaves like a multichoice, use the same component. @@ -44,9 +44,9 @@ export class AddonQtypeTrueFalseHandler implements CoreQuestionHandler { /** * Check if a response is complete. * - * @param {any} question The question. - * @param {any} answers Object with the question answers (without prefix). - * @return {number} 1 if complete, 0 if not complete, -1 if cannot determine. + * @param question The question. + * @param answers Object with the question answers (without prefix). + * @return 1 if complete, 0 if not complete, -1 if cannot determine. */ isCompleteResponse(question: any, answers: any): number { return answers['answer'] ? 1 : 0; @@ -55,7 +55,7 @@ export class AddonQtypeTrueFalseHandler implements CoreQuestionHandler { /** * Whether or not the handler is enabled on a site level. * - * @return {boolean|Promise} True or promise resolved with true if enabled. + * @return True or promise resolved with true if enabled. */ isEnabled(): boolean | Promise { return true; @@ -65,9 +65,9 @@ export class AddonQtypeTrueFalseHandler implements CoreQuestionHandler { * Check if a student has provided enough of an answer for the question to be graded automatically, * or whether it must be considered aborted. * - * @param {any} question The question. - * @param {any} answers Object with the question answers (without prefix). - * @return {number} 1 if gradable, 0 if not gradable, -1 if cannot determine. + * @param question The question. + * @param answers Object with the question answers (without prefix). + * @return 1 if gradable, 0 if not gradable, -1 if cannot determine. */ isGradableResponse(question: any, answers: any): number { return this.isCompleteResponse(question, answers); @@ -76,10 +76,10 @@ export class AddonQtypeTrueFalseHandler implements CoreQuestionHandler { /** * Check if two responses are the same. * - * @param {any} question Question. - * @param {any} prevAnswers Object with the previous question answers. - * @param {any} newAnswers Object with the new question answers. - * @return {boolean} Whether they're the same. + * @param question Question. + * @param prevAnswers Object with the previous question answers. + * @param newAnswers Object with the new question answers. + * @return Whether they're the same. */ isSameResponse(question: any, prevAnswers: any, newAnswers: any): boolean { return this.utils.sameAtKeyMissingIsBlank(prevAnswers, newAnswers, 'answer'); @@ -88,11 +88,11 @@ export class AddonQtypeTrueFalseHandler implements CoreQuestionHandler { /** * Prepare and add to answers the data to send to server based in the input. Return promise if async. * - * @param {any} question Question. - * @param {any} answers The answers retrieved from the form. Prepared answers must be stored in this object. - * @param {boolean} [offline] Whether the data should be saved in offline. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {void|Promise} Return a promise resolved when done if async, void if sync. + * @param question Question. + * @param answers The answers retrieved from the form. Prepared answers must be stored in this object. + * @param offline Whether the data should be saved in offline. + * @param siteId Site ID. If not defined, current site. + * @return Return a promise resolved when done if async, void if sync. */ prepareAnswers(question: any, answers: any, offline: boolean, siteId?: string): void | Promise { if (question && typeof answers[question.optionsName] != 'undefined' && !answers[question.optionsName]) { diff --git a/src/addon/qtype/truefalse/truefalse.module.ts b/src/addon/qtype/truefalse/truefalse.module.ts index 641a26d15..f6e6eae68 100644 --- a/src/addon/qtype/truefalse/truefalse.module.ts +++ b/src/addon/qtype/truefalse/truefalse.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/addon/remotethemes/providers/remotethemes.ts b/src/addon/remotethemes/providers/remotethemes.ts index 7b30b31ce..cba1cafe3 100644 --- a/src/addon/remotethemes/providers/remotethemes.ts +++ b/src/addon/remotethemes/providers/remotethemes.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -43,8 +43,8 @@ export class AddonRemoteThemesProvider { /** * Add a style element for a site and load the styles for that element. The style will be disabled. * - * @param {string} siteId Site ID. - * @return {Promise} Promise resolved when added and loaded. + * @param siteId Site ID. + * @return Promise resolved when added and loaded. */ addSite(siteId: string): Promise { if (!siteId || this.stylesEls[siteId]) { @@ -85,8 +85,8 @@ export class AddonRemoteThemesProvider { /** * Enabled or disable a certain style element. * - * @param {HTMLStyleElement} element The element to enable or disable. - * @param {boolean} disable Whether to disable or enable the element. + * @param element The element to enable or disable. + * @param disable Whether to disable or enable the element. */ disableElement(element: HTMLStyleElement, disable: boolean): void { // Setting disabled should be enough, but we also set the attribute so it can be seen in the DOM which ones are disabled. @@ -106,9 +106,9 @@ export class AddonRemoteThemesProvider { /** * Downloads a CSS file and remove old files if needed. * - * @param {string} siteId Site ID. - * @param {string} url File URL. - * @return {Promise} Promise resolved when the file is downloaded. + * @param siteId Site ID. + * @param url File URL. + * @return Promise resolved when the file is downloaded. */ protected downloadFileAndRemoveOld(siteId: string, url: string): Promise { // Check if the file is downloaded. @@ -130,7 +130,7 @@ export class AddonRemoteThemesProvider { /** * Enable the styles of a certain site. * - * @param {string} [siteId] Site ID. If not defined, current site. + * @param siteId Site ID. If not defined, current site. */ enable(siteId?: string): void { siteId = siteId || this.sitesProvider.getCurrentSiteId(); @@ -143,9 +143,9 @@ export class AddonRemoteThemesProvider { /** * Get remote styles of a certain site. * - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise<{fileUrl: string, styles: string}>} Promise resolved with the styles and the URL of the CSS file, - * resolved with undefined if no styles to load. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the styles and the URL of the CSS file, + * resolved with undefined if no styles to load. */ get(siteId?: string): Promise<{fileUrl: string, styles: string}> { siteId = siteId || this.sitesProvider.getCurrentSiteId(); @@ -193,8 +193,8 @@ export class AddonRemoteThemesProvider { /** * Check if the CSS code has a separator for 3.5 styles. If it does, get only the styles after the separator. * - * @param {string} cssCode The CSS code to check. - * @return {string} The filtered styles. + * @param cssCode The CSS code to check. + * @return The filtered styles. */ protected get35Styles(cssCode: string): string { const separatorPos = cssCode.search(this.SEPARATOR_35); @@ -208,9 +208,9 @@ export class AddonRemoteThemesProvider { /** * Load styles for a certain site. * - * @param {string} [siteId] Site ID. If not defined, current site. - * @param {boolean} [disabled] Whether loaded styles should be disabled. - * @return {Promise} Promise resolved when styles are loaded. + * @param siteId Site ID. If not defined, current site. + * @param disabled Whether loaded styles should be disabled. + * @return Promise resolved when styles are loaded. */ load(siteId?: string, disabled?: boolean): Promise { siteId = siteId || this.sitesProvider.getCurrentSiteId(); @@ -255,8 +255,8 @@ export class AddonRemoteThemesProvider { /** * Load styles for a temporary site. These styles aren't prefetched. * - * @param {string} url URL to get the styles from. - * @return {Promise} Promise resolved when loaded. + * @param url URL to get the styles from. + * @return Promise resolved when loaded. */ loadTmpStyles(url: string): Promise { if (!url) { @@ -286,7 +286,7 @@ export class AddonRemoteThemesProvider { /** * Preload the styles of the current site (stored in DB). * - * @return {Promise} Promise resolved when loaded. + * @return Promise resolved when loaded. */ preloadCurrentSite(): Promise { return this.sitesProvider.getStoredCurrentSiteId().then((siteId) => { @@ -299,7 +299,7 @@ export class AddonRemoteThemesProvider { /** * Preload the styles of all the stored sites. * - * @return {Promise} Promise resolved when loaded. + * @return Promise resolved when loaded. */ preloadSites(): Promise { return this.sitesProvider.getSitesIds().then((ids) => { @@ -315,7 +315,7 @@ export class AddonRemoteThemesProvider { /** * Remove the styles of a certain site. * - * @param {string} siteId Site ID. + * @param siteId Site ID. */ removeSite(siteId: string): void { if (siteId && this.stylesEls[siteId]) { diff --git a/src/addon/remotethemes/remotethemes.module.ts b/src/addon/remotethemes/remotethemes.module.ts index 4330cc9a8..b5ded1e3e 100644 --- a/src/addon/remotethemes/remotethemes.module.ts +++ b/src/addon/remotethemes/remotethemes.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/addon/storagemanager/pages/course-storage/course-storage.module.ts b/src/addon/storagemanager/pages/course-storage/course-storage.module.ts index 19db12630..ae12c685a 100644 --- a/src/addon/storagemanager/pages/course-storage/course-storage.module.ts +++ b/src/addon/storagemanager/pages/course-storage/course-storage.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/addon/storagemanager/pages/course-storage/course-storage.ts b/src/addon/storagemanager/pages/course-storage/course-storage.ts index 18769c1c9..eb605fc57 100644 --- a/src/addon/storagemanager/pages/course-storage/course-storage.ts +++ b/src/addon/storagemanager/pages/course-storage/course-storage.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -111,7 +111,7 @@ export class AddonStorageManagerCourseStoragePage { * * (This works by deleting data for each module in the section that has data.) * - * @param {any} section Section object with information about section and modules + * @param section Section object with information about section and modules */ deleteForSection(section: any): void { const modules = []; @@ -127,7 +127,7 @@ export class AddonStorageManagerCourseStoragePage { /** * The user has requested a delete for a module's data * - * @param {any} module Module details + * @param module Module details */ deleteForModule(module: any): void { if (module.totalSize > 0) { @@ -138,7 +138,7 @@ export class AddonStorageManagerCourseStoragePage { /** * Deletes the specified modules, showing the loading overlay while it happens. * - * @param {any[]} modules Modules to delete + * @param modules Modules to delete * @return Promise Once deleting has finished */ protected deleteModules(modules: any[]): Promise { diff --git a/src/addon/storagemanager/providers/coursemenu-handler.ts b/src/addon/storagemanager/providers/coursemenu-handler.ts index 69a578fe0..e03d02eed 100644 --- a/src/addon/storagemanager/providers/coursemenu-handler.ts +++ b/src/addon/storagemanager/providers/coursemenu-handler.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -27,11 +27,11 @@ export class AddonStorageManagerCourseMenuHandler implements CoreCourseOptionsMe /** * Checks if the handler is enabled for specified course. This handler is always available. * - * @param {number} courseId Course id - * @param {any} accessData Access data - * @param {any} [navOptions] Navigation options if any - * @param {any} [admOptions] Admin options if any - * @return {boolean | Promise} True + * @param courseId Course id + * @param accessData Access data + * @param navOptions Navigation options if any + * @param admOptions Admin options if any + * @return True */ isEnabledForCourse(courseId: number, accessData: any, navOptions?: any, admOptions?: any): boolean | Promise { return true; @@ -40,7 +40,7 @@ export class AddonStorageManagerCourseMenuHandler implements CoreCourseOptionsMe /** * Check if the handler is enabled on a site level. * - * @return {boolean | Promise} Whether or not the handler is enabled on a site level. + * @return Whether or not the handler is enabled on a site level. */ isEnabled(): boolean | Promise { return true; @@ -49,9 +49,9 @@ export class AddonStorageManagerCourseMenuHandler implements CoreCourseOptionsMe /** * Returns the data needed to render the handler. * - * @param {Injector} injector Injector. - * @param {any} course The course. - * @return {CoreCourseOptionsMenuHandlerData} Data needed to render the handler. + * @param injector Injector. + * @param course The course. + * @return Data needed to render the handler. */ getMenuDisplayData(injector: Injector, course: any): CoreCourseOptionsMenuHandlerData { return { diff --git a/src/addon/storagemanager/storagemanager.module.ts b/src/addon/storagemanager/storagemanager.module.ts index 9590abb60..c4b10a802 100644 --- a/src/addon/storagemanager/storagemanager.module.ts +++ b/src/addon/storagemanager/storagemanager.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/addon/userprofilefield/checkbox/checkbox.module.ts b/src/addon/userprofilefield/checkbox/checkbox.module.ts index ac828e8d4..8c7b92724 100644 --- a/src/addon/userprofilefield/checkbox/checkbox.module.ts +++ b/src/addon/userprofilefield/checkbox/checkbox.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/addon/userprofilefield/checkbox/component/checkbox.ts b/src/addon/userprofilefield/checkbox/component/checkbox.ts index 328153ff1..80a8143ab 100644 --- a/src/addon/userprofilefield/checkbox/component/checkbox.ts +++ b/src/addon/userprofilefield/checkbox/component/checkbox.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -28,6 +28,8 @@ export class AddonUserProfileFieldCheckboxComponent implements OnInit { @Input() edit = false; // True if editing the field. Defaults to false. @Input() disabled = false; // True if disabled. Defaults to false. @Input() form: FormGroup; // Form where to add the form control. + @Input() contextLevel?: string; // The context level. + @Input() contextInstanceId?: number; // The instance ID related to the context. constructor(private fb: FormBuilder, protected utils: CoreUtilsProvider) { } diff --git a/src/addon/userprofilefield/checkbox/providers/handler.ts b/src/addon/userprofilefield/checkbox/providers/handler.ts index 42297a1a2..0c63b417b 100644 --- a/src/addon/userprofilefield/checkbox/providers/handler.ts +++ b/src/addon/userprofilefield/checkbox/providers/handler.ts @@ -1,5 +1,5 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -31,7 +31,7 @@ export class AddonUserProfileFieldCheckboxHandler implements CoreUserProfileFiel /** * Whether or not the handler is enabled on a site level. * - * @return {boolean|Promise} True or promise resolved with true if enabled. + * @return True or promise resolved with true if enabled. */ isEnabled(): boolean | Promise { return true; @@ -40,11 +40,11 @@ export class AddonUserProfileFieldCheckboxHandler implements CoreUserProfileFiel /** * Get the data to send for the field based on the input data. * - * @param {any} field User field to get the data for. - * @param {boolean} signup True if user is in signup page. - * @param {string} [registerAuth] Register auth method. E.g. 'email'. - * @param {any} formValues Form Values. - * @return {CoreUserProfileFieldHandlerData} Data to send for the field. + * @param field User field to get the data for. + * @param signup True if user is in signup page. + * @param registerAuth Register auth method. E.g. 'email'. + * @param formValues Form Values. + * @return Data to send for the field. */ getData(field: any, signup: boolean, registerAuth: string, formValues: any): CoreUserProfileFieldHandlerData { const name = 'profile_field_' + field.shortname; @@ -62,8 +62,8 @@ export class AddonUserProfileFieldCheckboxHandler implements CoreUserProfileFiel * Return the Component to use to display the user profile field. * It's recommended to return the class of the component, but you can also return an instance of the component. * - * @param {Injector} injector Injector. - * @return {any|Promise} The component (or promise resolved with component) to use, undefined if not found. + * @param injector Injector. + * @return The component (or promise resolved with component) to use, undefined if not found. */ getComponent(injector: Injector): any | Promise { return AddonUserProfileFieldCheckboxComponent; diff --git a/src/addon/userprofilefield/datetime/component/datetime.ts b/src/addon/userprofilefield/datetime/component/datetime.ts index 8da732a64..a9b66bf1f 100644 --- a/src/addon/userprofilefield/datetime/component/datetime.ts +++ b/src/addon/userprofilefield/datetime/component/datetime.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -30,6 +30,8 @@ export class AddonUserProfileFieldDatetimeComponent implements OnInit { @Input() edit = false; // True if editing the field. Defaults to false. @Input() disabled = false; // True if disabled. Defaults to false. @Input() form?: FormGroup; // Form where to add the form control. + @Input() contextLevel?: string; // The context level. + @Input() contextInstanceId?: number; // The instance ID related to the context. constructor(private fb: FormBuilder, private timeUtils: CoreTimeUtilsProvider, protected utils: CoreUtilsProvider, private translate: TranslateService) { } diff --git a/src/addon/userprofilefield/datetime/datetime.module.ts b/src/addon/userprofilefield/datetime/datetime.module.ts index dd2b3f85d..05054e3c8 100644 --- a/src/addon/userprofilefield/datetime/datetime.module.ts +++ b/src/addon/userprofilefield/datetime/datetime.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/addon/userprofilefield/datetime/providers/handler.ts b/src/addon/userprofilefield/datetime/providers/handler.ts index 29d3d321e..875ca740c 100644 --- a/src/addon/userprofilefield/datetime/providers/handler.ts +++ b/src/addon/userprofilefield/datetime/providers/handler.ts @@ -1,5 +1,5 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -32,7 +32,7 @@ export class AddonUserProfileFieldDatetimeHandler implements CoreUserProfileFiel /** * Whether or not the handler is enabled on a site level. * - * @return {boolean|Promise} True or promise resolved with true if enabled. + * @return True or promise resolved with true if enabled. */ isEnabled(): boolean | Promise { return true; @@ -41,11 +41,11 @@ export class AddonUserProfileFieldDatetimeHandler implements CoreUserProfileFiel /** * Get the data to send for the field based on the input data. * - * @param {any} field User field to get the data for. - * @param {boolean} signup True if user is in signup page. - * @param {string} [registerAuth] Register auth method. E.g. 'email'. - * @param {any} formValues Form Values. - * @return {CoreUserProfileFieldHandlerData} Data to send for the field. + * @param field User field to get the data for. + * @param signup True if user is in signup page. + * @param registerAuth Register auth method. E.g. 'email'. + * @param formValues Form Values. + * @return Data to send for the field. */ getData(field: any, signup: boolean, registerAuth: string, formValues: any): CoreUserProfileFieldHandlerData { const name = 'profile_field_' + field.shortname; @@ -63,8 +63,8 @@ export class AddonUserProfileFieldDatetimeHandler implements CoreUserProfileFiel * Return the Component to use to display the user profile field. * It's recommended to return the class of the component, but you can also return an instance of the component. * - * @param {Injector} injector Injector. - * @return {any|Promise} The component (or promise resolved with component) to use, undefined if not found. + * @param injector Injector. + * @return The component (or promise resolved with component) to use, undefined if not found. */ getComponent(injector: Injector): any | Promise { return AddonUserProfileFieldDatetimeComponent; diff --git a/src/addon/userprofilefield/menu/component/addon-user-profile-field-menu.html b/src/addon/userprofilefield/menu/component/addon-user-profile-field-menu.html index dcde5daa8..ac8f11b04 100644 --- a/src/addon/userprofilefield/menu/component/addon-user-profile-field-menu.html +++ b/src/addon/userprofilefield/menu/component/addon-user-profile-field-menu.html @@ -1,7 +1,7 @@

{{ field.name }}

-

+

diff --git a/src/addon/userprofilefield/menu/component/menu.ts b/src/addon/userprofilefield/menu/component/menu.ts index 6e7ab22b9..c4365a44f 100644 --- a/src/addon/userprofilefield/menu/component/menu.ts +++ b/src/addon/userprofilefield/menu/component/menu.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -27,6 +27,9 @@ export class AddonUserProfileFieldMenuComponent implements OnInit { @Input() edit = false; // True if editing the field. Defaults to false. @Input() disabled = false; // True if disabled. Defaults to false. @Input() form?: FormGroup; // Form where to add the form control. + @Input() contextLevel?: string; // The context level. + @Input() contextInstanceId?: number; // The instance ID related to the context. + @Input() courseId?: number; // The course the field belongs to (if any). constructor(private fb: FormBuilder) { } diff --git a/src/addon/userprofilefield/menu/menu.module.ts b/src/addon/userprofilefield/menu/menu.module.ts index df81f32b5..138c65ad4 100644 --- a/src/addon/userprofilefield/menu/menu.module.ts +++ b/src/addon/userprofilefield/menu/menu.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/addon/userprofilefield/menu/providers/handler.ts b/src/addon/userprofilefield/menu/providers/handler.ts index 6516c0559..0be1f3dfc 100644 --- a/src/addon/userprofilefield/menu/providers/handler.ts +++ b/src/addon/userprofilefield/menu/providers/handler.ts @@ -1,5 +1,5 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -31,7 +31,7 @@ export class AddonUserProfileFieldMenuHandler implements CoreUserProfileFieldHan /** * Whether or not the handler is enabled on a site level. * - * @return {boolean|Promise} True or promise resolved with true if enabled. + * @return True or promise resolved with true if enabled. */ isEnabled(): boolean | Promise { return true; @@ -40,11 +40,11 @@ export class AddonUserProfileFieldMenuHandler implements CoreUserProfileFieldHan /** * Get the data to send for the field based on the input data. * - * @param {any} field User field to get the data for. - * @param {boolean} signup True if user is in signup page. - * @param {string} [registerAuth] Register auth method. E.g. 'email'. - * @param {any} formValues Form Values. - * @return {CoreUserProfileFieldHandlerData} Data to send for the field. + * @param field User field to get the data for. + * @param signup True if user is in signup page. + * @param registerAuth Register auth method. E.g. 'email'. + * @param formValues Form Values. + * @return Data to send for the field. */ getData(field: any, signup: boolean, registerAuth: string, formValues: any): CoreUserProfileFieldHandlerData { const name = 'profile_field_' + field.shortname; @@ -62,8 +62,8 @@ export class AddonUserProfileFieldMenuHandler implements CoreUserProfileFieldHan * Return the Component to use to display the user profile field. * It's recommended to return the class of the component, but you can also return an instance of the component. * - * @param {Injector} injector Injector. - * @return {any|Promise} The component (or promise resolved with component) to use, undefined if not found. + * @param injector Injector. + * @return The component (or promise resolved with component) to use, undefined if not found. */ getComponent(injector: Injector): any | Promise { return AddonUserProfileFieldMenuComponent; diff --git a/src/addon/userprofilefield/text/component/addon-user-profile-field-text.html b/src/addon/userprofilefield/text/component/addon-user-profile-field-text.html index 1ca1a727d..977c7cd49 100644 --- a/src/addon/userprofilefield/text/component/addon-user-profile-field-text.html +++ b/src/addon/userprofilefield/text/component/addon-user-profile-field-text.html @@ -1,7 +1,7 @@

{{ field.name }}

-

+

diff --git a/src/addon/userprofilefield/text/component/text.ts b/src/addon/userprofilefield/text/component/text.ts index e07e7f732..1eda97aca 100644 --- a/src/addon/userprofilefield/text/component/text.ts +++ b/src/addon/userprofilefield/text/component/text.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -28,6 +28,9 @@ export class AddonUserProfileFieldTextComponent implements OnInit { @Input() edit = false; // True if editing the field. Defaults to false. @Input() disabled = false; // True if disabled. Defaults to false. @Input() form?: FormGroup; // Form where to add the form control. + @Input() contextLevel?: string; // The context level. + @Input() contextInstanceId?: number; // The instance ID related to the context. + @Input() courseId?: number; // The course the field belongs to (if any). constructor(private fb: FormBuilder, protected utils: CoreUtilsProvider) { } diff --git a/src/addon/userprofilefield/text/providers/handler.ts b/src/addon/userprofilefield/text/providers/handler.ts index 2cdcac9ee..a177b4fdd 100644 --- a/src/addon/userprofilefield/text/providers/handler.ts +++ b/src/addon/userprofilefield/text/providers/handler.ts @@ -1,5 +1,5 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -30,7 +30,7 @@ export class AddonUserProfileFieldTextHandler implements CoreUserProfileFieldHan /** * Whether or not the handler is enabled on a site level. * - * @return {boolean|Promise} True or promise resolved with true if enabled. + * @return True or promise resolved with true if enabled. */ isEnabled(): boolean | Promise { return true; @@ -39,11 +39,11 @@ export class AddonUserProfileFieldTextHandler implements CoreUserProfileFieldHan /** * Get the data to send for the field based on the input data. * - * @param {any} field User field to get the data for. - * @param {boolean} signup True if user is in signup page. - * @param {string} [registerAuth] Register auth method. E.g. 'email'. - * @param {any} formValues Form Values. - * @return {CoreUserProfileFieldHandlerData} Data to send for the field. + * @param field User field to get the data for. + * @param signup True if user is in signup page. + * @param registerAuth Register auth method. E.g. 'email'. + * @param formValues Form Values. + * @return Data to send for the field. */ getData(field: any, signup: boolean, registerAuth: string, formValues: any): CoreUserProfileFieldHandlerData { const name = 'profile_field_' + field.shortname; @@ -59,8 +59,8 @@ export class AddonUserProfileFieldTextHandler implements CoreUserProfileFieldHan * Return the Component to use to display the user profile field. * It's recommended to return the class of the component, but you can also return an instance of the component. * - * @param {Injector} injector Injector. - * @return {any|Promise} The component (or promise resolved with component) to use, undefined if not found. + * @param injector Injector. + * @return The component (or promise resolved with component) to use, undefined if not found. */ getComponent(injector: Injector): any | Promise { return AddonUserProfileFieldTextComponent; diff --git a/src/addon/userprofilefield/text/text.module.ts b/src/addon/userprofilefield/text/text.module.ts index 8bc58388f..f4c8f894f 100644 --- a/src/addon/userprofilefield/text/text.module.ts +++ b/src/addon/userprofilefield/text/text.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/addon/userprofilefield/textarea/component/addon-user-profile-field-textarea.html b/src/addon/userprofilefield/textarea/component/addon-user-profile-field-textarea.html index 4a993015b..1d149a8ed 100644 --- a/src/addon/userprofilefield/textarea/component/addon-user-profile-field-textarea.html +++ b/src/addon/userprofilefield/textarea/component/addon-user-profile-field-textarea.html @@ -1,7 +1,7 @@

{{ field.name }}

-

+

diff --git a/src/addon/userprofilefield/textarea/component/textarea.ts b/src/addon/userprofilefield/textarea/component/textarea.ts index 1728d56c0..43c9701b2 100644 --- a/src/addon/userprofilefield/textarea/component/textarea.ts +++ b/src/addon/userprofilefield/textarea/component/textarea.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -27,6 +27,9 @@ export class AddonUserProfileFieldTextareaComponent implements OnInit { @Input() edit = false; // True if editing the field. Defaults to false. @Input() disabled = false; // True if disabled. Defaults to false. @Input() form?: FormGroup; // Form where to add the form control. + @Input() contextLevel?: string; // The context level. + @Input() contextInstanceId?: number; // The instance ID related to the context. + @Input() courseId?: number; // The course the field belongs to (if any). control: FormControl; diff --git a/src/addon/userprofilefield/textarea/providers/handler.ts b/src/addon/userprofilefield/textarea/providers/handler.ts index fc079ffb7..740e1fd87 100644 --- a/src/addon/userprofilefield/textarea/providers/handler.ts +++ b/src/addon/userprofilefield/textarea/providers/handler.ts @@ -1,5 +1,5 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -30,7 +30,7 @@ export class AddonUserProfileFieldTextareaHandler implements CoreUserProfileFiel /** * Whether or not the handler is enabled on a site level. * - * @return {boolean|Promise} True or promise resolved with true if enabled. + * @return True or promise resolved with true if enabled. */ isEnabled(): boolean | Promise { return true; @@ -39,11 +39,11 @@ export class AddonUserProfileFieldTextareaHandler implements CoreUserProfileFiel /** * Get the data to send for the field based on the input data. * - * @param {any} field User field to get the data for. - * @param {boolean} signup True if user is in signup page. - * @param {string} [registerAuth] Register auth method. E.g. 'email'. - * @param {any} formValues Form Values. - * @return {CoreUserProfileFieldHandlerData} Data to send for the field. + * @param field User field to get the data for. + * @param signup True if user is in signup page. + * @param registerAuth Register auth method. E.g. 'email'. + * @param formValues Form Values. + * @return Data to send for the field. */ getData(field: any, signup: boolean, registerAuth: string, formValues: any): CoreUserProfileFieldHandlerData { const name = 'profile_field_' + field.shortname; @@ -68,8 +68,8 @@ export class AddonUserProfileFieldTextareaHandler implements CoreUserProfileFiel * Return the Component to use to display the user profile field. * It's recommended to return the class of the component, but you can also return an instance of the component. * - * @param {Injector} injector Injector. - * @return {any|Promise} The component (or promise resolved with component) to use, undefined if not found. + * @param injector Injector. + * @return The component (or promise resolved with component) to use, undefined if not found. */ getComponent(injector: Injector): any | Promise { return AddonUserProfileFieldTextareaComponent; diff --git a/src/addon/userprofilefield/textarea/textarea.module.ts b/src/addon/userprofilefield/textarea/textarea.module.ts index 1a307b685..f64fc285d 100644 --- a/src/addon/userprofilefield/textarea/textarea.module.ts +++ b/src/addon/userprofilefield/textarea/textarea.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/addon/userprofilefield/userprofilefield.module.ts b/src/addon/userprofilefield/userprofilefield.module.ts index b99616335..e9e5f4d04 100644 --- a/src/addon/userprofilefield/userprofilefield.module.ts +++ b/src/addon/userprofilefield/userprofilefield.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 7df4951b2..8316ea5fb 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -14,6 +14,7 @@ import { Component, OnInit, NgZone } from '@angular/core'; import { Platform, IonicApp } from 'ionic-angular'; +import { Network } from '@ionic-native/network'; import { CoreAppProvider } from '@providers/app'; import { CoreEventsProvider } from '@providers/events'; import { CoreLangProvider } from '@providers/lang'; @@ -42,7 +43,7 @@ export class MoodleMobileApp implements OnInit { private eventsProvider: CoreEventsProvider, private loginHelper: CoreLoginHelperProvider, private zone: NgZone, private appProvider: CoreAppProvider, private langProvider: CoreLangProvider, private sitesProvider: CoreSitesProvider, private screenOrientation: ScreenOrientation, private urlSchemesProvider: CoreCustomURLSchemesProvider, - private utils: CoreUtilsProvider, private urlUtils: CoreUrlUtilsProvider) { + private utils: CoreUtilsProvider, private urlUtils: CoreUrlUtilsProvider, private network: Network) { this.logger = logger.getInstance('AppComponent'); platform.ready().then(() => { @@ -98,7 +99,7 @@ export class MoodleMobileApp implements OnInit { // Listen for passwordchange and usernotfullysetup events to open InAppBrowser. this.eventsProvider.on(CoreEventsProvider.PASSWORD_CHANGE_FORCED, (data) => { - this.loginHelper.openInAppForEdit(data.siteId, '/login/change_password.php', 'core.forcepasswordchangenotice', true); + this.loginHelper.passwordChangeForced(data.siteId); }); this.eventsProvider.on(CoreEventsProvider.USER_NOT_FULLY_SETUP, (data) => { this.loginHelper.openInAppForEdit(data.siteId, '/user/edit.php', 'core.usernotfullysetup'); @@ -109,6 +110,29 @@ export class MoodleMobileApp implements OnInit { this.loginHelper.sitePolicyNotAgreed(data.siteId); }); + this.platform.ready().then(() => { + // Refresh online status when changes. + this.network.onchange().subscribe(() => { + // Execute the callback in the Angular zone, so change detection doesn't stop working. + this.zone.run(() => { + const isOnline = this.appProvider.isOnline(), + hadOfflineMessage = document.body.classList.contains('core-offline'); + + document.body.classList.toggle('core-offline', !isOnline); + + if (isOnline && hadOfflineMessage) { + document.body.classList.add('core-online'); + + setTimeout(() => { + document.body.classList.remove('core-online'); + }, 3000); + } else if (!isOnline) { + document.body.classList.remove('core-online'); + } + }); + }); + }); + // Check URLs loaded in any InAppBrowser. this.eventsProvider.on(CoreEventsProvider.IAB_LOAD_START, (event) => { // URLs with a custom scheme can be prefixed with "http://" or "https://", we need to remove this. @@ -266,7 +290,7 @@ export class MoodleMobileApp implements OnInit { /** * Convenience function to add version to body classes. * - * @param {string} release Current release number of the site. + * @param release Current release number of the site. */ protected addVersionClass(release: string): void { const parts = release.split('.'); @@ -298,7 +322,7 @@ export class MoodleMobileApp implements OnInit { /** * Close one modal if any. * - * @return {boolean} True if one modal was present. + * @return True if one modal was present. */ closeModal(): boolean { // Following function is hidden in Ionic Code, however there's no solution for that. diff --git a/src/app/app.ios.scss b/src/app/app.ios.scss index 8eeaf67a3..76b7d379d 100644 --- a/src/app/app.ios.scss +++ b/src/app/app.ios.scss @@ -37,17 +37,20 @@ ion-app.app-root.ios { @include margin-horizontal(0, null); } - @each $color-name, $color-base, $color-contrast in get-colors($colors-ios) { .core-#{$color-name}-card { @extend .card-ios ; @extend .card-content-ios; + @include darkmode() { + background-color: $core-dark-item-bg-color; + } + &[icon-start] { - @include padding(null, null, null, $card-ios-padding-left * 2 + 20); + @include safe-area-padding(null, null, null, $card-ios-padding-left * 2 + 20); ion-icon { - @include position(null, null, null, $card-ios-padding-left); + @include safe-area-position(null, null, null, $card-ios-padding-left); } } } @@ -117,4 +120,4 @@ ion-app.app-root.ios { cursor: pointer; } } -} \ No newline at end of file +} diff --git a/src/app/app.md.scss b/src/app/app.md.scss index a97ce6e95..a1efde285 100644 --- a/src/app/app.md.scss +++ b/src/app/app.md.scss @@ -38,11 +38,15 @@ ion-app.app-root.md { @extend .card-md; @extend .card-content-md; + @include darkmode() { + background-color: $core-dark-item-bg-color; + } + &[icon-start] { - @include padding(null, null, null, $card-md-padding-left * 2 + 20); + @include safe-area-padding(null, null, null, $card-md-padding-left * 2 + 20); ion-icon { - @include position(null, null, null, $card-md-padding-left); + @include safe-area-position(null, null, null, $card-md-padding-left); } } } diff --git a/src/app/app.module.ts b/src/app/app.module.ts index c843dce54..f28974676 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -82,6 +82,8 @@ import { CoreCommentsModule } from '@core/comments/comments.module'; import { CoreBlockModule } from '@core/block/block.module'; import { CoreRatingModule } from '@core/rating/rating.module'; import { CoreTagModule } from '@core/tag/tag.module'; +import { CoreFilterModule } from '@core/filter/filter.module'; +import { CoreH5PModule } from '@core/h5p/h5p.module'; // Addon modules. import { AddonBadgesModule } from '@addon/badges/badges.module'; @@ -147,6 +149,7 @@ import { AddonRemoteThemesModule } from '@addon/remotethemes/remotethemes.module import { AddonQbehaviourModule } from '@addon/qbehaviour/qbehaviour.module'; import { AddonQtypeModule } from '@addon/qtype/qtype.module'; import { AddonStorageManagerModule } from '@addon/storagemanager/storagemanager.module'; +import { AddonFilterModule } from '@addon/filter/filter.module'; // For translate loader. AoT requires an exported function for factories. export function createTranslateLoader(http: HttpClient): TranslateHttpLoader { @@ -227,6 +230,8 @@ export const WP_PROVIDER: any = null; CoreRatingModule, CorePushNotificationsModule, CoreTagModule, + CoreFilterModule, + CoreH5PModule, AddonBadgesModule, AddonBlogModule, AddonCalendarModule, @@ -288,7 +293,8 @@ export const WP_PROVIDER: any = null; AddonRemoteThemesModule, AddonQbehaviourModule, AddonQtypeModule, - AddonStorageManagerModule + AddonStorageManagerModule, + AddonFilterModule ], bootstrap: [IonicApp], entryComponents: [ diff --git a/src/app/app.scss b/src/app/app.scss index 594dccfe3..75f830591 100644 --- a/src/app/app.scss +++ b/src/app/app.scss @@ -122,6 +122,12 @@ ion-app.app-root { @include core-items(); } + ion-header ion-title .toolbar-title .MathJax_Display { + display: inline-block !important; + margin: 0; + width: auto; + } + .core-oauth-icon, .item.core-oauth-icon, .list .item.core-oauth-icon { min-height: 32px; img, .label { @@ -255,6 +261,10 @@ ion-app.app-root { &.core-shortened { color: $gray-darker; + @include darkmode() { + color: $white; + } + overflow: hidden; min-height: 50px; @@ -268,6 +278,10 @@ ion-app.app-root { z-index: 1001; background-color: $white; @include padding(null, null, null, 10px); + @include darkmode() { + color: $white; + background-color: $core-dark-item-bg-color; + } } &:before { @@ -282,6 +296,15 @@ ion-app.app-root { background: -ms-linear-gradient(top, rgba(255, 255, 255, 0) calc(100% - 50px), white calc(100% - 15px)); background: linear-gradient(to bottom, rgba(255, 255, 255, 0) calc(100% - 50px), white calc(100% - 15px)); z-index: 1000; + + @include darkmode { + background: -moz-linear-gradient(top, rgba(255, 255, 255, 0) calc(100% - 50px), $core-dark-item-bg-color calc(100% - 15px)); + background: -webkit-gradient(left top, left bottom, color-stop(calc(100% - 50px), rgba(255, 255, 255, 0)), color-stop(calc(100% - 15px), $core-dark-item-bg-color)); + background: -webkit-linear-gradient(top, rgba(255, 255, 255, 0) calc(100% - 50px), $core-dark-item-bg-color calc(100% - 15px)); + background: -o-linear-gradient(top, rgba(255, 255, 255, 0) calc(100% - 50px), $core-dark-item-bg-color calc(100% - 15px)); + background: -ms-linear-gradient(top, rgba(255, 255, 255, 0) calc(100% - 50px), $core-dark-item-bg-color calc(100% - 15px)); + background: linear-gradient(to bottom, rgba(255, 255, 255, 0) calc(100% - 50px), $core-dark-item-bg-color calc(100% - 15px)); + } } } } @@ -344,6 +367,9 @@ ion-app.app-root { color: $black; border-radius: 5px; background: rgba(255, 255, 255, .5); + @include darkmode() { + background-color: rgba(0, 0, 0, .5); + } text-align: center; width: 32px; @@ -353,6 +379,8 @@ ion-app.app-root { font-size: 24px; ion-icon { font-size: 24px; + + } } @@ -469,6 +497,30 @@ ion-app.app-root { } } + @include darkmode() { + ion-select.core-button-select, + .core-button-select { + background-color: $core-dark-item-bg-color; + + + &.select-md, + &.button-md, + &.select-ios, + &.button-ios, + &.select-wp, + &.button-wp { + background: $core-dark-item-bg-color; + } + } + } + + button.core-button-select .core-button-select-text { + overflow: hidden; + text-overflow: ellipsis; + line-height: 2em; + white-space: nowrap; + } + // File uploader. // ------------------------- .core-fileuploader-file-handler { @@ -675,6 +727,17 @@ ion-app.app-root { .header .toolbar-ios { height: $toolbar-ios-height; + max-height: $toolbar-ios-height; + } + + .header .toolbar-wp { + height: $toolbar-wp-height; + max-height: $toolbar-wp-height; + } + + .header .toolbar-md { + height: $toolbar-md-height; + max-height: $toolbar-md-height; } // Footer with auto height. @@ -692,14 +755,18 @@ ion-app.app-root { @extend ion-card; border-bottom: 3px solid $color-base; + @include darkmode() { + background-color: $core-dark-item-bg-color; + } + &[icon-start] { - @include padding(null, null, null, 52px); + @include safe-area-padding(null, null, null, 52px); position: relative; > ion-icon { color: $color-base; position: absolute; - @include position(0, null, null, 16px); + @include safe-area-position(0, null, null, 16px); height: 100%; font-size: 24px; display: flex; @@ -713,6 +780,9 @@ ion-app.app-root { ion-icon { color: $color-base; } + @include darkmode() { + background-color: $core-dark-item-bg-color; + } } .core-#{$color-name}-item.item-input { @@ -738,17 +808,7 @@ ion-app.app-root { } .core-#{$color-name}-selected-item { - @include safe-area-border-start(5px, solid, $color-base); - - &.item-md { - @include padding(null, null, null, $item-md-padding-start - 5px); - } - &.item-ios { - @include padding(null, null, null, $item-ios-padding-start - 5px); - } - &.item-wp { - @include padding(null, null, null, $item-wp-padding-start - 5px); - } + @include core-selected-item($color-base); } .split-pane-main .core-#{$color-name}-selected-item { @@ -804,6 +864,8 @@ ion-app.app-root { @media only screen and (min-height: 400px) and (min-width: 300px) { .core-modal-lateral { + @include core-split-area-end(); + .modal-wrapper { position: absolute; @include position(0 !important, 0 !important, 0 !important, auto); @@ -983,6 +1045,14 @@ ion-app.app-root { flex-flow: nowrap; overflow-x: scroll; flex-direction: row; + + .item-ios.item-block { + @include padding-horizontal($item-ios-padding-end / 2, null); + + .item-inner { + @include padding-horizontal(null, $item-ios-padding-end / 2); + } + } } ion-content.core-expand-max .scroll-content { @@ -1047,11 +1117,6 @@ ion-app.app-root { } } -[dir="ltr"] body, [dir="rtl"] body { - padding-top: constant(safe-area-inset-top); //for iOS 11.2 - padding-top: env(safe-area-inset-top); //for iOS 11.1 -} - body.keyboard-is-open { ion-content:not(.has-footer) { > .scroll-content, > .fixed-content { @@ -1064,31 +1129,6 @@ body.keyboard-is-open { } } -.safe-padding-horizontal{ - @include safe-area-padding-horizontal(0px, 0px); -} - -[padding].safe-padding-horizontal, -ion-app.ios [padding].safe-padding-horizontal { - @include safe-area-padding-horizontal($content-padding, $content-padding); -} - -ion-app.ios .split-pane-side, -.split-pane-side { - .safe-padding-horizontal, - [padding].safe-padding-horizontal { - @include safe-area-padding-start(0px, $content-padding); - } -} - -ion-app.ios .split-pane-main, -.split-pane-main { - .safe-padding-horizontal, - [padding].safe-padding-horizontal { - @include safe-area-padding-end($content-padding, 0px); - } -} - details summary { pointer-events: auto; } @@ -1191,3 +1231,42 @@ ion-app.platform-desktop { font-size: 28px; } } + +// Safe areas +[dir="ltr"] body, [dir="rtl"] body { + padding-top: constant(safe-area-inset-top); //for iOS 11.2 + padding-top: env(safe-area-inset-top); //for iOS 11.1 +} + +ion-app.app-root { + .safe-area-page, + .safe-padding-horizontal { + @include safe-area-padding-horizontal(0px, 0px); + } + + [padding].safe-padding-horizontal, + &.ios [padding].safe-padding-horizontal { + @include safe-area-padding-horizontal($content-padding, $content-padding); + } + + // Disable safe area padding. + ion-popover, + .safe-area-page, + .safe-padding-horizontal { + .item-ios.item-block { + @include padding-horizontal($item-ios-padding-end, null); + + .item-inner { + @include padding-horizontal(null, $item-ios-padding-end / 2); + } + } + } + + .item-ios[detail-push] .item-inner, + button.item-ios:not([detail-none]) .item-inner, + a.item-ios:not([detail-none]) .item-inner { + [item-end] { + @include safe-area-margin-horizontal(($item-ios-padding-start / 2), ($item-ios-padding-end / 2)); + } + } +} diff --git a/src/app/app.wp.scss b/src/app/app.wp.scss index b91fb4f10..97a8317ae 100644 --- a/src/app/app.wp.scss +++ b/src/app/app.wp.scss @@ -35,11 +35,15 @@ ion-app.app-root.wp { @extend .card-wp ; @extend .card-content-wp; + @include darkmode() { + background-color: $core-dark-item-bg-color; + } + &[icon-start] { - @include padding(null, null, null, $card-wp-padding-left * 2 + 20); + @include safe-area-padding(null, null, null, $card-wp-padding-left * 2 + 20); ion-icon { - @include position(null, null, null, $card-wp-padding-left); + @include safe-area-position(null, null, null, $card-wp-padding-left); } } } diff --git a/src/app/main.ts b/src/app/main.ts index 69be48980..23d30e15b 100644 --- a/src/app/main.ts +++ b/src/app/main.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/assets/exttomime.json b/src/assets/exttomime.json index 445a11777..bab000445 100644 --- a/src/assets/exttomime.json +++ b/src/assets/exttomime.json @@ -359,6 +359,7 @@ "h261": {"type":"video/h261"}, "h263": {"type":"video/h263"}, "h264": {"type":"video/h264"}, +"h5p": {"type":"application/zip","icon":"archive","string":"archive","groups":["archive"]}, "hal": {"type":"application/vnd.hal+xml"}, "hbci": {"type":"application/vnd.hbci"}, "hdf": {"type":"application/x-hdf"}, diff --git a/src/assets/img/icons/h5p.svg b/src/assets/img/icons/h5p.svg new file mode 100644 index 000000000..7856f9efb --- /dev/null +++ b/src/assets/img/icons/h5p.svg @@ -0,0 +1,14 @@ + + + + + + + + diff --git a/src/assets/img/scorm/asset.gif b/src/assets/img/scorm/asset.gif deleted file mode 100644 index 09fa56516..000000000 Binary files a/src/assets/img/scorm/asset.gif and /dev/null differ diff --git a/src/assets/img/scorm/browsed.gif b/src/assets/img/scorm/browsed.gif deleted file mode 100644 index ea5e04081..000000000 Binary files a/src/assets/img/scorm/browsed.gif and /dev/null differ diff --git a/src/assets/img/scorm/completed.gif b/src/assets/img/scorm/completed.gif deleted file mode 100644 index 536191528..000000000 Binary files a/src/assets/img/scorm/completed.gif and /dev/null differ diff --git a/src/assets/img/scorm/failed.gif b/src/assets/img/scorm/failed.gif deleted file mode 100644 index d3ca67772..000000000 Binary files a/src/assets/img/scorm/failed.gif and /dev/null differ diff --git a/src/assets/img/scorm/incomplete.gif b/src/assets/img/scorm/incomplete.gif deleted file mode 100644 index fe2c6ea9d..000000000 Binary files a/src/assets/img/scorm/incomplete.gif and /dev/null differ diff --git a/src/assets/img/scorm/notattempted.gif b/src/assets/img/scorm/notattempted.gif deleted file mode 100644 index 7dbb43be5..000000000 Binary files a/src/assets/img/scorm/notattempted.gif and /dev/null differ diff --git a/src/assets/img/scorm/passed.gif b/src/assets/img/scorm/passed.gif deleted file mode 100644 index 536191528..000000000 Binary files a/src/assets/img/scorm/passed.gif and /dev/null differ diff --git a/src/assets/img/scorm/suspend.gif b/src/assets/img/scorm/suspend.gif deleted file mode 100644 index 35e8413f9..000000000 Binary files a/src/assets/img/scorm/suspend.gif and /dev/null differ diff --git a/src/assets/lang/en.json b/src/assets/lang/en.json index 20ec0d25d..9ef5b650c 100644 --- a/src/assets/lang/en.json +++ b/src/assets/lang/en.json @@ -37,7 +37,8 @@ "addon.block_completionstatus.pluginname": "Course completion status", "addon.block_glossaryrandom.pluginname": "Random glossary entry", "addon.block_learningplans.pluginname": "Learning plans", - "addon.block_myoverview.all": "All (except removed from view)", + "addon.block_myoverview.all": "All (except hidden)", + "addon.block_myoverview.allincludinghidden": "All", "addon.block_myoverview.favourites": "Starred", "addon.block_myoverview.future": "Future", "addon.block_myoverview.hiddencourses": "Removed from view", @@ -88,8 +89,10 @@ "addon.calendar.calendarevent": "Calendar event", "addon.calendar.calendarevents": "Calendar events", "addon.calendar.calendarreminders": "Calendar reminders", + "addon.calendar.categoryevents": "Category events", "addon.calendar.confirmeventdelete": "Are you sure you want to delete the \"{{$a}}\" event?", "addon.calendar.confirmeventseriesdelete": "The \"{{$a.name}}\" event is part of a series. Do you want to delete just this event, or all {{$a.count}} events in the series?", + "addon.calendar.courseevents": "Course events", "addon.calendar.currentmonth": "Current Month", "addon.calendar.daynext": "Next day", "addon.calendar.dayprev": "Previous day", @@ -113,6 +116,7 @@ "addon.calendar.fri": "Fri", "addon.calendar.friday": "Friday", "addon.calendar.gotoactivity": "Go to activity", + "addon.calendar.groupevents": "Group events", "addon.calendar.invalidtimedurationminutes": "The duration in minutes you have entered is invalid. Please enter the duration in minutes greater than 0 or select no duration.", "addon.calendar.invalidtimedurationuntil": "The date and time you selected for duration until is before the start time of the event. Please correct this before proceeding.", "addon.calendar.mon": "Mon", @@ -130,6 +134,7 @@ "addon.calendar.sat": "Sat", "addon.calendar.saturday": "Saturday", "addon.calendar.setnewreminder": "Set a new reminder", + "addon.calendar.siteevents": "Site events", "addon.calendar.sun": "Sun", "addon.calendar.sunday": "Sunday", "addon.calendar.thu": "Thu", @@ -148,6 +153,7 @@ "addon.calendar.typesite": "Site event", "addon.calendar.typeuser": "User event", "addon.calendar.upcomingevents": "Upcoming events", + "addon.calendar.userevents": "User events", "addon.calendar.wed": "Wed", "addon.calendar.wednesday": "Wednesday", "addon.calendar.when": "When", @@ -382,7 +388,6 @@ "addon.mod_assign.numwords": "{{$a}} words", "addon.mod_assign.outof": "{{$a.current}} out of {{$a.total}}", "addon.mod_assign.overdue": "Assignment is overdue by: {{$a}}", - "addon.mod_assign.savechanges": "Save changes", "addon.mod_assign.submission": "Submission", "addon.mod_assign.submissioneditable": "Student can edit this submission", "addon.mod_assign.submissionnoteditable": "Student cannot edit this submission", @@ -404,6 +409,7 @@ "addon.mod_assign.timemodified": "Last modified", "addon.mod_assign.timeremaining": "Time remaining", "addon.mod_assign.ungroupedusers": "The setting 'Require group to make submission' is enabled and some users are either not a member of any group, or are a member of more than one group, so are unable to make submissions.", + "addon.mod_assign.ungroupedusersoptional": "The setting 'Students submit in groups' is enabled and some users are either not a member of any group, or are a member of more than one group. Please be aware that these students will submit as members of the 'Default group'.", "addon.mod_assign.unlimitedattempts": "Unlimited", "addon.mod_assign.userswhoneedtosubmit": "Users who need to submit: {{$a}}", "addon.mod_assign.userwithid": "User with ID {{id}}", @@ -432,14 +438,17 @@ "addon.mod_chat.errorwhilegettingchatusers": "Error while getting chat users.", "addon.mod_chat.errorwhileretrievingmessages": "Error while retrieving messages from the server.", "addon.mod_chat.errorwhilesendingmessage": "Error while sending the message.", + "addon.mod_chat.messagebeepseveryone": "{{$a}} beeps everyone!", "addon.mod_chat.messagebeepsyou": "{{$a}} has just beeped you!", "addon.mod_chat.messageenter": "{{$a}} has just entered this chat", "addon.mod_chat.messageexit": "{{$a}} has left this chat", "addon.mod_chat.messages": "Messages", + "addon.mod_chat.messageyoubeep": "You beeped {{$a}}", "addon.mod_chat.modulenameplural": "Chats", "addon.mod_chat.mustbeonlinetosendmessages": "You must be online to send messages.", "addon.mod_chat.nomessages": "No messages yet", "addon.mod_chat.nosessionsfound": "No sessions found", + "addon.mod_chat.saidto": "said to", "addon.mod_chat.send": "Send", "addon.mod_chat.sessionstart": "The next chat session will start on {{$a.date}}, ({{$a.fromnow}} from now)", "addon.mod_chat.showincompletesessions": "Show incomplete sessions", @@ -490,10 +499,13 @@ "addon.mod_data.expired": "Sorry, this activity closed on {{$a}} and is no longer available", "addon.mod_data.fields": "Fields", "addon.mod_data.foundrecords": "Found records: {{$a.num}}/{{$a.max}} (Reset filters)", + "addon.mod_data.gettinglocation": "Getting location", "addon.mod_data.latlongboth": "Both latitude and longitude are required.", + "addon.mod_data.locationpermissiondenied": "Permission to access your location has been denied.", "addon.mod_data.menuchoose": "Choose...", "addon.mod_data.modulenameplural": "Databases", "addon.mod_data.more": "More", + "addon.mod_data.mylocation": "My location", "addon.mod_data.nomatch": "No matching entries found!", "addon.mod_data.norecords": "No entries in database", "addon.mod_data.notapproved": "Entry is not approved yet.", @@ -559,7 +571,11 @@ "addon.mod_forum.cannotadddiscussionall": "You do not have permission to add a new discussion topic for all participants.", "addon.mod_forum.cannotcreatediscussion": "Could not create new discussion", "addon.mod_forum.couldnotadd": "Could not add your post due to an unknown error", + "addon.mod_forum.couldnotupdate": "Could not update your post due to an unknown error", "addon.mod_forum.cutoffdatereached": "The cut-off date for posting to this forum is reached so you can no longer post to it.", + "addon.mod_forum.delete": "Delete", + "addon.mod_forum.deletedpost": "The post has been deleted", + "addon.mod_forum.deletesure": "Are you sure you want to delete this post?", "addon.mod_forum.discussion": "Discussion", "addon.mod_forum.discussionlistsortbycreatedasc": "Sort by creation date in ascending order", "addon.mod_forum.discussionlistsortbycreateddesc": "Sort by creation date in descending order", @@ -579,6 +595,7 @@ "addon.mod_forum.favouriteupdated": "Your star option has been updated.", "addon.mod_forum.forumnodiscussionsyet": "There are no discussions yet in this forum.", "addon.mod_forum.group": "Group", + "addon.mod_forum.lastpost": "Last post", "addon.mod_forum.lockdiscussion": "Lock this discussion", "addon.mod_forum.lockupdated": "The lock option has been updated.", "addon.mod_forum.message": "Message", @@ -608,6 +625,7 @@ "addon.mod_forum.unpindiscussion": "Unpin this discussion", "addon.mod_forum.unread": "Unread", "addon.mod_forum.unreadpostsnumber": "{{$a}} unread posts", + "addon.mod_forum.yourreply": "Your reply", "addon.mod_glossary.addentry": "Add a new entry", "addon.mod_glossary.aliases": "Keyword(s)", "addon.mod_glossary.attachment": "Attachment", @@ -862,6 +880,7 @@ "addon.mod_scorm.organizations": "Organisations", "addon.mod_scorm.passed": "Passed", "addon.mod_scorm.reviewmode": "Review mode", + "addon.mod_scorm.score": "Score", "addon.mod_scorm.scormstatusnotdownloaded": "This SCORM package is not downloaded. It will be automatically downloaded when you open it.", "addon.mod_scorm.scormstatusoutdated": "This SCORM package has been modified since the last download. It will be automatically downloaded when you open it.", "addon.mod_scorm.suspended": "Suspended", @@ -1310,7 +1329,7 @@ "core.back": "Back", "core.block.blocks": "Blocks", "core.cancel": "Cancel", - "core.cannotconnect": "Cannot connect: Verify that you have correctly typed the URL and that your site uses Moodle 3.1 or later.", + "core.cannotconnect": "Cannot connect: Verify that you have correctly typed the URL and that your site uses Moodle {{$a}} or later.", "core.cannotdownloadfiles": "File downloading is disabled. Please contact your site administrator.", "core.captureaudio": "Record audio", "core.capturedimage": "Taken picture.", @@ -1372,6 +1391,7 @@ "core.course.confirmdeletemodulefiles": "Are you sure you want to delete these files?", "core.course.confirmdownload": "You are about to download {{size}}.{{availableSpace}} Are you sure you want to continue?", "core.course.confirmdownloadunknownsize": "It was not possible to calculate the size of the download.{{availableSpace}} Are you sure you want to continue?", + "core.course.confirmdownloadzerosize": "You are about to start downloading.{{availableSpace}} Are you sure you want to continue?", "core.course.confirmlimiteddownload": "You are not currently connected to Wi-Fi. ", "core.course.confirmpartialdownloadsize": "You are about to download at least {{size}}.{{availableSpace}} Are you sure you want to continue?", "core.course.contents": "Contents", @@ -1510,6 +1530,7 @@ "core.fileuploader.uploading": "Uploading", "core.fileuploader.uploadingperc": "Uploading: {{$a}}%", "core.fileuploader.video": "Video", + "core.filter": "Filter", "core.folder": "Folder", "core.forcepasswordchangenotice": "You must change your password to proceed.", "core.fulllistofcourses": "All courses", @@ -1531,6 +1552,95 @@ "core.group": "Group", "core.groupsseparate": "Separate groups", "core.groupsvisible": "Visible groups", + "core.h5p.additionallicenseinfo": "Any additional information about the license", + "core.h5p.author": "Author", + "core.h5p.authorcomments": "Author comments", + "core.h5p.authorcommentsdescription": "Comments for the editor of the content. (This text will not be published as a part of the copyright info.)", + "core.h5p.authorname": "Author's name", + "core.h5p.authorrole": "Author's role", + "core.h5p.by": "by", + "core.h5p.cancellabel": "Cancel", + "core.h5p.ccattribution": "Attribution (CC BY)", + "core.h5p.ccattributionnc": "Attribution-NonCommercial (CC BY-NC)", + "core.h5p.ccattributionncnd": "Attribution-NonCommercial-NoDerivs (CC BY-NC-ND)", + "core.h5p.ccattributionncsa": "Attribution-NonCommercial-ShareAlike (CC BY-NC-SA)", + "core.h5p.ccattributionnd": "Attribution-NoDerivs (CC BY-ND)", + "core.h5p.ccattributionsa": "Attribution-ShareAlike (CC BY-SA)", + "core.h5p.ccpdd": "Public Domain Dedication (CC0)", + "core.h5p.changedby": "Changed by", + "core.h5p.changedescription": "Description of change", + "core.h5p.changelog": "Changelog", + "core.h5p.changeplaceholder": "Photo cropped, text changed, etc.", + "core.h5p.close": "Close", + "core.h5p.confirmdialogbody": "Please confirm that you wish to proceed. This action is not reversible.", + "core.h5p.confirmdialogheader": "Confirm action", + "core.h5p.confirmlabel": "Confirm", + "core.h5p.connectionLost": "Connection lost. Results will be stored and sent when you regain connection.", + "core.h5p.connectionReestablished": "Connection reestablished.", + "core.h5p.contentCopied": "Content is copied to the clipboard", + "core.h5p.contentchanged": "This content has changed since you last used it.", + "core.h5p.contenttype": "Content Type", + "core.h5p.copyright": "Rights of use", + "core.h5p.copyrightinfo": "Copyright information", + "core.h5p.copyrightstring": "Copyright", + "core.h5p.copyrighttitle": "View copyright information for this content.", + "core.h5p.creativecommons": "Creative Commons", + "core.h5p.date": "Date", + "core.h5p.disablefullscreen": "Disable fullscreen", + "core.h5p.download": "Download", + "core.h5p.downloadtitle": "Download this content as a H5P file.", + "core.h5p.editor": "Editor", + "core.h5p.embed": "Embed", + "core.h5p.embedtitle": "View the embed code for this content.", + "core.h5p.fullscreen": "Fullscreen", + "core.h5p.gpl": "General Public License v3", + "core.h5p.h5ptitle": "Visit H5P.org to check out more cool content.", + "core.h5p.hideadvanced": "Hide advanced", + "core.h5p.license": "License", + "core.h5p.licenseCC010": "CC0 1.0 Universal (CC0 1.0) Public Domain Dedication", + "core.h5p.licenseCC010U": "CC0 1.0 Universal", + "core.h5p.licenseCC10": "1.0 Generic", + "core.h5p.licenseCC20": "2.0 Generic", + "core.h5p.licenseCC25": "2.5 Generic", + "core.h5p.licenseCC30": "3.0 Unported", + "core.h5p.licenseCC40": "4.0 International", + "core.h5p.licenseGPL": "General Public License", + "core.h5p.licenseV1": "Version 1", + "core.h5p.licenseV2": "Version 2", + "core.h5p.licenseV3": "Version 3", + "core.h5p.licensee": "Licensee", + "core.h5p.licenseextras": "License Extras", + "core.h5p.licenseversion": "License version", + "core.h5p.nocopyright": "No copyright information available for this content.", + "core.h5p.offlineDialogBody": "We were unable to send information about your completion of this task. Please check your internet connection.", + "core.h5p.offlineDialogHeader": "Your connection to the server was lost", + "core.h5p.offlineDialogRetryButtonLabel": "Retry now", + "core.h5p.offlineDialogRetryMessage": "Retrying in :num....", + "core.h5p.offlineSuccessfulSubmit": "Successfully submitted results.", + "core.h5p.originator": "Originator", + "core.h5p.pd": "Public Domain", + "core.h5p.pddl": "Public Domain Dedication and Licence", + "core.h5p.pdm": "Public Domain Mark (PDM)", + "core.h5p.play": "Play H5P", + "core.h5p.resizescript": "Include this script on your website if you want dynamic sizing of the embedded content:", + "core.h5p.resubmitScores": "Attempting to submit stored results.", + "core.h5p.reuse": "Reuse", + "core.h5p.reuseContent": "Reuse Content", + "core.h5p.reuseDescription": "Reuse this content.", + "core.h5p.showadvanced": "Show advanced", + "core.h5p.showless": "Show less", + "core.h5p.showmore": "Show more", + "core.h5p.size": "Size", + "core.h5p.source": "Source", + "core.h5p.startingover": "You'll be starting over.", + "core.h5p.sublevel": "Sublevel", + "core.h5p.thumbnail": "Thumbnail", + "core.h5p.title": "Title", + "core.h5p.undisclosed": "Undisclosed", + "core.h5p.year": "Year", + "core.h5p.years": "Year(s)", + "core.h5p.yearsfrom": "Years (from)", + "core.h5p.yearsto": "Years (to)", "core.hasdatatosync": "This {{$a}} has offline data to be synchronised.", "core.help": "Help", "core.hide": "Hide", @@ -1556,7 +1666,12 @@ "core.login.auth_email": "Email-based self-registration", "core.login.authenticating": "Authenticating", "core.login.cancel": "Cancel", - "core.login.checksiteversion": "Check that your site uses Moodle 3.1 or later.", + "core.login.changepassword": "Change password", + "core.login.changepasswordbutton": "Open the change password page", + "core.login.changepasswordhelp": "If you have problems changing your password, please contact your site administrator. \"Site Administrators\" are the people who manages the Moodle at your school/university/company or learning organisation. If you don't know how to contact them, please contact your teachers/trainers.", + "core.login.changepasswordinstructions": "You cannot change your password in the app. Please click the following button to open the site in a web browser to change your password. Take into account you need to close the browser after changing the password as you will not be redirected to the app.", + "core.login.changepasswordlogoutinstructions": "If you prefer to change site or log out, please click the following button:", + "core.login.changepasswordreconnectinstructions": "Click the following button to reconnect to the site. (Take into account that if you didn't change your password successfully, you would return to the previous screen).", "core.login.confirmdeletesite": "Are you sure you want to delete the site {{sitename}}?", "core.login.connect": "Connect!", "core.login.connecttomoodle": "Connect to Moodle", @@ -1577,6 +1692,7 @@ "core.login.errorupdatesite": "An error occurred while updating the site's token.", "core.login.findyoursite": "Find your site", "core.login.firsttime": "Is this your first time here?", + "core.login.forcepasswordchangenotice": "You must change your password to proceed.", "core.login.forgotten": "Forgotten your username or password?", "core.login.getanothercaptcha": "Get another CAPTCHA", "core.login.help": "Help", @@ -1585,7 +1701,7 @@ "core.login.invalidaccount": "Please check your login details or ask your site administrator to check the site configuration.", "core.login.invaliddate": "Invalid date", "core.login.invalidemail": "Invalid email address", - "core.login.invalidmoodleversion": "Invalid Moodle version. The minimum version required is 3.1.", + "core.login.invalidmoodleversion": "

Invalid Moodle site version. The Moodle app only supports Moodle systems {{$a}} onwards.

\n

You can contact your site administrators and ask them to update their Moodle system.

\n

\"Site Administrators\" are the people who manages the Moodle at your school/university/company or learning organisation. If you don't know how to contact them, please contact your teachers/trainers.

", "core.login.invalidsite": "The site URL is invalid.", "core.login.invalidtime": "Invalid time", "core.login.invalidurl": "Invalid URL specified", @@ -1692,6 +1808,7 @@ "core.nocomments": "No comments", "core.nograde": "No grade", "core.none": "None", + "core.nooptionavailable": "No option available", "core.nopasswordchangeforced": "You cannot proceed without changing your password.", "core.nopermissionerror": "Sorry, but you do not currently have permissions to do that", "core.nopermissions": "Sorry, but you do not currently have permissions to do that ({{$a}}).", @@ -1752,6 +1869,7 @@ "core.redirectingtosite": "You will be redirected to the site.", "core.refresh": "Refresh", "core.remove": "Remove", + "core.removefiles": "Remove files {{$a}}", "core.required": "Required", "core.requireduserdatamissing": "This user lacks some required profile data. Please enter the data in your site and try again.
{{$a}}", "core.resourcedisplayopen": "Open", @@ -1760,6 +1878,7 @@ "core.restricted": "Restricted", "core.retry": "Retry", "core.save": "Save", + "core.savechanges": "Save changes", "core.search": "Search", "core.searching": "Searching", "core.searchresults": "Search results", @@ -1776,6 +1895,10 @@ "core.settings.appready": "App ready", "core.settings.cannotsyncoffline": "Cannot synchronise offline.", "core.settings.cannotsyncwithoutwifi": "Cannot synchronise because the current settings only allow to synchronise when connected to Wi-Fi. Please connect to a Wi-Fi network.", + "core.settings.colorscheme": "Color Scheme", + "core.settings.colorscheme-auto": "Auto (based on system settings)", + "core.settings.colorscheme-dark": "Dark", + "core.settings.colorscheme-light": "Light", "core.settings.compilationinfo": "Compilation info", "core.settings.cordovadevicemodel": "Cordova device model", "core.settings.cordovadeviceosversion": "Cordova device OS version", @@ -1903,6 +2026,8 @@ "core.unknown": "Unknown", "core.unlimited": "Unlimited", "core.unzipping": "Unzipping", + "core.updaterequired": "App update required", + "core.updaterequireddesc": "Please update your app to version {{$a}}", "core.upgraderunning": "Site is being upgraded, please retry later.", "core.user": "User", "core.user.address": "Address", @@ -1949,5 +2074,7 @@ "core.wsfunctionnotavailable": "The web service function is not available.", "core.year": "year", "core.years": "years", - "core.yes": "Yes" + "core.yes": "Yes", + "core.youreoffline": "You are offline", + "core.youreonline": "You are back online" } \ No newline at end of file diff --git a/src/classes/animations.ts b/src/classes/animations.ts index cb28a609b..361335b33 100644 --- a/src/classes/animations.ts +++ b/src/classes/animations.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/classes/base-sync.ts b/src/classes/base-sync.ts index d247783da..14bea2fd7 100644 --- a/src/classes/base-sync.ts +++ b/src/classes/base-sync.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -27,19 +27,16 @@ export class CoreSyncBaseProvider { /** * Logger instance get from CoreLoggerProvider. - * @type {any} */ protected logger; /** * Component of the sync provider. - * @type {string} */ component = 'core'; /** * Sync provider's interval. - * @type {number} */ syncInterval = 300000; @@ -58,10 +55,10 @@ export class CoreSyncBaseProvider { /** * Add an ongoing sync to the syncPromises list. On finish the promise will be removed. * - * @param {string | number} id Unique sync identifier per component. - * @param {Promise} promise The promise of the sync to add. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} The sync promise. + * @param id Unique sync identifier per component. + * @param promise The promise of the sync to add. + * @param siteId Site ID. If not defined, current site. + * @return The sync promise. */ addOngoingSync(id: string | number, promise: Promise, siteId?: string): Promise { siteId = siteId || this.sitesProvider.getCurrentSiteId(); @@ -82,9 +79,9 @@ export class CoreSyncBaseProvider { /** * If there's an ongoing sync for a certain identifier return it. * - * @param {string | number} id Unique sync identifier per component. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise of the current sync or undefined if there isn't any. + * @param id Unique sync identifier per component. + * @param siteId Site ID. If not defined, current site. + * @return Promise of the current sync or undefined if there isn't any. */ getOngoingSync(id: string | number, siteId?: string): Promise { siteId = siteId || this.sitesProvider.getCurrentSiteId(); @@ -100,9 +97,9 @@ export class CoreSyncBaseProvider { /** * Get the synchronization time in a human readable format. * - * @param {string | number} id Unique sync identifier per component. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with the readable time. + * @param id Unique sync identifier per component. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the readable time. */ getReadableSyncTime(id: string | number, siteId?: string): Promise { return this.getSyncTime(id, siteId).then((time) => { @@ -113,8 +110,8 @@ export class CoreSyncBaseProvider { /** * Given a timestamp return it in a human readable format. * - * @param {number} timestamp Timestamp - * @return {string} Human readable time. + * @param timestamp Timestamp + * @return Human readable time. */ getReadableTimeFromTimestamp(timestamp: number): string { if (!timestamp) { @@ -127,9 +124,9 @@ export class CoreSyncBaseProvider { /** * Get the synchronization time. Returns 0 if no time stored. * - * @param {string | number} id Unique sync identifier per component. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with the time. + * @param id Unique sync identifier per component. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the time. */ getSyncTime(id: string | number, siteId?: string): Promise { return this.syncProvider.getSyncRecord(this.component, id, siteId).then((entry) => { @@ -142,9 +139,9 @@ export class CoreSyncBaseProvider { /** * Get the synchronization warnings of an instance. * - * @param {string | number} id Unique sync identifier per component. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with the warnings. + * @param id Unique sync identifier per component. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the warnings. */ getSyncWarnings(id: string | number, siteId?: string): Promise { return this.syncProvider.getSyncRecord(this.component, id, siteId).then((entry) => { @@ -157,8 +154,8 @@ export class CoreSyncBaseProvider { /** * Create a unique identifier from component and id. * - * @param {string | number} id Unique sync identifier per component. - * @return {string} Unique identifier from component and id. + * @param id Unique sync identifier per component. + * @return Unique identifier from component and id. */ protected getUniqueSyncId(id: string | number): string { return this.component + '#' + id; @@ -167,9 +164,9 @@ export class CoreSyncBaseProvider { /** * Check if a there's an ongoing syncronization for the given id. * - * @param {string | number} id Unique sync identifier per component. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {boolean} Whether it's synchronizing. + * @param id Unique sync identifier per component. + * @param siteId Site ID. If not defined, current site. + * @return Whether it's synchronizing. */ isSyncing(id: string | number, siteId?: string): boolean { siteId = siteId || this.sitesProvider.getCurrentSiteId(); @@ -182,9 +179,9 @@ export class CoreSyncBaseProvider { /** * Check if a sync is needed: if a certain time has passed since the last time. * - * @param {string | number} id Unique sync identifier per component. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with boolean: whether sync is needed. + * @param id Unique sync identifier per component. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with boolean: whether sync is needed. */ isSyncNeeded(id: string | number, siteId?: string): Promise { return this.getSyncTime(id, siteId).then((time) => { @@ -195,10 +192,10 @@ export class CoreSyncBaseProvider { /** * Set the synchronization time. * - * @param {string | number} id Unique sync identifier per component. - * @param {string} [siteId] Site ID. If not defined, current site. - * @param {number} [time] Time to set. If not defined, current time. - * @return {Promise} Promise resolved when the time is set. + * @param id Unique sync identifier per component. + * @param siteId Site ID. If not defined, current site. + * @param time Time to set. If not defined, current time. + * @return Promise resolved when the time is set. */ setSyncTime(id: string | number, siteId?: string, time?: number): Promise { time = typeof time != 'undefined' ? time : Date.now(); @@ -209,10 +206,10 @@ export class CoreSyncBaseProvider { /** * Set the synchronization warnings. * - * @param {string | number} id Unique sync identifier per component. - * @param {string[]} warnings Warnings to set. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when done. + * @param id Unique sync identifier per component. + * @param warnings Warnings to set. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when done. */ setSyncWarnings(id: string | number, warnings: string[], siteId?: string): Promise { const warningsText = JSON.stringify(warnings || []); @@ -223,11 +220,11 @@ export class CoreSyncBaseProvider { /** * Execute a sync function on selected sites. * - * @param {string} syncFunctionLog Log message to explain the sync function purpose. - * @param {Function} syncFunction Sync function to execute. - * @param {any[]} [params] Array that defines the params that admit the funcion. - * @param {string} [siteId] Site ID to sync. If not defined, sync all sites. - * @return {Promise} Resolved with siteIds selected. Rejected if offline. + * @param syncFunctionLog Log message to explain the sync function purpose. + * @param syncFunction Sync function to execute. + * @param params Array that defines the params that admit the funcion. + * @param siteId Site ID to sync. If not defined, sync all sites. + * @return Resolved with siteIds selected. Rejected if offline. */ syncOnSites(syncFunctionLog: string, syncFunction: Function, params?: any[], siteId?: string): Promise { if (!this.appProvider.isOnline()) { @@ -263,9 +260,9 @@ export class CoreSyncBaseProvider { * If there's an ongoing sync for a certain identifier, wait for it to end. * If there's no sync ongoing the promise will be resolved right away. * - * @param {string | number} id Unique sync identifier per component. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when there's no sync going on for the identifier. + * @param id Unique sync identifier per component. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when there's no sync going on for the identifier. */ waitForSync(id: string | number, siteId?: string): Promise { const promise = this.getOngoingSync(id, siteId); diff --git a/src/classes/cache.ts b/src/classes/cache.ts index 38ba1f3c8..bc1f905ce 100644 --- a/src/classes/cache.ts +++ b/src/classes/cache.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -37,8 +37,8 @@ export class CoreCache { /** * Get all the data stored in the cache for a certain id. * - * @param {any} id The ID to identify the entry. - * @return {any} The data from the cache. Undefined if not found. + * @param id The ID to identify the entry. + * @return The data from the cache. Undefined if not found. */ getEntry(id: any): any { if (!this.cacheStore[id]) { @@ -51,10 +51,10 @@ export class CoreCache { /** * Get the status of a module from the "cache". * - * @param {any} id The ID to identify the entry. - * @param {string} name Name of the value to get. - * @param {boolean} [ignoreInvalidate] Whether it should always return the cached data, even if it's expired. - * @return {any} Cached value. Undefined if not cached or expired. + * @param id The ID to identify the entry. + * @param name Name of the value to get. + * @param ignoreInvalidate Whether it should always return the cached data, even if it's expired. + * @return Cached value. Undefined if not cached or expired. */ getValue(id: any, name: string, ignoreInvalidate?: boolean): any { const entry = this.getEntry(id); @@ -73,7 +73,7 @@ export class CoreCache { /** * Invalidate all the cached data for a certain entry. * - * @param {any} id The ID to identify the entry. + * @param id The ID to identify the entry. */ invalidate(id: any): void { const entry = this.getEntry(id); @@ -85,10 +85,10 @@ export class CoreCache { /** * Update the status of a module in the "cache". * - * @param {any} id The ID to identify the entry. - * @param {string} name Name of the value to set. - * @param {any} value Value to set. - * @return {any} The set value. + * @param id The ID to identify the entry. + * @param name Name of the value to set. + * @param value Value to set. + * @return The set value. */ setValue(id: any, name: string, value: any): any { const entry = this.getEntry(id); diff --git a/src/classes/delegate.ts b/src/classes/delegate.ts index 5710cbdd9..e342c1e62 100644 --- a/src/classes/delegate.ts +++ b/src/classes/delegate.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -21,13 +21,12 @@ export interface CoreDelegateHandler { /** * Name of the handler, or name and sub context (AddonMessages, AddonMessages:blockContact, ...). * This name will be used to check if the feature is disabled. - * @type {string} */ name: string; /** * Whether or not the handler is enabled on a site level. - * @return {boolean|Promise} Whether or not the handler is enabled on a site level. + * @return Whether or not the handler is enabled on a site level. */ isEnabled(): boolean | Promise; } @@ -39,38 +38,32 @@ export class CoreDelegate { /** * Logger instance get from CoreLoggerProvider. - * @type {any} */ protected logger; /** * List of registered handlers. - * @type {any} */ protected handlers: { [s: string]: CoreDelegateHandler } = {}; /** * List of registered handlers enabled for the current site. - * @type {any} */ protected enabledHandlers: { [s: string]: CoreDelegateHandler } = {}; /** * Default handler - * @type {CoreDelegateHandler} */ protected defaultHandler: CoreDelegateHandler; /** * Time when last updateHandler functions started. - * @type {number} */ protected lastUpdateHandlersStart: number; /** * Feature prefix to check is feature is enabled or disabled in site. * This check is only made if not false. Override on the subclass or override isFeatureDisabled function. - * @type {string} */ protected featurePrefix: string; @@ -78,29 +71,46 @@ export class CoreDelegate { * Name of the property to be used to index the handlers. By default, the handler's name will be used. * If your delegate uses a Moodle component name to identify the handlers, please override this property. * E.g. CoreCourseModuleDelegate uses 'modName' to index the handlers. - * @type {string} */ protected handlerNameProperty = 'name'; /** * Set of promises to update a handler, to prevent doing the same operation twice. - * @type {{[siteId: string]: {[name: string]: Promise}}} */ protected updatePromises: {[siteId: string]: {[name: string]: Promise}} = {}; + /** + * Whether handlers have been initialized. + */ + protected handlersInitialized = false; + + /** + * Promise to wait for handlers to be initialized. + */ + protected handlersInitPromise: Promise; + + /** + * Function to resolve the handlers init promise. + */ + protected handlersInitResolve: (value?: any) => void; + /** * Constructor of the Delegate. * - * @param {string} delegateName Delegate name used for logging purposes. - * @param {CoreLoggerProvider} loggerProvider CoreLoggerProvider instance, cannot be directly injected. - * @param {CoreSitesProvider} sitesProvider CoreSitesProvider instance, cannot be directly injected. - * @param {CoreEventsProvider} [eventsProvider] CoreEventsProvider instance, cannot be directly injected. - * If not set, no events will be fired. + * @param delegateName Delegate name used for logging purposes. + * @param loggerProvider CoreLoggerProvider instance, cannot be directly injected. + * @param sitesProvider CoreSitesProvider instance, cannot be directly injected. + * @param eventsProvider CoreEventsProvider instance, cannot be directly injected. + * If not set, no events will be fired. */ constructor(delegateName: string, protected loggerProvider: CoreLoggerProvider, protected sitesProvider: CoreSitesProvider, protected eventsProvider?: CoreEventsProvider) { this.logger = this.loggerProvider.getInstance(delegateName); + this.handlersInitPromise = new Promise((resolve): void => { + this.handlersInitResolve = resolve; + }); + if (eventsProvider) { // Update handlers on this cases. eventsProvider.on(CoreEventsProvider.LOGIN, this.updateHandlers.bind(this)); @@ -113,10 +123,10 @@ export class CoreDelegate { * Execute a certain function in a enabled handler. * If the handler isn't found or function isn't defined, call the same function in the default handler. * - * @param {string} handlerName The handler name. - * @param {string} fnName Name of the function to execute. - * @param {any[]} params Parameters to pass to the function. - * @return {any} Function returned value or default value. + * @param handlerName The handler name. + * @param fnName Name of the function to execute. + * @param params Parameters to pass to the function. + * @return Function returned value or default value. */ protected executeFunctionOnEnabled(handlerName: string, fnName: string, params?: any[]): any { return this.execute(this.enabledHandlers[handlerName], fnName, params); @@ -126,10 +136,10 @@ export class CoreDelegate { * Execute a certain function in a handler. * If the handler isn't found or function isn't defined, call the same function in the default handler. * - * @param {string} handlerName The handler name. - * @param {string} fnName Name of the function to execute. - * @param {any[]} params Parameters to pass to the function. - * @return {any} Function returned value or default value. + * @param handlerName The handler name. + * @param fnName Name of the function to execute. + * @param params Parameters to pass to the function. + * @return Function returned value or default value. */ protected executeFunction(handlerName: string, fnName: string, params?: any[]): any { return this.execute(this.handlers[handlerName], fnName, params); @@ -139,10 +149,10 @@ export class CoreDelegate { * Execute a certain function in a handler. * If the handler isn't found or function isn't defined, call the same function in the default handler. * - * @param {any} handler The handler. - * @param {string} fnName Name of the function to execute. - * @param {any[]} params Parameters to pass to the function. - * @return {any} Function returned value or default value. + * @param handler The handler. + * @param fnName Name of the function to execute. + * @param params Parameters to pass to the function. + * @return Function returned value or default value. */ private execute(handler: any, fnName: string, params?: any[]): any { if (handler && handler[fnName]) { @@ -155,9 +165,9 @@ export class CoreDelegate { /** * Get a handler. * - * @param {string} handlerName The handler name. - * @param {boolean} [enabled] Only enabled, or any. - * @return {CoreDelegateHandler} Handler. + * @param handlerName The handler name. + * @param enabled Only enabled, or any. + * @return Handler. */ protected getHandler(handlerName: string, enabled: boolean = false): CoreDelegateHandler { return enabled ? this.enabledHandlers[handlerName] : this.handlers[handlerName]; @@ -167,8 +177,8 @@ export class CoreDelegate { * Gets the handler full name for a given name. This is useful when the handlerNameProperty is different than "name". * E.g. blocks are indexed by blockName. If you call this function passing the blockName it will return the name. * - * @param {string} name Name used to indentify the handler. - * @return {string} Full name of corresponding handler. + * @param name Name used to indentify the handler. + * @return Full name of corresponding handler. */ getHandlerName(name: string): string { const handler = this.getHandler(name, true); @@ -183,10 +193,10 @@ export class CoreDelegate { /** * Check if function exists on a handler. * - * @param {string} handlerName The handler name. - * @param {string} fnName Name of the function to execute. - * @param {booealn} [onlyEnabled=true] If check only enabled handlers or all. - * @return {any} Function returned value or default value. + * @param handlerName The handler name. + * @param fnName Name of the function to execute. + * @param onlyEnabled If check only enabled handlers or all. + * @return Function returned value or default value. */ protected hasFunction(handlerName: string, fnName: string, onlyEnabled: boolean = true): any { const handler = onlyEnabled ? this.enabledHandlers[handlerName] : this.handlers[handlerName]; @@ -197,9 +207,9 @@ export class CoreDelegate { /** * Check if a handler name has a registered handler (not necessarily enabled). * - * @param {string} name The handler name. - * @param {boolean} [enabled] Only enabled, or any. - * @return {boolean} If the handler is registered or not. + * @param name The handler name. + * @param enabled Only enabled, or any. + * @return If the handler is registered or not. */ hasHandler(name: string, enabled: boolean = false): boolean { return enabled ? typeof this.enabledHandlers[name] !== 'undefined' : typeof this.handlers[name] !== 'undefined'; @@ -209,8 +219,8 @@ export class CoreDelegate { * Check if a time belongs to the last update handlers call. * This is to handle the cases where updateHandlers don't finish in the same order as they're called. * - * @param {number} time Time to check. - * @return {boolean} Whether it's the last call. + * @param time Time to check. + * @return Whether it's the last call. */ isLastUpdateCall(time: number): boolean { if (!this.lastUpdateHandlersStart) { @@ -223,18 +233,20 @@ export class CoreDelegate { /** * Register a handler. * - * @param {CoreDelegateHandler} handler The handler delegate object to register. - * @return {boolean} True when registered, false if already registered. + * @param handler The handler delegate object to register. + * @return True when registered, false if already registered. */ registerHandler(handler: CoreDelegateHandler): boolean { - if (typeof this.handlers[handler[this.handlerNameProperty]] !== 'undefined') { + const key = handler[this.handlerNameProperty] || handler.name; + + if (typeof this.handlers[key] !== 'undefined') { this.logger.log(`Handler '${handler[this.handlerNameProperty]}' already registered`); return false; } this.logger.log(`Registered handler '${handler[this.handlerNameProperty]}'`); - this.handlers[handler[this.handlerNameProperty]] = handler; + this.handlers[key] = handler; return true; } @@ -242,9 +254,9 @@ export class CoreDelegate { /** * Update the handler for the current site. * - * @param {CoreDelegateHandler} handler The handler to check. - * @param {number} time Time this update process started. - * @return {Promise} Resolved when done. + * @param handler The handler to check. + * @param time Time this update process started. + * @return Resolved when done. */ protected updateHandler(handler: CoreDelegateHandler, time: number): Promise { const siteId = this.sitesProvider.getCurrentSiteId(), @@ -272,10 +284,12 @@ export class CoreDelegate { }).then((enabled: boolean) => { // Check that site hasn't changed since the check started. if (this.sitesProvider.getCurrentSiteId() === siteId) { + const key = handler[this.handlerNameProperty] || handler.name; + if (enabled) { - this.enabledHandlers[handler[this.handlerNameProperty]] = handler; + this.enabledHandlers[key] = handler; } else { - delete this.enabledHandlers[handler[this.handlerNameProperty]]; + delete this.enabledHandlers[key]; } } }).finally(() => { @@ -289,9 +303,9 @@ export class CoreDelegate { /** * Check if feature is enabled or disabled in the site, depending on the feature prefix and the handler name. * - * @param {CoreDelegateHandler} handler Handler to check. - * @param {CoreSite} site Site to check. - * @return {boolean} Whether is enabled or disabled in site. + * @param handler Handler to check. + * @param site Site to check. + * @return Whether is enabled or disabled in site. */ protected isFeatureDisabled(handler: CoreDelegateHandler, site: CoreSite): boolean { return typeof this.featurePrefix != 'undefined' && site.isFeatureDisabled(this.featurePrefix + handler.name); @@ -300,7 +314,7 @@ export class CoreDelegate { /** * Update the handlers for the current site. * - * @return {Promise} Resolved when done. + * @return Resolved when done. */ protected updateHandlers(): Promise { const promises = [], @@ -324,6 +338,9 @@ export class CoreDelegate { // Verify that this call is the last one that was started. if (this.isLastUpdateCall(now)) { + this.handlersInitialized = true; + this.handlersInitResolve(); + this.updateData(); } }); diff --git a/src/classes/interceptor.ts b/src/classes/interceptor.ts index 35e5c4597..81a1f45c9 100644 --- a/src/classes/interceptor.ts +++ b/src/classes/interceptor.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -42,9 +42,9 @@ export class CoreInterceptor implements HttpInterceptor { /** * Serialize an object to be used in a request. * - * @param {any} obj Object to serialize. - * @param {boolean} [addNull] Add null values to the serialized as empty parameters. - * @return {string} Serialization of the object. + * @param obj Object to serialize. + * @param addNull Add null values to the serialized as empty parameters. + * @return Serialization of the object. */ static serialize(obj: any, addNull?: boolean): string { let query = '', diff --git a/src/classes/modal-lateral-transition.ts b/src/classes/modal-lateral-transition.ts index 69d4db3c0..33856a157 100644 --- a/src/classes/modal-lateral-transition.ts +++ b/src/classes/modal-lateral-transition.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/classes/page-transition.ts b/src/classes/page-transition.ts index 575e08c1e..50c2329ff 100644 --- a/src/classes/page-transition.ts +++ b/src/classes/page-transition.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/classes/site.ts b/src/classes/site.ts index 1fddd5770..dcf06ef29 100644 --- a/src/classes/site.ts +++ b/src/classes/site.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -38,98 +38,82 @@ import { InAppBrowserObject } from '@ionic-native/in-app-browser'; export interface CoreSiteWSPreSets { /** * Get the value from the cache if it's still valid. - * @type {boolean} */ getFromCache?: boolean; /** * Save the result to the cache. - * @type {boolean} */ saveToCache?: boolean; /** * Ignore cache expiration. - * @type {boolean} */ omitExpires?: boolean; /** * Use the cache when a request fails. Defaults to true. - * @type {boolean} */ emergencyCache?: boolean; /** * If true, the app won't call the WS. If the data isn't cached, the call will fail. - * @type {boolean} */ forceOffline?: boolean; /** * Extra key to add to the cache when storing this call, to identify the entry. - * @type {string} */ cacheKey?: string; /** * Whether it should use cache key to retrieve the cached data instead of the request params. - * @type {boolean} */ getCacheUsingCacheKey?: boolean; /** * Same as getCacheUsingCacheKey, but for emergency cache. - * @type {boolean} */ getEmergencyCacheUsingCacheKey?: boolean; /** * If true, the cache entry will be deleted if the WS call returns an exception. - * @type {boolean} */ deleteCacheIfWSError?: boolean; /** * Whether it should only be 1 entry for this cache key (all entries with same key will be deleted). - * @type {boolean} */ uniqueCacheKey?: boolean; /** * Whether to filter WS response (moodlewssettingfilter). Defaults to true. - * @type {boolean} */ filter?: boolean; /** * Whether to rewrite URLs (moodlewssettingfileurl). Defaults to true. - * @type {boolean} */ rewriteurls?: boolean; /** * Defaults to true. Set to false when the expected response is null. - * @type {boolean} */ responseExpected?: boolean; /** * Defaults to 'object'. Use it when you expect a type that's not an object|array. - * @type {string} */ typeExpected?: string; /** * Wehther a pending request in the queue matching the same function and arguments can be reused instead of adding * a new request to the queue. Defaults to true for read requests. - * @type {boolean} */ reusePending?: boolean; /** * Whether the request will be be sent immediately as a single request. Defaults to false. - * @type {boolean} */ skipQueue?: boolean; @@ -142,7 +126,6 @@ export interface CoreSiteWSPreSets { * Update frequency. This value determines how often the cached data will be updated. Possible values: * CoreSite.FREQUENCY_USUALLY, CoreSite.FREQUENCY_OFTEN, CoreSite.FREQUENCY_SOMETIMES, CoreSite.FREQUENCY_RARELY. * Defaults to CoreSite.FREQUENCY_USUALLY. - * @type {number} */ updateFrequency?: number; } @@ -153,25 +136,21 @@ export interface CoreSiteWSPreSets { export interface LocalMobileResponse { /** * Code to identify the authentication method to use. - * @type {number} */ code: number; /** * Name of the service to use. - * @type {string} */ service?: string; /** * Code of the warning message. - * @type {string} */ warning?: string; /** * Whether core SSO is supported. - * @type {boolean} */ coreSupported?: boolean; } @@ -233,6 +212,7 @@ export class CoreSite { 3.6: 2018120300, 3.7: 2019052000 }; + static MINIMUM_MOODLE_VERSION = '3.1'; // Possible cache update frequencies. protected UPDATE_FREQUENCIES = [ @@ -251,18 +231,20 @@ export class CoreSite { protected ongoingRequests: { [cacheId: string]: Promise } = {}; protected requestQueue: RequestQueueItem[] = []; protected requestQueueTimeout = null; + protected tokenPluginFileWorks: boolean; + protected tokenPluginFileWorksPromise: Promise; /** * Create a site. * - * @param {Injector} injector Angular injector to prevent having to pass all the required services. - * @param {string} id Site ID. - * @param {string} siteUrl Site URL. - * @param {string} [token] Site's WS token. - * @param {any} [info] Site info. - * @param {string} [privateToken] Private token. - * @param {any} [config] Site public config. - * @param {boolean} [loggedOut] Whether user is logged out. + * @param injector Angular injector to prevent having to pass all the required services. + * @param id Site ID. + * @param siteUrl Site URL. + * @param token Site's WS token. + * @param info Site info. + * @param privateToken Private token. + * @param config Site public config. + * @param loggedOut Whether user is logged out. */ constructor(injector: Injector, public id: string, public siteUrl: string, public token?: string, public infos?: any, public privateToken?: string, public config?: any, public loggedOut?: boolean) { @@ -300,7 +282,7 @@ export class CoreSite { /** * Get site ID. * - * @return {string} Site ID. + * @return Site ID. */ getId(): string { return this.id; @@ -309,7 +291,7 @@ export class CoreSite { /** * Get site URL. * - * @return {string} Site URL. + * @return Site URL. */ getURL(): string { return this.siteUrl; @@ -318,7 +300,7 @@ export class CoreSite { /** * Get site token. * - * @return {string} Site token. + * @return Site token. */ getToken(): string { return this.token; @@ -327,7 +309,7 @@ export class CoreSite { /** * Get site info. * - * @return {any} Site info. + * @return Site info. */ getInfo(): any { return this.infos; @@ -336,7 +318,7 @@ export class CoreSite { /** * Get site private token. * - * @return {string} Site private token. + * @return Site private token. */ getPrivateToken(): string { return this.privateToken; @@ -345,7 +327,7 @@ export class CoreSite { /** * Get site DB. * - * @return {SQLiteDB} Site DB. + * @return Site DB. */ getDb(): SQLiteDB { return this.db; @@ -354,7 +336,7 @@ export class CoreSite { /** * Get site user's ID. * - * @return {number} User's ID. + * @return User's ID. */ getUserId(): number { if (typeof this.infos != 'undefined' && typeof this.infos.userid != 'undefined') { @@ -365,7 +347,7 @@ export class CoreSite { /** * Get site Course ID for frontpage course. If not declared it will return 1 as default. * - * @return {number} Site Home ID. + * @return Site Home ID. */ getSiteHomeId(): number { return this.infos && this.infos.siteid || 1; @@ -374,7 +356,7 @@ export class CoreSite { /** * Get site name. * - * @return {string} Site name. + * @return Site name. */ getSiteName(): string { if (CoreConfigConstants.sitename) { @@ -388,7 +370,7 @@ export class CoreSite { /** * Set site ID. * - * @param {string} New ID. + * @param New ID. */ setId(id: string): void { this.id = id; @@ -398,7 +380,7 @@ export class CoreSite { /** * Set site token. * - * @param {string} New token. + * @param New token. */ setToken(token: string): void { this.token = token; @@ -407,7 +389,7 @@ export class CoreSite { /** * Set site private token. * - * @param {string} privateToken New private token. + * @param privateToken New private token. */ setPrivateToken(privateToken: string): void { this.privateToken = privateToken; @@ -416,7 +398,7 @@ export class CoreSite { /** * Check if user logged out from the site and needs to authenticate again. * - * @return {boolean} Whether is logged out. + * @return Whether is logged out. */ isLoggedOut(): boolean { return !!this.loggedOut; @@ -425,7 +407,7 @@ export class CoreSite { /** * Set site info. * - * @param {any} New info. + * @param New info. */ setInfo(infos: any): void { this.infos = infos; @@ -442,7 +424,7 @@ export class CoreSite { /** * Set site config. * - * @param {any} Config. + * @param Config. */ setConfig(config: any): void { if (config) { @@ -456,7 +438,7 @@ export class CoreSite { /** * Set site logged out. * - * @param {boolean} loggedOut True if logged out and needs to authenticate again, false otherwise. + * @param loggedOut True if logged out and needs to authenticate again, false otherwise. */ setLoggedOut(loggedOut: boolean): void { this.loggedOut = !!loggedOut; @@ -465,7 +447,7 @@ export class CoreSite { /** * Can the user access their private files? * - * @return {boolean} Whether can access my files. + * @return Whether can access my files. */ canAccessMyFiles(): boolean { const infos = this.getInfo(); @@ -476,7 +458,7 @@ export class CoreSite { /** * Can the user download files? * - * @return {boolean} Whether can download files. + * @return Whether can download files. */ canDownloadFiles(): boolean { const infos = this.getInfo(); @@ -487,9 +469,9 @@ export class CoreSite { /** * Can the user use an advanced feature? * - * @param {string} feature The name of the feature. - * @param {boolean} [whenUndefined=true] The value to return when the parameter is undefined. - * @return {boolean} Whether can use advanced feature. + * @param feature The name of the feature. + * @param whenUndefined The value to return when the parameter is undefined. + * @return Whether can use advanced feature. */ canUseAdvancedFeature(feature: string, whenUndefined: boolean = true): boolean { const infos = this.getInfo(); @@ -513,7 +495,7 @@ export class CoreSite { /** * Can the user upload files? * - * @return {boolean} Whether can upload files. + * @return Whether can upload files. */ canUploadFiles(): boolean { const infos = this.getInfo(); @@ -524,7 +506,7 @@ export class CoreSite { /** * Fetch site info from the Moodle site. * - * @return {Promise} A promise to be resolved when the site info is retrieved. + * @return A promise to be resolved when the site info is retrieved. */ fetchSiteInfo(): Promise { // The get_site_info WS call won't be cached. @@ -543,10 +525,10 @@ export class CoreSite { /** * Read some data from the Moodle site using WS. Requests are cached by default. * - * @param {string} method WS method to use. - * @param {any} data Data to send to the WS. - * @param {CoreSiteWSPreSets} [preSets] Extra options. - * @return {Promise} Promise resolved with the response, rejected with CoreWSError if it fails. + * @param method WS method to use. + * @param data Data to send to the WS. + * @param preSets Extra options. + * @return Promise resolved with the response, rejected with CoreWSError if it fails. */ read(method: string, data: any, preSets?: CoreSiteWSPreSets): Promise { preSets = preSets || {}; @@ -566,10 +548,10 @@ export class CoreSite { /** * Sends some data to the Moodle site using WS. Requests are NOT cached by default. * - * @param {string} method WS method to use. - * @param {any} data Data to send to the WS. - * @param {CoreSiteWSPreSets} [preSets] Extra options. - * @return {Promise} Promise resolved with the response, rejected with CoreWSError if it fails. + * @param method WS method to use. + * @param data Data to send to the WS. + * @param preSets Extra options. + * @return Promise resolved with the response, rejected with CoreWSError if it fails. */ write(method: string, data: any, preSets?: CoreSiteWSPreSets): Promise { preSets = preSets || {}; @@ -589,11 +571,11 @@ export class CoreSite { /** * WS request to the site. * - * @param {string} method The WebService method to be called. - * @param {any} data Arguments to pass to the method. - * @param {CoreSiteWSPreSets} preSets Extra options. - * @param {boolean} [retrying] True if we're retrying the call for some reason. This is to prevent infinite loops. - * @return {Promise} Promise resolved with the response, rejected with CoreWSError if it fails. + * @param method The WebService method to be called. + * @param data Arguments to pass to the method. + * @param preSets Extra options. + * @param retrying True if we're retrying the call for some reason. This is to prevent infinite loops. + * @return Promise resolved with the response, rejected with CoreWSError if it fails. * @description * * Sends a webservice request to the site. This method will automatically add the @@ -676,7 +658,8 @@ export class CoreSite { const promise = this.getFromCache(method, data, preSets, false, originalData).catch(() => { if (preSets.forceOffline) { // Don't call the WS, just fail. - return Promise.reject(this.wsProvider.createFakeWSError('core.cannotconnect', true)); + return Promise.reject(this.wsProvider.createFakeWSError('core.cannotconnect', true, + {$a: CoreSite.MINIMUM_MOODLE_VERSION})); } // Call the WS. @@ -805,11 +788,11 @@ export class CoreSite { /** * Adds a request to the queue or calls it immediately when not using the queue. * - * @param {string} method The WebService method to be called. - * @param {any} data Arguments to pass to the method. - * @param {CoreSiteWSPreSets} preSets Extra options related to the site. - * @param {CoreWSPreSets} wsPreSets Extra options related to the WS call. - * @returns {Promise} Promise resolved with the response when the WS is called. + * @param method The WebService method to be called. + * @param data Arguments to pass to the method. + * @param preSets Extra options related to the site. + * @param wsPreSets Extra options related to the WS call. + * @return Promise resolved with the response when the WS is called. */ protected callOrEnqueueRequest(method: string, data: any, preSets: CoreSiteWSPreSets, wsPreSets: CoreWSPreSets): Promise { if (preSets.skipQueue || !this.wsAvailable('tool_mobile_call_external_functions')) { @@ -846,8 +829,8 @@ export class CoreSite { /** * Adds a request to the queue. * - * @param {RequestQueueItem} request The request to enqueue. - * @returns {Promise} Promise resolved with the response when the WS is called. + * @param request The request to enqueue. + * @return Promise resolved with the response when the WS is called. */ protected enqueueRequest(request: RequestQueueItem): Promise { @@ -957,9 +940,9 @@ export class CoreSite { /** * Check if a WS is available in this site. * - * @param {string} method WS name. - * @param {boolean} [checkPrefix=true] When true also checks with the compatibility prefix. - * @return {boolean} Whether the WS is available. + * @param method WS name. + * @param checkPrefix When true also checks with the compatibility prefix. + * @return Whether the WS is available. */ wsAvailable(method: string, checkPrefix: boolean = true): boolean { if (typeof this.infos == 'undefined') { @@ -981,9 +964,9 @@ export class CoreSite { /** * Get cache ID. * - * @param {string} method The WebService method. - * @param {any} data Arguments to pass to the method. - * @return {string} Cache ID. + * @param method The WebService method. + * @param data Arguments to pass to the method. + * @return Cache ID. */ protected getCacheId(method: string, data: any): string { return Md5.hashAsciiStr(method + ':' + this.utils.sortAndStringify(data)); @@ -992,9 +975,9 @@ export class CoreSite { /** * Get the cache ID used in Ionic 1 version of the app. * - * @param {string} method The WebService method. - * @param {any} data Arguments to pass to the method. - * @return {string} Cache ID. + * @param method The WebService method. + * @param data Arguments to pass to the method. + * @return Cache ID. */ protected getCacheOldId(method: string, data: any): string { return Md5.hashAsciiStr(method + ':' + JSON.stringify(data)); @@ -1003,12 +986,12 @@ export class CoreSite { /** * Get a WS response from cache. * - * @param {string} method The WebService method to be called. - * @param {any} data Arguments to pass to the method. - * @param {CoreSiteWSPreSets} preSets Extra options. - * @param {boolean} [emergency] Whether it's an "emergency" cache call (WS call failed). - * @param {any} [originalData] Arguments to pass to the method before being converted to strings. - * @return {Promise} Promise resolved with the WS response. + * @param method The WebService method to be called. + * @param data Arguments to pass to the method. + * @param preSets Extra options. + * @param emergency Whether it's an "emergency" cache call (WS call failed). + * @param originalData Arguments to pass to the method before being converted to strings. + * @return Promise resolved with the WS response. */ protected getFromCache(method: string, data: any, preSets: CoreSiteWSPreSets, emergency?: boolean, originalData?: any) : Promise { @@ -1057,15 +1040,7 @@ export class CoreSite { preSets.omitExpires = preSets.omitExpires || preSets.forceOffline || !this.appProvider.isOnline(); if (!preSets.omitExpires) { - let expirationDelay = this.UPDATE_FREQUENCIES[preSets.updateFrequency] || - this.UPDATE_FREQUENCIES[CoreSite.FREQUENCY_USUALLY]; - - if (this.appProvider.isNetworkAccessLimited()) { - // Not WiFi, increase the expiration delay a 50% to decrease the data usage in this case. - expirationDelay *= 1.5; - } - - expirationTime = entry.expirationTime + expirationDelay; + expirationTime = entry.expirationTime + this.getExpirationDelay(preSets.updateFrequency); if (now > expirationTime) { this.logger.debug('Cached element found, but it is expired'); @@ -1092,11 +1067,11 @@ export class CoreSite { /** * Save a WS response to cache. * - * @param {string} method The WebService method. - * @param {any} data Arguments to pass to the method. - * @param {any} response The WS response. - * @param {CoreSiteWSPreSets} preSets Extra options. - * @return {Promise} Promise resolved when the response is saved. + * @param method The WebService method. + * @param data Arguments to pass to the method. + * @param response The WS response. + * @param preSets Extra options. + * @return Promise resolved when the response is saved. */ protected saveToCache(method: string, data: any, response: any, preSets: CoreSiteWSPreSets): Promise { if (!this.db) { @@ -1135,11 +1110,11 @@ export class CoreSite { /** * Delete a WS cache entry or entries. * - * @param {string} method The WebService method to be called. - * @param {any} data Arguments to pass to the method. - * @param {CoreSiteWSPreSets} preSets Extra options. - * @param {boolean} [allCacheKey] True to delete all entries with the cache key, false to delete only by ID. - * @return {Promise} Promise resolved when the entries are deleted. + * @param method The WebService method to be called. + * @param data Arguments to pass to the method. + * @param preSets Extra options. + * @param allCacheKey True to delete all entries with the cache key, false to delete only by ID. + * @return Promise resolved when the entries are deleted. */ protected deleteFromCache(method: string, data: any, preSets: CoreSiteWSPreSets, allCacheKey?: boolean): Promise { if (!this.db) { @@ -1158,10 +1133,10 @@ export class CoreSite { /* * Uploads a file using Cordova File API. * - * @param {string} filePath File path. - * @param {CoreWSFileUploadOptions} options File upload options. - * @param {Function} [onProgress] Function to call on progress. - * @return {Promise} Promise resolved when uploaded. + * @param filePath File path. + * @param options File upload options. + * @param onProgress Function to call on progress. + * @return Promise resolved when uploaded. */ uploadFile(filePath: string, options: CoreWSFileUploadOptions, onProgress?: (event: ProgressEvent) => any): Promise { if (!options.fileArea) { @@ -1177,7 +1152,7 @@ export class CoreSite { /** * Invalidates all the cache entries. * - * @return {Promise} Promise resolved when the cache entries are invalidated. + * @return Promise resolved when the cache entries are invalidated. */ invalidateWsCache(): Promise { if (!this.db) { @@ -1186,14 +1161,16 @@ export class CoreSite { this.logger.debug('Invalidate all the cache for site: ' + this.id); - return this.db.updateRecords(CoreSite.WS_CACHE_TABLE, { expirationTime: 0 }); + return this.db.updateRecords(CoreSite.WS_CACHE_TABLE, { expirationTime: 0 }).finally(() => { + this.eventsProvider.trigger(CoreEventsProvider.WS_CACHE_INVALIDATED, {}, this.getId()); + }); } /** * Invalidates all the cache entries with a certain key. * - * @param {string} key Key to search. - * @return {Promise} Promise resolved when the cache entries are invalidated. + * @param key Key to search. + * @return Promise resolved when the cache entries are invalidated. */ invalidateWsCacheForKey(key: string): Promise { if (!this.db) { @@ -1211,8 +1188,8 @@ export class CoreSite { /** * Invalidates all the cache entries in an array of keys. * - * @param {string[]} keys Keys to search. - * @return {Promise} Promise resolved when the cache entries are invalidated. + * @param keys Keys to search. + * @return Promise resolved when the cache entries are invalidated. */ invalidateMultipleWsCacheForKey(keys: string[]): Promise { if (!this.db) { @@ -1235,8 +1212,8 @@ export class CoreSite { /** * Invalidates all the cache entries whose key starts with a certain value. * - * @param {string} key Key to search. - * @return {Promise} Promise resolved when the cache entries are invalidated. + * @param key Key to search. + * @return Promise resolved when the cache entries are invalidated. */ invalidateWsCacheForKeyStartingWith(key: string): Promise { if (!this.db) { @@ -1253,21 +1230,36 @@ export class CoreSite { return this.db.execute(sql, [key + '%']); } + /** + * Check if tokenpluginfile can be used, and fix the URL afterwards. + * + * @param url The url to be fixed. + * @return Promise resolved with the fixed URL. + */ + checkAndFixPluginfileURL(url: string): Promise { + return this.checkTokenPluginFile(url).then(() => { + return this.fixPluginfileURL(url); + }); + } + /** * Generic function for adding the wstoken to Moodle urls and for pointing to the correct script. * Uses CoreUtilsProvider.fixPluginfileURL, passing site's token. * - * @param {string} url The url to be fixed. - * @return {string} Fixed URL. + * @param url The url to be fixed. + * @return Fixed URL. */ fixPluginfileURL(url: string): string { - return this.urlUtils.fixPluginfileURL(url, this.token, this.siteUrl); + const accessKey = this.tokenPluginFileWorks || typeof this.tokenPluginFileWorks == 'undefined' ? + this.infos && this.infos.userprivateaccesskey : undefined; + + return this.urlUtils.fixPluginfileURL(url, this.token, this.siteUrl, accessKey); } /** * Deletes site's DB. * - * @return {Promise} Promise to be resolved when the DB is deleted. + * @return Promise to be resolved when the DB is deleted. */ deleteDB(): Promise { return this.dbProvider.deleteDB('Site-' + this.id); @@ -1276,7 +1268,7 @@ export class CoreSite { /** * Deletes site's folder. * - * @return {Promise} Promise to be resolved when the DB is deleted. + * @return Promise to be resolved when the DB is deleted. */ deleteFolder(): Promise { if (this.fileProvider.isAvailable()) { @@ -1293,7 +1285,7 @@ export class CoreSite { /** * Get space usage of the site. * - * @return {Promise} Promise resolved with the site space usage (size). + * @return Promise resolved with the site space usage (size). */ getSpaceUsage(): Promise { if (this.fileProvider.isAvailable()) { @@ -1310,8 +1302,8 @@ export class CoreSite { /** * Returns the URL to the documentation of the app, based on Moodle version and current language. * - * @param {string} [page] Docs page to go to. - * @return {Promise} Promise resolved with the Moodle docs URL. + * @param page Docs page to go to. + * @return Promise resolved with the Moodle docs URL. */ getDocsUrl(page?: string): Promise { const release = this.infos.release ? this.infos.release : undefined; @@ -1319,11 +1311,23 @@ export class CoreSite { return this.urlUtils.getDocsUrl(release, page); } + /** + * Returns a url to link an specific page on the site. + * + * @param path Path of the url to go to. + * @param params Object with the params to add. + * @param anchor Anchor text if needed. + * @return URL with params. + */ + createSiteUrl(path: string, params?: {[key: string]: any}, anchor?: string): string { + return this.urlUtils.addParamsToUrl(this.siteUrl + path, params, anchor); + } + /** * Check if the local_mobile plugin is installed in the Moodle site. * - * @param {boolean} [retrying] True if we're retrying the check. - * @return {Promise} Promise resolved when the check is done. + * @param retrying True if we're retrying the check. + * @return Promise resolved when the check is done. */ checkLocalMobilePlugin(retrying?: boolean): Promise { const checkUrl = this.siteUrl + '/local/mobile/check.php', @@ -1379,7 +1383,7 @@ export class CoreSite { /** * Check if local_mobile has been installed in Moodle. * - * @return {boolean} Whether the App is able to use local_mobile plugin for this site. + * @return Whether the App is able to use local_mobile plugin for this site. */ checkIfAppUsesLocalMobile(): boolean { let appUsesLocalMobile = false; @@ -1400,7 +1404,7 @@ export class CoreSite { /** * Check if local_mobile has been installed in Moodle but the app is not using it. * - * @return {Promise} Promise resolved it local_mobile was added, rejected otherwise. + * @return Promise resolved it local_mobile was added, rejected otherwise. */ checkIfLocalMobileInstalledAndNotUsed(): Promise { const appUsesLocalMobile = this.checkIfAppUsesLocalMobile(); @@ -1423,8 +1427,8 @@ export class CoreSite { /** * Check if a URL belongs to this site. * - * @param {string} url URL to check. - * @return {boolean} Whether the URL belongs to this site. + * @param url URL to check. + * @return Whether the URL belongs to this site. */ containsUrl(url: string): boolean { if (!url) { @@ -1440,7 +1444,7 @@ export class CoreSite { /** * Get the public config of this site. * - * @return {Promise} Promise resolved with public config. Rejected with an object if error, see CoreWSProvider.callAjax. + * @return Promise resolved with public config. Rejected with an object if error, see CoreWSProvider.callAjax. */ getPublicConfig(): Promise { const preSets: CoreWSAjaxPreSets = { @@ -1479,9 +1483,9 @@ export class CoreSite { /** * Open a URL in browser using auto-login in the Moodle site if available. * - * @param {string} url The URL to open. - * @param {string} [alertMessage] If defined, an alert will be shown before opening the browser. - * @return {Promise} Promise resolved when done, rejected otherwise. + * @param url The URL to open. + * @param alertMessage If defined, an alert will be shown before opening the browser. + * @return Promise resolved when done, rejected otherwise. */ openInBrowserWithAutoLogin(url: string, alertMessage?: string): Promise { return this.openWithAutoLogin(false, url, undefined, alertMessage); @@ -1490,9 +1494,9 @@ export class CoreSite { /** * Open a URL in browser using auto-login in the Moodle site if available and the URL belongs to the site. * - * @param {string} url The URL to open. - * @param {string} [alertMessage] If defined, an alert will be shown before opening the browser. - * @return {Promise} Promise resolved when done, rejected otherwise. + * @param url The URL to open. + * @param alertMessage If defined, an alert will be shown before opening the browser. + * @return Promise resolved when done, rejected otherwise. */ openInBrowserWithAutoLoginIfSameSite(url: string, alertMessage?: string): Promise { return this.openWithAutoLoginIfSameSite(false, url, undefined, alertMessage); @@ -1501,10 +1505,10 @@ export class CoreSite { /** * Open a URL in inappbrowser using auto-login in the Moodle site if available. * - * @param {string} url The URL to open. - * @param {any} [options] Override default options passed to InAppBrowser. - * @param {string} [alertMessage] If defined, an alert will be shown before opening the inappbrowser. - * @return {Promise} Promise resolved when done. + * @param url The URL to open. + * @param options Override default options passed to InAppBrowser. + * @param alertMessage If defined, an alert will be shown before opening the inappbrowser. + * @return Promise resolved when done. */ openInAppWithAutoLogin(url: string, options?: any, alertMessage?: string): Promise { return this.openWithAutoLogin(true, url, options, alertMessage); @@ -1513,10 +1517,10 @@ export class CoreSite { /** * Open a URL in inappbrowser using auto-login in the Moodle site if available and the URL belongs to the site. * - * @param {string} url The URL to open. - * @param {object} [options] Override default options passed to inappbrowser. - * @param {string} [alertMessage] If defined, an alert will be shown before opening the inappbrowser. - * @return {Promise} Promise resolved when done. + * @param url The URL to open. + * @param options Override default options passed to inappbrowser. + * @param alertMessage If defined, an alert will be shown before opening the inappbrowser. + * @return Promise resolved when done. */ openInAppWithAutoLoginIfSameSite(url: string, options?: any, alertMessage?: string): Promise { return this.openWithAutoLoginIfSameSite(true, url, options, alertMessage); @@ -1525,11 +1529,11 @@ export class CoreSite { /** * Open a URL in browser or InAppBrowser using auto-login in the Moodle site if available. * - * @param {boolean} inApp True to open it in InAppBrowser, false to open in browser. - * @param {string} url The URL to open. - * @param {object} [options] Override default options passed to $cordovaInAppBrowser#open. - * @param {string} [alertMessage] If defined, an alert will be shown before opening the browser/inappbrowser. - * @return {Promise} Promise resolved when done. Resolve param is returned only if inApp=true. + * @param inApp True to open it in InAppBrowser, false to open in browser. + * @param url The URL to open. + * @param options Override default options passed to $cordovaInAppBrowser#open. + * @param alertMessage If defined, an alert will be shown before opening the browser/inappbrowser. + * @return Promise resolved when done. Resolve param is returned only if inApp=true. */ openWithAutoLogin(inApp: boolean, url: string, options?: any, alertMessage?: string): Promise { // Get the URL to open. @@ -1564,11 +1568,11 @@ export class CoreSite { /** * Open a URL in browser or InAppBrowser using auto-login in the Moodle site if available and the URL belongs to the site. * - * @param {boolean} inApp True to open it in InAppBrowser, false to open in browser. - * @param {string} url The URL to open. - * @param {object} [options] Override default options passed to inappbrowser. - * @param {string} [alertMessage] If defined, an alert will be shown before opening the browser/inappbrowser. - * @return {Promise} Promise resolved when done. Resolve param is returned only if inApp=true. + * @param inApp True to open it in InAppBrowser, false to open in browser. + * @param url The URL to open. + * @param options Override default options passed to inappbrowser. + * @param alertMessage If defined, an alert will be shown before opening the browser/inappbrowser. + * @return Promise resolved when done. Resolve param is returned only if inApp=true. */ openWithAutoLoginIfSameSite(inApp: boolean, url: string, options?: any, alertMessage?: string) : Promise { @@ -1589,9 +1593,9 @@ export class CoreSite { * Get the config of this site. * It is recommended to use getStoredConfig instead since it's faster and doesn't use network. * - * @param {string} [name] Name of the setting to get. If not set or false, all settings will be returned. - * @param {boolean} [ignoreCache] True if it should ignore cached data. - * @return {Promise} Promise resolved with site config. + * @param name Name of the setting to get. If not set or false, all settings will be returned. + * @param ignoreCache True if it should ignore cached data. + * @return Promise resolved with site config. */ getConfig(name?: string, ignoreCache?: boolean): Promise { const preSets: CoreSiteWSPreSets = { @@ -1628,7 +1632,7 @@ export class CoreSite { /** * Invalidates config WS call. * - * @return {Promise} Promise resolved when the data is invalidated. + * @return Promise resolved when the data is invalidated. */ invalidateConfig(): Promise { return this.invalidateWsCacheForKey(this.getConfigCacheKey()); @@ -1637,7 +1641,7 @@ export class CoreSite { /** * Get cache key for getConfig WS calls. * - * @return {string} Cache key. + * @return Cache key. */ protected getConfigCacheKey(): string { return 'tool_mobile_get_config'; @@ -1646,8 +1650,8 @@ export class CoreSite { /** * Get the stored config of this site. * - * @param {string} [name] Name of the setting to get. If not set, all settings will be returned. - * @return {any} Site config or a specific setting. + * @param name Name of the setting to get. If not set, all settings will be returned. + * @return Site config or a specific setting. */ getStoredConfig(name?: string): any { if (!this.config) { @@ -1664,8 +1668,8 @@ export class CoreSite { /** * Check if a certain feature is disabled in the site. * - * @param {string} name Name of the feature to check. - * @return {boolean} Whether it's disabled. + * @param name Name of the feature to check. + * @return Whether it's disabled. */ isFeatureDisabled(name: string): boolean { const disabledFeatures = this.getStoredConfig('tool_mobile_disabledfeatures'); @@ -1688,7 +1692,7 @@ export class CoreSite { /** * Get whether offline is disabled in the site. * - * @return {boolean} Whether it's disabled. + * @return Whether it's disabled. */ isOfflineDisabled(): boolean { return this.offlineDisabled; @@ -1698,8 +1702,8 @@ export class CoreSite { * Check if the site version is greater than one or several versions. * This function accepts a string or an array of strings. If array, the last version must be the highest. * - * @param {string | string[]} versions Version or list of versions to check. - * @return {boolean} Whether it's greater or equal, false otherwise. + * @param versions Version or list of versions to check. + * @return Whether it's greater or equal, false otherwise. * @description * If a string is supplied (e.g. '3.2.1'), it will check if the site version is greater or equal than this version. * @@ -1746,9 +1750,9 @@ export class CoreSite { /** * Given a URL, convert it to a URL that will auto-login if supported. * - * @param {string} url The URL to convert. - * @param {boolean} [showModal=true] Whether to show a loading modal. - * @return {Promise} Promise resolved with the converted URL. + * @param url The URL to convert. + * @param showModal Whether to show a loading modal. + * @return Promise resolved with the converted URL. */ getAutoLoginUrl(url: string, showModal: boolean = true): Promise { @@ -1793,8 +1797,8 @@ export class CoreSite { * Get a version number from a release version. * If release version is valid but not found in the list of Moodle releases, it will use the last released major version. * - * @param {string} version Release version to convert to version number. - * @return {number} Version number, 0 if invalid. + * @param version Release version to convert to version number. + * @return Version number, 0 if invalid. */ protected getVersionNumber(version: string): number { const data = this.getMajorAndMinor(version); @@ -1815,8 +1819,8 @@ export class CoreSite { /** * Given a release version, return the major and minor versions. * - * @param {string} version Release version (e.g. '3.1.0'). - * @return {object} Object with major and minor. Returns false if invalid version. + * @param version Release version (e.g. '3.1.0'). + * @return Object with major and minor. Returns false if invalid version. */ protected getMajorAndMinor(version: string): any { const match = version.match(/(\d)+(?:\.(\d)+)?(?:\.(\d)+)?/); @@ -1834,8 +1838,8 @@ export class CoreSite { /** * Given a release version, return the next major version number. * - * @param {string} version Release version (e.g. '3.1.0'). - * @return {number} Next major version number. + * @param version Release version (e.g. '3.1.0'). + * @return Next major version number. */ protected getNextMajorVersionNumber(version: string): number { const data = this.getMajorAndMinor(version), @@ -1860,8 +1864,8 @@ export class CoreSite { /** * Deletes a site setting. * - * @param {string} name The config name. - * @return {Promise} Promise resolved when done. + * @param name The config name. + * @return Promise resolved when done. */ deleteSiteConfig(name: string): Promise { return this.db.deleteRecords(CoreSite.CONFIG_TABLE, { name: name }); @@ -1870,9 +1874,9 @@ export class CoreSite { /** * Get a site setting on local device. * - * @param {string} name The config name. - * @param {any} [defaultValue] Default value to use if the entry is not found. - * @return {Promise} Resolves upon success along with the config data. Reject on failure. + * @param name The config name. + * @param defaultValue Default value to use if the entry is not found. + * @return Resolves upon success along with the config data. Reject on failure. */ getLocalSiteConfig(name: string, defaultValue?: any): Promise { return this.db.getRecord(CoreSite.CONFIG_TABLE, { name: name }).then((entry) => { @@ -1889,11 +1893,65 @@ export class CoreSite { /** * Set a site setting on local device. * - * @param {string} name The config name. - * @param {number|string} value The config value. Can only store number or strings. - * @return {Promise} Promise resolved when done. + * @param name The config name. + * @param value The config value. Can only store number or strings. + * @return Promise resolved when done. */ setLocalSiteConfig(name: string, value: number | string): Promise { return this.db.insertRecord(CoreSite.CONFIG_TABLE, { name: name, value: value }); } + + /** + * Get a certain cache expiration delay. + * + * @param updateFrequency The update frequency of the entry. + * @return Expiration delay. + */ + getExpirationDelay(updateFrequency?: number): number { + let expirationDelay = this.UPDATE_FREQUENCIES[updateFrequency] || this.UPDATE_FREQUENCIES[CoreSite.FREQUENCY_USUALLY]; + + if (this.appProvider.isNetworkAccessLimited()) { + // Not WiFi, increase the expiration delay a 50% to decrease the data usage in this case. + expirationDelay *= 1.5; + } + + return expirationDelay; + } + + /* + * Check if tokenpluginfile script works in the site. + * + * @param url URL to check. + * @return Promise resolved with boolean: whether it works or not. + */ + checkTokenPluginFile(url: string): Promise { + if (!this.urlUtils.canUseTokenPluginFile(url, this.siteUrl, this.infos && this.infos.userprivateaccesskey)) { + // Cannot use tokenpluginfile. + return Promise.resolve(false); + } else if (typeof this.tokenPluginFileWorks != 'undefined') { + // Already checked. + return Promise.resolve(this.tokenPluginFileWorks); + } else if (this.tokenPluginFileWorksPromise) { + // Check ongoing, use the same promise. + return this.tokenPluginFileWorksPromise; + } else if (!this.appProvider.isOnline()) { + // Not online, cannot check it. Assume it's working, but don't save the result. + return Promise.resolve(true); + } + + url = this.fixPluginfileURL(url); + + this.tokenPluginFileWorksPromise = this.wsProvider.performHead(url).then((result) => { + return result.ok; + }).catch((error) => { + // Error performing head request. + return false; + }).then((result) => { + this.tokenPluginFileWorks = result; + + return result; + }); + + return this.tokenPluginFileWorksPromise; + } } diff --git a/src/classes/sqlitedb.ts b/src/classes/sqlitedb.ts index 9bc24272b..239a5edcf 100644 --- a/src/classes/sqlitedb.ts +++ b/src/classes/sqlitedb.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -21,37 +21,31 @@ import { Platform } from 'ionic-angular'; export interface SQLiteDBTableSchema { /** * The table name. - * @type {string} */ name: string; /** * The columns to create in the table. - * @type {SQLiteDBColumnSchema[]} */ columns: SQLiteDBColumnSchema[]; /** * Names of columns that are primary key. Use it for compound primary keys. - * @type {string[]} */ primaryKeys?: string[]; /** * List of sets of unique columns. E.g: [['section', 'title'], ['author', 'title']]. - * @type {string[][]} */ uniqueKeys?: string[][]; /** * List of foreign keys. - * @type {SQLiteDBForeignKeySchema[]} */ foreignKeys?: SQLiteDBForeignKeySchema[]; /** * Check constraint for the table. - * @type {string} */ tableCheck?: string; } @@ -62,49 +56,41 @@ export interface SQLiteDBTableSchema { export interface SQLiteDBColumnSchema { /** * Column's name. - * @type {string} */ name: string; /** * Column's type. - * @type {string} */ type?: 'INTEGER' | 'REAL' | 'TEXT' | 'BLOB'; /** * Whether the column is a primary key. Use it only if primary key is a single column. - * @type {boolean} */ primaryKey?: boolean; /** * Whether it should be autoincremented. Only if primaryKey is true. - * @type {boolean} */ autoIncrement?: boolean; /** * True if column shouldn't be null. - * @type {boolean} */ notNull?: boolean; /** * WWhether the column is unique. - * @type {boolean} */ unique?: boolean; /** * Check constraint for the column. - * @type {string} */ check?: string; /** * Default value for the column. - * @type {string} */ default?: string; } @@ -115,25 +101,21 @@ export interface SQLiteDBColumnSchema { export interface SQLiteDBForeignKeySchema { /** * Columns to include in this foreign key. - * @type {string[]} */ columns: string[]; /** * The external table referenced by this key. - * @type {string} */ table: string; /** * List of referenced columns from the referenced table. - * @type {string[]} */ foreignColumns?: string[]; /** * Text with the actions to apply to the foreign key. - * @type {string} */ actions?: string; } @@ -154,9 +136,9 @@ export class SQLiteDB { /** * Create and open the database. * - * @param {string} name Database name. - * @param {SQLite} sqlite SQLite library. - * @param {Platform} platform Ionic platform. + * @param name Database name. + * @param sqlite SQLite library. + * @param platform Ionic platform. */ constructor(public name: string, private sqlite: SQLite, private platform: Platform) { this.init(); @@ -165,12 +147,12 @@ export class SQLiteDB { /** * Helper function to create a table if it doesn't exist. * - * @param {string} name The table name. - * @param {SQLiteDBColumnSchema[]} columns The columns to create in the table. - * @param {string[]} [primaryKeys] Names of columns that are primary key. Use it for compound primary keys. - * @param {string[][]} [uniqueKeys] List of sets of unique columns. E.g: [['section', 'title'], ['author', 'title']]. - * @param {SQLiteDBForeignKeySchema[]} [foreignKeys] List of foreign keys. - * @param {string} [tableCheck] Check constraint for the table. + * @param name The table name. + * @param columns The columns to create in the table. + * @param primaryKeys Names of columns that are primary key. Use it for compound primary keys. + * @param uniqueKeys List of sets of unique columns. E.g: [['section', 'title'], ['author', 'title']]. + * @param foreignKeys List of foreign keys. + * @param tableCheck Check constraint for the table. * @return SQL query. */ buildCreateTableSql(name: string, columns: SQLiteDBColumnSchema[], primaryKeys?: string[], uniqueKeys?: string[][], @@ -257,7 +239,7 @@ export class SQLiteDB { /** * Close the database. * - * @return {Promise} Promise resolved when done. + * @return Promise resolved when done. */ close(): Promise { return this.ready().then(() => { @@ -268,9 +250,9 @@ export class SQLiteDB { /** * Count the records in a table where all the given conditions met. * - * @param {string} table The table to query. - * @param {object} [conditions] The conditions to build the where clause. Must not contain numeric indexes. - * @return {Promise} Promise resolved with the count of records returned from the specified criteria. + * @param table The table to query. + * @param conditions The conditions to build the where clause. Must not contain numeric indexes. + * @return Promise resolved with the count of records returned from the specified criteria. */ countRecords(table: string, conditions?: object): Promise { const selectAndParams = this.whereClause(conditions); @@ -281,11 +263,11 @@ export class SQLiteDB { /** * Count the records in a table which match a particular WHERE clause. * - * @param {string} table The table to query. - * @param {string} [select] A fragment of SQL to be used in a where clause in the SQL call. - * @param {any} [params] An array of sql parameters. - * @param {string} [countItem] The count string to be used in the SQL call. Default is COUNT('x'). - * @return {Promise} Promise resolved with the count of records returned from the specified criteria. + * @param table The table to query. + * @param select A fragment of SQL to be used in a where clause in the SQL call. + * @param params An array of sql parameters. + * @param countItem The count string to be used in the SQL call. Default is COUNT('x'). + * @return Promise resolved with the count of records returned from the specified criteria. */ countRecordsSelect(table: string, select: string = '', params?: any, countItem: string = 'COUNT(\'x\')'): Promise { if (select) { @@ -300,9 +282,9 @@ export class SQLiteDB { * * Given a query that counts rows, return that count. * - * @param {string} sql The SQL string you wish to be executed. - * @param {any} [params] An array of sql parameters. - * @return {Promise} Promise resolved with the count. + * @param sql The SQL string you wish to be executed. + * @param params An array of sql parameters. + * @return Promise resolved with the count. */ countRecordsSql(sql: string, params?: any): Promise { return this.getFieldSql(sql, params).then((count) => { @@ -317,13 +299,13 @@ export class SQLiteDB { /** * Create a table if it doesn't exist. * - * @param {string} name The table name. - * @param {SQLiteDBColumnSchema[]} columns The columns to create in the table. - * @param {string[]} [primaryKeys] Names of columns that are primary key. Use it for compound primary keys. - * @param {string[][]} [uniqueKeys] List of sets of unique columns. E.g: [['section', 'title'], ['author', 'title']]. - * @param {SQLiteDBForeignKeySchema[]} [foreignKeys] List of foreign keys. - * @param {string} [tableCheck] Check constraint for the table. - * @return {Promise} Promise resolved when success. + * @param name The table name. + * @param columns The columns to create in the table. + * @param primaryKeys Names of columns that are primary key. Use it for compound primary keys. + * @param uniqueKeys List of sets of unique columns. E.g: [['section', 'title'], ['author', 'title']]. + * @param foreignKeys List of foreign keys. + * @param tableCheck Check constraint for the table. + * @return Promise resolved when success. */ createTable(name: string, columns: SQLiteDBColumnSchema[], primaryKeys?: string[], uniqueKeys?: string[][], foreignKeys?: SQLiteDBForeignKeySchema[], tableCheck?: string): Promise { @@ -335,8 +317,8 @@ export class SQLiteDB { /** * Create a table if it doesn't exist from a schema. * - * @param {SQLiteDBTableSchema} table Table schema. - * @return {Promise} Promise resolved when success. + * @param table Table schema. + * @return Promise resolved when success. */ createTableFromSchema(table: SQLiteDBTableSchema): Promise { return this.createTable(table.name, table.columns, table.primaryKeys, table.uniqueKeys, @@ -346,8 +328,8 @@ export class SQLiteDB { /** * Create several tables if they don't exist from a list of schemas. * - * @param {SQLiteDBTableSchema[]} tables List of table schema. - * @return {Promise} Promise resolved when success. + * @param tables List of table schema. + * @return Promise resolved when success. */ createTablesFromSchema(tables: SQLiteDBTableSchema[]): Promise { const promises = []; @@ -362,9 +344,9 @@ export class SQLiteDB { * Delete the records from a table where all the given conditions met. * If conditions not specified, table is truncated. * - * @param {string} table The table to delete from. - * @param {object} [conditions] The conditions to build the where clause. Must not contain numeric indexes. - * @return {Promise} Promise resolved when done. + * @param table The table to delete from. + * @param conditions The conditions to build the where clause. Must not contain numeric indexes. + * @return Promise resolved when done. */ deleteRecords(table: string, conditions?: object): Promise { if (conditions === null || typeof conditions == 'undefined') { @@ -380,10 +362,10 @@ export class SQLiteDB { /** * Delete the records from a table where one field match one list of values. * - * @param {string} table The table to delete from. - * @param {string} field The name of a field. - * @param {any[]} values The values field might take. - * @return {Promise} Promise resolved when done. + * @param table The table to delete from. + * @param field The name of a field. + * @param values The values field might take. + * @return Promise resolved when done. */ deleteRecordsList(table: string, field: string, values: any[]): Promise { const selectAndParams = this.whereClauseList(field, values); @@ -394,10 +376,10 @@ export class SQLiteDB { /** * Delete one or more records from a table which match a particular WHERE clause. * - * @param {string} table The table to delete from. - * @param {string} [select] A fragment of SQL to be used in a where clause in the SQL call. - * @param {any[]} [params] Array of sql parameters. - * @return {Promise} Promise resolved when done. + * @param table The table to delete from. + * @param select A fragment of SQL to be used in a where clause in the SQL call. + * @param params Array of sql parameters. + * @return Promise resolved when done. */ deleteRecordsSelect(table: string, select: string = '', params?: any[]): Promise { if (select) { @@ -410,8 +392,8 @@ export class SQLiteDB { /** * Drop a table if it exists. * - * @param {string} name The table name. - * @return {Promise} Promise resolved when success. + * @param name The table name. + * @return Promise resolved when success. */ dropTable(name: string): Promise { return this.execute(`DROP TABLE IF EXISTS ${name}`); @@ -422,9 +404,9 @@ export class SQLiteDB { * IMPORTANT: Use this function only if you cannot use any of the other functions in this API. Please take into account that * these query will be run in SQLite (Mobile) and Web SQL (desktop), so your query should work in both environments. * - * @param {string} sql SQL query to execute. - * @param {any[]} params Query parameters. - * @return {Promise} Promise resolved with the result. + * @param sql SQL query to execute. + * @param params Query parameters. + * @return Promise resolved with the result. */ execute(sql: string, params?: any[]): Promise { return this.ready().then(() => { @@ -437,8 +419,8 @@ export class SQLiteDB { * IMPORTANT: Use this function only if you cannot use any of the other functions in this API. Please take into account that * these query will be run in SQLite (Mobile) and Web SQL (desktop), so your query should work in both environments. * - * @param {any[]} sqlStatements SQL statements to execute. - * @return {Promise} Promise resolved with the result. + * @param sqlStatements SQL statements to execute. + * @return Promise resolved with the result. */ executeBatch(sqlStatements: any[]): Promise { return this.ready().then(() => { @@ -449,7 +431,7 @@ export class SQLiteDB { /** * Format the data to insert in the database. Removes undefined entries so they are stored as null instead of 'undefined'. * - * @param {object} data Data to insert. + * @param data Data to insert. */ protected formatDataToInsert(data: object): void { if (!data) { @@ -468,8 +450,8 @@ export class SQLiteDB { /** * Get all the records from a table. * - * @param {string} table The table to query. - * @return {Promise} Promise resolved with the records. + * @param table The table to query. + * @return Promise resolved with the records. */ getAllRecords(table: string): Promise { return this.getRecords(table); @@ -478,10 +460,10 @@ export class SQLiteDB { /** * Get a single field value from a table record where all the given conditions met. * - * @param {string} table The table to query. - * @param {string} field The field to return the value of. - * @param {object} [conditions] The conditions to build the where clause. Must not contain numeric indexes. - * @return {Promise} Promise resolved with the field's value. + * @param table The table to query. + * @param field The field to return the value of. + * @param conditions The conditions to build the where clause. Must not contain numeric indexes. + * @return Promise resolved with the field's value. */ getField(table: string, field: string, conditions?: object): Promise { const selectAndParams = this.whereClause(conditions); @@ -492,11 +474,11 @@ export class SQLiteDB { /** * Get a single field value from a table record which match a particular WHERE clause. * - * @param {string} table The table to query. - * @param {string} field The field to return the value of. - * @param {string} [select=''] A fragment of SQL to be used in a where clause returning one row with one column. - * @param {any[]} [params] Array of sql parameters. - * @return {Promise} Promise resolved with the field's value. + * @param table The table to query. + * @param field The field to return the value of. + * @param select A fragment of SQL to be used in a where clause returning one row with one column. + * @param params Array of sql parameters. + * @return Promise resolved with the field's value. */ getFieldSelect(table: string, field: string, select: string = '', params?: any[]): Promise { if (select) { @@ -509,9 +491,9 @@ export class SQLiteDB { /** * Get a single field value (first field) using a SQL statement. * - * @param {string} sql The SQL query returning one row with one column. - * @param {any[]} [params] An array of sql parameters. - * @return {Promise} Promise resolved with the field's value. + * @param sql The SQL query returning one row with one column. + * @param params An array of sql parameters. + * @return Promise resolved with the field's value. */ getFieldSql(sql: string, params?: any[]): Promise { return this.getRecordSql(sql, params).then((record) => { @@ -527,11 +509,11 @@ export class SQLiteDB { /** * Constructs 'IN()' or '=' sql fragment * - * @param {any} items A single value or array of values for the expression. It doesn't accept objects. - * @param {boolean} [equal=true] True means we want to equate to the constructed expression. - * @param {any} [onEmptyItems] This defines the behavior when the array of items provided is empty. Defaults to false, - * meaning return empty. Other values will become part of the returned SQL fragment. - * @return {any[]} A list containing the constructed sql fragment and an array of parameters. + * @param items A single value or array of values for the expression. It doesn't accept objects. + * @param equal True means we want to equate to the constructed expression. + * @param onEmptyItems This defines the behavior when the array of items provided is empty. Defaults to false, + * meaning return empty. Other values will become part of the returned SQL fragment. + * @return A list containing the constructed sql fragment and an array of parameters. */ getInOrEqual(items: any, equal: boolean = true, onEmptyItems?: any): any[] { let sql, @@ -571,7 +553,7 @@ export class SQLiteDB { /** * Get the database name. * - * @return {string} Database name. + * @return Database name. */ getName(): string { return this.name; @@ -580,10 +562,10 @@ export class SQLiteDB { /** * Get a single database record where all the given conditions met. * - * @param {string} table The table to query. - * @param {object} [conditions] The conditions to build the where clause. Must not contain numeric indexes. - * @param {string} [fields='*'] A comma separated list of fields to return. - * @return {Promise} Promise resolved with the record, rejected if not found. + * @param table The table to query. + * @param conditions The conditions to build the where clause. Must not contain numeric indexes. + * @param fields A comma separated list of fields to return. + * @return Promise resolved with the record, rejected if not found. */ getRecord(table: string, conditions?: object, fields: string = '*'): Promise { const selectAndParams = this.whereClause(conditions); @@ -594,11 +576,11 @@ export class SQLiteDB { /** * Get a single database record as an object which match a particular WHERE clause. * - * @param {string} table The table to query. - * @param {string} [select] A fragment of SQL to be used in a where clause in the SQL call. - * @param {any[]} [params] An array of sql parameters. - * @param {string} [fields='*'] A comma separated list of fields to return. - * @return {Promise} Promise resolved with the record, rejected if not found. + * @param table The table to query. + * @param select A fragment of SQL to be used in a where clause in the SQL call. + * @param params An array of sql parameters. + * @param fields A comma separated list of fields to return. + * @return Promise resolved with the record, rejected if not found. */ getRecordSelect(table: string, select: string = '', params: any[] = [], fields: string = '*'): Promise { if (select) { @@ -614,9 +596,9 @@ export class SQLiteDB { * The SQL statement should normally only return one record. * It is recommended to use getRecordsSql() if more matches possible! * - * @param {string} sql The SQL string you wish to be executed, should normally only return one record. - * @param {any[]} [params] List of sql parameters - * @return {Promise} Promise resolved with the records. + * @param sql The SQL string you wish to be executed, should normally only return one record. + * @param params List of sql parameters + * @return Promise resolved with the records. */ getRecordSql(sql: string, params?: any[]): Promise { return this.getRecordsSql(sql, params, 0, 1).then((result) => { @@ -633,13 +615,13 @@ export class SQLiteDB { /** * Get a number of records where all the given conditions met. * - * @param {string} table The table to query. - * @param {object} [conditions] The conditions to build the where clause. Must not contain numeric indexes. - * @param {string} [sort=''] An order to sort the results in. - * @param {string} [fields='*'] A comma separated list of fields to return. - * @param {number} [limitFrom=0] Return a subset of records, starting at this point. - * @param {number} [limitNum=0] Return a subset comprising this many records in total. - * @return {Promise} Promise resolved with the records. + * @param table The table to query. + * @param conditions The conditions to build the where clause. Must not contain numeric indexes. + * @param sort An order to sort the results in. + * @param fields A comma separated list of fields to return. + * @param limitFrom Return a subset of records, starting at this point. + * @param limitNum Return a subset comprising this many records in total. + * @return Promise resolved with the records. */ getRecords(table: string, conditions?: object, sort: string = '', fields: string = '*', limitFrom: number = 0, limitNum: number = 0): Promise { @@ -651,14 +633,14 @@ export class SQLiteDB { /** * Get a number of records where one field match one list of values. * - * @param {string} table The database table to be checked against. - * @param {string} field The name of a field. - * @param {any[]} values The values field might take. - * @param {string} [sort=''] An order to sort the results in. - * @param {string} [fields='*'] A comma separated list of fields to return. - * @param {number} [limitFrom=0] Return a subset of records, starting at this point. - * @param {number} [limitNum=0] Return a subset comprising this many records in total. - * @return {Promise} Promise resolved with the records. + * @param table The database table to be checked against. + * @param field The name of a field. + * @param values The values field might take. + * @param sort An order to sort the results in. + * @param fields A comma separated list of fields to return. + * @param limitFrom Return a subset of records, starting at this point. + * @param limitNum Return a subset comprising this many records in total. + * @return Promise resolved with the records. */ getRecordsList(table: string, field: string, values: any[], sort: string = '', fields: string = '*', limitFrom: number = 0, limitNum: number = 0): Promise { @@ -670,14 +652,14 @@ export class SQLiteDB { /** * Get a number of records which match a particular WHERE clause. * - * @param {string} table The table to query. - * @param {string} [select] A fragment of SQL to be used in a where clause in the SQL call. - * @param {any[]} [params] An array of sql parameters. - * @param {string} [sort=''] An order to sort the results in. - * @param {string} [fields='*'] A comma separated list of fields to return. - * @param {number} [limitFrom=0] Return a subset of records, starting at this point. - * @param {number} [limitNum=0] Return a subset comprising this many records in total. - * @return {Promise} Promise resolved with the records. + * @param table The table to query. + * @param select A fragment of SQL to be used in a where clause in the SQL call. + * @param params An array of sql parameters. + * @param sort An order to sort the results in. + * @param fields A comma separated list of fields to return. + * @param limitFrom Return a subset of records, starting at this point. + * @param limitNum Return a subset comprising this many records in total. + * @return Promise resolved with the records. */ getRecordsSelect(table: string, select: string = '', params: any[] = [], sort: string = '', fields: string = '*', limitFrom: number = 0, limitNum: number = 0): Promise { @@ -696,11 +678,11 @@ export class SQLiteDB { /** * Get a number of records using a SQL statement. * - * @param {string} sql The SQL select query to execute. - * @param {any[]} [params] List of sql parameters - * @param {number} [limitFrom] Return a subset of records, starting at this point. - * @param {number} [limitNum] Return a subset comprising this many records. - * @return {Promise} Promise resolved with the records. + * @param sql The SQL select query to execute. + * @param params List of sql parameters + * @param limitFrom Return a subset of records, starting at this point. + * @param limitNum Return a subset comprising this many records. + * @return Promise resolved with the records. */ getRecordsSql(sql: string, params?: any[], limitFrom?: number, limitNum?: number): Promise { const limits = this.normaliseLimitFromNum(limitFrom, limitNum); @@ -726,9 +708,9 @@ export class SQLiteDB { /** * Given a data object, returns the SQL query and the params to insert that record. * - * @param {string} table The database table. - * @param {object} data A data object with values for one or more fields in the record. - * @return {any[]} Array with the SQL query and the params. + * @param table The database table. + * @param data A data object with values for one or more fields in the record. + * @return Array with the SQL query and the params. */ protected getSqlInsertQuery(table: string, data: object): any[] { this.formatDataToInsert(data); @@ -760,9 +742,9 @@ export class SQLiteDB { /** * Insert a record into a table and return the "rowId" field. * - * @param {string} table The database table to be inserted into. - * @param {object} data A data object with values for one or more fields in the record. - * @return {Promise} Promise resolved with new rowId. Please notice this rowId is internal from SQLite. + * @param table The database table to be inserted into. + * @param data A data object with values for one or more fields in the record. + * @return Promise resolved with new rowId. Please notice this rowId is internal from SQLite. */ insertRecord(table: string, data: object): Promise { const sqlAndParams = this.getSqlInsertQuery(table, data); @@ -775,9 +757,9 @@ export class SQLiteDB { /** * Insert multiple records into database as fast as possible. * - * @param {string} table The database table to be inserted into. - * @param {object[]} dataObjects List of objects to be inserted. - * @return {Promise} Promise resolved when done. + * @param table The database table to be inserted into. + * @param dataObjects List of objects to be inserted. + * @return Promise resolved when done. */ insertRecords(table: string, dataObjects: object[]): Promise { if (!Array.isArray(dataObjects)) { @@ -796,11 +778,11 @@ export class SQLiteDB { /** * Insert multiple records into database from another table. * - * @param {string} table The database table to be inserted into. - * @param {string} source The database table to get the records from. - * @param {object} [conditions] The conditions to build the where clause. Must not contain numeric indexes. - * @param {string} [fields='*'] A comma separated list of fields to return. - * @return {Promise} Promise resolved when done. + * @param table The database table to be inserted into. + * @param source The database table to get the records from. + * @param conditions The conditions to build the where clause. Must not contain numeric indexes. + * @param fields A comma separated list of fields to return. + * @return Promise resolved when done. */ insertRecordsFrom(table: string, source: string, conditions?: object, fields: string = '*'): Promise { const selectAndParams = this.whereClause(conditions); @@ -815,9 +797,9 @@ export class SQLiteDB { * We explicitly treat null, '' and -1 as 0 in order to provide compatibility with how limit * values have been passed historically. * - * @param {any} limitFrom Where to start results from. - * @param {any} limitNum How many results to return. - * @return {number[]} Normalised limit params in array: [limitFrom, limitNum]. + * @param limitFrom Where to start results from. + * @param limitNum How many results to return. + * @return Normalised limit params in array: [limitFrom, limitNum]. */ normaliseLimitFromNum(limitFrom: any, limitNum: any): number[] { // We explicilty treat these cases as 0. @@ -839,7 +821,7 @@ export class SQLiteDB { /** * Open the database. Only needed if it was closed before, a database is automatically opened when created. * - * @return {Promise} Promise resolved when open. + * @return Promise resolved when open. */ open(): Promise { return this.ready().then(() => { @@ -850,7 +832,7 @@ export class SQLiteDB { /** * Wait for the DB to be ready. * - * @return {Promise} Promise resolved when ready. + * @return Promise resolved when ready. */ ready(): Promise { return this.promise; @@ -859,9 +841,9 @@ export class SQLiteDB { /** * Test whether a record exists in a table where all the given conditions met. * - * @param {string} table The table to check. - * @param {object} [conditions] The conditions to build the where clause. Must not contain numeric indexes. - * @return {Promise} Promise resolved if exists, rejected otherwise. + * @param table The table to check. + * @param conditions The conditions to build the where clause. Must not contain numeric indexes. + * @return Promise resolved if exists, rejected otherwise. */ recordExists(table: string, conditions?: object): Promise { return this.getRecord(table, conditions).then((record) => { @@ -874,10 +856,10 @@ export class SQLiteDB { /** * Test whether any records exists in a table which match a particular WHERE clause. * - * @param {string} table The table to query. - * @param {string} [select] A fragment of SQL to be used in a where clause in the SQL call. - * @param {any[]} [params] An array of sql parameters. - * @return {Promise} Promise resolved if exists, rejected otherwise. + * @param table The table to query. + * @param select A fragment of SQL to be used in a where clause in the SQL call. + * @param params An array of sql parameters. + * @return Promise resolved if exists, rejected otherwise. */ recordExistsSelect(table: string, select: string = '', params: any[] = []): Promise { return this.getRecordSelect(table, select, params).then((record) => { @@ -890,9 +872,9 @@ export class SQLiteDB { /** * Test whether a SQL SELECT statement returns any records. * - * @param {string} sql The SQL query returning one row with one column. - * @param {any[]} [params] An array of sql parameters. - * @return {Promise} Promise resolved if exists, rejected otherwise. + * @param sql The SQL query returning one row with one column. + * @param params An array of sql parameters. + * @return Promise resolved if exists, rejected otherwise. */ recordExistsSql(sql: string, params?: any[]): Promise { return this.getRecordSql(sql, params).then((record) => { @@ -905,8 +887,8 @@ export class SQLiteDB { /** * Test whether a table exists.. * - * @param {string} name The table name. - * @return {Promise} Promise resolved if exists, rejected otherwise. + * @param name The table name. + * @return Promise resolved if exists, rejected otherwise. */ tableExists(name: string): Promise { return this.recordExists('sqlite_master', {type: 'table', tbl_name: name}); @@ -915,10 +897,10 @@ export class SQLiteDB { /** * Update one or more records in a table. * - * @param {string} string table The database table to update. - * @param {any} data An object with the fields to update: fieldname=>fieldvalue. - * @param {any} [conditions] The conditions to build the where clause. Must not contain numeric indexes. - * @return {Promise} Promise resolved when updated. + * @param string table The database table to update. + * @param data An object with the fields to update: fieldname=>fieldvalue. + * @param conditions The conditions to build the where clause. Must not contain numeric indexes. + * @return Promise resolved when updated. */ updateRecords(table: string, data: any, conditions?: any): Promise { @@ -948,11 +930,11 @@ export class SQLiteDB { /** * Update one or more records in a table. It accepts a WHERE clause as a string. * - * @param {string} string table The database table to update. - * @param {any} data An object with the fields to update: fieldname=>fieldvalue. - * @param {string} [where] Where clause. Must not include the "WHERE" word. - * @param {any[]} [whereParams] Params for the where clause. - * @return {Promise} Promise resolved when updated. + * @param string table The database table to update. + * @param data An object with the fields to update: fieldname=>fieldvalue. + * @param where Where clause. Must not include the "WHERE" word. + * @param whereParams Params for the where clause. + * @return Promise resolved when updated. */ updateRecordsWhere(table: string, data: any, where?: string, whereParams?: any[]): Promise { if (!data || !Object.keys(data).length) { @@ -985,8 +967,8 @@ export class SQLiteDB { /** * Returns the SQL WHERE conditions. * - * @param {object} [conditions] The conditions to build the where clause. Must not contain numeric indexes. - * @return {any[]} An array list containing sql 'where' part and 'params'. + * @param conditions The conditions to build the where clause. Must not contain numeric indexes. + * @return An array list containing sql 'where' part and 'params'. */ whereClause(conditions: any = {}): any[] { if (!conditions || !Object.keys(conditions).length) { @@ -1013,9 +995,9 @@ export class SQLiteDB { /** * Returns SQL WHERE conditions for the ..._list group of methods. * - * @param {string} field The name of a field. - * @param {any[]} values The values field might take. - * @return {any[]} An array containing sql 'where' part and 'params'. + * @param field The name of a field. + * @param values The values field might take. + * @return An array containing sql 'where' part and 'params'. */ whereClauseList(field: string, values: any[]): any[] { if (!values || !values.length) { diff --git a/src/components/attachments/attachments.ts b/src/components/attachments/attachments.ts index 03ada9d5d..810165c39 100644 --- a/src/components/attachments/attachments.ts +++ b/src/components/attachments/attachments.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -106,14 +106,14 @@ export class CoreAttachmentsComponent implements OnInit { /** * Delete a file from the list. * - * @param {number} index The index of the file. - * @param {boolean} [askConfirm] Whether to ask confirm. + * @param index The index of the file. + * @param askConfirm Whether to ask confirm. */ delete(index: number, askConfirm?: boolean): void { let promise; if (askConfirm) { - promise = this.domUtils.showConfirm(this.translate.instant('core.confirmdeletefile')); + promise = this.domUtils.showDeleteConfirm('core.confirmdeletefile'); } else { promise = Promise.resolve(); } @@ -129,8 +129,8 @@ export class CoreAttachmentsComponent implements OnInit { /** * A file was renamed. * - * @param {number} index Index of the file. - * @param {any} data The data received. + * @param index Index of the file. + * @param data The data received. */ renamed(index: number, data: any): void { this.files[index] = data.file; diff --git a/src/components/bs-tooltip/bs-tooltip.ts b/src/components/bs-tooltip/bs-tooltip.ts index 8248fa9f1..55d413731 100644 --- a/src/components/bs-tooltip/bs-tooltip.ts +++ b/src/components/bs-tooltip/bs-tooltip.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/components/chart/chart.ts b/src/components/chart/chart.ts index 55ec191ca..b3715ad43 100644 --- a/src/components/chart/chart.ts +++ b/src/components/chart/chart.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -14,6 +14,9 @@ import { Component, Input, OnDestroy, OnInit, ElementRef, OnChanges, ViewChild } from '@angular/core'; import { Chart } from 'chart.js'; +import { CoreFilterProvider } from '@core/filter/providers/filter'; +import { CoreFilterHelperProvider } from '@core/filter/providers/helper'; +import { CoreUtilsProvider } from '@providers/utils/utils'; /** * This component shows a chart using chart.js. @@ -44,13 +47,17 @@ export class CoreChartComponent implements OnDestroy, OnInit, OnChanges { @Input() type: string; // Type of chart. @Input() legend: any; // Legend options. @Input() height = 300; // Height of the chart element. + @Input() filter?: boolean | string; // Whether to filter labels. If not defined, true if contextLevel and instanceId are set. + @Input() contextLevel?: string; // The context level of the text. + @Input() contextInstanceId?: number; // The instance ID related to the context. + @Input() courseId?: number; // Course ID the text belongs to. It can be used to improve performance with filters. + @Input() wsNotFiltered?: boolean | string; // If true it means the WS didn't filter the labels for some reason. @ViewChild('canvas') canvas: ElementRef; chart: any; - constructor() { - // Nothing to do. - } + constructor(protected filterProvider: CoreFilterProvider, private utils: CoreUtilsProvider, + private filterHelper: CoreFilterHelperProvider) { } /** * Component being initialized. @@ -86,17 +93,21 @@ export class CoreChartComponent implements OnDestroy, OnInit, OnChanges { this.type = 'horizontalBar'; } - const context = this.canvas.nativeElement.getContext('2d'); - this.chart = new Chart(context, { - type: this.type, - data: { - labels: this.labels, - datasets: [{ - data: this.data, - backgroundColor: this.getRandomColors(this.data.length) - }] - }, - options: {legend: legend} + // Format labels if needed. + this.formatLabels().then(() => { + + const context = this.canvas.nativeElement.getContext('2d'); + this.chart = new Chart(context, { + type: this.type, + data: { + labels: this.labels, + datasets: [{ + data: this.data, + backgroundColor: this.getRandomColors(this.data.length) + }] + }, + options: {legend: legend} + }); }); } @@ -105,20 +116,55 @@ export class CoreChartComponent implements OnDestroy, OnInit, OnChanges { */ ngOnChanges(): void { if (this.chart) { - this.chart.data.datasets[0] = { - data: this.data, - backgroundColor: this.getRandomColors(this.data.length) - }; - this.chart.data.labels = this.labels; - this.chart.update(); + // Format labels if needed. + this.formatLabels().then(() => { + this.chart.data.datasets[0] = { + data: this.data, + backgroundColor: this.getRandomColors(this.data.length) + }; + this.chart.data.labels = this.labels; + this.chart.update(); + }); } } + /** + * Format labels if needed. + * + * @return Promise resolved when done. + */ + protected formatLabels(): Promise { + this.filter = typeof this.filter == 'undefined' ? !!(this.contextLevel && this.contextInstanceId) : !!this.filter; + + if (!this.filter) { + return Promise.resolve(); + } + + const options = { + clean: true, + singleLine: true, + courseId: this.courseId, + wsNotFiltered: this.utils.isTrueOrOne(this.wsNotFiltered) + }; + + return this.filterHelper.getFilters(this.contextLevel, this.contextInstanceId, options).then((filters) => { + const promises = []; + + this.labels.forEach((label, i) => { + promises.push(this.filterProvider.formatText(label, options, filters).then((text) => { + this.labels[i] = text; + })); + }); + + return Promise.all(promises); + }); + } + /** * Generate random colors if needed. * - * @param {number} n Number of colors needed. - * @return {any[]} Array with the number of background colors requested. + * @param n Number of colors needed. + * @return Array with the number of background colors requested. */ protected getRandomColors(n: number): any[] { while (CoreChartComponent.backgroundColors.length < n) { diff --git a/src/components/chrono/chrono.ts b/src/components/chrono/chrono.ts index 8783416af..6dd993060 100644 --- a/src/components/chrono/chrono.ts +++ b/src/components/chrono/chrono.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/components/components.module.ts b/src/components/components.module.ts index c47653a06..fe663a255 100644 --- a/src/components/components.module.ts +++ b/src/components/components.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -27,6 +27,7 @@ import { CoreProgressBarComponent } from './progress-bar/progress-bar'; import { CoreEmptyBoxComponent } from './empty-box/empty-box'; import { CoreSearchBoxComponent } from './search-box/search-box'; import { CoreFileComponent } from './file/file'; +import { CoreFilesComponent } from './files/files'; import { CoreIconComponent } from './icon/icon'; import { CoreContextMenuComponent } from './context-menu/context-menu'; import { CoreContextMenuItemComponent } from './context-menu/context-menu-item'; @@ -67,6 +68,7 @@ import { CoreBSTooltipComponent } from './bs-tooltip/bs-tooltip'; CoreEmptyBoxComponent, CoreSearchBoxComponent, CoreFileComponent, + CoreFilesComponent, CoreIconComponent, CoreContextMenuComponent, CoreContextMenuItemComponent, @@ -118,6 +120,7 @@ import { CoreBSTooltipComponent } from './bs-tooltip/bs-tooltip'; CoreEmptyBoxComponent, CoreSearchBoxComponent, CoreFileComponent, + CoreFilesComponent, CoreIconComponent, CoreContextMenuComponent, CoreContextMenuItemComponent, diff --git a/src/components/context-menu/context-menu-item.ts b/src/components/context-menu/context-menu-item.ts index 0870cbcd3..b15a5f82f 100644 --- a/src/components/context-menu/context-menu-item.ts +++ b/src/components/context-menu/context-menu-item.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -51,12 +51,14 @@ export class CoreContextMenuItemComponent implements OnInit, OnDestroy, OnChange @Input() badgeClass?: number; // A class to set in the badge. @Input() hidden?: boolean; // Whether the item should be hidden. @Output() action?: EventEmitter<() => void>; // Will emit an event when the item clicked. + @Output() onClosed?: EventEmitter<() => void>; // Will emit an event when the popover is closed because the item was clicked. protected hasAction = false; protected destroyed = false; constructor(private ctxtMenu: CoreContextMenuComponent) { this.action = new EventEmitter(); + this.onClosed = new EventEmitter(); } /** @@ -85,9 +87,9 @@ export class CoreContextMenuItemComponent implements OnInit, OnDestroy, OnChange /** * Get a boolean value from item. * - * @param {any} value Value to check. - * @param {boolean} defaultValue Value to use if undefined. - * @return {boolean} Boolean value. + * @param value Value to check. + * @param defaultValue Value to use if undefined. + * @return Boolean value. */ protected getBooleanValue(value: any, defaultValue: boolean): boolean { if (typeof value == 'undefined') { diff --git a/src/components/context-menu/context-menu-popover.ts b/src/components/context-menu/context-menu-popover.ts index aa468f189..6d43c77cf 100644 --- a/src/components/context-menu/context-menu-popover.ts +++ b/src/components/context-menu/context-menu-popover.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -40,16 +40,16 @@ export class CoreContextMenuPopoverComponent { /** * Close the popover. */ - closeMenu(): void { - this.viewCtrl.dismiss(); + closeMenu(item?: CoreContextMenuItemComponent): void { + this.viewCtrl.dismiss(item); } /** * Function called when an item is clicked. * - * @param {Event} event Click event. - * @param {CoreContextMenuItemComponent} item Item clicked. - * @return {boolean} Return true if success, false if error. + * @param event Click event. + * @param item Item clicked. + * @return Return true if success, false if error. */ itemClicked(event: Event, item: CoreContextMenuItemComponent): boolean { if (item.action.observers.length > 0) { @@ -61,12 +61,12 @@ export class CoreContextMenuPopoverComponent { } if (item.closeOnClick) { - this.closeMenu(); + this.closeMenu(item); } - item.action.emit(this.closeMenu.bind(this)); - } else if (item.href && item.closeOnClick) { - this.closeMenu(); + item.action.emit(this.closeMenu.bind(this, item)); + } else if (item.closeOnClick && (item.href || item.onClosed.observers.length > 0)) { + this.closeMenu(item); } return true; diff --git a/src/components/context-menu/context-menu.ts b/src/components/context-menu/context-menu.ts index 42fca4504..49f7a1e43 100644 --- a/src/components/context-menu/context-menu.ts +++ b/src/components/context-menu/context-menu.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -76,7 +76,7 @@ export class CoreContextMenuComponent implements OnInit, OnDestroy { /** * Add a context menu item. * - * @param {CoreContextMenuItemComponent} item The item to add. + * @param item The item to add. */ addItem(item: CoreContextMenuItemComponent): void { if (this.parentContextMenu) { @@ -108,7 +108,7 @@ export class CoreContextMenuComponent implements OnInit, OnDestroy { * Merge the current context menu with the one passed as parameter. All the items in this menu will be moved to the * one passed as parameter. * - * @param {CoreContextMenuComponent} contextMenu The context menu where to move the items. + * @param contextMenu The context menu where to move the items. */ mergeContextMenus(contextMenu: CoreContextMenuComponent): void { this.parentContextMenu = contextMenu; @@ -128,7 +128,7 @@ export class CoreContextMenuComponent implements OnInit, OnDestroy { /** * Remove an item from the context menu. * - * @param {CoreContextMenuItemComponent} item The item to remove. + * @param item The item to remove. */ removeItem(item: CoreContextMenuItemComponent): void { if (this.parentContextMenu) { @@ -173,15 +173,19 @@ export class CoreContextMenuComponent implements OnInit, OnDestroy { /** * Show the context menu. * - * @param {MouseEvent} event Event. + * @param event Event. */ showContextMenu(event: MouseEvent): void { if (!this.expanded) { const popover = this.popoverCtrl.create(CoreContextMenuPopoverComponent, { title: this.title, items: this.items, id: this.uniqueId, showBackdrop: true }); - popover.onDidDismiss(() => { + popover.onDidDismiss((item: CoreContextMenuItemComponent) => { this.expanded = false; + + if (item) { + item.onClosed.emit(); + } }); popover.present({ ev: event diff --git a/src/components/context-menu/core-context-menu-popover.html b/src/components/context-menu/core-context-menu-popover.html index 69ffdadf7..76a6b96d1 100644 --- a/src/components/context-menu/core-context-menu-popover.html +++ b/src/components/context-menu/core-context-menu-popover.html @@ -2,7 +2,7 @@ {{title}} - + {{item.badge}} diff --git a/src/components/course-picker-menu/core-course-picker-menu-popover.html b/src/components/course-picker-menu/core-course-picker-menu-popover.html index 6eea11d5e..ebfa0f52a 100644 --- a/src/components/course-picker-menu/core-course-picker-menu-popover.html +++ b/src/components/course-picker-menu/core-course-picker-menu-popover.html @@ -1,6 +1,6 @@ - + \ No newline at end of file diff --git a/src/components/course-picker-menu/course-picker-menu-popover.ts b/src/components/course-picker-menu/course-picker-menu-popover.ts index 2e53d9f61..01e553660 100644 --- a/src/components/course-picker-menu/course-picker-menu-popover.ts +++ b/src/components/course-picker-menu/course-picker-menu-popover.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -34,9 +34,9 @@ export class CoreCoursePickerMenuPopoverComponent { /** * Function called when a course is clicked. * - * @param {Event} event Click event. - * @param {any} course Course object clicked. - * @return {boolean} Return true if success, false if error. + * @param event Click event. + * @param course Course object clicked. + * @return Return true if success, false if error. */ coursePicked(event: Event, course: any): boolean { this.viewCtrl.dismiss(course); diff --git a/src/components/download-refresh/download-refresh.ts b/src/components/download-refresh/download-refresh.ts index 87d964c9b..67f855e29 100644 --- a/src/components/download-refresh/download-refresh.ts +++ b/src/components/download-refresh/download-refresh.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -44,8 +44,8 @@ export class CoreDownloadRefreshComponent { /** * Download clicked. * - * @param {Event} e Click event. - * @param {boolean} refresh Whether it's refreshing. + * @param e Click event. + * @param refresh Whether it's refreshing. */ download(e: Event, refresh: boolean): void { e.preventDefault(); diff --git a/src/components/dynamic-component/dynamic-component.ts b/src/components/dynamic-component/dynamic-component.ts index 36fe69cdc..c2ed305b7 100644 --- a/src/components/dynamic-component/dynamic-component.ts +++ b/src/components/dynamic-component/dynamic-component.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -118,9 +118,9 @@ export class CoreDynamicComponent implements OnInit, OnChanges, DoCheck { /** * Call a certain function on the component. * - * @param {string} name Name of the function to call. - * @param {any[]} params List of params to send to the function. - * @return {any} Result of the call. Undefined if no component instance or the function doesn't exist. + * @param name Name of the function to call. + * @param params List of params to send to the function. + * @return Result of the call. Undefined if no component instance or the function doesn't exist. */ callComponentFunction(name: string, params?: any[]): any { if (this.instance && typeof this.instance[name] == 'function') { @@ -131,7 +131,7 @@ export class CoreDynamicComponent implements OnInit, OnChanges, DoCheck { /** * Create a component, add it to a container and set the input data. * - * @return {boolean} Whether the component was successfully created. + * @return Whether the component was successfully created. */ protected createComponent(): boolean { this.lastComponent = this.component; diff --git a/src/components/empty-box/empty-box.ts b/src/components/empty-box/empty-box.ts index 827cde39a..5c693c84c 100644 --- a/src/components/empty-box/empty-box.ts +++ b/src/components/empty-box/empty-box.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/components/file/file.scss b/src/components/file/file.scss index 227c565b8..9fa957c53 100644 --- a/src/components/file/file.scss +++ b/src/components/file/file.scss @@ -20,7 +20,7 @@ ion-app.app-root { core-file > .item.item-block > .item-inner { border-bottom: 0; - @include padding(null, 0, null, null); + @include safe-area-padding(null, 0px, null, null); .buttons { display: flex; flex-flow: row; diff --git a/src/components/file/file.ts b/src/components/file/file.ts index 22d0c98af..57c509107 100644 --- a/src/components/file/file.ts +++ b/src/components/file/file.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -23,6 +23,7 @@ import { CoreMimetypeUtilsProvider } from '@providers/utils/mimetype'; import { CoreUtilsProvider } from '@providers/utils/utils'; import { CoreTextUtilsProvider } from '@providers/utils/text'; import { CoreConstants } from '@core/constants'; +import { CorePluginFileDelegate } from '@providers/plugin-file-delegate'; /** * Component to handle a remote file. Shows the file name, icon (depending on mimetype) and a button @@ -56,10 +57,16 @@ export class CoreFileComponent implements OnInit, OnDestroy { protected timemodified: number; protected observer; - constructor(private sitesProvider: CoreSitesProvider, private utils: CoreUtilsProvider, private domUtils: CoreDomUtilsProvider, - private filepoolProvider: CoreFilepoolProvider, private appProvider: CoreAppProvider, - private fileHelper: CoreFileHelperProvider, private mimeUtils: CoreMimetypeUtilsProvider, - private eventsProvider: CoreEventsProvider, private textUtils: CoreTextUtilsProvider) { + constructor(private sitesProvider: CoreSitesProvider, + private utils: CoreUtilsProvider, + private domUtils: CoreDomUtilsProvider, + private filepoolProvider: CoreFilepoolProvider, + private appProvider: CoreAppProvider, + private fileHelper: CoreFileHelperProvider, + private mimeUtils: CoreMimetypeUtilsProvider, + private eventsProvider: CoreEventsProvider, + private textUtils: CoreTextUtilsProvider, + private pluginFileDelegate: CorePluginFileDelegate) { this.onDelete = new EventEmitter(); } @@ -104,7 +111,7 @@ export class CoreFileComponent implements OnInit, OnDestroy { /** * Convenience function to get the file state and set variables based on it. * - * @return {Promise} Promise resolved when state has been calculated. + * @return Promise resolved when state has been calculated. */ protected calculateState(): Promise { return this.filepoolProvider.getFileStateByUrl(this.siteId, this.fileUrl, this.timemodified).then((state) => { @@ -118,7 +125,7 @@ export class CoreFileComponent implements OnInit, OnDestroy { /** * Convenience function to open a file, downloading it if needed. * - * @return {Promise} Promise resolved when file is opened. + * @return Promise resolved when file is opened. */ protected openFile(): Promise { return this.fileHelper.downloadAndOpenFile(this.file, this.component, this.componentId, this.state, (event) => { @@ -134,15 +141,13 @@ export class CoreFileComponent implements OnInit, OnDestroy { /** * Download a file and, optionally, open it afterwards. * - * @param {Event} [e] Click event. - * @param {boolean} openAfterDownload Whether the file should be opened after download. + * @param e Click event. + * @param openAfterDownload Whether the file should be opened after download. */ download(e?: Event, openAfterDownload: boolean = false): void { e && e.preventDefault(); e && e.stopPropagation(); - let promise; - if (this.isDownloading && !openAfterDownload) { return; } @@ -164,7 +169,7 @@ export class CoreFileComponent implements OnInit, OnDestroy { } if (!this.appProvider.isOnline() && (!openAfterDownload || (openAfterDownload && - !(this.state === CoreConstants.DOWNLOADED || this.state === CoreConstants.OUTDATED)))) { + !this.fileHelper.isStateDownloaded(this.state)))) { this.domUtils.showErrorModal('core.networkerrormsg', true); return; @@ -177,20 +182,26 @@ export class CoreFileComponent implements OnInit, OnDestroy { }); } else { // File doesn't need to be opened (it's a prefetch). Show confirm modal if file size is defined and it's big. - promise = this.fileSize ? this.domUtils.confirmDownloadSize({ size: this.fileSize, total: true }) : Promise.resolve(); - promise.then(() => { - // User confirmed, add the file to queue. - return this.filepoolProvider.invalidateFileByUrl(this.siteId, this.fileUrl).finally(() => { - this.isDownloading = true; + this.pluginFileDelegate.getFileSize({fileurl: this.fileUrl, filesize: this.fileSize}, this.siteId).then((size) => { - this.filepoolProvider.addToQueueByUrl(this.siteId, this.fileUrl, this.component, - this.componentId, this.timemodified, undefined, undefined, 0, this.file).catch((error) => { - this.domUtils.showErrorModalDefault(error, 'core.errordownloading', true); - this.calculateState(); - }); + const promise = size ? this.domUtils.confirmDownloadSize({ size: size, total: true }) : Promise.resolve(); + + return promise.then(() => { + // User confirmed, add the file to queue. + return this.filepoolProvider.invalidateFileByUrl(this.siteId, this.fileUrl).finally(() => { + this.isDownloading = true; + + this.filepoolProvider.addToQueueByUrl(this.siteId, this.fileUrl, this.component, + this.componentId, this.timemodified, undefined, undefined, 0, this.file).catch((error) => { + this.domUtils.showErrorModalDefault(error, 'core.errordownloading', true); + this.calculateState(); + }); + }); + }).catch(() => { + // User cancelled. }); - }).catch(() => { - // Ignore error. + }).catch((error) => { + this.domUtils.showErrorModalDefault(error, 'core.errordownloading', true); }); } } @@ -198,7 +209,7 @@ export class CoreFileComponent implements OnInit, OnDestroy { /** * Delete the file. * - * @param {Event} e Click event. + * @param e Click event. */ delete(e: Event): void { e.preventDefault(); diff --git a/src/components/files/core-files.html b/src/components/files/core-files.html new file mode 100644 index 000000000..239728e29 --- /dev/null +++ b/src/components/files/core-files.html @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/src/components/files/files.ts b/src/components/files/files.ts new file mode 100644 index 000000000..551c81137 --- /dev/null +++ b/src/components/files/files.ts @@ -0,0 +1,82 @@ +// (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 { Component, Input, OnInit, DoCheck, KeyValueDiffers } from '@angular/core'; +import { CoreMimetypeUtilsProvider } from '@providers/utils/mimetype'; +import { CoreUtilsProvider } from '@providers/utils/utils'; + +/** + * Component to render a file list. + * + * + * + */ +@Component({ + selector: 'core-files', + templateUrl: 'core-files.html' +}) +export class CoreFilesComponent implements OnInit, DoCheck { + @Input() files: any[]; // List of files. + @Input() component: string; // Component the downloaded files will be linked to. + @Input() componentId: string | number; // Component ID. + @Input() alwaysDownload?: boolean | string; // Whether it should always display the refresh button when the file is downloaded. + // Use it for files that you cannot determine if they're outdated or not. + @Input() canDownload?: boolean | string = true; // Whether file can be downloaded. + @Input() showSize?: boolean | string = true; // Whether show filesize. + @Input() showTime?: boolean | string = true; // Whether show file time modified. + @Input() showInline = false; // If true, it will reorder and try to show inline files first. + + contentText: string; + + protected differ: any; // To detect changes in the data input. + + constructor(protected mimetypeUtils: CoreMimetypeUtilsProvider, + protected utils: CoreUtilsProvider, + differs: KeyValueDiffers) { + this.differ = differs.find([]).create(); + } + + /** + * Component being initialized. + */ + ngOnInit(): void { + if (this.utils.isTrueOrOne(this.showInline)) { + this.renderInlineFiles(); + } + } + + /** + * Detect and act upon changes that Angular can’t or won’t detect on its own (objects and arrays). + */ + ngDoCheck(): void { + if (this.utils.isTrueOrOne(this.showInline)) { + // Check if there's any change in the files array. + const changes = this.differ.diff(this.files); + if (changes) { + this.renderInlineFiles(); + } + } + } + + /** + * Calculate contentText based on fils that can be rendered inline. + */ + protected renderInlineFiles(): void { + this.contentText = this.files.reduce((previous, file) => { + const text = this.mimetypeUtils.getEmbeddedHtml(file); + + return text ? previous + '
' + text : previous; + }, ''); + } +} diff --git a/src/components/icon/icon.scss b/src/components/icon/icon.scss index f909e876e..a80559c8e 100644 --- a/src/components/icon/icon.scss +++ b/src/components/icon/icon.scss @@ -5,6 +5,12 @@ } } +@each $color-name, $color-base, $color-contrast in get-colors($colors-dark) { + .fa-#{$color-name} { + color: $color-base !important; + } +} + [dir=rtl] .icon { &.core-icon-dir-flip, diff --git a/src/components/icon/icon.ts b/src/components/icon/icon.ts index 82e53d165..9bab34f9b 100644 --- a/src/components/icon/icon.ts +++ b/src/components/icon/icon.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -82,6 +82,7 @@ export class CoreIconComponent implements OnChanges, OnDestroy { !this.ariaLabel && this.newElement.setAttribute('aria-hidden', 'true'); !this.ariaLabel && this.newElement.setAttribute('role', 'presentation'); this.ariaLabel && this.newElement.setAttribute('aria-label', this.ariaLabel); + this.ariaLabel && this.newElement.setAttribute('title', this.ariaLabel); const attrs = this.element.attributes; for (let i = attrs.length - 1; i >= 0; i--) { @@ -111,8 +112,8 @@ export class CoreIconComponent implements OnChanges, OnDestroy { /** * Check if the value is true or on. * - * @param {any} val value to be checked. - * @return {boolean} If has a value equivalent to true. + * @param val value to be checked. + * @return If has a value equivalent to true. */ isTrueProperty(val: any): boolean { if (typeof val === 'string') { diff --git a/src/components/iframe/core-iframe.html b/src/components/iframe/core-iframe.html index a94b5d110..9e5f98ae6 100644 --- a/src/components/iframe/core-iframe.html +++ b/src/components/iframe/core-iframe.html @@ -1,5 +1,5 @@ -
- +
+ diff --git a/src/components/iframe/iframe.scss b/src/components/iframe/iframe.scss index 82b13e078..782a554cb 100644 --- a/src/components/iframe/iframe.scss +++ b/src/components/iframe/iframe.scss @@ -1,11 +1,9 @@ ion-app.app-root core-iframe { - > div { - height: 100%; - } iframe { border: 0; display: block; max-width: 100%; + background-color: $gray-light; } .core-loading-container { diff --git a/src/components/iframe/iframe.ts b/src/components/iframe/iframe.ts index 722d9eae6..516012a8f 100644 --- a/src/components/iframe/iframe.ts +++ b/src/components/iframe/iframe.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -19,11 +19,11 @@ import { DomSanitizer, SafeResourceUrl } from '@angular/platform-browser'; import { NavController } from 'ionic-angular'; import { CoreLoggerProvider } from '@providers/logger'; import { CoreDomUtilsProvider } from '@providers/utils/dom'; +import { CoreUrlUtilsProvider } from '@providers/utils/url'; import { CoreIframeUtilsProvider } from '@providers/utils/iframe'; +import { CoreUtilsProvider } from '@providers/utils/utils'; import { CoreSplitViewComponent } from '@components/split-view/split-view'; -/** - */ @Component({ selector: 'core-iframe', templateUrl: 'core-iframe.html' @@ -34,6 +34,7 @@ export class CoreIframeComponent implements OnInit, OnChanges { @Input() src: string; @Input() iframeWidth: string; @Input() iframeHeight: string; + @Input() allowFullscreen: boolean | string; @Output() loaded?: EventEmitter = new EventEmitter(); loading: boolean; safeUrl: SafeResourceUrl; @@ -41,9 +42,14 @@ export class CoreIframeComponent implements OnInit, OnChanges { protected logger; protected IFRAME_TIMEOUT = 15000; - constructor(logger: CoreLoggerProvider, private iframeUtils: CoreIframeUtilsProvider, private domUtils: CoreDomUtilsProvider, - private sanitizer: DomSanitizer, private navCtrl: NavController, - @Optional() private svComponent: CoreSplitViewComponent) { + constructor(logger: CoreLoggerProvider, + protected iframeUtils: CoreIframeUtilsProvider, + protected domUtils: CoreDomUtilsProvider, + protected sanitizer: DomSanitizer, + protected navCtrl: NavController, + protected urlUtils: CoreUrlUtilsProvider, + protected utils: CoreUtilsProvider, + @Optional() protected svComponent: CoreSplitViewComponent) { this.logger = logger.getInstance('CoreIframe'); this.loaded = new EventEmitter(); @@ -57,6 +63,7 @@ export class CoreIframeComponent implements OnInit, OnChanges { this.iframeWidth = this.domUtils.formatPixelsSize(this.iframeWidth) || '100%'; this.iframeHeight = this.domUtils.formatPixelsSize(this.iframeHeight) || '100%'; + this.allowFullscreen = this.utils.isTrueOrOne(this.allowFullscreen); // Show loading only with external URLs. this.loading = !this.src || !!this.src.match(/^https?:\/\//i); @@ -86,7 +93,8 @@ export class CoreIframeComponent implements OnInit, OnChanges { */ ngOnChanges(changes: {[name: string]: SimpleChange }): void { if (changes.src) { - this.safeUrl = this.sanitizer.bypassSecurityTrustResourceUrl(changes.src.currentValue); + const youtubeUrl = this.urlUtils.getYoutubeEmbedUrl(changes.src.currentValue); + this.safeUrl = this.sanitizer.bypassSecurityTrustResourceUrl(youtubeUrl || changes.src.currentValue); } } } diff --git a/src/components/infinite-loading/infinite-loading.ts b/src/components/infinite-loading/infinite-loading.ts index 977eba6b7..1e636fcd2 100644 --- a/src/components/infinite-loading/infinite-loading.ts +++ b/src/components/infinite-loading/infinite-loading.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -48,7 +48,7 @@ export class CoreInfiniteLoadingComponent implements OnChanges { /** * Detect changes on input properties. * - * @param {SimpleChange}} changes Changes. + * @param changes Changes. */ ngOnChanges(changes: {[name: string]: SimpleChange}): void { if (changes.enabled && this.enabled && this.position == 'bottom') { @@ -64,7 +64,7 @@ export class CoreInfiniteLoadingComponent implements OnChanges { /** * Load More items calling the action provided. * - * @param {InfiniteScroll} [infiniteScroll] Infinite scroll object only if triggered from the scroll. + * @param infiniteScroll Infinite scroll object only if triggered from the scroll. */ loadMore(infiniteScroll?: InfiniteScroll): void { if (this.loadingMore) { @@ -110,7 +110,7 @@ export class CoreInfiniteLoadingComponent implements OnChanges { /** * Get the height of the element. * - * @return {number} Height. + * @return Height. */ getHeight(): number { return this.getElementHeight(this.topButton) + this.getElementHeight(this.infiniteEl) + @@ -120,8 +120,8 @@ export class CoreInfiniteLoadingComponent implements OnChanges { /** * Get the height of an element. * - * @param {ElementRef} element Element ref. - * @return {number} Height. + * @param element Element ref. + * @return Height. */ protected getElementHeight(element: ElementRef): number { if (element && element.nativeElement) { diff --git a/src/components/input-errors/input-errors.ts b/src/components/input-errors/input-errors.ts index 2c4b5361d..cf12d126d 100644 --- a/src/components/input-errors/input-errors.ts +++ b/src/components/input-errors/input-errors.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/components/ion-tabs/ion-tab.ts b/src/components/ion-tabs/ion-tab.ts index a8c409f33..0d656594f 100644 --- a/src/components/ion-tabs/ion-tab.ts +++ b/src/components/ion-tabs/ion-tab.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/components/ion-tabs/ion-tabs.scss b/src/components/ion-tabs/ion-tabs.scss index 31f667650..5088c5969 100644 --- a/src/components/ion-tabs/ion-tabs.scss +++ b/src/components/ion-tabs/ion-tabs.scss @@ -35,6 +35,7 @@ ion-app.app-root core-ion-tabs { width: $core-sidetab-size; height: 100%; flex-direction: column; + @include border-end(0.55px, solid, rgba(0, 0, 0, 0.3)); .tab-button { width: 100%; .tab-badge.badge { @@ -60,7 +61,8 @@ ion-app.app-root core-ion-tabs { } } - .scroll-content, .fixed-content { + ion-content:not(.has-footer) > .scroll-content, + ion-content:not(.has-footer) > .fixed-content { margin-bottom: 0 !important; } } diff --git a/src/components/ion-tabs/ion-tabs.ts b/src/components/ion-tabs/ion-tabs.ts index 914d97ab6..da3c1e4f2 100644 --- a/src/components/ion-tabs/ion-tabs.ts +++ b/src/components/ion-tabs/ion-tabs.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -61,7 +61,6 @@ export class CoreIonTabsComponent extends Tabs implements OnDestroy { /** * List of tabs that haven't been initialized yet. This is required because IonTab calls add() on the constructor, * but we need it to be called in OnInit to be able to determine the tab position. - * @type {CoreIonTabComponent[]} */ protected tabsNotInit: CoreIonTabComponent[] = []; @@ -95,9 +94,9 @@ export class CoreIonTabsComponent extends Tabs implements OnDestroy { /** * Add a new tab if it isn't already in the list of tabs. * - * @param {CoreIonTabComponent} tab The tab to add. - * @param {boolean} [isInit] Whether the tab has been initialized. - * @return {string} The tab ID. + * @param tab The tab to add. + * @param isInit Whether the tab has been initialized. + * @return The tab ID. */ add(tab: CoreIonTabComponent, isInit?: boolean): string { // Check if tab is already in the list of initialized tabs. @@ -146,7 +145,7 @@ export class CoreIonTabsComponent extends Tabs implements OnDestroy { /** * Initialize the tabs. * - * @return {Promise} Promise resolved when done. + * @return Promise resolved when done. */ initTabs(): Promise { if (!this.initialized && (this._loaded || typeof this._loaded == 'undefined')) { @@ -224,7 +223,7 @@ export class CoreIonTabsComponent extends Tabs implements OnDestroy { /** * Remove a tab from the list of tabs. * - * @param {CoreIonTabComponent} tab The tab to remove. + * @param tab The tab to remove. */ remove(tab: CoreIonTabComponent): void { // First search in the list of initialized tabs. @@ -274,11 +273,11 @@ export class CoreIonTabsComponent extends Tabs implements OnDestroy { /** * Select a tab. * - * @param {number|Tab} tabOrIndex Index, or the Tab instance, of the tab to select. - * @param {NavOptions} Nav options. - * @param {boolean} [fromUrl] Whether to load from a URL. - * @param {boolean} [manualClick] Whether the user manually clicked the tab. - * @return {Promise} Promise resolved when selected. + * @param tabOrIndex Index, or the Tab instance, of the tab to select. + * @param Nav options. + * @param fromUrl Whether to load from a URL. + * @param manualClick Whether the user manually clicked the tab. + * @return Promise resolved when selected. */ select(tabOrIndex: number | Tab, opts: NavOptions = {}, fromUrl?: boolean, manualClick?: boolean): Promise { @@ -314,8 +313,8 @@ export class CoreIonTabsComponent extends Tabs implements OnDestroy { /** * Select a tab by Index. First it will reset the status of the tab. * - * @param {number} index Index of the tab. - * @return {Promise} Promise resolved when selected. + * @param index Index of the tab. + * @return Promise resolved when selected. */ selectTabRootByIndex(index: number): Promise { if (this.initialized) { @@ -350,7 +349,7 @@ export class CoreIonTabsComponent extends Tabs implements OnDestroy { /** * Change tabs visibility to show/hide them from the view. * - * @param {boolean} visible If show or hide the tabs. + * @param visible If show or hide the tabs. */ changeVisibility(visible: boolean): void { if (this.hidden == visible) { @@ -374,8 +373,8 @@ export class CoreIonTabsComponent extends Tabs implements OnDestroy { /** * Confirm if the user wants to go to the root of the current tab. * - * @param {Tab} tab Tab to go to root. - * @return {Promise} Promise resolved when confirmed. + * @param tab Tab to go to root. + * @return Promise resolved when confirmed. */ confirmGoToRoot(tab: Tab): Promise { if (!tab || !tab.isSelected || (tab.getActive() && tab.getActive().isFirst())) { diff --git a/src/components/loading/loading.scss b/src/components/loading/loading.scss index bc1faec9f..6b5bbaa21 100644 --- a/src/components/loading/loading.scss +++ b/src/components/loading/loading.scss @@ -7,6 +7,9 @@ ion-app.app-root { text-align: center; padding-top: 10px; clear: both; + @include darkmode() { + color: $core-dark-text-color; + } } .core-loading-content { @@ -17,6 +20,16 @@ ion-app.app-root { &.core-loading-noheight .core-loading-content { height: auto; } + + &.safe-area-page { + padding-left: 0 !important; + padding-right: 0 !important; + + > .core-loading-content > *:not[padding], + > .core-loading-content-loading > *:not[padding] { + @include safe-area-padding-horizontal(0px, 0px); + } + } } .scroll-content > core-loading, diff --git a/src/components/loading/loading.ts b/src/components/loading/loading.ts index 21cb4e92f..4d658fbd6 100644 --- a/src/components/loading/loading.ts +++ b/src/components/loading/loading.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -84,11 +84,13 @@ export class CoreLoadingComponent implements OnInit, OnChanges { setTimeout(() => { // Change CSS to force calculate height. this.content.nativeElement.classList.add('core-loading-content'); + this.content.nativeElement.classList.remove('core-loading-content-loading'); }, 500); }); } else { this.element.classList.remove('core-loading-loaded'); this.content.nativeElement.classList.remove('core-loading-content'); + this.content.nativeElement.classList.add('core-loading-content-loading'); } // Trigger the event after a timeout since the elements inside ngIf haven't been added to DOM yet. diff --git a/src/components/local-file/local-file.ts b/src/components/local-file/local-file.ts index b5f3140ec..e1c46b1c2 100644 --- a/src/components/local-file/local-file.ts +++ b/src/components/local-file/local-file.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -13,7 +13,6 @@ // limitations under the License. import { Component, Input, Output, OnInit, EventEmitter } from '@angular/core'; -import { TranslateService } from '@ngx-translate/core'; import { CoreFileProvider } from '@providers/file'; import { CoreDomUtilsProvider } from '@providers/utils/dom'; import { CoreMimetypeUtilsProvider } from '@providers/utils/mimetype'; @@ -48,9 +47,12 @@ export class CoreLocalFileComponent implements OnInit { editMode: boolean; relativePath: string; - constructor(private mimeUtils: CoreMimetypeUtilsProvider, private utils: CoreUtilsProvider, private translate: TranslateService, - private textUtils: CoreTextUtilsProvider, private fileProvider: CoreFileProvider, - private domUtils: CoreDomUtilsProvider, private timeUtils: CoreTimeUtilsProvider) { + constructor(private mimeUtils: CoreMimetypeUtilsProvider, + private utils: CoreUtilsProvider, + private textUtils: CoreTextUtilsProvider, + private fileProvider: CoreFileProvider, + private domUtils: CoreDomUtilsProvider, + private timeUtils: CoreTimeUtilsProvider) { this.onDelete = new EventEmitter(); this.onRename = new EventEmitter(); this.onClick = new EventEmitter(); @@ -93,7 +95,7 @@ export class CoreLocalFileComponent implements OnInit { /** * File clicked. * - * @param {Event} e Click event. + * @param e Click event. */ fileClicked(e: Event): void { if (this.editMode) { @@ -113,7 +115,7 @@ export class CoreLocalFileComponent implements OnInit { /** * Activate the edit mode. * - * @param {Event} e Click event. + * @param e Click event. */ activateEdit(e: Event): void { e.preventDefault(); @@ -125,8 +127,8 @@ export class CoreLocalFileComponent implements OnInit { /** * Rename the file. * - * @param {string} newName New name. - * @param {Event} e Click event. + * @param newName New name. + * @param e Click event. */ changeName(newName: string, e: Event): void { e.preventDefault(); @@ -165,15 +167,15 @@ export class CoreLocalFileComponent implements OnInit { /** * Delete the file. * - * @param {Event} e Click event. + * @param e Click event. */ deleteFile(e: Event): void { e.preventDefault(); e.stopPropagation(); // Ask confirmation. - this.domUtils.showConfirm(this.translate.instant('core.confirmdeletefile')).then(() => { - const modal = this.domUtils.showModalLoading(); + this.domUtils.showDeleteConfirm('core.confirmdeletefile').then(() => { + const modal = this.domUtils.showModalLoading('core.deleting', true); return this.fileProvider.removeFile(this.relativePath).then(() => { this.onDelete.emit(); diff --git a/src/components/mark-required/mark-required.scss b/src/components/mark-required/mark-required.scss index 0453ba119..3b49f99ee 100644 --- a/src/components/mark-required/mark-required.scss +++ b/src/components/mark-required/mark-required.scss @@ -4,4 +4,8 @@ ion-app.app-root .core-input-required-asterisk, ion-app.app-root .icon.core-inpu @include padding(null, null, null, 4px); line-height: 100%; vertical-align: top; + + @include darkmode() { + color: $red-light !important; + } } diff --git a/src/components/mark-required/mark-required.ts b/src/components/mark-required/mark-required.ts index 28df17ed9..5a8da5d1a 100644 --- a/src/components/mark-required/mark-required.ts +++ b/src/components/mark-required/mark-required.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/components/navbar-buttons/navbar-buttons.ts b/src/components/navbar-buttons/navbar-buttons.ts index c38931322..8c45b8c14 100644 --- a/src/components/navbar-buttons/navbar-buttons.ts +++ b/src/components/navbar-buttons/navbar-buttons.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -111,7 +111,7 @@ export class CoreNavBarButtonsComponent implements OnInit, OnDestroy { /** * Force or unforce hiding all buttons. If this is true, it will override the "hidden" input. * - * @param {boolean} value The value to set. + * @param value The value to set. */ forceHide(value: boolean): void { this.forceHidden = value; @@ -122,7 +122,7 @@ export class CoreNavBarButtonsComponent implements OnInit, OnDestroy { /** * If both button containers have a context menu, merge them into a single one. * - * @param {HTMLElement} buttonsContainer The container where the buttons will be moved. + * @param buttonsContainer The container where the buttons will be moved. */ protected mergeContextMenus(buttonsContainer: HTMLElement): void { // Check if both button containers have a context menu. @@ -155,8 +155,8 @@ export class CoreNavBarButtonsComponent implements OnInit, OnDestroy { /** * Search the ion-header where the buttons should be added. * - * @param {number} [retries] Number of retries so far. - * @return {Promise} Promise resolved with the header element. + * @param retries Number of retries so far. + * @return Promise resolved with the header element. */ protected searchHeader(retries: number = 0): Promise { let parentPage: HTMLElement = this.element; @@ -196,8 +196,8 @@ export class CoreNavBarButtonsComponent implements OnInit, OnDestroy { /** * Search ion-header inside a page. The header should be a direct child. * - * @param {HTMLElement} page Page to search in. - * @return {HTMLElement} Header element. Undefined if not found. + * @param page Page to search in. + * @return Header element. Undefined if not found. */ protected searchHeaderInPage(page: HTMLElement): HTMLElement { for (let i = 0; i < page.children.length; i++) { @@ -232,7 +232,7 @@ export class CoreNavBarButtonsComponent implements OnInit, OnDestroy { /** * Show or hide an element. * - * @param {Node} element Element to show or hide. + * @param element Element to show or hide. */ protected showHideElement(element: Node): void { // Check if it's an HTML Element diff --git a/src/components/navigation-bar/navigation-bar.ts b/src/components/navigation-bar/navigation-bar.ts index 0a0db162d..2942e5c2d 100644 --- a/src/components/navigation-bar/navigation-bar.ts +++ b/src/components/navigation-bar/navigation-bar.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -35,6 +35,9 @@ export class CoreNavigationBarComponent { @Input() title?: string; // Title to show when seeing the info (new page). @Input() component?: string; // Component the bar belongs to. @Input() componentId?: number; // Component ID. + @Input() contextLevel?: string; // The context level. + @Input() contextInstanceId?: number; // The instance ID related to the context. + @Input() courseId?: number; // Course ID the text belongs to. It can be used to improve performance with filters. @Output() action?: EventEmitter; // Function to call when an arrow is clicked. Will receive as a param the item to load. constructor(private textUtils: CoreTextUtilsProvider) { @@ -42,6 +45,7 @@ export class CoreNavigationBarComponent { } showInfo(): void { - this.textUtils.expandText(this.title, this.info, this.component, this.componentId); + this.textUtils.expandText(this.title, this.info, this.component, this.componentId, [], true, this.contextLevel, + this.contextInstanceId, this.courseId); } } diff --git a/src/components/progress-bar/progress-bar.scss b/src/components/progress-bar/progress-bar.scss index a385388e0..4092a6c3c 100644 --- a/src/components/progress-bar/progress-bar.scss +++ b/src/components/progress-bar/progress-bar.scss @@ -13,6 +13,10 @@ ion-app.app-root core-progress-bar { color: $gray-darker; @include position(-16px, 10px, null, null); position: absolute; + + @include darkmode() { + color: $gray-lighter; + } } progress { @@ -35,6 +39,9 @@ ion-app.app-root core-progress-bar { .progress-bar-fallback span, &[value]::-webkit-progress-value { background-color: $core-progressbar-color; + @include darkmode() { + background-color: $core-progressbar-color; + } border-radius: 0; } diff --git a/src/components/progress-bar/progress-bar.ts b/src/components/progress-bar/progress-bar.ts index 1a417af91..5fae25e17 100644 --- a/src/components/progress-bar/progress-bar.ts +++ b/src/components/progress-bar/progress-bar.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/components/recaptcha/recaptcha.ts b/src/components/recaptcha/recaptcha.ts index f04f99ef3..8710c761a 100644 --- a/src/components/recaptcha/recaptcha.ts +++ b/src/components/recaptcha/recaptcha.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/components/recaptcha/recaptchamodal.ts b/src/components/recaptcha/recaptchamodal.ts index 3739a74b5..1280e569f 100644 --- a/src/components/recaptcha/recaptchamodal.ts +++ b/src/components/recaptcha/recaptchamodal.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -44,7 +44,7 @@ export class CoreRecaptchaModalComponent { /** * The iframe with the recaptcha was loaded. * - * @param {HTMLIFrameElement} iframe Iframe element. + * @param iframe Iframe element. */ loaded(iframe: HTMLIFrameElement): void { // Search the iframe content. diff --git a/src/components/rich-text-editor/rich-text-editor.scss b/src/components/rich-text-editor/rich-text-editor.scss index 9460297fd..e2dd2ac07 100644 --- a/src/components/rich-text-editor/rich-text-editor.scss +++ b/src/components/rich-text-editor/rich-text-editor.scss @@ -6,6 +6,9 @@ ion-app.app-root core-rich-text-editor { width: 100%; display: flex; flex-direction: column; + @include darkmode() { + background-color: $gray-darker; + } .core-rte-editor, .core-textarea { padding: 2px; @@ -13,6 +16,10 @@ ion-app.app-root core-rich-text-editor { width: 100%; resize: none; background-color: $white; + @include darkmode() { + background-color: $gray-darker; + color: $white; + } } .core-rte-editor { @@ -32,6 +39,10 @@ ion-app.app-root core-rich-text-editor { display: block; color: $gray-light; font-weight: bold; + + @include darkmode() { + color: $gray; + } } // Make empty elements selectable (to move the cursor). @@ -65,6 +76,10 @@ ion-app.app-root core-rich-text-editor { flex-grow: 0; flex-shrink: 0; background-color: $white; + + @include darkmode() { + background-color: $black; + } @include padding(5px, null); border-top: 1px solid $gray; @@ -90,12 +105,20 @@ ion-app.app-root core-rich-text-editor { color: $text-color; cursor: pointer; + @include darkmode() { + background-color: $black; + color: $core-dark-text-color; + } + &.toolbar-button-enable { width: 100%; } &:active, &[aria-pressed="true"] { background-color: $gray; + @include darkmode() { + background-color: $gray-dark; + } } &.toolbar-arrow { @@ -107,6 +130,9 @@ ion-app.app-root core-rich-text-editor { &:active { background-color: $white; + @include darkmode() { + background-color: $black; + } } &.toolbar-arrow-hidden { diff --git a/src/components/rich-text-editor/rich-text-editor.ts b/src/components/rich-text-editor/rich-text-editor.ts index e9c8bc2b5..d4367e509 100644 --- a/src/components/rich-text-editor/rich-text-editor.ts +++ b/src/components/rich-text-editor/rich-text-editor.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -153,7 +153,7 @@ export class CoreRichTextEditorComponent implements AfterContentInit, OnDestroy /** * Resize editor to maximize the space occupied. * - * @return {Promise} Resolved with calculated editor size. + * @return Resolved with calculated editor size. */ protected maximizeEditorSize = (): Promise => { this.content.resize(); @@ -180,18 +180,19 @@ export class CoreRichTextEditorComponent implements AfterContentInit, OnDestroy if (this.platform.is('android')) { // In Android we ignore the keyboard height because it is not part of the web view. height = this.domUtils.getContentHeight(this.content) - this.getSurroundingHeight(this.element); - } else if (this.platform.is('ios') && this.kbHeight > 0) { - // Keyboard open in iOS. - // In this case, the header disappears or is scrollable, so we need to adjust the calculations. + } else if (this.platform.is('ios') && this.kbHeight > 0 && this.platform.version().major < 12) { + // Keyboard open in iOS 11 or previous. The window height changes when the keyboard is open. height = window.innerHeight - this.getSurroundingHeight(this.element); if (this.element.getBoundingClientRect().top < 40) { // In iOS sometimes the editor is placed below the status bar. Move the scroll a bit so it doesn't happen. window.scrollTo(window.scrollX, window.scrollY - 40); } + } else { // Header is fixed, use the content to calculate the editor height. height = this.domUtils.getContentHeight(this.content) - this.kbHeight - this.getSurroundingHeight(this.element); + } if (height > this.minHeight) { @@ -210,8 +211,8 @@ export class CoreRichTextEditorComponent implements AfterContentInit, OnDestroy /** * Get the height of the surrounding elements from the current to the top element. * - * @param {any} element Directive DOM element to get surroundings elements from. - * @return {number} Surrounding height in px. + * @param element Directive DOM element to get surroundings elements from. + * @return Surrounding height in px. */ protected getSurroundingHeight(element: any): number { let height = 0; @@ -246,7 +247,7 @@ export class CoreRichTextEditorComponent implements AfterContentInit, OnDestroy /** * On change function to sync with form data. * - * @param {Event} $event The event. + * @param $event The event. */ onChange($event: Event): void { if (this.rteEnabled) { @@ -279,7 +280,7 @@ export class CoreRichTextEditorComponent implements AfterContentInit, OnDestroy * On key down function to move the cursor. * https://stackoverflow.com/questions/6249095/how-to-set-caretcursor-position-in-contenteditable-element-div * - * @param {Event} $event The event. + * @param $event The event. */ moveCursor($event: Event): void { if (!this.rteEnabled) { @@ -302,8 +303,8 @@ export class CoreRichTextEditorComponent implements AfterContentInit, OnDestroy /** * Returns the number of chars from the beggining where is placed the cursor. * - * @param {Node} parent Parent where to get the position from. - * @return {number} Position in chars. + * @param parent Parent where to get the position from. + * @return Position in chars. */ protected getCurrentCursorPosition(parent: Node): number { const selection = window.getSelection(); @@ -340,18 +341,18 @@ export class CoreRichTextEditorComponent implements AfterContentInit, OnDestroy /** * Set the caret position on the character number. * - * @param {Node} parent Parent where to set the position. - * @param {number} [chars] Number of chars where to place the caret. If not defined it will go to the end. + * @param parent Parent where to set the position. + * @param chars Number of chars where to place the caret. If not defined it will go to the end. */ protected setCurrentCursorPosition(parent: Node, chars?: number): void { /** * Loops round all the child text nodes within the supplied node and sets a range from the start of the initial node to * the characters. * - * @param {Node} node Node where to start. - * @param {Range} range Previous calculated range. - * @param {any} chars Object with counting of characters (input-output param). - * @return {Range} Selection range. + * @param node Node where to start. + * @param range Previous calculated range. + * @param chars Object with counting of characters (input-output param). + * @return Selection range. */ const setRange = (node: Node, range: Range, chars: any): Range => { if (chars.count === 0) { @@ -404,7 +405,7 @@ export class CoreRichTextEditorComponent implements AfterContentInit, OnDestroy /** * Toggle from rte editor to textarea syncing values. * - * @param {Event} $event The event. + * @param $event The event. */ toggleEditor($event: Event): void { $event.preventDefault(); @@ -481,7 +482,7 @@ export class CoreRichTextEditorComponent implements AfterContentInit, OnDestroy /** * Check if text is empty. - * @param {string} value text + * @param value text */ protected isNullOrWhiteSpace(value: string): boolean { if (value == null || typeof value == 'undefined') { @@ -497,7 +498,7 @@ export class CoreRichTextEditorComponent implements AfterContentInit, OnDestroy /** * Set the content of the textarea and the editor element. * - * @param {string} value New content. + * @param value New content. */ protected setContent(value: string): void { if (this.isNullOrWhiteSpace(value)) { @@ -530,8 +531,8 @@ export class CoreRichTextEditorComponent implements AfterContentInit, OnDestroy * Execute an action over the selected text. * API docs: https://developer.mozilla.org/en-US/docs/Web/API/Document/execCommand * - * @param {any} $event Event data - * @param {string} command Command to execute. + * @param $event Event data + * @param command Command to execute. */ buttonAction($event: any, command: string): void { this.stopBubble($event); @@ -549,12 +550,14 @@ export class CoreRichTextEditorComponent implements AfterContentInit, OnDestroy } /** - * Hide the toolbar. + * Hide the toolbar in phone mode. */ hideToolbar($event: any): void { this.stopBubble($event); - this.toolbarHidden = true; + if (this.isPhone) { + this.toolbarHidden = true; + } } /** @@ -568,7 +571,7 @@ export class CoreRichTextEditorComponent implements AfterContentInit, OnDestroy /** * Stop event default and propagation. * - * @param {Event} event Event. + * @param event Event. */ stopBubble(event: Event): void { event.preventDefault(); diff --git a/src/components/search-box/core-search-box.html b/src/components/search-box/core-search-box.html index 260d45215..84a8e4b74 100644 --- a/src/components/search-box/core-search-box.html +++ b/src/components/search-box/core-search-box.html @@ -2,10 +2,10 @@
- - diff --git a/src/components/search-box/search-box.ts b/src/components/search-box/search-box.ts index e978908d1..3846e2f77 100644 --- a/src/components/search-box/search-box.ts +++ b/src/components/search-box/search-box.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -62,7 +62,7 @@ export class CoreSearchBoxComponent implements OnInit { /** * Form submitted. * - * @param {Event} e Event. + * @param e Event. */ submitForm(e: Event): void { e.preventDefault(); diff --git a/src/components/send-message-form/core-send-message-form.html b/src/components/send-message-form/core-send-message-form.html index b1829854a..8caa886c3 100644 --- a/src/components/send-message-form/core-send-message-form.html +++ b/src/components/send-message-form/core-send-message-form.html @@ -1,7 +1,7 @@ - diff --git a/src/components/send-message-form/send-message-form.scss b/src/components/send-message-form/send-message-form.scss index ea9f7c367..cec94f20e 100644 --- a/src/components/send-message-form/send-message-form.scss +++ b/src/components/send-message-form/send-message-form.scss @@ -34,4 +34,9 @@ ion-app.app-root core-send-message-form { min-height: 0; align-self: self-end; } + @include darkmode() { + ion-icon.icon { + color: $gray-darker; + } + } } \ No newline at end of file diff --git a/src/components/send-message-form/send-message-form.ts b/src/components/send-message-form/send-message-form.ts index ae7a25ff9..7b5761e39 100644 --- a/src/components/send-message-form/send-message-form.ts +++ b/src/components/send-message-form/send-message-form.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -39,6 +39,7 @@ export class CoreSendMessageFormComponent implements OnInit { @Input() message: string; // Input text. @Input() placeholder = ''; // Placeholder for the input area. @Input() showKeyboard = false; // If keyboard is shown or not. + @Input() sendDisabled = false; // If send is disabled. @Output() onSubmit: EventEmitter; // Send data when submitting the message form. @Output() onResize: EventEmitter; // Emit when resizing the textarea. @@ -66,7 +67,7 @@ export class CoreSendMessageFormComponent implements OnInit { /** * Form submitted. * - * @param {Event} $event Mouse event. + * @param $event Mouse event. */ submitForm($event: Event): void { $event.preventDefault(); @@ -95,10 +96,14 @@ export class CoreSendMessageFormComponent implements OnInit { /** * Enter key clicked. * - * @param {Event} e Event. - * @param {string} other The name of the other key that was clicked, undefined if no other key. + * @param e Event. + * @param other The name of the other key that was clicked, undefined if no other key. */ enterClicked(e: Event, other: string): void { + if (this.sendDisabled) { + return; + } + if (this.sendOnEnter && !other) { // Enter clicked, send the message. this.submitForm(e); diff --git a/src/components/show-password/show-password.ts b/src/components/show-password/show-password.ts index 68abdbf70..6ccf84a05 100644 --- a/src/components/show-password/show-password.ts +++ b/src/components/show-password/show-password.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -103,7 +103,7 @@ export class CoreShowPasswordComponent implements OnInit, AfterViewInit { /** * Toggle show/hide password. * - * @param {Event} event The mouse event. + * @param event The mouse event. */ toggle(event: Event): void { event.preventDefault(); diff --git a/src/components/site-picker/site-picker.ts b/src/components/site-picker/site-picker.ts index 51538eec7..60bfd17e5 100644 --- a/src/components/site-picker/site-picker.ts +++ b/src/components/site-picker/site-picker.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -15,7 +15,7 @@ import { Component, Input, Output, EventEmitter, OnInit } from '@angular/core'; import { TranslateService } from '@ngx-translate/core'; import { CoreSitesProvider } from '@providers/sites'; -import { CoreTextUtilsProvider } from '@providers/utils/text'; +import { CoreFilterProvider } from '@core/filter/providers/filter'; /** * Component to display a site selector. It will display a select with the list of sites. If the selected site changes, @@ -35,8 +35,9 @@ export class CoreSitePickerComponent implements OnInit { selectedSite: string; sites: any[]; - constructor(private translate: TranslateService, private sitesProvider: CoreSitesProvider, - private textUtils: CoreTextUtilsProvider) { + constructor(private translate: TranslateService, + private sitesProvider: CoreSitesProvider, + private filterProvider: CoreFilterProvider) { this.siteSelected = new EventEmitter(); } @@ -49,11 +50,12 @@ export class CoreSitePickerComponent implements OnInit { sites.forEach((site: any) => { // Format the site name. - promises.push(this.textUtils.formatText(site.siteName, true, true).catch(() => { + promises.push(this.filterProvider.formatText(site.siteName, {clean: true, singleLine: true, filter: false}, [], + site.id).catch(() => { return site.siteName; - }).then((formatted) => { + }).then((siteName) => { site.fullNameAndSiteName = this.translate.instant('core.fullnameandsitename', - { fullname: site.fullName, sitename: formatted }); + { fullname: site.fullName, sitename: siteName }); })); }); diff --git a/src/components/split-view/placeholder/placeholder.module.ts b/src/components/split-view/placeholder/placeholder.module.ts index d17ef7a88..cb6b7f0c9 100644 --- a/src/components/split-view/placeholder/placeholder.module.ts +++ b/src/components/split-view/placeholder/placeholder.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/components/split-view/placeholder/placeholder.ts b/src/components/split-view/placeholder/placeholder.ts index 1afaeda4b..3152506d4 100644 --- a/src/components/split-view/placeholder/placeholder.ts +++ b/src/components/split-view/placeholder/placeholder.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/components/split-view/split-view.scss b/src/components/split-view/split-view.scss index 5c399b5e5..355e1ef4e 100644 --- a/src/components/split-view/split-view.scss +++ b/src/components/split-view/split-view.scss @@ -17,17 +17,20 @@ ion-app.app-root core-split-view { .split-pane-visible { .split-pane-main { display: block; + @include core-split-area-end(); + } + + .split-pane-side { + @include core-split-area-start(); } .split-pane-side .core-split-item-selected { background-color: $gray-lighter; @include core-selected-item($core-splitview-selected); - } - .item-ios[detail-push] .item-inner, - button.item-ios:not([detail-none]) .item-inner, - a.item-ios:not([detail-none]) .item-inner { - @include background-position(end, $item-ios-padding-end - 2, center); + @include darkmode() { + background-color: $black; + } } } ion-header { @@ -37,32 +40,3 @@ ion-app.app-root core-split-view { padding-top: 0 !important; } } - -.safe-area-page { - @include safe-area-padding-horizontal(0px, 0px); -} - -ion-app.app-root .split-pane-visible .split-pane-side { - .safe-area-page { - @include safe-area-padding-start(0px, 0px); - - .core-split-item-selected { - @include border-start(5px, solid, $core-splitview-selected); - } - } - - // Disable safe area padding. - .item-ios.item-block .item-inner { - @include padding-horizontal(null, $item-ios-padding-end / 2); - } -} - -ion-app.app-root .split-pane-visible .split-pane-main { - .safe-area-page { - @include safe-area-padding-end(0px, 0px); - } - - .toolbar { - @include safe-area-padding-end(0px, 0px); - } -} diff --git a/src/components/split-view/split-view.ts b/src/components/split-view/split-view.ts index ca807f10d..11f6e4dbd 100644 --- a/src/components/split-view/split-view.ts +++ b/src/components/split-view/split-view.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -30,8 +30,8 @@ import { Subscription } from 'rxjs'; * * Accepts the following params: * - * @param {string|boolean} [when] When the split-pane should be shown. Can be a CSS media query expression, or a shortcut - * expression. Can also be a boolean expression. Check split-pane component documentation for more information. + * @param when When the split-pane should be shown. Can be a CSS media query expression, or a shortcut + * expression. Can also be a boolean expression. Check split-pane component documentation for more information. * * Example: * @@ -98,7 +98,7 @@ export class CoreSplitViewComponent implements OnInit, OnDestroy { /** * Get the details NavController. If split view is not enabled, it will return the master nav. * - * @return {NavController} Details NavController. + * @return Details NavController. */ getDetailsNav(): NavController { if (this.isEnabled) { @@ -111,7 +111,7 @@ export class CoreSplitViewComponent implements OnInit, OnDestroy { /** * Get the master NavController. * - * @return {NavController} Master NavController. + * @return Master NavController. */ getMasterNav(): NavController { return this.masterNav; @@ -172,7 +172,7 @@ export class CoreSplitViewComponent implements OnInit, OnDestroy { /** * Check if both panels are shown. It depends on screen width. * - * @return {boolean} If split view is enabled. + * @return If split view is enabled. */ isOn(): boolean { return !!this.isEnabled; @@ -181,9 +181,9 @@ export class CoreSplitViewComponent implements OnInit, OnDestroy { /** * Push a page to the navigation stack. It will decide where to load it depending on the size of the screen. * - * @param {any} page The component class or deeplink name you want to push onto the navigation stack. - * @param {any} params Any NavParams you want to pass along to the next view. - * @param {boolean} [retrying] Whether it's retrying. + * @param page The component class or deeplink name you want to push onto the navigation stack. + * @param params Any NavParams you want to pass along to the next view. + * @param retrying Whether it's retrying. */ push(page: any, params?: any, retrying?: boolean): void { // Check there's no ongoing push. @@ -224,7 +224,7 @@ export class CoreSplitViewComponent implements OnInit, OnDestroy { /** * Splitpanel visibility has changed. * - * @param {Boolean} isOn If it fits both panels at the same time. + * @param isOn If it fits both panels at the same time. */ onSplitPaneChanged(isOn: boolean): void { if (this.ignoreSplitChanged) { diff --git a/src/components/style/style.ts b/src/components/style/style.ts index b0790ca96..a554bceca 100644 --- a/src/components/style/style.ts +++ b/src/components/style/style.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -50,9 +50,9 @@ export class CoreStyleComponent implements OnChanges { /** * Add a prefix to all rules in a CSS string. * - * @param {string} css CSS code to be prefixed. - * @param {string} prefix Prefix css selector. - * @return {string} Prefixed CSS. + * @param css CSS code to be prefixed. + * @param prefix Prefix css selector. + * @return Prefixed CSS. */ protected prefixCSS(css: string, prefix: string): string { if (!css) { diff --git a/src/components/tabs/tab.ts b/src/components/tabs/tab.ts index fc4aaf2fc..e8866d8ca 100644 --- a/src/components/tabs/tab.ts +++ b/src/components/tabs/tab.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -148,7 +148,7 @@ export class CoreTabComponent implements OnInit, OnDestroy { * Get all child core-navbar-buttons. We need to use querySelectorAll because ContentChildren doesn't work with ng-template. * https://github.com/angular/angular/issues/14842 * - * @return {CoreNavBarButtonsComponent[]} List of component instances. + * @return List of component instances. */ protected getChildrenNavBarButtons(): CoreNavBarButtonsComponent[] { const elements = this.element.querySelectorAll('core-navbar-buttons'), @@ -167,7 +167,7 @@ export class CoreTabComponent implements OnInit, OnDestroy { /** * Show all hide all children navbar buttons. * - * @param {boolean} show Whether to show or hide the buttons. + * @param show Whether to show or hide the buttons. */ protected showHideNavBarButtons(show: boolean): void { const instances = this.getChildrenNavBarButtons(); diff --git a/src/components/tabs/tabs.ts b/src/components/tabs/tabs.ts index ba7b9fdb1..578367399 100644 --- a/src/components/tabs/tabs.ts +++ b/src/components/tabs/tabs.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -194,7 +194,7 @@ export class CoreTabsComponent implements OnInit, AfterViewInit, OnChanges, OnDe /** * Add a new tab if it isn't already in the list of tabs. * - * @param {CoreTabComponent} tab The tab to add. + * @param tab The tab to add. */ addTab(tab: CoreTabComponent): void { // Check if tab is already in the list. @@ -223,10 +223,8 @@ export class CoreTabsComponent implements OnInit, AfterViewInit, OnChanges, OnDe return; } - setTimeout(() => { - this.calculateMaxSlides(); - this.updateSlides(); - }); + this.calculateMaxSlides(); + this.updateSlides(); } /** @@ -248,8 +246,8 @@ export class CoreTabsComponent implements OnInit, AfterViewInit, OnChanges, OnDe /** * Get the index of tab. * - * @param {any} tab Tab object to check. - * @return {number} Index number on the tabs array or -1 if not found. + * @param tab Tab object to check. + * @return Index number on the tabs array or -1 if not found. */ getIndex(tab: any): number { for (let i = 0; i < this.tabs.length; i++) { @@ -265,7 +263,7 @@ export class CoreTabsComponent implements OnInit, AfterViewInit, OnChanges, OnDe /** * Get the current selected tab. * - * @return {CoreTabComponent} Selected tab. + * @return Selected tab. */ getSelected(): CoreTabComponent { return this.tabs[this.selected]; @@ -352,32 +350,30 @@ export class CoreTabsComponent implements OnInit, AfterViewInit, OnChanges, OnDe this.slideChanged(); - setTimeout(() => { - this.calculateTabBarHeight(); - this.slides.update(); - this.slides.resize(); + this.calculateTabBarHeight(); + this.slides.update(); + this.slides.resize(); - if (!this.hasSliddenToInitial && this.selected && this.selected >= this.slidesShown) { - this.hasSliddenToInitial = true; - this.shouldSlideToInitial = true; - - setTimeout(() => { - if (this.shouldSlideToInitial) { - this.slides.slideTo(this.selected, 0); - this.shouldSlideToInitial = false; - this.updateAriaHidden(); // Slide's slideTo() sets aria-hidden to true, update it. - } - }, 400); - - return; - } else if (this.selected) { - this.hasSliddenToInitial = true; - } + if (!this.hasSliddenToInitial && this.selected && this.selected >= this.slidesShown) { + this.hasSliddenToInitial = true; + this.shouldSlideToInitial = true; setTimeout(() => { - this.updateAriaHidden(); // Slide's update() sets aria-hidden to true, update it. + if (this.shouldSlideToInitial) { + this.slides.slideTo(this.selected, 0); + this.shouldSlideToInitial = false; + this.updateAriaHidden(); // Slide's slideTo() sets aria-hidden to true, update it. + } }, 400); - }); + + return; + } else if (this.selected) { + this.hasSliddenToInitial = true; + } + + setTimeout(() => { + this.updateAriaHidden(); // Slide's update() sets aria-hidden to true, update it. + }, 400); } protected calculateMaxSlides(): void { @@ -415,7 +411,7 @@ export class CoreTabsComponent implements OnInit, AfterViewInit, OnChanges, OnDe /** * Show or hide the tabs. This is used when the user is scrolling inside a tab. * - * @param {any} scrollElement Scroll element to check scroll position. + * @param scrollElement Scroll element to check scroll position. */ showHideTabs(scrollElement: any): void { if (!this.tabBarHeight) { @@ -447,7 +443,7 @@ export class CoreTabsComponent implements OnInit, AfterViewInit, OnChanges, OnDe this.calculateSlides(); } - if (this.tabsShown) { + if (this.tabsShown && scrollElement.scrollHeight > scrollElement.clientHeight + (this.tabBarHeight - scroll)) { // Smooth translation. this.topTabsElement.style.transform = 'translateY(-' + scroll + 'px)'; this.originalTabsContainer.style.transform = 'translateY(-' + scroll + 'px)'; @@ -460,7 +456,7 @@ export class CoreTabsComponent implements OnInit, AfterViewInit, OnChanges, OnDe /** * Remove a tab from the list of tabs. * - * @param {CoreTabComponent} tab The tab to remove. + * @param tab The tab to remove. */ removeTab(tab: CoreTabComponent): void { const index = this.getIndex(tab); @@ -472,7 +468,7 @@ export class CoreTabsComponent implements OnInit, AfterViewInit, OnChanges, OnDe /** * Select a certain tab. * - * @param {number} index The index of the tab to select. + * @param index The index of the tab to select. */ selectTab(index: number): void { if (index == this.selected) { diff --git a/src/components/timer/core-timer.html b/src/components/timer/core-timer.html index 729a8803e..cb59967c6 100644 --- a/src/components/timer/core-timer.html +++ b/src/components/timer/core-timer.html @@ -1,4 +1,4 @@ - + {{ timeLeft | coreSecondsToHMS }} diff --git a/src/components/timer/timer.scss b/src/components/timer/timer.scss index 89c7b2414..c29a8ec98 100644 --- a/src/components/timer/timer.scss +++ b/src/components/timer/timer.scss @@ -4,7 +4,7 @@ ion-app.app-root core-timer { } .core-timer { - background-color: transparent; + background-color: transparent !important; span { font-weight: bold; @@ -13,7 +13,7 @@ ion-app.app-root core-timer { // Create the timer warning colors. Go to $core-timer-warn-color. @for $i from 0 through $core-timer-iterations { &.core-timer-timeleft-#{$i} { - background-color: rgba($core-timer-warn-color, 1 - ($i / $core-timer-iterations)); + background-color: rgba($core-timer-warn-color, 1 - ($i / $core-timer-iterations)) !important; @if $i <= $core-timer-iterations / 2 { label, span, ion-icon { diff --git a/src/components/timer/timer.ts b/src/components/timer/timer.ts index 52bf54e02..f8d70cdfb 100644 --- a/src/components/timer/timer.ts +++ b/src/components/timer/timer.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/components/user-avatar/user-avatar.ts b/src/components/user-avatar/user-avatar.ts index 22c6fe5a2..d537765df 100644 --- a/src/components/user-avatar/user-avatar.ts +++ b/src/components/user-avatar/user-avatar.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/config.json b/src/config.json index f61e644ab..528d35ca0 100644 --- a/src/config.json +++ b/src/config.json @@ -2,8 +2,8 @@ "app_id": "com.moodle.moodlemobile", "appname": "Moodle Mobile", "desktopappname": "Moodle Desktop", - "versioncode": 3720, - "versionname": "3.7.2", + "versioncode": 3800, + "versionname": "3.8.0", "cache_update_frequency_usually": 420000, "cache_update_frequency_often": 1200000, "cache_update_frequency_sometimes": 3600000, @@ -89,5 +89,6 @@ "statusbarlighttextandroid": true, "statusbarbgremotetheme": "#000000", "statusbarlighttextremotetheme": true, - "enableanalytics": false + "enableanalytics": false, + "forceColorScheme": "" } diff --git a/src/core/block/block.module.ts b/src/core/block/block.module.ts index 6361d2c2d..d39945ab2 100644 --- a/src/core/block/block.module.ts +++ b/src/core/block/block.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/core/block/classes/base-block-component.ts b/src/core/block/classes/base-block-component.ts index 46dc2c33b..4562a95ce 100644 --- a/src/core/block/classes/base-block-component.ts +++ b/src/core/block/classes/base-block-component.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -15,6 +15,8 @@ import { Injector, OnInit, Input } from '@angular/core'; import { CoreLoggerProvider } from '@providers/logger'; import { CoreDomUtilsProvider } from '@providers/utils/dom'; +import { CoreUtilsProvider } from '@providers/utils/utils'; +import { CoreTextUtilsProvider } from '@providers/utils/text'; /** * Template class to easily create components for blocks. @@ -31,10 +33,14 @@ export class CoreBlockBaseComponent implements OnInit { protected fetchContentDefaultError: string; // Default error to show when loading contents. protected domUtils: CoreDomUtilsProvider; + protected textUtils: CoreTextUtilsProvider; + protected utils: CoreUtilsProvider; protected logger; constructor(injector: Injector, loggerName: string = 'AddonBlockComponent') { this.domUtils = injector.get(CoreDomUtilsProvider); + this.utils = injector.get(CoreUtilsProvider); + this.textUtils = injector.get(CoreTextUtilsProvider); const loggerProvider = injector.get(CoreLoggerProvider); this.logger = loggerProvider.getInstance(loggerName); } @@ -43,16 +49,26 @@ export class CoreBlockBaseComponent implements OnInit { */ ngOnInit(): void { this.loaded = false; + if (this.block.configs && this.block.configs.length > 0) { + this.block.configs.map((config) => { + config.value = this.textUtils.parseJSON(config.value); + + return config; + }); + + this.block.configs = this.utils.arrayToObject(this.block.configs, 'name'); + } + this.loadContent(); } /** * Refresh the data. * - * @param {any} [refresher] Refresher. - * @param {Function} [done] Function to call when done. - * @param {boolean} [showErrors=false] If show errors to the user of hide them. - * @return {Promise} Promise resolved when done. + * @param refresher Refresher. + * @param done Function to call when done. + * @param showErrors If show errors to the user of hide them. + * @return Promise resolved when done. */ doRefresh(refresher?: any, done?: () => void, showErrors: boolean = false): Promise { if (this.loaded) { @@ -68,8 +84,8 @@ export class CoreBlockBaseComponent implements OnInit { /** * Perform the refresh content function. * - * @param {boolean} [showErrors=false] Wether to show errors to the user or hide them. - * @return {Promise} Resolved when done. + * @param showErrors Wether to show errors to the user or hide them. + * @return Resolved when done. */ protected refreshContent(showErrors: boolean = false): Promise { // Wrap the call in a try/catch so the workflow isn't interrupted if an error occurs. @@ -94,7 +110,7 @@ export class CoreBlockBaseComponent implements OnInit { /** * Perform the invalidate content function. * - * @return {Promise} Resolved when done. + * @return Resolved when done. */ protected invalidateContent(): Promise { return Promise.resolve(); @@ -103,9 +119,9 @@ export class CoreBlockBaseComponent implements OnInit { /** * Loads the component contents and shows the corresponding error. * - * @param {boolean} [refresh=false] Whether we're refreshing data. - * @param {boolean} [showErrors=false] Wether to show errors to the user or hide them. - * @return {Promise} Promise resolved when done. + * @param refresh Whether we're refreshing data. + * @param showErrors Wether to show errors to the user or hide them. + * @return Promise resolved when done. */ protected loadContent(refresh?: boolean, showErrors: boolean = false): Promise { // Wrap the call in a try/catch so the workflow isn't interrupted if an error occurs. @@ -131,8 +147,8 @@ export class CoreBlockBaseComponent implements OnInit { /** * Download the component contents. * - * @param {boolean} [refresh] Whether we're refreshing data. - * @return {Promise} Promise resolved when done. + * @param refresh Whether we're refreshing data. + * @return Promise resolved when done. */ protected fetchContent(refresh?: boolean): Promise { return Promise.resolve(); diff --git a/src/core/block/classes/base-block-handler.ts b/src/core/block/classes/base-block-handler.ts index 695d8182e..acc6e3448 100644 --- a/src/core/block/classes/base-block-handler.ts +++ b/src/core/block/classes/base-block-handler.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -32,7 +32,7 @@ export class CoreBlockBaseHandler implements CoreBlockHandler { /** * Whether or not the handler is enabled on a site level. * - * @return {boolean|Promise} True or promise resolved with true if enabled. + * @return True or promise resolved with true if enabled. */ isEnabled(): boolean | Promise { return true; @@ -41,11 +41,11 @@ export class CoreBlockBaseHandler implements CoreBlockHandler { /** * Returns the data needed to render the block. * - * @param {Injector} injector Injector. - * @param {any} block The block to render. - * @param {string} contextLevel The context where the block will be used. - * @param {number} instanceId The instance ID associated with the context level. - * @return {CoreBlockHandlerData|Promise} Data or promise resolved with the data. + * @param injector Injector. + * @param block The block to render. + * @param contextLevel The context where the block will be used. + * @param instanceId The instance ID associated with the context level. + * @return Data or promise resolved with the data. */ getDisplayData(injector: Injector, block: any, contextLevel: string, instanceId: number) : CoreBlockHandlerData | Promise { diff --git a/src/core/block/components/block/block.scss b/src/core/block/components/block/block.scss index 4029d7ffa..95fcc68e7 100644 --- a/src/core/block/components/block/block.scss +++ b/src/core/block/components/block/block.scss @@ -19,7 +19,7 @@ ion-app.app-root core-block { } .item-divider { - @include safe-area-padding-horizontal(null, 0px); + @include padding-horizontal(null, 0px); } .item-divider .core-button-spinner { @@ -29,4 +29,10 @@ ion-app.app-root core-block { .item-divider .icon { color: $black; } + + @include darkmode() { + .item-divider .icon { + color: $core-dark-text-color + } + } } \ No newline at end of file diff --git a/src/core/block/components/block/block.ts b/src/core/block/components/block/block.ts index eebf4fd4d..17134ed69 100644 --- a/src/core/block/components/block/block.ts +++ b/src/core/block/components/block/block.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -57,8 +57,10 @@ export class CoreBlockComponent implements OnInit, OnDestroy, DoCheck { return; } - // Get the data to render the block. - this.initBlock(); + if (this.block.visible) { + // Get the data to render the block. + this.initBlock(); + } } /** @@ -128,10 +130,10 @@ export class CoreBlockComponent implements OnInit, OnDestroy, DoCheck { /** * Refresh the data. * - * @param {any} [refresher] Refresher. Please pass this only if the refresher should finish when this function finishes. - * @param {Function} [done] Function to call when done. - * @param {boolean} [showErrors=false] If show errors to the user of hide them. - * @return {Promise} Promise resolved when done. + * @param refresher Refresher. Please pass this only if the refresher should finish when this function finishes. + * @param done Function to call when done. + * @param showErrors If show errors to the user of hide them. + * @return Promise resolved when done. */ doRefresh(refresher?: any, done?: () => void, showErrors: boolean = false): Promise { if (this.dynamicComponent) { @@ -144,7 +146,7 @@ export class CoreBlockComponent implements OnInit, OnDestroy, DoCheck { /** * Invalidate some data. * - * @return {Promise} Promise resolved when done. + * @return Promise resolved when done. */ invalidate(): Promise { if (this.dynamicComponent) { diff --git a/src/core/block/components/block/core-block.html b/src/core/block/components/block/core-block.html index 591977ecd..1c1e3a215 100644 --- a/src/core/block/components/block/core-block.html +++ b/src/core/block/components/block/core-block.html @@ -1,4 +1,4 @@ -
+
diff --git a/src/core/block/components/components.module.ts b/src/core/block/components/components.module.ts index 70627f6bf..be1e427b5 100644 --- a/src/core/block/components/components.module.ts +++ b/src/core/block/components/components.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/core/block/components/course-blocks/core-block-course-blocks.html b/src/core/block/components/course-blocks/core-block-course-blocks.html index a34ada03c..47494de17 100644 --- a/src/core/block/components/course-blocks/core-block-course-blocks.html +++ b/src/core/block/components/course-blocks/core-block-course-blocks.html @@ -7,7 +7,7 @@ - + diff --git a/src/core/block/components/course-blocks/course-blocks.scss b/src/core/block/components/course-blocks/course-blocks.scss index ef2da86c0..b81858bc9 100644 --- a/src/core/block/components/course-blocks/course-blocks.scss +++ b/src/core/block/components/course-blocks/course-blocks.scss @@ -22,12 +22,14 @@ ion-app.app-root core-block-course-blocks { box-shadow: none !important; flex-grow: 1; max-width: 100%; + @include core-split-area-start(); } div.core-course-blocks-side { max-width: $core-side-blocks-max-width; min-width: $core-side-blocks-min-width; @include border-start(1px, solid, $list-md-border-color); + @include core-split-area-end(); } .core-course-blocks-content, diff --git a/src/core/block/components/course-blocks/course-blocks.ts b/src/core/block/components/course-blocks/course-blocks.ts index c1b849c56..a95a77178 100644 --- a/src/core/block/components/course-blocks/course-blocks.ts +++ b/src/core/block/components/course-blocks/course-blocks.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -57,7 +57,7 @@ export class CoreBlockCourseBlocksComponent implements OnInit { /** * Invalidate blocks data. * - * @return {Promise} Promise resolved when done. + * @return Promise resolved when done. */ invalidateBlocks(): Promise { const promises = []; @@ -79,7 +79,7 @@ export class CoreBlockCourseBlocksComponent implements OnInit { /** * Convenience function to fetch the data. * - * @return {Promise} Promise resolved when done. + * @return Promise resolved when done. */ loadContent(): Promise { return this.blockHelper.getCourseBlocks(this.courseId).then((blocks) => { diff --git a/src/core/block/components/only-title-block/core-block-only-title.html b/src/core/block/components/only-title-block/core-block-only-title.html index 358fbde44..287592371 100644 --- a/src/core/block/components/only-title-block/core-block-only-title.html +++ b/src/core/block/components/only-title-block/core-block-only-title.html @@ -1,3 +1,3 @@ -

+

{{ title | translate }}

\ No newline at end of file diff --git a/src/core/block/components/only-title-block/only-title-block.ts b/src/core/block/components/only-title-block/only-title-block.ts index 2c1145003..cb5fde462 100644 --- a/src/core/block/components/only-title-block/only-title-block.ts +++ b/src/core/block/components/only-title-block/only-title-block.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/core/block/components/pre-rendered-block/core-block-pre-rendered.html b/src/core/block/components/pre-rendered-block/core-block-pre-rendered.html index 84cf2ac52..ab4dcb92e 100644 --- a/src/core/block/components/pre-rendered-block/core-block-pre-rendered.html +++ b/src/core/block/components/pre-rendered-block/core-block-pre-rendered.html @@ -1,11 +1,11 @@ -

+

- + - + diff --git a/src/core/block/components/pre-rendered-block/pre-rendered-block.ts b/src/core/block/components/pre-rendered-block/pre-rendered-block.ts index 0acd1712f..0f9cd1087 100644 --- a/src/core/block/components/pre-rendered-block/pre-rendered-block.ts +++ b/src/core/block/components/pre-rendered-block/pre-rendered-block.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -24,6 +24,8 @@ import { CoreBlockBaseComponent } from '../../classes/base-block-component'; }) export class CoreBlockPreRenderedComponent extends CoreBlockBaseComponent implements OnInit { + courseId: number; + constructor(injector: Injector) { super(injector, 'CoreBlockPreRenderedComponent'); } @@ -34,6 +36,8 @@ export class CoreBlockPreRenderedComponent extends CoreBlockBaseComponent imple ngOnInit(): void { super.ngOnInit(); + this.courseId = this.contextLevel == 'course' ? this.instanceId : undefined; + this.fetchContentDefaultError = 'Error getting ' + this.block.contents.title + ' data.'; } diff --git a/src/core/block/providers/default-block-handler.ts b/src/core/block/providers/default-block-handler.ts index 23257b2c5..a5c6415b4 100644 --- a/src/core/block/providers/default-block-handler.ts +++ b/src/core/block/providers/default-block-handler.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/core/block/providers/delegate.ts b/src/core/block/providers/delegate.ts index 7f0f245f3..3fcf573ca 100644 --- a/src/core/block/providers/delegate.ts +++ b/src/core/block/providers/delegate.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -28,18 +28,17 @@ import { Subject } from 'rxjs'; export interface CoreBlockHandler extends CoreDelegateHandler { /** * Name of the block the handler supports. E.g. 'activity_modules'. - * @type {string} */ blockName: string; /** * Returns the data needed to render the block. * - * @param {Injector} injector Injector. - * @param {any} block The block to render. - * @param {string} contextLevel The context where the block will be used. - * @param {number} instanceId The instance ID associated with the context level. - * @return {CoreBlockHandlerData|Promise} Data or promise resolved with the data. + * @param injector Injector. + * @param block The block to render. + * @param contextLevel The context where the block will be used. + * @param instanceId The instance ID associated with the context level. + * @return Data or promise resolved with the data. */ getDisplayData?(injector: Injector, block: any, contextLevel: string, instanceId: number) : CoreBlockHandlerData | Promise; @@ -51,38 +50,32 @@ export interface CoreBlockHandler extends CoreDelegateHandler { export interface CoreBlockHandlerData { /** * Title to display for the block. - * @type {string} */ title: string; /** * Class to add to the displayed block. - * @type {string} */ class?: string; /** * The component to render the contents of the block. * It's recommended to return the class of the component, but you can also return an instance of the component. - * @type {any} */ component: any; /** * Data to pass to the component. All the properties in this object will be passed to the component as inputs. - * @type {any} */ componentData?: any; /** * Link to go when showing only title. - * @type {string} */ link?: string; /** * Params of the link. - * @type {[type]} */ linkParams?: any; } @@ -108,8 +101,8 @@ export class CoreBlockDelegate extends CoreDelegate { /** * Check if blocks are disabled in a certain site. * - * @param {CoreSite} [site] Site. If not defined, use current site. - * @return {boolean} Whether it's disabled. + * @param site Site. If not defined, use current site. + * @return Whether it's disabled. */ areBlocksDisabledInSite(site?: CoreSite): boolean { site = site || this.sitesProvider.getCurrentSite(); @@ -120,8 +113,8 @@ export class CoreBlockDelegate extends CoreDelegate { /** * Check if blocks are disabled in a certain site for courses. * - * @param {CoreSite} [site] Site. If not defined, use current site. - * @return {boolean} Whether it's disabled. + * @param site Site. If not defined, use current site. + * @return Whether it's disabled. */ areBlocksDisabledInCourses(site?: CoreSite): boolean { site = site || this.sitesProvider.getCurrentSite(); @@ -132,8 +125,8 @@ export class CoreBlockDelegate extends CoreDelegate { /** * Check if blocks are disabled in a certain site. * - * @param {string} [siteId] Site Id. If not defined, use current site. - * @return {Promise} Promise resolved with true if disabled, rejected or resolved with false otherwise. + * @param siteId Site Id. If not defined, use current site. + * @return Promise resolved with true if disabled, rejected or resolved with false otherwise. */ areBlocksDisabled(siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -144,11 +137,11 @@ export class CoreBlockDelegate extends CoreDelegate { /** * Get the display data for a certain block. * - * @param {Injector} injector Injector. - * @param {any} block The block to render. - * @param {string} contextLevel The context where the block will be used. - * @param {number} instanceId The instance ID associated with the context level. - * @return {Promise} Promise resolved with the display data. + * @param injector Injector. + * @param block The block to render. + * @param contextLevel The context where the block will be used. + * @param instanceId The instance ID associated with the context level. + * @return Promise resolved with the display data. */ getBlockDisplayData(injector: Injector, block: any, contextLevel: string, instanceId: number): Promise { return Promise.resolve(this.executeFunctionOnEnabled(block.name, 'getDisplayData', @@ -158,8 +151,8 @@ export class CoreBlockDelegate extends CoreDelegate { /** * Check if any of the blocks in a list is supported. * - * @param {any[]} blocks The list of blocks. - * @return {boolean} Whether any of the blocks is supported. + * @param blocks The list of blocks. + * @return Whether any of the blocks is supported. */ hasSupportedBlock(blocks: any[]): boolean { blocks = blocks || []; @@ -170,8 +163,8 @@ export class CoreBlockDelegate extends CoreDelegate { /** * Check if a block is supported. * - * @param {string} name Block "name". E.g. 'activity_modules'. - * @return {boolean} Whether it's supported. + * @param name Block "name". E.g. 'activity_modules'. + * @return Whether it's supported. */ isBlockSupported(name: string): boolean { return this.hasHandler(name, true); @@ -180,9 +173,9 @@ export class CoreBlockDelegate extends CoreDelegate { /** * Check if feature is enabled or disabled in the site, depending on the feature prefix and the handler name. * - * @param {CoreDelegateHandler} handler Handler to check. - * @param {CoreSite} site Site to check. - * @return {boolean} Whether is enabled or disabled in site. + * @param handler Handler to check. + * @param site Site to check. + * @return Whether is enabled or disabled in site. */ protected isFeatureDisabled(handler: CoreDelegateHandler, site: CoreSite): boolean { return this.areBlocksDisabledInSite(site) || super.isFeatureDisabled(handler, site); diff --git a/src/core/block/providers/helper.ts b/src/core/block/providers/helper.ts index 14473f757..a29fa37d2 100644 --- a/src/core/block/providers/helper.ts +++ b/src/core/block/providers/helper.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -27,7 +27,7 @@ export class CoreBlockHelperProvider { /** * Return if it get course blocks options is enabled for the current site. * - * @return {boolean} true if enabled, false otherwise. + * @return true if enabled, false otherwise. */ canGetCourseBlocks(): boolean { return this.courseProvider.canGetCourseBlocks() && !this.blockDelegate.areBlocksDisabledInCourses(); @@ -36,8 +36,8 @@ export class CoreBlockHelperProvider { /** * Returns the list of blocks for the selected course. * - * @param {number} courseId Course ID. - * @return {Promise} List of supported blocks. + * @param courseId Course ID. + * @return List of supported blocks. */ getCourseBlocks(courseId: number): Promise { const canGetBlocks = this.canGetCourseBlocks(); diff --git a/src/core/comments/comments.module.ts b/src/core/comments/comments.module.ts index d19d2ccf3..cb9f8cf55 100644 --- a/src/core/comments/comments.module.ts +++ b/src/core/comments/comments.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -36,7 +36,7 @@ export class CoreCommentsModule { constructor(eventsProvider: CoreEventsProvider, cronDelegate: CoreCronDelegate, syncHandler: CoreCommentsSyncCronHandler) { // Reset comments page size. eventsProvider.on(CoreEventsProvider.LOGIN, () => { - CoreCommentsProvider.pageSize = null; + CoreCommentsProvider.pageSize = 1; CoreCommentsProvider.pageSizeOK = false; }); diff --git a/src/core/comments/components/comments/comments.ts b/src/core/comments/components/comments/comments.ts index 6bbe34fc4..442f58b06 100644 --- a/src/core/comments/components/comments/comments.ts +++ b/src/core/comments/components/comments/comments.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -35,6 +35,7 @@ export class CoreCommentsCommentsComponent implements OnChanges, OnDestroy { @Input() title?: string; @Input() displaySpinner = true; // Whether to display the loading spinner. @Output() onLoading: EventEmitter; // Eevent that indicates whether the component is loading data. + @Input() courseId?: number; // Course ID the comments belong to. It can be used to improve performance with filters. commentsLoaded = false; commentsCount: string; @@ -43,9 +44,12 @@ export class CoreCommentsCommentsComponent implements OnChanges, OnDestroy { protected updateSiteObserver; protected refreshCommentsObserver; + protected commentsCountObserver; - constructor(private navCtrl: NavController, private commentsProvider: CoreCommentsProvider, - sitesProvider: CoreSitesProvider, eventsProvider: CoreEventsProvider, + constructor(private navCtrl: NavController, + private commentsProvider: CoreCommentsProvider, + sitesProvider: CoreSitesProvider, + eventsProvider: CoreEventsProvider, @Optional() private svComponent: CoreSplitViewComponent) { this.onLoading = new EventEmitter(); @@ -75,6 +79,20 @@ export class CoreCommentsCommentsComponent implements OnChanges, OnDestroy { }); } }, sitesProvider.getCurrentSiteId()); + + // Refresh comments count if event received. + this.commentsCountObserver = eventsProvider.on(CoreCommentsProvider.COMMENTS_COUNT_CHANGED_EVENT, (data) => { + // Verify these comments need to be updated. + if (!this.commentsCount.endsWith('+') && this.undefinedOrEqual(data, 'contextLevel') && + this.undefinedOrEqual(data, 'instanceId') && this.undefinedOrEqual(data, 'component') && + this.undefinedOrEqual(data, 'itemId') && this.undefinedOrEqual(data, 'area') && !this.countError) { + let newNumber = parseInt(this.commentsCount, 10) + data.countChange; + newNumber = newNumber >= 0 ? newNumber : 0; + + // Parse and unparse string. + this.commentsCount = newNumber + ''; + } + }, sitesProvider.getCurrentSiteId()); } /** @@ -117,7 +135,7 @@ export class CoreCommentsCommentsComponent implements OnChanges, OnDestroy { /** * Refresh comments. * - * @return {Promise} Promise resolved when done. + * @return Promise resolved when done. */ doRefresh(): Promise { return this.invalidateComments().then(() => { @@ -128,7 +146,7 @@ export class CoreCommentsCommentsComponent implements OnChanges, OnDestroy { /** * Invalidate comments data. * - * @return {Promise} Promise resolved when done. + * @return Promise resolved when done. */ invalidateComments(): Promise { return this.commentsProvider.invalidateCommentsData(this.contextLevel, this.instanceId, this.component, this.itemId, @@ -155,6 +173,7 @@ export class CoreCommentsCommentsComponent implements OnChanges, OnDestroy { itemId: this.itemId, area: this.area, title: this.title, + courseId: this.courseId }); } } @@ -165,14 +184,15 @@ export class CoreCommentsCommentsComponent implements OnChanges, OnDestroy { ngOnDestroy(): void { this.updateSiteObserver && this.updateSiteObserver.off(); this.refreshCommentsObserver && this.refreshCommentsObserver.off(); + this.commentsCountObserver && this.commentsCountObserver.off(); } /** * Check if a certain value in data is undefined or equal to this instance value. * - * @param {any} data Data object. - * @param {string} name Name of the property to check. - * @return {boolean} Whether it's undefined or equal. + * @param data Data object. + * @param name Name of the property to check. + * @return Whether it's undefined or equal. */ protected undefinedOrEqual(data: any, name: string): boolean { return typeof data[name] == 'undefined' || data[name] == this[name]; diff --git a/src/core/comments/components/comments/core-comments.html b/src/core/comments/components/comments/core-comments.html index 4375edc5a..b5ac94cb1 100644 --- a/src/core/comments/components/comments/core-comments.html +++ b/src/core/comments/components/comments/core-comments.html @@ -1,5 +1,5 @@ -
+
{{ 'core.comments.commentscount' | translate : {'$a': commentsCount} }}
diff --git a/src/core/comments/components/components.module.ts b/src/core/comments/components/components.module.ts index 32d57b82e..f8d2dbf9b 100644 --- a/src/core/comments/components/components.module.ts +++ b/src/core/comments/components/components.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/core/comments/pages/add/add.module.ts b/src/core/comments/pages/add/add.module.ts index a6b6661a0..669e9fd35 100644 --- a/src/core/comments/pages/add/add.module.ts +++ b/src/core/comments/pages/add/add.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/core/comments/pages/add/add.ts b/src/core/comments/pages/add/add.ts index 66510fb79..414109138 100644 --- a/src/core/comments/pages/add/add.ts +++ b/src/core/comments/pages/add/add.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -49,7 +49,7 @@ export class CoreCommentsAddPage { /** * Send the comment or store it offline. * - * @param {Event} e Event. + * @param e Event. */ addComment(e: Event): void { e.preventDefault(); diff --git a/src/core/comments/pages/viewer/viewer.html b/src/core/comments/pages/viewer/viewer.html index 2a023a419..6c4678862 100644 --- a/src/core/comments/pages/viewer/viewer.html +++ b/src/core/comments/pages/viewer/viewer.html @@ -1,6 +1,6 @@ - + - + @@ -56,7 +56,7 @@ - + diff --git a/src/core/comments/pages/viewer/viewer.module.ts b/src/core/comments/pages/viewer/viewer.module.ts index 3326cfe19..bc1350258 100644 --- a/src/core/comments/pages/viewer/viewer.module.ts +++ b/src/core/comments/pages/viewer/viewer.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/core/comments/pages/viewer/viewer.ts b/src/core/comments/pages/viewer/viewer.ts index e84d27e0c..678769992 100644 --- a/src/core/comments/pages/viewer/viewer.ts +++ b/src/core/comments/pages/viewer/viewer.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -47,6 +47,7 @@ export class CoreCommentsViewerPage implements OnDestroy { area: string; page: number; title: string; + courseId: number; canLoadMore = false; loadMoreError = false; canAddComments = false; @@ -62,11 +63,18 @@ export class CoreCommentsViewerPage implements OnDestroy { protected syncObserver: any; protected currentUser: any; - constructor(navParams: NavParams, private sitesProvider: CoreSitesProvider, private userProvider: CoreUserProvider, - private domUtils: CoreDomUtilsProvider, private translate: TranslateService, private modalCtrl: ModalController, - private commentsProvider: CoreCommentsProvider, private offlineComments: CoreCommentsOfflineProvider, - eventsProvider: CoreEventsProvider, private commentsSync: CoreCommentsSyncProvider, - private textUtils: CoreTextUtilsProvider, private timeUtils: CoreTimeUtilsProvider) { + constructor(navParams: NavParams, + protected sitesProvider: CoreSitesProvider, + protected userProvider: CoreUserProvider, + protected domUtils: CoreDomUtilsProvider, + protected translate: TranslateService, + protected modalCtrl: ModalController, + protected commentsProvider: CoreCommentsProvider, + protected offlineComments: CoreCommentsOfflineProvider, + protected eventsProvider: CoreEventsProvider, + protected commentsSync: CoreCommentsSyncProvider, + protected textUtils: CoreTextUtilsProvider, + protected timeUtils: CoreTimeUtilsProvider) { this.contextLevel = navParams.get('contextLevel'); this.instanceId = navParams.get('instanceId'); @@ -74,6 +82,7 @@ export class CoreCommentsViewerPage implements OnDestroy { this.itemId = navParams.get('itemId'); this.area = navParams.get('area') || ''; this.title = navParams.get('title') || this.translate.instant('core.comments.comments'); + this.courseId = navParams.get('courseId'); this.page = 0; // Refresh data if comments are synchronized automatically. @@ -113,9 +122,9 @@ export class CoreCommentsViewerPage implements OnDestroy { /** * Fetches the comments. * - * @param {boolean} sync When to resync comments. - * @param {boolean} [showErrors] When to display errors or not. - * @return {Promise} Resolved when done. + * @param sync When to resync comments. + * @param showErrors When to display errors or not. + * @return Resolved when done. */ protected fetchComments(sync: boolean, showErrors?: boolean): Promise { this.loadMoreError = false; @@ -125,67 +134,29 @@ export class CoreCommentsViewerPage implements OnDestroy { return promise.catch(() => { // Ignore errors. }).then(() => { - return this.offlineComments.getComment(this.contextLevel, this.instanceId, this.componentName, this.itemId, - this.area).then((offlineComment) => { - this.offlineComment = offlineComment; - - if (offlineComment && !this.currentUser) { - return this.userProvider.getProfile(this.currentUserId, undefined, true).then((user) => { - this.currentUser = user; - this.offlineComment.profileimageurl = user.profileimageurl; - this.offlineComment.fullname = user.fullname; - this.offlineComment.userid = user.id; - }).catch(() => { - // Ignore errors. - }); - } else if (offlineComment) { - this.offlineComment.profileimageurl = this.currentUser.profileimageurl; - this.offlineComment.fullname = this.currentUser.fullname; - this.offlineComment.userid = this.currentUser.id; - } - - return this.offlineComments.getDeletedComments(this.contextLevel, this.instanceId, this.componentName, this.itemId, - this.area); - }); - }).then((deletedComments) => { - this.hasOffline = !!this.offlineComment || deletedComments.length > 0; - // Get comments data. return this.commentsProvider.getComments(this.contextLevel, this.instanceId, this.componentName, this.itemId, this.area, this.page).then((response) => { this.canAddComments = this.addDeleteCommentsAvailable && response.canpost; const comments = response.comments.sort((a, b) => b.timecreated - a.timecreated); - this.canLoadMore = comments.length > 0 && comments.length >= CoreCommentsProvider.pageSize; + if (typeof response.count != 'undefined') { + this.canLoadMore = (this.comments.length + comments.length) > response.count; + } else { + // Old style. + this.canLoadMore = response.comments.length > 0 && response.comments.length >= CoreCommentsProvider.pageSize; + } - return Promise.all(comments.map((comment) => { - // Get the user profile image. - return this.userProvider.getProfile(comment.userid, undefined, true).then((user) => { - comment.profileimageurl = user.profileimageurl; - - return comment; - }).catch(() => { - // Ignore errors. - return comment; - }); - })); + return Promise.all(comments.map((comment) => this.loadCommentProfile(comment))); }).then((comments) => { this.comments = this.comments.concat(comments); - deletedComments && deletedComments.forEach((deletedComment) => { - const comment = this.comments.find((comment) => { - return comment.id == deletedComment.commentid; - }); - - if (comment) { - comment.deleted = deletedComment.deleted; - } - }); - this.canDeleteComments = this.addDeleteCommentsAvailable && (this.hasOffline || this.comments.some((comment) => { return !!comment.delete; })); }); + }).then(() => { + return this.loadOfflineData(); }).catch((error) => { this.loadMoreError = true; // Set to prevent infinite calls with infinite-loading. if (error && this.componentName == 'assignsubmission_comments') { @@ -204,8 +175,8 @@ export class CoreCommentsViewerPage implements OnDestroy { /** * Function to load more commemts. * - * @param {any} [infiniteComplete] Infinite scroll complete function. Only used from core-infinite-loading. - * @return {Promise} Resolved when done. + * @param infiniteComplete Infinite scroll complete function. Only used from core-infinite-loading. + * @return Resolved when done. */ loadMore(infiniteComplete?: any): Promise { this.page++; @@ -219,16 +190,16 @@ export class CoreCommentsViewerPage implements OnDestroy { /** * Refresh the comments. * - * @param {boolean} showErrors Whether to display errors or not. - * @param {any} [refresher] Refresher. - * @return {Promise} Resolved when done. + * @param showErrors Whether to display errors or not. + * @param refresher Refresher. + * @return Resolved when done. */ refreshComments(showErrors: boolean, refresher?: any): Promise { + this.commentsLoaded = false; this.refreshIcon = 'spinner'; this.syncIcon = 'spinner'; - return this.commentsProvider.invalidateCommentsData(this.contextLevel, this.instanceId, this.componentName, - this.itemId, this.area).finally(() => { + return this.invalidateComments().finally(() => { this.page = 0; this.comments = []; @@ -241,7 +212,7 @@ export class CoreCommentsViewerPage implements OnDestroy { /** * Show sync warnings if any. * - * @param {string[]} warnings the warnings + * @param warnings the warnings */ private showSyncWarnings(warnings: string[]): void { const message = this.textUtils.buildMessage(warnings); @@ -253,8 +224,8 @@ export class CoreCommentsViewerPage implements OnDestroy { /** * Tries to synchronize comments. * - * @param {boolean} showErrors Whether to display errors or not. - * @return {Promise} Promise resolved if sync is successful, rejected otherwise. + * @param showErrors Whether to display errors or not. + * @return Promise resolved if sync is successful, rejected otherwise. */ private syncComments(showErrors: boolean): Promise { return this.commentsSync.syncComments(this.contextLevel, this.instanceId, this.componentName, this.itemId, @@ -272,7 +243,7 @@ export class CoreCommentsViewerPage implements OnDestroy { /** * Add a new comment to the list. * - * @param {Event} e Event. + * @param e Event. */ addComment(e: Event): void { e.preventDefault(); @@ -290,10 +261,25 @@ export class CoreCommentsViewerPage implements OnDestroy { const modal = this.modalCtrl.create('CoreCommentsAddPage', params); modal.onDidDismiss((data) => { if (data && data.comments) { - this.comments = data.comments.concat(this.comments); - this.canDeleteComments = this.addDeleteCommentsAvailable; + this.invalidateComments(); + + return Promise.all(data.comments.map((comment) => this.loadCommentProfile(comment))).then((addedComments) => { + // Add the comment to the top. + this.comments = addedComments.concat(this.comments); + this.canDeleteComments = this.addDeleteCommentsAvailable; + + this.eventsProvider.trigger(CoreCommentsProvider.COMMENTS_COUNT_CHANGED_EVENT, { + contextLevel: this.contextLevel, + instanceId: this.instanceId, + component: this.componentName, + itemId: this.itemId, + area: this.area, + countChange: addedComments.length, + }, this.sitesProvider.getCurrentSiteId()); + }); } else if (data && !data.comments) { - this.fetchComments(false); + // Comments added in offline mode. + return this.loadOfflineData(); } }); modal.present(); @@ -302,27 +288,46 @@ export class CoreCommentsViewerPage implements OnDestroy { /** * Delete a comment. * - * @param {Event} e Click event. - * @param {any} comment Comment to delete. + * @param e Click event. + * @param deleteComment Comment to delete. */ - deleteComment(e: Event, comment: any): void { + deleteComment(e: Event, deleteComment: any): void { e.preventDefault(); e.stopPropagation(); - const time = this.timeUtils.userDate((comment.lastmodified || comment.timecreated) * 1000, 'core.strftimerecentfull'); + const time = this.timeUtils.userDate((deleteComment.lastmodified || deleteComment.timecreated) * 1000, + 'core.strftimerecentfull'); - comment.contextlevel = this.contextLevel; - comment.instanceid = this.instanceId; - comment.component = this.componentName; - comment.itemid = this.itemId; - comment.area = this.area; + deleteComment.contextlevel = this.contextLevel; + deleteComment.instanceid = this.instanceId; + deleteComment.component = this.componentName; + deleteComment.itemid = this.itemId; + deleteComment.area = this.area; - this.domUtils.showConfirm(this.translate.instant('core.comments.deletecommentbyon', {$a: - { user: comment.fullname || '', time: time } })).then(() => { - this.commentsProvider.deleteComment(comment).then(() => { + this.domUtils.showDeleteConfirm('core.comments.deletecommentbyon', {$a: + { user: deleteComment.fullname || '', time: time } }).then(() => { + this.commentsProvider.deleteComment(deleteComment).then((deletedOnline) => { this.showDelete = false; - this.refreshComments(true); + if (deletedOnline) { + const index = this.comments.findIndex((comment) => comment.id == deleteComment.id); + if (index >= 0) { + this.comments.splice(index, 1); + + this.eventsProvider.trigger(CoreCommentsProvider.COMMENTS_COUNT_CHANGED_EVENT, { + contextLevel: this.contextLevel, + instanceId: this.instanceId, + component: this.componentName, + itemId: this.itemId, + area: this.area, + countChange: -1, + }, this.sitesProvider.getCurrentSiteId()); + } + } else { + this.loadOfflineData(); + } + + this.invalidateComments(); this.domUtils.showToast('core.comments.eventcommentdeleted', true, 3000); }).catch((error) => { @@ -333,11 +338,96 @@ export class CoreCommentsViewerPage implements OnDestroy { }); } + /** + * Invalidate comments. + * + * @return Resolved when done. + */ + protected invalidateComments(): Promise { + return this.commentsProvider.invalidateCommentsData(this.contextLevel, this.instanceId, this.componentName, this.itemId, + this.area); + } + + /** + * Loads the profile info onto the comment object. + * + * @param comment Comment object. + * @return Promise resolved with modified comment when done. + */ + protected loadCommentProfile(comment: any): Promise { + // Get the user profile image. + return this.userProvider.getProfile(comment.userid, undefined, true).then((user) => { + comment.profileimageurl = user.profileimageurl; + comment.fullname = user.fullname; + comment.userid = user.id; + + return comment; + }).catch(() => { + // Ignore errors. + return comment; + }); + } + + /** + * Load offline comments. + * + * @return Promise resolved when done. + */ + protected loadOfflineData(): Promise { + const promises = []; + let hasDeletedComments = false; + + // Load the only offline comment allowed if any. + promises.push(this.offlineComments.getComment(this.contextLevel, this.instanceId, this.componentName, this.itemId, + this.area).then((offlineComment) => { + + if (offlineComment && !this.currentUser) { + offlineComment.userid = this.currentUserId; + + this.loadCommentProfile(offlineComment).then((comment) => { + // Save this fields for further requests. + if (comment.fullname) { + this.currentUser = {}; + this.currentUser.profileimageurl = comment.profileimageurl; + this.currentUser.fullname = comment.fullname; + this.currentUser.userid = comment.userid; + } + }); + } else if (offlineComment) { + offlineComment.profileimageurl = this.currentUser.profileimageurl; + offlineComment.fullname = this.currentUser.fullname; + offlineComment.userid = this.currentUser.id; + } + + this.offlineComment = offlineComment; + })); + + // Load deleted comments offline. + promises.push(this.offlineComments.getDeletedComments(this.contextLevel, this.instanceId, this.componentName, this.itemId, + this.area).then((deletedComments) => { + hasDeletedComments = deletedComments && deletedComments.length > 0; + + hasDeletedComments && deletedComments.forEach((deletedComment) => { + const comment = this.comments.find((comment) => { + return comment.id == deletedComment.commentid; + }); + + if (comment) { + comment.deleted = deletedComment.deleted; + } + }); + })); + + return Promise.all(promises).then(() => { + this.hasOffline = !!this.offlineComment || hasDeletedComments; + }); + } + /** * Restore a comment. * - * @param {Event} e Click event. - * @param {any} comment Comment to delete. + * @param e Click event. + * @param comment Comment to delete. */ undoDeleteComment(e: Event, comment: any): void { e.preventDefault(); diff --git a/src/core/comments/providers/comments.ts b/src/core/comments/providers/comments.ts index 16d7c8f95..960e6688b 100644 --- a/src/core/comments/providers/comments.ts +++ b/src/core/comments/providers/comments.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -26,9 +26,10 @@ import { CoreCommentsOfflineProvider } from './offline'; export class CoreCommentsProvider { static REFRESH_COMMENTS_EVENT = 'core_comments_refresh_comments'; + static COMMENTS_COUNT_CHANGED_EVENT = 'core_comments_count_changed'; protected ROOT_CACHE_KEY = 'mmComments:'; - static pageSize = null; + static pageSize = 1; // At least it will be one. static pageSizeOK = false; // If true, the pageSize is definitive. If not, it's a temporal value to reduce WS calls. constructor(private sitesProvider: CoreSitesProvider, private utils: CoreUtilsProvider, private appProvider: CoreAppProvider, @@ -37,14 +38,14 @@ export class CoreCommentsProvider { /** * Add a comment. * - * @param {string} content Comment text. - * @param {string} contextLevel Contextlevel system, course, user... - * @param {number} instanceId The Instance id of item associated with the context level. - * @param {string} component Component name. - * @param {number} itemId Associated id. - * @param {string} [area=''] String comment area. Default empty. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with boolean: true if comment was sent to server, false if stored in device. + * @param content Comment text. + * @param contextLevel Contextlevel system, course, user... + * @param instanceId The Instance id of item associated with the context level. + * @param component Component name. + * @param itemId Associated id. + * @param area String comment area. Default empty. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with boolean: true if comment was sent to server, false if stored in device. */ addComment(content: string, contextLevel: string, instanceId: number, component: string, itemId: number, area: string = '', siteId?: string): Promise { @@ -79,14 +80,14 @@ export class CoreCommentsProvider { /** * Add a comment. It will fail if offline or cannot connect. * - * @param {string} content Comment text. - * @param {string} contextLevel Contextlevel system, course, user... - * @param {number} instanceId The Instance id of item associated with the context level. - * @param {string} component Component name. - * @param {number} itemId Associated id. - * @param {string} [area=''] String comment area. Default empty. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when added, rejected otherwise. + * @param content Comment text. + * @param contextLevel Contextlevel system, course, user... + * @param instanceId The Instance id of item associated with the context level. + * @param component Component name. + * @param itemId Associated id. + * @param area String comment area. Default empty. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when added, rejected otherwise. */ addCommentOnline(content: string, contextLevel: string, instanceId: number, component: string, itemId: number, area: string = '', siteId?: string): Promise { @@ -114,10 +115,10 @@ export class CoreCommentsProvider { /** * Add several comments. It will fail if offline or cannot connect. * - * @param {any[]} comments Comments to save. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when added, rejected otherwise. Promise resolved doesn't mean that comments - * have been added, the resolve param can contain errors for comments not sent. + * @param comments Comments to save. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when added, rejected otherwise. Promise resolved doesn't mean that comments + * have been added, the resolve param can contain errors for comments not sent. */ addCommentsOnline(comments: any[], siteId?: string): Promise { if (!comments || !comments.length) { @@ -136,8 +137,8 @@ export class CoreCommentsProvider { /** * Check if Calendar is disabled in a certain site. * - * @param {CoreSite} [site] Site. If not defined, use current site. - * @return {boolean} Whether it's disabled. + * @param site Site. If not defined, use current site. + * @return Whether it's disabled. */ areCommentsDisabledInSite(site?: CoreSite): boolean { site = site || this.sitesProvider.getCurrentSite(); @@ -148,8 +149,8 @@ export class CoreCommentsProvider { /** * Check if comments are disabled in a certain site. * - * @param {string} [siteId] Site Id. If not defined, use current site. - * @return {Promise} Promise resolved with true if disabled, rejected or resolved with false otherwise. + * @param siteId Site Id. If not defined, use current site. + * @return Promise resolved with true if disabled, rejected or resolved with false otherwise. */ areCommentsDisabled(siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -160,17 +161,20 @@ export class CoreCommentsProvider { /** * Delete a comment. * - * @param {any} comment Comment object to delete. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when deleted, rejected otherwise. Promise resolved doesn't mean that comments - * have been deleted, the resolve param can contain errors for comments not deleted. + * @param comment Comment object to delete. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when deleted (with true if deleted in online, false otherwise), rejected otherwise. Promise resolved + * doesn't mean that comments have been deleted, the resolve param can contain errors for comments not deleted. */ - deleteComment(comment: any, siteId?: string): Promise { + deleteComment(comment: any, siteId?: string): Promise { siteId = siteId || this.sitesProvider.getCurrentSiteId(); + // Offline comment, just delete it. if (!comment.id) { return this.commentsOffline.removeComment(comment.contextlevel, comment.instanceid, comment.component, comment.itemid, - comment.area, siteId); + comment.area, siteId).then(() => { + return false; + }); } // Convenience function to store the action to be synchronized later. @@ -204,15 +208,15 @@ export class CoreCommentsProvider { /** * Delete a comment. It will fail if offline or cannot connect. * - * @param {number[]} commentIds Comment IDs to delete. - * @param {string} contextLevel Contextlevel system, course, user... - * @param {number} instanceId The Instance id of item associated with the context level. - * @param {string} component Component name. - * @param {number} itemId Associated id. - * @param {string} [area=''] String comment area. Default empty. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when deleted, rejected otherwise. Promise resolved doesn't mean that comments - * have been deleted, the resolve param can contain errors for comments not deleted. + * @param commentIds Comment IDs to delete. + * @param contextLevel Contextlevel system, course, user... + * @param instanceId The Instance id of item associated with the context level. + * @param component Component name. + * @param itemId Associated id. + * @param area String comment area. Default empty. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when deleted, rejected otherwise. Promise resolved doesn't mean that comments + * have been deleted, the resolve param can contain errors for comments not deleted. */ deleteCommentsOnline(commentIds: number[], contextLevel: string, instanceId: number, component: string, itemId: number, area: string = '', siteId?: string): Promise { @@ -233,8 +237,8 @@ export class CoreCommentsProvider { /** * Returns whether WS to add/delete comments are available in site. * - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with true if available, resolved with false or rejected otherwise. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with true if available, resolved with false or rejected otherwise. * @since 3.8 */ isAddCommentsAvailable(siteId?: string): Promise { @@ -251,12 +255,12 @@ export class CoreCommentsProvider { /** * Get cache key for get comments data WS calls. * - * @param {string} contextLevel Contextlevel system, course, user... - * @param {number} instanceId The Instance id of item associated with the context level. - * @param {string} component Component name. - * @param {number} itemId Associated id. - * @param {string} [area=''] String comment area. Default empty. - * @return {string} Cache key. + * @param contextLevel Contextlevel system, course, user... + * @param instanceId The Instance id of item associated with the context level. + * @param component Component name. + * @param itemId Associated id. + * @param area String comment area. Default empty. + * @return Cache key. */ protected getCommentsCacheKey(contextLevel: string, instanceId: number, component: string, itemId: number, area: string = ''): string { @@ -266,9 +270,9 @@ export class CoreCommentsProvider { /** * Get cache key for get comments instance data WS calls. * - * @param {string} contextLevel Contextlevel system, course, user... - * @param {number} instanceId The Instance id of item associated with the context level. - * @return {string} Cache key. + * @param contextLevel Contextlevel system, course, user... + * @param instanceId The Instance id of item associated with the context level. + * @return Cache key. */ protected getCommentsPrefixCacheKey(contextLevel: string, instanceId: number): string { return this.ROOT_CACHE_KEY + 'comments:' + contextLevel + ':' + instanceId; @@ -277,14 +281,14 @@ export class CoreCommentsProvider { /** * Retrieve a list of comments. * - * @param {string} contextLevel Contextlevel system, course, user... - * @param {number} instanceId The Instance id of item associated with the context level. - * @param {string} component Component name. - * @param {number} itemId Associated id. - * @param {string} [area=''] String comment area. Default empty. - * @param {number} [page=0] Page number (0 based). Default 0. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with the comments. + * @param contextLevel Contextlevel system, course, user... + * @param instanceId The Instance id of item associated with the context level. + * @param component Component name. + * @param itemId Associated id. + * @param area String comment area. Default empty. + * @param page Page number (0 based). Default 0. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the comments. */ getComments(contextLevel: string, instanceId: number, component: string, itemId: number, area: string = '', page: number = 0, siteId?: string): Promise { @@ -305,6 +309,11 @@ export class CoreCommentsProvider { return site.read('core_comment_get_comments', params, preSets).then((response) => { if (response.comments) { + // Update pageSize with the greatest count at the moment. + if (typeof response.count == 'undefined' && response.comments.length > CoreCommentsProvider.pageSize) { + CoreCommentsProvider.pageSize = response.comments.length; + } + return response; } @@ -316,29 +325,31 @@ export class CoreCommentsProvider { /** * Get comments count number to show on the comments component. * - * @param {string} contextLevel Contextlevel system, course, user... - * @param {number} instanceId The Instance id of item associated with the context level. - * @param {string} component Component name. - * @param {number} itemId Associated id. - * @param {string} [area=''] String comment area. Default empty. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Comments count with plus sign if needed. + * @param contextLevel Contextlevel system, course, user... + * @param instanceId The Instance id of item associated with the context level. + * @param component Component name. + * @param itemId Associated id. + * @param area String comment area. Default empty. + * @param siteId Site ID. If not defined, current site. + * @return Comments count with plus sign if needed. */ getCommentsCount(contextLevel: string, instanceId: number, component: string, itemId: number, area: string = '', siteId?: string): Promise { siteId = siteId ? siteId : this.sitesProvider.getCurrentSiteId(); + let trueCount = false; // Convenience function to get comments number on a page. const getCommentsPageCount = (page: number): Promise => { return this.getComments(contextLevel, instanceId, component, itemId, area, page, siteId).then((response) => { - if (response.comments) { - // Update pageSize with the greatest count at the moment. - if (response.comments && response.comments.length > CoreCommentsProvider.pageSize) { - CoreCommentsProvider.pageSize = response.comments.length; - } + if (typeof response.count != 'undefined') { + trueCount = true; - return response.comments && response.comments.length ? response.comments.length : 0; + return response.count; + } + + if (response.comments) { + return response.comments.length || 0; } return -1; @@ -348,17 +359,18 @@ export class CoreCommentsProvider { }; return getCommentsPageCount(0).then((count) => { - if (CoreCommentsProvider.pageSizeOK && count >= CoreCommentsProvider.pageSize) { + if (trueCount || count < CoreCommentsProvider.pageSize) { + return count + ''; + } else if (CoreCommentsProvider.pageSizeOK && count >= CoreCommentsProvider.pageSize) { // Page Size is ok, show + in case it reached the limit. return (CoreCommentsProvider.pageSize - 1) + '+'; - } else if (count < 0 || (CoreCommentsProvider.pageSize && count < CoreCommentsProvider.pageSize)) { - return count + ''; } // Call to update page size. return getCommentsPageCount(1).then((countMore) => { // Page limit was reached on the previous call. if (countMore > 0) { + CoreCommentsProvider.pageSizeOK = true; return (CoreCommentsProvider.pageSize - 1) + '+'; } @@ -371,13 +383,13 @@ export class CoreCommentsProvider { /** * Invalidates comments data. * - * @param {string} contextLevel Contextlevel system, course, user... - * @param {number} instanceId The Instance id of item associated with the context level. - * @param {string} component Component name. - * @param {number} itemId Associated id. - * @param {string} [area=''] String comment area. Default empty. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the data is invalidated. + * @param contextLevel Contextlevel system, course, user... + * @param instanceId The Instance id of item associated with the context level. + * @param component Component name. + * @param itemId Associated id. + * @param area String comment area. Default empty. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the data is invalidated. */ invalidateCommentsData(contextLevel: string, instanceId: number, component: string, itemId: number, area: string = '', siteId?: string): Promise { @@ -396,10 +408,10 @@ export class CoreCommentsProvider { /** * Invalidates all comments data for an instance. * - * @param {string} contextLevel Contextlevel system, course, user... - * @param {number} instanceId The Instance id of item associated with the context level. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the data is invalidated. + * @param contextLevel Contextlevel system, course, user... + * @param instanceId The Instance id of item associated with the context level. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the data is invalidated. */ invalidateCommentsByInstance(contextLevel: string, instanceId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -407,3 +419,27 @@ export class CoreCommentsProvider { }); } } + +/** + * Data returned by comment_area_exporter. + */ +export type CoreCommentsArea = { + component: string; // Component. + commentarea: string; // Commentarea. + itemid: number; // Itemid. + courseid: number; // Courseid. + contextid: number; // Contextid. + cid: string; // Cid. + autostart: boolean; // Autostart. + canpost: boolean; // Canpost. + canview: boolean; // Canview. + count: number; // Count. + collapsediconkey: string; // @since 3.3. Collapsediconkey. + displaytotalcount: boolean; // Displaytotalcount. + displaycancel: boolean; // Displaycancel. + fullwidth: boolean; // Fullwidth. + linktext: string; // Linktext. + notoggle: boolean; // Notoggle. + template: string; // Template. + canpostorhascomments: boolean; // Canpostorhascomments. +}; diff --git a/src/core/comments/providers/offline.ts b/src/core/comments/providers/offline.ts index 94caf9fb3..dd24113da 100644 --- a/src/core/comments/providers/offline.ts +++ b/src/core/comments/providers/offline.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -107,8 +107,8 @@ export class CoreCommentsOfflineProvider { /** * Get all offline comments. * - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with comments. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with comments. */ getAllComments(siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -122,13 +122,13 @@ export class CoreCommentsOfflineProvider { /** * Get an offline comment. * - * @param {string} contextLevel Contextlevel system, course, user... - * @param {number} instanceId The Instance id of item associated with the context level. - * @param {string} component Component name. - * @param {number} itemId Associated id. - * @param {string} [area=''] String comment area. Default empty. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with the comments. + * @param contextLevel Contextlevel system, course, user... + * @param instanceId The Instance id of item associated with the context level. + * @param component Component name. + * @param itemId Associated id. + * @param area String comment area. Default empty. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the comments. */ getComment(contextLevel: string, instanceId: number, component: string, itemId: number, area: string = '', siteId?: string): Promise { @@ -148,13 +148,13 @@ export class CoreCommentsOfflineProvider { /** * Get all offline comments added or deleted of a special area. * - * @param {string} contextLevel Contextlevel system, course, user... - * @param {number} instanceId The Instance id of item associated with the context level. - * @param {string} component Component name. - * @param {number} itemId Associated id. - * @param {string} [area=''] String comment area. Default empty. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with the comments. + * @param contextLevel Contextlevel system, course, user... + * @param instanceId The Instance id of item associated with the context level. + * @param component Component name. + * @param itemId Associated id. + * @param area String comment area. Default empty. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the comments. */ getComments(contextLevel: string, instanceId: number, component: string, itemId: number, area: string = '', siteId?: string): Promise { @@ -176,8 +176,8 @@ export class CoreCommentsOfflineProvider { /** * Get all offline deleted comments. * - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with comments. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with comments. */ getAllDeletedComments(siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -188,13 +188,13 @@ export class CoreCommentsOfflineProvider { /** * Get an offline comment. * - * @param {string} contextLevel Contextlevel system, course, user... - * @param {number} instanceId The Instance id of item associated with the context level. - * @param {string} component Component name. - * @param {number} itemId Associated id. - * @param {string} [area=''] String comment area. Default empty. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with the comments. + * @param contextLevel Contextlevel system, course, user... + * @param instanceId The Instance id of item associated with the context level. + * @param component Component name. + * @param itemId Associated id. + * @param area String comment area. Default empty. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the comments. */ getDeletedComments(contextLevel: string, instanceId: number, component: string, itemId: number, area: string = '', siteId?: string): Promise { @@ -214,13 +214,13 @@ export class CoreCommentsOfflineProvider { /** * Remove an offline comment. * - * @param {string} contextLevel Contextlevel system, course, user... - * @param {number} instanceId The Instance id of item associated with the context level. - * @param {string} component Component name. - * @param {number} itemId Associated id. - * @param {string} [area=''] String comment area. Default empty. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved if deleted, rejected if failure. + * @param contextLevel Contextlevel system, course, user... + * @param instanceId The Instance id of item associated with the context level. + * @param component Component name. + * @param itemId Associated id. + * @param area String comment area. Default empty. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved if deleted, rejected if failure. */ removeComment(contextLevel: string, instanceId: number, component: string, itemId: number, area: string = '', siteId?: string): Promise { @@ -238,13 +238,13 @@ export class CoreCommentsOfflineProvider { /** * Remove an offline deleted comment. * - * @param {string} contextLevel Contextlevel system, course, user... - * @param {number} instanceId The Instance id of item associated with the context level. - * @param {string} component Component name. - * @param {number} itemId Associated id. - * @param {string} [area=''] String comment area. Default empty. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved if deleted, rejected if failure. + * @param contextLevel Contextlevel system, course, user... + * @param instanceId The Instance id of item associated with the context level. + * @param component Component name. + * @param itemId Associated id. + * @param area String comment area. Default empty. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved if deleted, rejected if failure. */ removeDeletedComments(contextLevel: string, instanceId: number, component: string, itemId: number, area: string = '', siteId?: string): Promise { @@ -262,14 +262,14 @@ export class CoreCommentsOfflineProvider { /** * Save a comment to be sent later. * - * @param {string} content Comment text. - * @param {string} contextLevel Contextlevel system, course, user... - * @param {number} instanceId The Instance id of item associated with the context level. - * @param {string} component Component name. - * @param {number} itemId Associated id. - * @param {string} [area=''] String comment area. Default empty. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved if stored, rejected if failure. + * @param content Comment text. + * @param contextLevel Contextlevel system, course, user... + * @param instanceId The Instance id of item associated with the context level. + * @param component Component name. + * @param itemId Associated id. + * @param area String comment area. Default empty. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved if stored, rejected if failure. */ saveComment(content: string, contextLevel: string, instanceId: number, component: string, itemId: number, area: string = '', siteId?: string): Promise { @@ -294,14 +294,14 @@ export class CoreCommentsOfflineProvider { /** * Delete a comment offline to be sent later. * - * @param {number} commentId Comment ID. - * @param {string} contextLevel Contextlevel system, course, user... - * @param {number} instanceId The Instance id of item associated with the context level. - * @param {string} component Component name. - * @param {number} itemId Associated id. - * @param {string} [area=''] String comment area. Default empty. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved if stored, rejected if failure. + * @param commentId Comment ID. + * @param contextLevel Contextlevel system, course, user... + * @param instanceId The Instance id of item associated with the context level. + * @param component Component name. + * @param itemId Associated id. + * @param area String comment area. Default empty. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved if stored, rejected if failure. */ deleteComment(commentId: number, contextLevel: string, instanceId: number, component: string, itemId: number, area: string = '', siteId?: string): Promise { @@ -326,9 +326,9 @@ export class CoreCommentsOfflineProvider { /** * Undo delete a comment. * - * @param {number} commentId Comment ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved if deleted, rejected if failure. + * @param commentId Comment ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved if deleted, rejected if failure. */ undoDeleteComment(commentId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { diff --git a/src/core/comments/providers/sync-cron-handler.ts b/src/core/comments/providers/sync-cron-handler.ts index 5803f936e..6ee3701d6 100644 --- a/src/core/comments/providers/sync-cron-handler.ts +++ b/src/core/comments/providers/sync-cron-handler.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -29,9 +29,9 @@ export class CoreCommentsSyncCronHandler implements CoreCronHandler { * Execute the process. * Receives the ID of the site affected, undefined for all sites. * - * @param {string} [siteId] ID of the site affected, undefined for all sites. - * @param {boolean} [force] Wether the execution is forced (manual sync). - * @return {Promise} Promise resolved when done, rejected if failure. + * @param siteId ID of the site affected, undefined for all sites. + * @param force Wether the execution is forced (manual sync). + * @return Promise resolved when done, rejected if failure. */ execute(siteId?: string, force?: boolean): Promise { return this.commentsSync.syncAllComments(siteId, force); @@ -40,7 +40,7 @@ export class CoreCommentsSyncCronHandler implements CoreCronHandler { /** * Get the time between consecutive executions. * - * @return {number} Time between consecutive executions (in ms). + * @return Time between consecutive executions (in ms). */ getInterval(): number { return 300000; // 5 minutes. diff --git a/src/core/comments/providers/sync.ts b/src/core/comments/providers/sync.ts index c8466cac7..4397c88c4 100644 --- a/src/core/comments/providers/sync.ts +++ b/src/core/comments/providers/sync.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -46,9 +46,9 @@ export class CoreCommentsSyncProvider extends CoreSyncBaseProvider { /** * Try to synchronize all the comments in a certain site or in all sites. * - * @param {string} [siteId] Site ID to sync. If not defined, sync all sites. - * @param {boolean} [force] Wether to force sync not depending on last execution. - * @return {Promise} Promise resolved if sync is successful, rejected if sync fails. + * @param siteId Site ID to sync. If not defined, sync all sites. + * @param force Wether to force sync not depending on last execution. + * @return Promise resolved if sync is successful, rejected if sync fails. */ syncAllComments(siteId?: string, force?: boolean): Promise { return this.syncOnSites('all comments', this.syncAllCommentsFunc.bind(this), [force], siteId); @@ -57,9 +57,9 @@ export class CoreCommentsSyncProvider extends CoreSyncBaseProvider { /** * Synchronize all the comments in a certain site * - * @param {string} siteId Site ID to sync. - * @param {boolean} force Wether to force sync not depending on last execution. - * @return {Promise} Promise resolved if sync is successful, rejected if sync fails. + * @param siteId Site ID to sync. + * @param force Wether to force sync not depending on last execution. + * @return Promise resolved if sync is successful, rejected if sync fails. */ private syncAllCommentsFunc(siteId: string, force: boolean): Promise { return this.commentsOffline.getAllComments(siteId).then((comments) => { @@ -100,13 +100,13 @@ export class CoreCommentsSyncProvider extends CoreSyncBaseProvider { /** * Sync course comments only if a certain time has passed since the last time. * - * @param {string} contextLevel Contextlevel system, course, user... - * @param {number} instanceId The Instance id of item associated with the context level. - * @param {string} component Component name. - * @param {number} itemId Associated id. - * @param {string} [area=''] String comment area. Default empty. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the comments are synced or if they don't need to be synced. + * @param contextLevel Contextlevel system, course, user... + * @param instanceId The Instance id of item associated with the context level. + * @param component Component name. + * @param itemId Associated id. + * @param area String comment area. Default empty. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the comments are synced or if they don't need to be synced. */ private syncCommentsIfNeeded(contextLevel: string, instanceId: number, component: string, itemId: number, area: string = '', siteId?: string): Promise { @@ -122,13 +122,13 @@ export class CoreCommentsSyncProvider extends CoreSyncBaseProvider { /** * Synchronize comments in a particular area. * - * @param {string} contextLevel Contextlevel system, course, user... - * @param {number} instanceId The Instance id of item associated with the context level. - * @param {string} component Component name. - * @param {number} itemId Associated id. - * @param {string} [area=''] String comment area. Default empty. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved if sync is successful, rejected otherwise. + * @param contextLevel Contextlevel system, course, user... + * @param instanceId The Instance id of item associated with the context level. + * @param component Component name. + * @param itemId Associated id. + * @param area String comment area. Default empty. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved if sync is successful, rejected otherwise. */ syncComments(contextLevel: string, instanceId: number, component: string, itemId: number, area: string = '', siteId?: string): Promise { @@ -159,6 +159,7 @@ export class CoreCommentsSyncProvider extends CoreSyncBaseProvider { const errors = [], promises = [], deleteCommentIds = []; + let countChange = 0; comments.forEach((comment) => { if (comment.commentid) { @@ -166,6 +167,8 @@ export class CoreCommentsSyncProvider extends CoreSyncBaseProvider { } else { promises.push(this.commentsProvider.addCommentOnline(comment.content, contextLevel, instanceId, component, itemId, area, siteId).then((response) => { + countChange++; + return this.commentsOffline.removeComment(contextLevel, instanceId, component, itemId, area, siteId); })); } @@ -174,6 +177,8 @@ export class CoreCommentsSyncProvider extends CoreSyncBaseProvider { if (deleteCommentIds.length > 0) { promises.push(this.commentsProvider.deleteCommentsOnline(deleteCommentIds, contextLevel, instanceId, component, itemId, area, siteId).then((response) => { + countChange--; + return this.commentsOffline.removeDeletedComments(contextLevel, instanceId, component, itemId, area, siteId); })); @@ -181,6 +186,15 @@ export class CoreCommentsSyncProvider extends CoreSyncBaseProvider { // Send the comments. return Promise.all(promises).then(() => { + this.eventsProvider.trigger(CoreCommentsProvider.COMMENTS_COUNT_CHANGED_EVENT, { + contextLevel: contextLevel, + instanceId: instanceId, + component: component, + itemId: itemId, + area: area, + countChange: countChange, + }, this.sitesProvider.getCurrentSiteId()); + // Fetch the comments from server to be sure they're up to date. return this.commentsProvider.invalidateCommentsData(contextLevel, instanceId, component, itemId, area, siteId) .then(() => { @@ -216,12 +230,12 @@ export class CoreCommentsSyncProvider extends CoreSyncBaseProvider { /** * Get the ID of a comments sync. * - * @param {string} contextLevel Contextlevel system, course, user... - * @param {number} instanceId The Instance id of item associated with the context level. - * @param {string} component Component name. - * @param {number} itemId Associated id. - * @param {string} [area=''] String comment area. Default empty. - * @return {string} Sync ID. + * @param contextLevel Contextlevel system, course, user... + * @param instanceId The Instance id of item associated with the context level. + * @param component Component name. + * @param itemId Associated id. + * @param area String comment area. Default empty. + * @return Sync ID. */ protected getSyncId(contextLevel: string, instanceId: number, component: string, itemId: number, area: string = ''): string { return contextLevel + '#' + instanceId + '#' + component + '#' + itemId + '#' + area; diff --git a/src/core/compile/compile.module.ts b/src/core/compile/compile.module.ts index 610277494..38cb113b9 100644 --- a/src/core/compile/compile.module.ts +++ b/src/core/compile/compile.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/core/compile/components/compile-html/compile-html.module.ts b/src/core/compile/components/compile-html/compile-html.module.ts index 72d030a00..2dc6560ac 100644 --- a/src/core/compile/components/compile-html/compile-html.module.ts +++ b/src/core/compile/components/compile-html/compile-html.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/core/compile/components/compile-html/compile-html.ts b/src/core/compile/components/compile-html/compile-html.ts index 82c52cd8e..e72143d5a 100644 --- a/src/core/compile/components/compile-html/compile-html.ts +++ b/src/core/compile/components/compile-html/compile-html.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -129,7 +129,7 @@ export class CoreCompileHtmlComponent implements OnChanges, OnDestroy, DoCheck { /** * Get a class that defines the dynamic component. * - * @return {any} The component class. + * @return The component class. */ protected getComponentClass(): any { // tslint:disable: no-this-assignment @@ -221,11 +221,11 @@ export class CoreCompileHtmlComponent implements OnChanges, OnDestroy, DoCheck { /** * Call a certain function on the component instance. * - * @param {string} name Name of the function to call. - * @param {any[]} params List of params to send to the function. - * @param {boolean} [callWhenCreated=true] If this param is true and the component hasn't been created yet, call the function - * once the component has been created. - * @return {any} Result of the call. Undefined if no component instance or the function doesn't exist. + * @param name Name of the function to call. + * @param params List of params to send to the function. + * @param callWhenCreated If this param is true and the component hasn't been created yet, call the function + * once the component has been created. + * @return Result of the call. Undefined if no component instance or the function doesn't exist. */ callComponentFunction(name: string, params?: any[], callWhenCreated: boolean = true): any { if (this.componentInstance) { diff --git a/src/core/compile/providers/compile.ts b/src/core/compile/providers/compile.ts index 7b76555ce..8f0c7e1b6 100644 --- a/src/core/compile/providers/compile.ts +++ b/src/core/compile/providers/compile.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -28,7 +28,9 @@ import { CORE_CONTENTLINKS_PROVIDERS } from '@core/contentlinks/contentlinks.mod import { CORE_COURSE_PROVIDERS } from '@core/course/course.module'; import { CORE_COURSES_PROVIDERS } from '@core/courses/courses.module'; import { CORE_FILEUPLOADER_PROVIDERS } from '@core/fileuploader/fileuploader.module'; +import { CORE_FILTER_PROVIDERS } from '@core/filter/filter.module'; import { CORE_GRADES_PROVIDERS } from '@core/grades/grades.module'; +import { CORE_H5P_PROVIDERS } from '@core/h5p/h5p.module'; import { CORE_LOGIN_PROVIDERS } from '@core/login/login.module'; import { CORE_MAINMENU_PROVIDERS } from '@core/mainmenu/mainmenu.module'; import { CORE_QUESTION_PROVIDERS } from '@core/question/question.module'; @@ -154,10 +156,10 @@ export class CoreCompileProvider { /** * Create and compile a dynamic component. * - * @param {string} template The template of the component. - * @param {any} componentClass The JS class of the component. - * @param {any[]} [extraImports] Extra imported modules if needed and not imported by this class. - * @return {Promise>} Promise resolved with the factory to instantiate the component. + * @param template The template of the component. + * @param componentClass The JS class of the component. + * @param extraImports Extra imported modules if needed and not imported by this class. + * @return Promise resolved with the factory to instantiate the component. */ createAndCompileComponent(template: string, componentClass: any, extraImports: any[] = []): Promise> { // Create the component using the template and the class. @@ -190,8 +192,8 @@ export class CoreCompileProvider { /** * Eval some javascript using the context of the function. * - * @param {string} javascript The javascript to eval. - * @return {any} Result of the eval. + * @param javascript The javascript to eval. + * @return Result of the eval. */ protected evalInContext(javascript: string): any { // tslint:disable: no-eval @@ -201,9 +203,9 @@ export class CoreCompileProvider { /** * Execute some javascript code, using a certain instance as the context. * - * @param {any} instance Instance to use as the context. In the JS code, "this" will be this instance. - * @param {string} javascript The javascript code to eval. - * @return {any} Result of the javascript execution. + * @param instance Instance to use as the context. In the JS code, "this" will be this instance. + * @param javascript The javascript code to eval. + * @return Result of the javascript execution. */ executeJavascript(instance: any, javascript: string): any { try { @@ -216,8 +218,8 @@ export class CoreCompileProvider { /** * Inject all the core libraries in a certain object. * - * @param {any} instance The instance where to inject the libraries. - * @param {any[]} [extraProviders] Extra imported providers if needed and not imported by this class. + * @param instance The instance where to inject the libraries. + * @param extraProviders Extra imported providers if needed and not imported by this class. */ injectLibraries(instance: any, extraProviders: any[] = []): void { const providers = ( CORE_PROVIDERS).concat(CORE_CONTENTLINKS_PROVIDERS).concat(CORE_COURSE_PROVIDERS) @@ -234,7 +236,8 @@ export class CoreCompileProvider { .concat(ADDON_MOD_QUIZ_PROVIDERS).concat(ADDON_MOD_RESOURCE_PROVIDERS).concat(ADDON_MOD_SCORM_PROVIDERS) .concat(ADDON_MOD_SURVEY_PROVIDERS).concat(ADDON_MOD_URL_PROVIDERS).concat(ADDON_MOD_WIKI_PROVIDERS) .concat(ADDON_MOD_WORKSHOP_PROVIDERS).concat(ADDON_NOTES_PROVIDERS).concat(ADDON_NOTIFICATIONS_PROVIDERS) - .concat(CORE_PUSHNOTIFICATIONS_PROVIDERS).concat(ADDON_REMOTETHEMES_PROVIDERS).concat(CORE_BLOCK_PROVIDERS); + .concat(CORE_PUSHNOTIFICATIONS_PROVIDERS).concat(ADDON_REMOTETHEMES_PROVIDERS).concat(CORE_BLOCK_PROVIDERS) + .concat(CORE_FILTER_PROVIDERS).concat(CORE_H5P_PROVIDERS); // We cannot inject anything to this constructor. Use the Injector to inject all the providers into the instance. for (const i in providers) { @@ -284,10 +287,10 @@ export class CoreCompileProvider { /** * Instantiate a dynamic component. * - * @param {string} template The template of the component. - * @param {any} componentClass The JS class of the component. - * @param {Injector} [injector] The injector to use. It's recommended to pass it so NavController and similar can be injected. - * @return {Promise>} Promise resolved with the component instance. + * @param template The template of the component. + * @param componentClass The JS class of the component. + * @param injector The injector to use. It's recommended to pass it so NavController and similar can be injected. + * @return Promise resolved with the component instance. */ instantiateDynamicComponent(template: string, componentClass: any, injector?: Injector): Promise> { injector = injector || this.injector; diff --git a/src/core/constants.ts b/src/core/constants.ts index a6f31c3a5..c9784ccc3 100644 --- a/src/core/constants.ts +++ b/src/core/constants.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -12,6 +12,18 @@ // See the License for the specific language governing permissions and // limitations under the License. +/** + * Context levels enumeration. + */ +export const enum ContextLevel { + SYSTEM = 'system', + USER = 'user', + COURSECAT = 'coursecat', + COURSE = 'course', + MODULE = 'module', + BLOCK = 'block', +} + /** * Static class to contain all the core constants. */ @@ -36,6 +48,7 @@ export class CoreConstants { static SETTINGS_REPORT_IN_BACKGROUND = 'CoreSettingsReportInBackground'; // @deprecated since 3.5.0 static SETTINGS_SEND_ON_ENTER = 'CoreSettingsSendOnEnter'; static SETTINGS_FONT_SIZE = 'CoreSettingsFontSize'; + static SETTINGS_COLOR_SCHEME = 'CoreSettingsColorScheme'; static SETTINGS_ANALYTICS_ENABLED = 'CoreSettingsAnalyticsEnabled'; // WS constants. diff --git a/src/core/contentlinks/classes/base-handler.ts b/src/core/contentlinks/classes/base-handler.ts index 9f79ddbda..3d74b8f38 100644 --- a/src/core/contentlinks/classes/base-handler.ts +++ b/src/core/contentlinks/classes/base-handler.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -23,34 +23,29 @@ import { CoreContentLinksHandler, CoreContentLinksAction } from '../providers/de export class CoreContentLinksHandlerBase implements CoreContentLinksHandler { /** * A name to identify the handler. - * @type {string} */ name = 'CoreContentLinksHandlerBase'; /** * Handler's priority. The highest priority is treated first. - * @type {number} */ priority = 0; /** * Whether the isEnabled function should be called for all the users in a site. It should be true only if the isEnabled call * can return different values for different users in same site. - * @type {boolean} */ checkAllUsers = false; /** * Name of the feature this handler is related to. * It will be used to check if the feature is disabled (@see CoreSite.isFeatureDisabled). - * @type {string} */ featureName = ''; /** * A pattern to use to detect if the handler handles a URL and to get its site URL. Required if "handles" and * "getSiteUrl" functions aren't overridden. - * @type {RexExp} */ pattern?: RegExp; @@ -61,11 +56,11 @@ export class CoreContentLinksHandlerBase implements CoreContentLinksHandler { /** * Get the list of actions for a link (url). * - * @param {string[]} siteIds List of sites the URL belongs to. - * @param {string} url The URL to treat. - * @param {any} params The params of the URL. E.g. 'mysite.com?id=1' -> {id: 1} - * @param {number} [courseId] Course ID related to the URL. Optional but recommended. - * @return {CoreContentLinksAction[]|Promise} List of (or promise resolved with list of) actions. + * @param siteIds List of sites the URL belongs to. + * @param url The URL to treat. + * @param params The params of the URL. E.g. 'mysite.com?id=1' -> {id: 1} + * @param courseId Course ID related to the URL. Optional but recommended. + * @return List of (or promise resolved with list of) actions. */ getActions(siteIds: string[], url: string, params: any, courseId?: number): CoreContentLinksAction[] | Promise { @@ -75,8 +70,8 @@ export class CoreContentLinksHandlerBase implements CoreContentLinksHandler { /** * Check if a URL is handled by this handler. * - * @param {string} url The URL to check. - * @return {boolean} Whether the URL is handled by this handler + * @param url The URL to check. + * @return Whether the URL is handled by this handler */ handles(url: string): boolean { return this.pattern && url.search(this.pattern) >= 0; @@ -85,8 +80,8 @@ export class CoreContentLinksHandlerBase implements CoreContentLinksHandler { /** * If the URL is handled by this handler, return the site URL. * - * @param {string} url The URL to check. - * @return {string} Site URL if it is handled, undefined otherwise. + * @param url The URL to check. + * @return Site URL if it is handled, undefined otherwise. */ getSiteUrl(url: string): string { if (this.pattern) { @@ -101,11 +96,11 @@ export class CoreContentLinksHandlerBase implements CoreContentLinksHandler { * Check if the handler is enabled for a certain site (site + user) and a URL. * If not defined, defaults to true. * - * @param {string} siteId The site ID. - * @param {string} url The URL to treat. - * @param {any} params The params of the URL. E.g. 'mysite.com?id=1' -> {id: 1} - * @param {number} [courseId] Course ID related to the URL. Optional but recommended. - * @return {boolean|Promise} Whether the handler is enabled for the URL and site. + * @param siteId The site ID. + * @param url The URL to treat. + * @param params The params of the URL. E.g. 'mysite.com?id=1' -> {id: 1} + * @param courseId Course ID related to the URL. Optional but recommended. + * @return Whether the handler is enabled for the URL and site. */ isEnabled(siteId: string, url: string, params: any, courseId?: number): boolean | Promise { return true; diff --git a/src/core/contentlinks/classes/module-grade-handler.ts b/src/core/contentlinks/classes/module-grade-handler.ts index c1a5124e4..67fc2bdf9 100644 --- a/src/core/contentlinks/classes/module-grade-handler.ts +++ b/src/core/contentlinks/classes/module-grade-handler.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -26,25 +26,23 @@ export class CoreContentLinksModuleGradeHandler extends CoreContentLinksHandlerB /** * Whether the module can be reviewed in the app. If true, the handler needs to implement the goToReview function. - * @type {boolean} */ canReview: boolean; /** * If this boolean is set to true, the app will retrieve all modules with this modName with a single WS call. * This reduces the number of WS calls, but it isn't recommended for modules that can return a lot of contents. - * @type {boolean} */ protected useModNameToGetModule = false; /** * Construct the handler. * - * @param {CoreCourseHelperProvider} courseHelper The CoreCourseHelperProvider instance. - * @param {CoreDomUtilsProvider} domUtils The CoreDomUtilsProvider instance. - * @param {CoreSitesProvider} sitesProvider The CoreSitesProvider instance. - * @param {string} addon Name of the addon as it's registered in course delegate. It'll be used to check if it's disabled. - * @param {string} modName Name of the module (assign, book, ...). + * @param courseHelper The CoreCourseHelperProvider instance. + * @param domUtils The CoreDomUtilsProvider instance. + * @param sitesProvider The CoreSitesProvider instance. + * @param addon Name of the addon as it's registered in course delegate. It'll be used to check if it's disabled. + * @param modName Name of the module (assign, book, ...). */ constructor(protected courseHelper: CoreCourseHelperProvider, protected domUtils: CoreDomUtilsProvider, protected sitesProvider: CoreSitesProvider, public addon: string, public modName: string) { @@ -58,11 +56,11 @@ export class CoreContentLinksModuleGradeHandler extends CoreContentLinksHandlerB /** * Get the list of actions for a link (url). * - * @param {string[]} siteIds List of sites the URL belongs to. - * @param {string} url The URL to treat. - * @param {any} params The params of the URL. E.g. 'mysite.com?id=1' -> {id: 1} - * @param {number} [courseId] Course ID related to the URL. Optional but recommended. - * @return {CoreContentLinksAction[]|Promise} List of (or promise resolved with list of) actions. + * @param siteIds List of sites the URL belongs to. + * @param url The URL to treat. + * @param params The params of the URL. E.g. 'mysite.com?id=1' -> {id: 1} + * @param courseId Course ID related to the URL. Optional but recommended. + * @return List of (or promise resolved with list of) actions. */ getActions(siteIds: string[], url: string, params: any, courseId?: number): CoreContentLinksAction[] | Promise { @@ -95,12 +93,12 @@ export class CoreContentLinksModuleGradeHandler extends CoreContentLinksHandlerB /** * Go to the page to review. * - * @param {string} url The URL to treat. - * @param {any} params The params of the URL. E.g. 'mysite.com?id=1' -> {id: 1} - * @param {number} courseId Course ID related to the URL. - * @param {string} siteId Site to use. - * @param {NavController} [navCtrl] Nav Controller to use to navigate. - * @return {Promise} Promise resolved when done. + * @param url The URL to treat. + * @param params The params of the URL. E.g. 'mysite.com?id=1' -> {id: 1} + * @param courseId Course ID related to the URL. + * @param siteId Site to use. + * @param navCtrl Nav Controller to use to navigate. + * @return Promise resolved when done. */ protected goToReview(url: string, params: any, courseId: number, siteId: string, navCtrl?: NavController): Promise { // This function should be overridden. diff --git a/src/core/contentlinks/classes/module-index-handler.ts b/src/core/contentlinks/classes/module-index-handler.ts index 67159b012..6487d4e29 100644 --- a/src/core/contentlinks/classes/module-index-handler.ts +++ b/src/core/contentlinks/classes/module-index-handler.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -24,43 +24,72 @@ export class CoreContentLinksModuleIndexHandler extends CoreContentLinksHandlerB /** * If this boolean is set to true, the app will retrieve all modules with this modName with a single WS call. * This reduces the number of WS calls, but it isn't recommended for modules that can return a lot of contents. - * @type {boolean} */ protected useModNameToGetModule = false; /** * Construct the handler. * - * @param {CoreCourseHelperProvider} courseHelper The CoreCourseHelperProvider instance. - * @param {string} addon Name of the addon as it's registered in course delegate. It'll be used to check if it's disabled. - * @param {string} modName Name of the module (assign, book, ...). + * @param courseHelper The CoreCourseHelperProvider instance. + * @param addon Name of the addon as it's registered in course delegate. It'll be used to check if it's disabled. + * @param modName Name of the module (assign, book, ...). + * @param instanceIdParam Param name for instance ID gathering. Only if set. */ - constructor(protected courseHelper: CoreCourseHelperProvider, public addon: string, public modName: string) { + constructor(protected courseHelper: CoreCourseHelperProvider, public addon: string, public modName: string, + protected instanceIdParam?: string) { super(); + const pattern = instanceIdParam ? + '\/mod\/' + modName + '\/view\.php.*([\&\?](' + instanceIdParam + '|id)=\\d+)' : + '\/mod\/' + modName + '\/view\.php.*([\&\?]id=\\d+)'; + // Match the view.php URL with an id param. - this.pattern = new RegExp('\/mod\/' + modName + '\/view\.php.*([\&\?]id=\\d+)'); + this.pattern = new RegExp(pattern); this.featureName = 'CoreCourseModuleDelegate_' + addon; } + /** + * Get the mod params necessary to open an activity. + * + * @param url The URL to treat. + * @param params The params of the URL. E.g. 'mysite.com?id=1' -> {id: 1} + * @param courseId Course ID related to the URL. Optional but recommended. + * @return List of params to pass to navigateToModule / navigateToModuleByInstance. + */ + getPageParams(url: string, params: any, courseId?: number): any { + return undefined; + } + /** * Get the list of actions for a link (url). * - * @param {string[]} siteIds List of sites the URL belongs to. - * @param {string} url The URL to treat. - * @param {any} params The params of the URL. E.g. 'mysite.com?id=1' -> {id: 1} - * @param {number} [courseId] Course ID related to the URL. Optional but recommended. - * @return {CoreContentLinksAction[]|Promise} List of (or promise resolved with list of) actions. + * @param siteIds List of sites the URL belongs to. + * @param url The URL to treat. + * @param params The params of the URL. E.g. 'mysite.com?id=1' -> {id: 1} + * @param courseId Course ID related to the URL. Optional but recommended. + * @return List of (or promise resolved with list of) actions. */ getActions(siteIds: string[], url: string, params: any, courseId?: number): CoreContentLinksAction[] | Promise { courseId = courseId || params.courseid || params.cid; + const pageParams = this.getPageParams(url, params, courseId); + + if (this.instanceIdParam && typeof params[this.instanceIdParam] != 'undefined') { + const instanceId = parseInt(params[this.instanceIdParam], 10); + + return [{ + action: (siteId, navCtrl?): void => { + this.courseHelper.navigateToModuleByInstance(instanceId, this.modName, siteId, courseId, undefined, + this.useModNameToGetModule, pageParams, navCtrl); + } + }]; + } return [{ action: (siteId, navCtrl?): void => { this.courseHelper.navigateToModule(parseInt(params.id, 10), siteId, courseId, undefined, - this.useModNameToGetModule ? this.modName : undefined, undefined, navCtrl); + this.useModNameToGetModule ? this.modName : undefined, pageParams, navCtrl); } }]; } diff --git a/src/core/contentlinks/classes/module-list-handler.ts b/src/core/contentlinks/classes/module-list-handler.ts index 37e44e7d7..36a3f6381 100644 --- a/src/core/contentlinks/classes/module-list-handler.ts +++ b/src/core/contentlinks/classes/module-list-handler.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -24,17 +24,16 @@ export class CoreContentLinksModuleListHandler extends CoreContentLinksHandlerBa /** * The title to use in the new page. If not defined, the app will try to calculate it. - * @type {string} */ protected title: string; /** * Construct the handler. * - * @param {CoreContentLinksHelperProvider} linkHelper The CoreContentLinksHelperProvider instance. - * @param {TranslateService} translate The TranslateService instance. - * @param {string} addon Name of the addon as it's registered in course delegate. It'll be used to check if it's disabled. - * @param {string} modName Name of the module (assign, book, ...). + * @param linkHelper The CoreContentLinksHelperProvider instance. + * @param translate The TranslateService instance. + * @param addon Name of the addon as it's registered in course delegate. It'll be used to check if it's disabled. + * @param modName Name of the module (assign, book, ...). */ constructor(protected linkHelper: CoreContentLinksHelperProvider, protected translate: TranslateService, public addon: string, public modName: string) { @@ -48,10 +47,10 @@ export class CoreContentLinksModuleListHandler extends CoreContentLinksHandlerBa /** * Get the list of actions for a link (url). * - * @param {string[]} siteIds List of sites the URL belongs to. - * @param {string} url The URL to treat. - * @param {any} params The params of the URL. E.g. 'mysite.com?id=1' -> {id: 1} - * @return {CoreContentLinksAction[]|Promise} List of (or promise resolved with list of) actions. + * @param siteIds List of sites the URL belongs to. + * @param url The URL to treat. + * @param params The params of the URL. E.g. 'mysite.com?id=1' -> {id: 1} + * @return List of (or promise resolved with list of) actions. */ getActions(siteIds: string[], url: string, params: any): CoreContentLinksAction[] | Promise { diff --git a/src/core/contentlinks/contentlinks.module.ts b/src/core/contentlinks/contentlinks.module.ts index 0abe605db..ce70cc762 100644 --- a/src/core/contentlinks/contentlinks.module.ts +++ b/src/core/contentlinks/contentlinks.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/core/contentlinks/pages/choose-site/choose-site.module.ts b/src/core/contentlinks/pages/choose-site/choose-site.module.ts index 881bde925..ec52dfed5 100644 --- a/src/core/contentlinks/pages/choose-site/choose-site.module.ts +++ b/src/core/contentlinks/pages/choose-site/choose-site.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/core/contentlinks/pages/choose-site/choose-site.ts b/src/core/contentlinks/pages/choose-site/choose-site.ts index b10a36bc7..2506a4551 100644 --- a/src/core/contentlinks/pages/choose-site/choose-site.ts +++ b/src/core/contentlinks/pages/choose-site/choose-site.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -96,7 +96,7 @@ export class CoreContentLinksChooseSitePage implements OnInit { /** * Perform the action on a certain site. * - * @param {string} siteId Site ID. + * @param siteId Site ID. */ siteClicked(siteId: string): void { if (this.isRootURL) { diff --git a/src/core/contentlinks/providers/delegate.ts b/src/core/contentlinks/providers/delegate.ts index b753ac02e..b323f60be 100644 --- a/src/core/contentlinks/providers/delegate.ts +++ b/src/core/contentlinks/providers/delegate.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -25,39 +25,35 @@ import { CoreUtilsProvider } from '@providers/utils/utils'; export interface CoreContentLinksHandler { /** * A name to identify the handler. - * @type {string} */ name: string; /** * Handler's priority. The highest priority is treated first. - * @type {number} */ priority?: number; /** * Whether the isEnabled function should be called for all the users in a site. It should be true only if the isEnabled call * can return different values for different users in same site. - * @type {boolean} */ checkAllUsers?: boolean; /** * Name of the feature this handler is related to. * It will be used to check if the feature is disabled (@see CoreSite.isFeatureDisabled). - * @type {string} */ featureName?: string; /** * Get the list of actions for a link (url). * - * @param {string[]} siteIds List of sites the URL belongs to. - * @param {string} url The URL to treat. - * @param {any} params The params of the URL. E.g. 'mysite.com?id=1' -> {id: 1} - * @param {number} [courseId] Course ID related to the URL. Optional but recommended. - * @param {any} [data] Extra data to handle the URL. - * @return {CoreContentLinksAction[]|Promise} List of (or promise resolved with list of) actions. + * @param siteIds List of sites the URL belongs to. + * @param url The URL to treat. + * @param params The params of the URL. E.g. 'mysite.com?id=1' -> {id: 1} + * @param courseId Course ID related to the URL. Optional but recommended. + * @param data Extra data to handle the URL. + * @return List of (or promise resolved with list of) actions. */ getActions(siteIds: string[], url: string, params: any, courseId?: number, data?: any): CoreContentLinksAction[] | Promise; @@ -65,16 +61,16 @@ export interface CoreContentLinksHandler { /** * Check if a URL is handled by this handler. * - * @param {string} url The URL to check. - * @return {boolean} Whether the URL is handled by this handler + * @param url The URL to check. + * @return Whether the URL is handled by this handler */ handles(url: string): boolean; /** * If the URL is handled by this handler, return the site URL. * - * @param {string} url The URL to check. - * @return {string} Site URL if it is handled, undefined otherwise. + * @param url The URL to check. + * @return Site URL if it is handled, undefined otherwise. */ getSiteUrl(url: string): string; @@ -82,11 +78,11 @@ export interface CoreContentLinksHandler { * Check if the handler is enabled for a certain site (site + user) and a URL. * If not defined, defaults to true. * - * @param {string} siteId The site ID. - * @param {string} url The URL to treat. - * @param {any} params The params of the URL. E.g. 'mysite.com?id=1' -> {id: 1} - * @param {number} [courseId] Course ID related to the URL. Optional but recommended. - * @return {boolean|Promise} Whether the handler is enabled for the URL and site. + * @param siteId The site ID. + * @param url The URL to treat. + * @param params The params of the URL. E.g. 'mysite.com?id=1' -> {id: 1} + * @param courseId Course ID related to the URL. Optional but recommended. + * @return Whether the handler is enabled for the URL and site. */ isEnabled?(siteId: string, url: string, params: any, courseId?: number): boolean | Promise; } @@ -97,27 +93,24 @@ export interface CoreContentLinksHandler { export interface CoreContentLinksAction { /** * A message to identify the action. Default: 'core.view'. - * @type {string} */ message?: string; /** * Name of the icon of the action. Default: 'eye'. - * @type {string} */ icon?: string; /** * IDs of the sites that support the action. - * @type {string[]} */ sites?: string[]; /** * Action to perform when the link is clicked. * - * @param {string} siteId The site ID. - * @param {NavController} [navCtrl] Nav Controller to use to navigate. + * @param siteId The site ID. + * @param navCtrl Nav Controller to use to navigate. */ action(siteId: string, navCtrl?: NavController): void; } @@ -128,13 +121,11 @@ export interface CoreContentLinksAction { export interface CoreContentLinksHandlerActions { /** * Handler's priority. - * @type {number} */ priority: number; /** * List of actions. - * @type {CoreContentLinksAction[]} */ actions: CoreContentLinksAction[]; } @@ -155,11 +146,11 @@ export class CoreContentLinksDelegate { /** * Get the list of possible actions to do for a URL. * - * @param {string} url URL to handle. - * @param {number} [courseId] Course ID related to the URL. Optional but recommended. - * @param {string} [username] Username to use to filter sites. - * @param {any} [data] Extra data to handle the URL. - * @return {Promise} Promise resolved with the actions. + * @param url URL to handle. + * @param courseId Course ID related to the URL. Optional but recommended. + * @param username Username to use to filter sites. + * @param data Extra data to handle the URL. + * @return Promise resolved with the actions. */ getActionsFor(url: string, courseId?: number, username?: string, data?: any): Promise { if (!url) { @@ -220,8 +211,8 @@ export class CoreContentLinksDelegate { /** * Get the site URL if the URL is supported by any handler. * - * @param {string} url URL to handle. - * @return {string} Site URL if the URL is supported by any handler, undefined otherwise. + * @param url URL to handle. + * @return Site URL if the URL is supported by any handler, undefined otherwise. */ getSiteUrl(url: string): string { if (!url) { @@ -242,12 +233,12 @@ export class CoreContentLinksDelegate { /** * Check if a handler is enabled for a certain site and URL. * - * @param {CoreContentLinksHandler} handler Handler to check. - * @param {string} url The URL to check. - * @param {any} params The params of the URL - * @param {number} courseId Course ID the URL belongs to (can be undefined). - * @param {string} siteId The site ID to check. - * @return {Promise} Promise resolved with boolean: whether the handler is enabled. + * @param handler Handler to check. + * @param url The URL to check. + * @param params The params of the URL + * @param courseId Course ID the URL belongs to (can be undefined). + * @param siteId The site ID to check. + * @return Promise resolved with boolean: whether the handler is enabled. */ protected isHandlerEnabled(handler: CoreContentLinksHandler, url: string, params: any, courseId: number, siteId: string) : Promise { @@ -277,8 +268,8 @@ export class CoreContentLinksDelegate { /** * Register a handler. * - * @param {CoreContentLinksHandler} handler The handler to register. - * @return {boolean} True if registered successfully, false otherwise. + * @param handler The handler to register. + * @return True if registered successfully, false otherwise. */ registerHandler(handler: CoreContentLinksHandler): boolean { if (typeof this.handlers[handler.name] !== 'undefined') { @@ -295,8 +286,8 @@ export class CoreContentLinksDelegate { /** * Sort actions by priority. * - * @param {CoreContentLinksHandlerActions[]} actions Actions to sort. - * @return {CoreContentLinksAction[]} Sorted actions. + * @param actions Actions to sort. + * @return Sorted actions. */ protected sortActionsByPriority(actions: CoreContentLinksHandlerActions[]): CoreContentLinksAction[] { let sorted: CoreContentLinksAction[] = []; diff --git a/src/core/contentlinks/providers/helper.ts b/src/core/contentlinks/providers/helper.ts index 6e9f77e91..54bb261c1 100644 --- a/src/core/contentlinks/providers/helper.ts +++ b/src/core/contentlinks/providers/helper.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -51,11 +51,11 @@ export class CoreContentLinksHelperProvider { /** * Check whether a link can be handled by the app. * - * @param {string} url URL to handle. - * @param {number} [courseId] Course ID related to the URL. Optional but recommended. - * @param {string} [username] Username to use to filter sites. - * @param {boolean} [checkRoot] Whether to check if the URL is the root URL of a site. - * @return {Promise} Promise resolved with a boolean: whether the URL can be handled. + * @param url URL to handle. + * @param courseId Course ID related to the URL. Optional but recommended. + * @param username Username to use to filter sites. + * @param checkRoot Whether to check if the URL is the root URL of a site. + * @return Promise resolved with a boolean: whether the URL can be handled. */ canHandleLink(url: string, courseId?: number, username?: string, checkRoot?: boolean): Promise { let promise; @@ -83,8 +83,8 @@ export class CoreContentLinksHelperProvider { /** * Get the first valid action in a list of actions. * - * @param {CoreContentLinksAction[]} actions List of actions. - * @return {CoreContentLinksAction} First valid action. Returns undefined if no valid action found. + * @param actions List of actions. + * @return First valid action. Returns undefined if no valid action found. */ getFirstValidAction(actions: CoreContentLinksAction[]): CoreContentLinksAction { if (actions) { @@ -101,12 +101,12 @@ export class CoreContentLinksHelperProvider { * Goes to a certain page in a certain site. If the site is current site it will perform a regular navigation, * otherwise it will 'redirect' to the other site. * - * @param {NavController} navCtrl The NavController instance to use. - * @param {string} pageName Name of the page to go. - * @param {any} [pageParams] Params to send to the page. - * @param {string} [siteId] Site ID. If not defined, current site. - * @param {boolean} [checkMenu] If true, check if the root page of a main menu tab. Only the page name will be checked. - * @return {Promise} Promise resolved when done. + * @param navCtrl The NavController instance to use. + * @param pageName Name of the page to go. + * @param pageParams Params to send to the page. + * @param siteId Site ID. If not defined, current site. + * @param checkMenu If true, check if the root page of a main menu tab. Only the page name will be checked. + * @return Promise resolved when done. */ goInSite(navCtrl: NavController, pageName: string, pageParams: any, siteId?: string, checkMenu?: boolean): Promise { siteId = siteId || this.sitesProvider.getCurrentSiteId(); @@ -144,7 +144,7 @@ export class CoreContentLinksHelperProvider { /** * Go to the page to choose a site. * - * @param {string} url URL to treat. + * @param url URL to treat. */ goToChooseSite(url: string): void { this.appProvider.getRootNavController().setRoot('CoreContentLinksChooseSitePage', { url: url }); @@ -153,8 +153,8 @@ export class CoreContentLinksHelperProvider { /** * Handle a URL received by Custom URL Scheme. * - * @param {string} url URL to handle. - * @return {boolean} True if the URL should be handled by this component, false otherwise. + * @param url URL to handle. + * @return True if the URL should be handled by this component, false otherwise. * @deprecated Please use CoreCustomURLSchemesProvider.handleCustomURL instead. */ handleCustomUrl(url: string): boolean { @@ -270,13 +270,13 @@ export class CoreContentLinksHelperProvider { /** * Handle a link. * - * @param {string} url URL to handle. - * @param {string} [username] Username related with the URL. E.g. in 'http://myuser@m.com', url would be 'http://m.com' and - * the username 'myuser'. Don't use it if you don't want to filter by username. - * @param {NavController} [navCtrl] Nav Controller to use to navigate. - * @param {boolean} [checkRoot] Whether to check if the URL is the root URL of a site. - * @param {boolean} [openBrowserRoot] Whether to open in browser if it's root URL and it belongs to current site. - * @return {Promise} Promise resolved with a boolean: true if URL was treated, false otherwise. + * @param url URL to handle. + * @param username Username related with the URL. E.g. in 'http://myuser@m.com', url would be 'http://m.com' and + * the username 'myuser'. Don't use it if you don't want to filter by username. + * @param navCtrl Nav Controller to use to navigate. + * @param checkRoot Whether to check if the URL is the root URL of a site. + * @param openBrowserRoot Whether to open in browser if it's root URL and it belongs to current site. + * @return Promise resolved with a boolean: true if URL was treated, false otherwise. */ handleLink(url: string, username?: string, navCtrl?: NavController, checkRoot?: boolean, openBrowserRoot?: boolean) : Promise { @@ -336,11 +336,11 @@ export class CoreContentLinksHelperProvider { /** * Handle a root URL of a site. * - * @param {CoreSite} site Site to handle. - * @param {boolean} [openBrowserRoot] Whether to open in browser if it's root URL and it belongs to current site. - * @param {boolean} [checkToken] Whether to check that token is the same to verify it's current site. If false or not defined, - * only the URL will be checked. - * @return {Promise} Promise resolved when done. + * @param site Site to handle. + * @param openBrowserRoot Whether to open in browser if it's root URL and it belongs to current site. + * @param checkToken Whether to check that token is the same to verify it's current site. If false or not defined, + * only the URL will be checked. + * @return Promise resolved when done. */ handleRootURL(site: CoreSite, openBrowserRoot?: boolean, checkToken?: boolean): Promise { const currentSite = this.sitesProvider.getCurrentSite(); diff --git a/src/core/course/classes/activity-prefetch-handler.ts b/src/core/course/classes/activity-prefetch-handler.ts index 3c0294a93..b655979f6 100644 --- a/src/core/course/classes/activity-prefetch-handler.ts +++ b/src/core/course/classes/activity-prefetch-handler.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -22,12 +22,12 @@ import { CoreCourseModulePrefetchHandlerBase } from './module-prefetch-handler'; * The string returned by this function will be stored as "extra" data in the filepool package. If you don't need to store * extra data, don't return anything. * - * @param {any} module Module. - * @param {number} courseId Course ID the module belongs to. - * @param {boolean} single True if we're downloading a single module, false if we're downloading a whole section. - * @param {string} siteId Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the prefetch finishes. The string returned will be stored as "extra" data in the - * filepool package. If you don't need to store extra data, don't return anything. + * @param module Module. + * @param courseId Course ID the module belongs to. + * @param single True if we're downloading a single module, false if we're downloading a whole section. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the prefetch finishes. The string returned will be stored as "extra" data in the + * filepool package. If you don't need to store extra data, don't return anything. */ export type prefetchFunction = (module: any, courseId: number, single: boolean, siteId: string, ...args: any[]) => Promise; @@ -46,10 +46,10 @@ export class CoreCourseActivityPrefetchHandlerBase extends CoreCourseModulePrefe /** * Download the module. * - * @param {any} module The module object returned by WS. - * @param {number} courseId Course ID. - * @param {string} [dirPath] Path of the directory where to store all the content files. - * @return {Promise} Promise resolved when all content is downloaded. + * @param module The module object returned by WS. + * @param courseId Course ID. + * @param dirPath Path of the directory where to store all the content files. + * @return Promise resolved when all content is downloaded. */ download(module: any, courseId: number, dirPath?: string): Promise { // Same implementation for download and prefetch. @@ -59,11 +59,11 @@ export class CoreCourseActivityPrefetchHandlerBase extends CoreCourseModulePrefe /** * Prefetch a module. * - * @param {any} module Module. - * @param {number} courseId Course ID the module belongs to. - * @param {boolean} [single] True if we're downloading a single module, false if we're downloading a whole section. - * @param {string} [dirPath] Path of the directory where to store all the content files. - * @return {Promise} Promise resolved when done. + * @param module Module. + * @param courseId Course ID the module belongs to. + * @param single True if we're downloading a single module, false if we're downloading a whole section. + * @param dirPath Path of the directory where to store all the content files. + * @return Promise resolved when done. */ prefetch(module: any, courseId?: number, single?: boolean, dirPath?: string): Promise { // To be overridden. It should call prefetchPackage @@ -79,12 +79,12 @@ export class CoreCourseActivityPrefetchHandlerBase extends CoreCourseModulePrefe * Then the function "prefetchModule" will receive params: * prefetchModule(module, courseId, single, siteId, someParam, anotherParam) * - * @param {any} module Module. - * @param {number} courseId Course ID the module belongs to. - * @param {boolean} [single] True if we're downloading a single module, false if we're downloading a whole section. - * @param {prefetchFunction} downloadFn Function to perform the prefetch. Please check the documentation of prefetchFunction. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the module has been downloaded. Data returned is not reliable. + * @param module Module. + * @param courseId Course ID the module belongs to. + * @param single True if we're downloading a single module, false if we're downloading a whole section. + * @param downloadFn Function to perform the prefetch. Please check the documentation of prefetchFunction. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the module has been downloaded. Data returned is not reliable. */ prefetchPackage(module: any, courseId: number, single: boolean, downloadFn: prefetchFunction, siteId?: string, ...args: any[]) : Promise { @@ -101,10 +101,11 @@ export class CoreCourseActivityPrefetchHandlerBase extends CoreCourseModulePrefe } const prefetchPromise = this.setDownloading(module.id, siteId).then(() => { - // Package marked as downloading, get module info to be able to handle links. + // Package marked as downloading, get module info to be able to handle links. Get module filters too. return Promise.all([ this.courseProvider.getModuleBasicInfo(module.id, siteId), this.courseProvider.getModule(module.id, courseId, undefined, false, true, siteId), + this.filterHelper.getFilters('module', module.id, {courseId: courseId}) ]); }).then(() => { // Call the download function, send all the params except downloadFn. This includes all params passed after siteId. @@ -128,10 +129,10 @@ export class CoreCourseActivityPrefetchHandlerBase extends CoreCourseModulePrefe /** * Mark the module as downloaded. * - * @param {number} id Unique identifier per component. - * @param {string} [siteId] Site ID. If not defined, current site. - * @param {string} [extra] Extra data to store. - * @return {Promise} Promise resolved when done. + * @param id Unique identifier per component. + * @param siteId Site ID. If not defined, current site. + * @param extra Extra data to store. + * @return Promise resolved when done. */ setDownloaded(id: number, siteId?: string, extra?: string): Promise { siteId = siteId || this.sitesProvider.getCurrentSiteId(); @@ -142,9 +143,9 @@ export class CoreCourseActivityPrefetchHandlerBase extends CoreCourseModulePrefe /** * Mark the module as downloading. * - * @param {number} id Unique identifier per component. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when done. + * @param id Unique identifier per component. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when done. */ setDownloading(id: number, siteId?: string): Promise { siteId = siteId || this.sitesProvider.getCurrentSiteId(); @@ -155,10 +156,10 @@ export class CoreCourseActivityPrefetchHandlerBase extends CoreCourseModulePrefe /** * Set previous status and return a rejected promise. * - * @param {number} id Unique identifier per component. - * @param {any} [error] Error to return. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Rejected promise. + * @param id Unique identifier per component. + * @param error Error to return. + * @param siteId Site ID. If not defined, current site. + * @return Rejected promise. */ setPreviousStatusAndReject(id: number, error?: any, siteId?: string): Promise { siteId = siteId || this.sitesProvider.getCurrentSiteId(); diff --git a/src/core/course/classes/activity-sync.ts b/src/core/course/classes/activity-sync.ts index 8d7333963..9a6f0595b 100644 --- a/src/core/course/classes/activity-sync.ts +++ b/src/core/course/classes/activity-sync.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -40,11 +40,11 @@ export class CoreCourseActivitySyncBaseProvider extends CoreSyncBaseProvider { /** * Conveniece function to prefetch data after an update. * - * @param {any} module Module. - * @param {number} courseId Course ID. - * @param {RegExp} [regex] If regex matches, don't download the data. Defaults to check files. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when done. + * @param module Module. + * @param courseId Course ID. + * @param regex If regex matches, don't download the data. Defaults to check files. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when done. */ prefetchAfterUpdate(module: any, courseId: number, regex?: RegExp, siteId?: string): Promise { regex = regex || /^.*files$/; diff --git a/src/core/course/classes/main-activity-component.ts b/src/core/course/classes/main-activity-component.ts index 752a140e2..5b2d7f830 100644 --- a/src/core/course/classes/main-activity-component.ts +++ b/src/core/course/classes/main-activity-component.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -93,8 +93,8 @@ export class CoreCourseModuleMainActivityComponent extends CoreCourseModuleMainR /** * Compares sync event data with current data to check if refresh content is needed. * - * @param {any} syncEventData Data received on sync observer. - * @return {boolean} True if refresh is needed, false otherwise. + * @param syncEventData Data received on sync observer. + * @return True if refresh is needed, false otherwise. */ protected isRefreshSyncNeeded(syncEventData: any): boolean { return false; @@ -103,7 +103,7 @@ export class CoreCourseModuleMainActivityComponent extends CoreCourseModuleMainR /** * An autosync event has been received, check if refresh is needed and update the view. * - * @param {any} syncEventData Data receiven on sync observer. + * @param syncEventData Data receiven on sync observer. */ protected autoSyncEventReceived(syncEventData: any): void { if (this.isRefreshSyncNeeded(syncEventData)) { @@ -115,9 +115,9 @@ export class CoreCourseModuleMainActivityComponent extends CoreCourseModuleMainR /** * Perform the refresh content function. * - * @param {boolean} [sync=false] If the refresh needs syncing. - * @param {boolean} [showErrors=false] Wether to show errors to the user or hide them. - * @return {Promise} Resolved when done. + * @param sync If the refresh needs syncing. + * @param showErrors Wether to show errors to the user or hide them. + * @return Resolved when done. */ protected refreshContent(sync: boolean = false, showErrors: boolean = false): Promise { if (!this.module) { @@ -154,9 +154,9 @@ export class CoreCourseModuleMainActivityComponent extends CoreCourseModuleMainR /** * Show loading and perform the load content function. * - * @param {boolean} [sync=false] If the fetch needs syncing. - * @param {boolean} [showErrors=false] Wether to show errors to the user or hide them. - * @return {Promise} Resolved when done. + * @param sync If the fetch needs syncing. + * @param showErrors Wether to show errors to the user or hide them. + * @return Resolved when done. */ protected showLoadingAndFetch(sync: boolean = false, showErrors: boolean = false): Promise { this.refreshIcon = 'spinner'; @@ -173,9 +173,9 @@ export class CoreCourseModuleMainActivityComponent extends CoreCourseModuleMainR /** * Show loading and perform the refresh content function. * - * @param {boolean} [sync=false] If the refresh needs syncing. - * @param {boolean} [showErrors=false] Wether to show errors to the user or hide them. - * @return {Promise} Resolved when done. + * @param sync If the refresh needs syncing. + * @param showErrors Wether to show errors to the user or hide them. + * @return Resolved when done. */ protected showLoadingAndRefresh(sync: boolean = false, showErrors: boolean = false): Promise { this.refreshIcon = 'spinner'; @@ -189,10 +189,10 @@ export class CoreCourseModuleMainActivityComponent extends CoreCourseModuleMainR /** * Download the component contents. * - * @param {boolean} [refresh=false] Whether we're refreshing data. - * @param {boolean} [sync=false] If the refresh needs syncing. - * @param {boolean} [showErrors=false] Wether to show errors to the user or hide them. - * @return {Promise} Promise resolved when done. + * @param refresh Whether we're refreshing data. + * @param sync If the refresh needs syncing. + * @param showErrors Wether to show errors to the user or hide them. + * @return Promise resolved when done. */ protected fetchContent(refresh: boolean = false, sync: boolean = false, showErrors: boolean = false): Promise { return Promise.resolve(); @@ -201,10 +201,10 @@ export class CoreCourseModuleMainActivityComponent extends CoreCourseModuleMainR /** * Loads the component contents and shows the corresponding error. * - * @param {boolean} [refresh=false] Whether we're refreshing data. - * @param {boolean} [sync=false] If the refresh needs syncing. - * @param {boolean} [showErrors=false] Wether to show errors to the user or hide them. - * @return {Promise} Promise resolved when done. + * @param refresh Whether we're refreshing data. + * @param sync If the refresh needs syncing. + * @param showErrors Wether to show errors to the user or hide them. + * @return Promise resolved when done. */ protected loadContent(refresh?: boolean, sync: boolean = false, showErrors: boolean = false): Promise { this.isOnline = this.appProvider.isOnline(); @@ -245,8 +245,8 @@ export class CoreCourseModuleMainActivityComponent extends CoreCourseModuleMainR /** * Displays some data based on the current status. * - * @param {string} status The current status. - * @param {string} [previousStatus] The previous status. If not defined, there is no previous status. + * @param status The current status. + * @param previousStatus The previous status. If not defined, there is no previous status. */ protected showStatus(status: string, previousStatus?: string): void { // To be overridden. @@ -255,7 +255,7 @@ export class CoreCourseModuleMainActivityComponent extends CoreCourseModuleMainR /** * Watch for changes on the status. * - * @return {Promise} Promise resolved when done. + * @return Promise resolved when done. */ protected setStatusListener(): Promise { if (typeof this.statusObserver == 'undefined') { @@ -283,7 +283,7 @@ export class CoreCourseModuleMainActivityComponent extends CoreCourseModuleMainR /** * Performs the sync of the activity. * - * @return {Promise} Promise resolved when done. + * @return Promise resolved when done. */ protected sync(): Promise { return Promise.resolve(true); @@ -292,8 +292,8 @@ export class CoreCourseModuleMainActivityComponent extends CoreCourseModuleMainR /** * Checks if sync has succeed from result sync data. * - * @param {any} result Data returned on the sync function. - * @return {boolean} If suceed or not. + * @param result Data returned on the sync function. + * @return If suceed or not. */ protected hasSyncSucceed(result: any): boolean { return true; @@ -302,8 +302,8 @@ export class CoreCourseModuleMainActivityComponent extends CoreCourseModuleMainR /** * Tries to synchronize the activity. * - * @param {boolean} [showErrors=false] If show errors to the user of hide them. - * @return {Promise} Promise resolved with true if sync succeed, or false if failed. + * @param showErrors If show errors to the user of hide them. + * @return Promise resolved with true if sync succeed, or false if failed. */ protected syncActivity(showErrors: boolean = false): Promise { return this.sync().then((result) => { diff --git a/src/core/course/classes/main-resource-component.ts b/src/core/course/classes/main-resource-component.ts index e357d0888..87691f7d8 100644 --- a/src/core/course/classes/main-resource-component.ts +++ b/src/core/course/classes/main-resource-component.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -99,10 +99,10 @@ export class CoreCourseModuleMainResourceComponent implements OnInit, OnDestroy, /** * Refresh the data. * - * @param {any} [refresher] Refresher. - * @param {Function} [done] Function to call when done. - * @param {boolean} [showErrors=false] If show errors to the user of hide them. - * @return {Promise} Promise resolved when done. + * @param refresher Refresher. + * @param done Function to call when done. + * @param showErrors If show errors to the user of hide them. + * @return Promise resolved when done. */ doRefresh(refresher?: any, done?: () => void, showErrors: boolean = false): Promise { if (this.loaded && this.module) { @@ -129,9 +129,9 @@ export class CoreCourseModuleMainResourceComponent implements OnInit, OnDestroy, /** * Perform the refresh content function. * - * @param {boolean} [sync=false] If the refresh needs syncing. - * @param {boolean} [showErrors=false] Wether to show errors to the user or hide them. - * @return {Promise} Resolved when done. + * @param sync If the refresh needs syncing. + * @param showErrors Wether to show errors to the user or hide them. + * @return Resolved when done. */ protected refreshContent(sync: boolean = false, showErrors: boolean = false): Promise { if (!this.module) { @@ -166,7 +166,7 @@ export class CoreCourseModuleMainResourceComponent implements OnInit, OnDestroy, /** * Perform the invalidate content function. * - * @return {Promise} Resolved when done. + * @return Resolved when done. */ protected invalidateContent(): Promise { return Promise.resolve(); @@ -175,8 +175,8 @@ export class CoreCourseModuleMainResourceComponent implements OnInit, OnDestroy, /** * Download the component contents. * - * @param {boolean} [refresh] Whether we're refreshing data. - * @return {Promise} Promise resolved when done. + * @param refresh Whether we're refreshing data. + * @return Promise resolved when done. */ protected fetchContent(refresh?: boolean): Promise { return Promise.resolve(); @@ -185,8 +185,8 @@ export class CoreCourseModuleMainResourceComponent implements OnInit, OnDestroy, /** * Loads the component contents and shows the corresponding error. * - * @param {boolean} [refresh] Whether we're refreshing data. - * @return {Promise} Promise resolved when done. + * @param refresh Whether we're refreshing data. + * @return Promise resolved when done. */ protected loadContent(refresh?: boolean): Promise { if (!this.module) { @@ -236,13 +236,14 @@ export class CoreCourseModuleMainResourceComponent implements OnInit, OnDestroy, * Expand the description. */ expandDescription(): void { - this.textUtils.expandText(this.translate.instant('core.description'), this.description, this.component, this.module.id); + this.textUtils.expandText(this.translate.instant('core.description'), this.description, this.component, this.module.id, + [], true, 'module', this.module.id, this.courseId); } /** * Go to blog posts. * - * @param {any} event Event. + * @param event Event. */ gotoBlog(event: any): void { this.linkHelper.goInSite(this.navCtrl, 'AddonBlogEntriesPage', { cmId: this.module.id }); @@ -251,7 +252,7 @@ export class CoreCourseModuleMainResourceComponent implements OnInit, OnDestroy, /** * Prefetch the module. * - * @param {Function} [done] Function to call when done. + * @param done Function to call when done. */ prefetch(done?: () => void): void { this.courseHelper.contextMenuPrefetch(this, this.module, this.courseId, done); diff --git a/src/core/course/classes/module-prefetch-handler.ts b/src/core/course/classes/module-prefetch-handler.ts index e079055ed..815cbf5c4 100644 --- a/src/core/course/classes/module-prefetch-handler.ts +++ b/src/core/course/classes/module-prefetch-handler.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -20,6 +20,8 @@ import { CoreDomUtilsProvider } from '@providers/utils/dom'; import { CoreUtilsProvider } from '@providers/utils/utils'; import { CoreCourseProvider } from '../providers/course'; import { CoreCourseModulePrefetchHandler } from '../providers/module-prefetch-delegate'; +import { CoreFilterHelperProvider } from '@core/filter/providers/helper'; +import { CorePluginFileDelegate } from '@providers/plugin-file-delegate'; /** * Base prefetch handler to be registered in CoreCourseModulePrefetchDelegate. Prefetch handlers should inherit either @@ -29,53 +31,53 @@ import { CoreCourseModulePrefetchHandler } from '../providers/module-prefetch-de export class CoreCourseModulePrefetchHandlerBase implements CoreCourseModulePrefetchHandler { /** * Name of the handler. - * @type {string} */ name = 'CoreCourseModulePrefetchHandler'; /** * Name of the module. It should match the "modname" of the module returned in core_course_get_contents. - * @type {string} */ modName = 'default'; /** * The handler's component. - * @type {string} */ component = 'core_module'; /** * The RegExp to check updates. If a module has an update whose name matches this RegExp, the module will be marked * as outdated. This RegExp is ignored if hasUpdates function is defined. - * @type {RegExp} */ updatesNames = /^.*files$/; /** * If true, this module will be ignored when determining the status of a list of modules. The module will * still be downloaded when downloading the section/course, it only affects whether the button should be displayed. - * @type {boolean} */ skipListStatus = false; /** * List of download promises to prevent downloading the module twice at the same time. - * @type {{[s: string]: {[s: string]: Promise}}} */ protected downloadPromises: { [s: string]: { [s: string]: Promise } } = {}; - constructor(protected translate: TranslateService, protected appProvider: CoreAppProvider, protected utils: CoreUtilsProvider, - protected courseProvider: CoreCourseProvider, protected filepoolProvider: CoreFilepoolProvider, - protected sitesProvider: CoreSitesProvider, protected domUtils: CoreDomUtilsProvider) { } + constructor(protected translate: TranslateService, + protected appProvider: CoreAppProvider, + protected utils: CoreUtilsProvider, + protected courseProvider: CoreCourseProvider, + protected filepoolProvider: CoreFilepoolProvider, + protected sitesProvider: CoreSitesProvider, + protected domUtils: CoreDomUtilsProvider, + protected filterHelper: CoreFilterHelperProvider, + protected pluginFileDelegate: CorePluginFileDelegate) { } /** * Add an ongoing download to the downloadPromises list. When the promise finishes it will be removed. * - * @param {number} id Unique identifier per component. - * @param {Promise} promise Promise to add. - * @param {String} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise of the current download. + * @param id Unique identifier per component. + * @param promise Promise to add. + * @param siteId Site ID. If not defined, current site. + * @return Promise of the current download. */ addOngoingDownload(id: number, promise: Promise, siteId?: string): Promise { siteId = siteId || this.sitesProvider.getCurrentSiteId(); @@ -96,10 +98,10 @@ export class CoreCourseModulePrefetchHandlerBase implements CoreCourseModulePref /** * Download the module. * - * @param {any} module The module object returned by WS. - * @param {number} courseId Course ID. - * @param {string} [dirPath] Path of the directory where to store all the content files. - * @return {Promise} Promise resolved when all content is downloaded. + * @param module The module object returned by WS. + * @param courseId Course ID. + * @param dirPath Path of the directory where to store all the content files. + * @return Promise resolved when all content is downloaded. */ download(module: any, courseId: number, dirPath?: string): Promise { // To be overridden. @@ -109,8 +111,8 @@ export class CoreCourseModulePrefetchHandlerBase implements CoreCourseModulePref /** * Returns a list of content files that can be downloaded. * - * @param {any} module The module object returned by WS. - * @return {any[]} List of files. + * @param module The module object returned by WS. + * @return List of files. */ getContentDownloadableFiles(module: any): any[] { const files = []; @@ -129,15 +131,15 @@ export class CoreCourseModulePrefetchHandlerBase implements CoreCourseModulePref /** * Get the download size of a module. * - * @param {any} module Module. - * @param {Number} courseId Course ID the module belongs to. - * @param {boolean} [single] True if we're downloading a single module, false if we're downloading a whole section. - * @return {Promise<{size: number, total: boolean}>} Promise resolved with the size and a boolean indicating if it was able - * to calculate the total size. + * @param module Module. + * @param courseId Course ID the module belongs to. + * @param single True if we're downloading a single module, false if we're downloading a whole section. + * @return Promise resolved with the size and a boolean indicating if it was able + * to calculate the total size. */ getDownloadSize(module: any, courseId: number, single?: boolean): Promise<{ size: number, total: boolean }> { return this.getFiles(module, courseId).then((files) => { - return this.utils.sumFileSizes(files); + return this.pluginFileDelegate.getFilesSize(files); }).catch(() => { return { size: -1, total: false }; }); @@ -146,9 +148,9 @@ export class CoreCourseModulePrefetchHandlerBase implements CoreCourseModulePref /** * Get the downloaded size of a module. If not defined, we'll use getFiles to calculate it (it can be slow). * - * @param {any} module Module. - * @param {number} courseId Course ID the module belongs to. - * @return {number|Promise} Size, or promise resolved with the size. + * @param module Module. + * @param courseId Course ID the module belongs to. + * @return Size, or promise resolved with the size. */ getDownloadedSize(module: any, courseId: number): number | Promise { const siteId = this.sitesProvider.getCurrentSiteId(); @@ -159,10 +161,10 @@ export class CoreCourseModulePrefetchHandlerBase implements CoreCourseModulePref /** * Get list of files. If not defined, we'll assume they're in module.contents. * - * @param {any} module Module. - * @param {Number} courseId Course ID the module belongs to. - * @param {boolean} [single] True if we're downloading a single module, false if we're downloading a whole section. - * @return {Promise} Promise resolved with the list of files. + * @param module Module. + * @param courseId Course ID the module belongs to. + * @param single True if we're downloading a single module, false if we're downloading a whole section. + * @return Promise resolved with the list of files. */ getFiles(module: any, courseId: number, single?: boolean): Promise { // To be overridden. @@ -172,10 +174,10 @@ export class CoreCourseModulePrefetchHandlerBase implements CoreCourseModulePref /** * Returns module intro files. * - * @param {any} module The module object returned by WS. - * @param {number} courseId Course ID. - * @param {boolean} [ignoreCache] True if it should ignore cached data (it will always fail in offline or server down). - * @return {Promise} Promise resolved with list of intro files. + * @param module The module object returned by WS. + * @param courseId Course ID. + * @param ignoreCache True if it should ignore cached data (it will always fail in offline or server down). + * @return Promise resolved with list of intro files. */ getIntroFiles(module: any, courseId: number, ignoreCache?: boolean): Promise { return Promise.resolve(this.getIntroFilesFromInstance(module)); @@ -184,21 +186,21 @@ export class CoreCourseModulePrefetchHandlerBase implements CoreCourseModulePref /** * Returns module intro files from instance. * - * @param {any} module The module object returned by WS. - * @param {any} [instance] The instance to get the intro files (book, assign, ...). If not defined, module will be used. - * @return {any[]} List of intro files. + * @param module The module object returned by WS. + * @param instance The instance to get the intro files (book, assign, ...). If not defined, module will be used. + * @return List of intro files. */ getIntroFilesFromInstance(module: any, instance?: any): any[] { if (instance) { if (typeof instance.introfiles != 'undefined') { return instance.introfiles; } else if (instance.intro) { - return this.domUtils.extractDownloadableFilesFromHtmlAsFakeFileObjects(instance.intro); + return this.filepoolProvider.extractDownloadableFilesFromHtmlAsFakeFileObjects(instance.intro); } } if (module.description) { - return this.domUtils.extractDownloadableFilesFromHtmlAsFakeFileObjects(module.description); + return this.filepoolProvider.extractDownloadableFilesFromHtmlAsFakeFileObjects(module.description); } return []; @@ -207,9 +209,9 @@ export class CoreCourseModulePrefetchHandlerBase implements CoreCourseModulePref /** * If there's an ongoing download for a certain identifier return it. * - * @param {number} id Unique identifier per component. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise of the current download. + * @param id Unique identifier per component. + * @param siteId Site ID. If not defined, current site. + * @return Promise of the current download. */ getOngoingDownload(id: number, siteId?: string): Promise { siteId = siteId || this.sitesProvider.getCurrentSiteId(); @@ -225,8 +227,8 @@ export class CoreCourseModulePrefetchHandlerBase implements CoreCourseModulePref /** * Create unique identifier using component and id. * - * @param {number} id Unique ID inside component. - * @return {string} Unique ID. + * @param id Unique ID inside component. + * @return Unique ID. */ getUniqueId(id: number): string { return this.component + '#' + id; @@ -235,9 +237,9 @@ export class CoreCourseModulePrefetchHandlerBase implements CoreCourseModulePref /** * Invalidate the prefetched content. * - * @param {number} moduleId The module ID. - * @param {number} courseId The course ID the module belongs to. - * @return {Promise} Promise resolved when the data is invalidated. + * @param moduleId The module ID. + * @param courseId The course ID the module belongs to. + * @return Promise resolved when the data is invalidated. */ invalidateContent(moduleId: number, courseId: number): Promise { // To be overridden. @@ -248,9 +250,9 @@ export class CoreCourseModulePrefetchHandlerBase implements CoreCourseModulePref * Invalidate WS calls needed to determine module status (usually, to check if module is downloadable). * It doesn't need to invalidate check updates. It should NOT invalidate files nor all the prefetched data. * - * @param {any} module Module. - * @param {number} courseId Course ID the module belongs to. - * @return {Promise} Promise resolved when invalidated. + * @param module Module. + * @param courseId Course ID the module belongs to. + * @return Promise resolved when invalidated. */ invalidateModule(module: any, courseId: number): Promise { return this.courseProvider.invalidateModule(module.id); @@ -259,9 +261,9 @@ export class CoreCourseModulePrefetchHandlerBase implements CoreCourseModulePref /** * Check if a module can be downloaded. If the function is not defined, we assume that all modules are downloadable. * - * @param {any} module Module. - * @param {number} courseId Course ID the module belongs to. - * @return {boolean|Promise} Whether the module can be downloaded. The promise should never be rejected. + * @param module Module. + * @param courseId Course ID the module belongs to. + * @return Whether the module can be downloaded. The promise should never be rejected. */ isDownloadable(module: any, courseId: number): boolean | Promise { // By default, mark all instances as downloadable. @@ -271,9 +273,9 @@ export class CoreCourseModulePrefetchHandlerBase implements CoreCourseModulePref /** * Check if a there's an ongoing download for the given identifier. * - * @param {number} id Unique identifier per component. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {boolean} True if downloading, false otherwise. + * @param id Unique identifier per component. + * @param siteId Site ID. If not defined, current site. + * @return True if downloading, false otherwise. */ isDownloading(id: number, siteId?: string): boolean { siteId = siteId || this.sitesProvider.getCurrentSiteId(); @@ -284,7 +286,7 @@ export class CoreCourseModulePrefetchHandlerBase implements CoreCourseModulePref /** * Whether or not the handler is enabled on a site level. * - * @return {boolean|Promise} A boolean, or a promise resolved with a boolean, indicating if the handler is enabled. + * @return A boolean, or a promise resolved with a boolean, indicating if the handler is enabled. */ isEnabled(): boolean | Promise { return true; @@ -293,8 +295,8 @@ export class CoreCourseModulePrefetchHandlerBase implements CoreCourseModulePref /** * Check if a file is downloadable. * - * @param {any} file File to check. - * @return {boolean} Whether the file is downloadable. + * @param file File to check. + * @return Whether the file is downloadable. */ isFileDownloadable(file: any): boolean { return file.type === 'file'; @@ -303,10 +305,10 @@ export class CoreCourseModulePrefetchHandlerBase implements CoreCourseModulePref /** * Load module contents into module.contents if they aren't loaded already. * - * @param {any} module Module to load the contents. - * @param {number} [courseId] The course ID. Recommended to speed up the process and minimize data usage. - * @param {boolean} [ignoreCache] True if it should ignore cached data (it will always fail in offline or server down). - * @return {Promise} Promise resolved when loaded. + * @param module Module to load the contents. + * @param courseId The course ID. Recommended to speed up the process and minimize data usage. + * @param ignoreCache True if it should ignore cached data (it will always fail in offline or server down). + * @return Promise resolved when loaded. */ loadContents(module: any, courseId: number, ignoreCache?: boolean): Promise { // To be overridden. @@ -316,11 +318,11 @@ export class CoreCourseModulePrefetchHandlerBase implements CoreCourseModulePref /** * Prefetch a module. * - * @param {any} module Module. - * @param {number} courseId Course ID the module belongs to. - * @param {boolean} [single] True if we're downloading a single module, false if we're downloading a whole section. - * @param {string} [dirPath] Path of the directory where to store all the content files. - * @return {Promise} Promise resolved when done. + * @param module Module. + * @param courseId Course ID the module belongs to. + * @param single True if we're downloading a single module, false if we're downloading a whole section. + * @param dirPath Path of the directory where to store all the content files. + * @return Promise resolved when done. */ prefetch(module: any, courseId?: number, single?: boolean, dirPath?: string): Promise { // To be overridden. @@ -330,9 +332,9 @@ export class CoreCourseModulePrefetchHandlerBase implements CoreCourseModulePref /** * Remove module downloaded files. If not defined, we'll use getFiles to remove them (slow). * - * @param {any} module Module. - * @param {number} courseId Course ID the module belongs to. - * @return {Promise} Promise resolved when done. + * @param module Module. + * @param courseId Course ID the module belongs to. + * @return Promise resolved when done. */ removeFiles(module: any, courseId: number): Promise { return this.filepoolProvider.removeFilesByComponent(this.sitesProvider.getCurrentSiteId(), this.component, module.id); diff --git a/src/core/course/classes/resource-prefetch-handler.ts b/src/core/course/classes/resource-prefetch-handler.ts index f7e7386d3..db7a3a568 100644 --- a/src/core/course/classes/resource-prefetch-handler.ts +++ b/src/core/course/classes/resource-prefetch-handler.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -28,10 +28,10 @@ export class CoreCourseResourcePrefetchHandlerBase extends CoreCourseModulePrefe /** * Download the module. * - * @param {any} module The module object returned by WS. - * @param {number} courseId Course ID. - * @param {string} [dirPath] Path of the directory where to store all the content files. - * @return {Promise} Promise resolved when all content is downloaded. + * @param module The module object returned by WS. + * @param courseId Course ID. + * @param dirPath Path of the directory where to store all the content files. + * @return Promise resolved when all content is downloaded. */ download(module: any, courseId: number, dirPath?: string): Promise { return this.downloadOrPrefetch(module, courseId, false, dirPath); @@ -40,13 +40,13 @@ export class CoreCourseResourcePrefetchHandlerBase extends CoreCourseModulePrefe /** * Download or prefetch the content. * - * @param {any} module The module object returned by WS. - * @param {number} courseId Course ID. - * @param {boolean} [prefetch] True to prefetch, false to download right away. - * @param {string} [dirPath] Path of the directory where to store all the content files. This is to keep the files - * relative paths and make the package work in an iframe. Undefined to download the files - * in the filepool root folder. - * @return {Promise} Promise resolved when all content is downloaded. Data returned is not reliable. + * @param module The module object returned by WS. + * @param courseId Course ID. + * @param prefetch True to prefetch, false to download right away. + * @param dirPath Path of the directory where to store all the content files. This is to keep the files + * relative paths and make the package work in an iframe. Undefined to download the files + * in the filepool root folder. + * @return Promise resolved when all content is downloaded. Data returned is not reliable. */ downloadOrPrefetch(module: any, courseId: number, prefetch?: boolean, dirPath?: string): Promise { if (!this.appProvider.isOnline()) { @@ -87,6 +87,8 @@ export class CoreCourseResourcePrefetchHandlerBase extends CoreCourseModulePrefe promises.push(downloadFn(siteId, files, this.component, module.id)); } + promises.push(this.filterHelper.getFilters('module', module.id, {courseId: courseId})); + return Promise.all(promises); }); @@ -96,10 +98,10 @@ export class CoreCourseResourcePrefetchHandlerBase extends CoreCourseModulePrefe /** * Get list of files. If not defined, we'll assume they're in module.contents. * - * @param {any} module Module. - * @param {Number} courseId Course ID the module belongs to. - * @param {boolean} [single] True if we're downloading a single module, false if we're downloading a whole section. - * @return {Promise} Promise resolved with the list of files. + * @param module Module. + * @param courseId Course ID the module belongs to. + * @param single True if we're downloading a single module, false if we're downloading a whole section. + * @return Promise resolved with the list of files. */ getFiles(module: any, courseId: number, single?: boolean): Promise { // Load module contents if needed. @@ -113,9 +115,9 @@ export class CoreCourseResourcePrefetchHandlerBase extends CoreCourseModulePrefe /** * Invalidate the prefetched content. * - * @param {number} moduleId The module ID. - * @param {number} courseId The course ID the module belongs to. - * @return {Promise} Promise resolved when the data is invalidated. + * @param moduleId The module ID. + * @param courseId The course ID the module belongs to. + * @return Promise resolved when the data is invalidated. */ invalidateContent(moduleId: number, courseId: number): Promise { const promises = [], @@ -130,10 +132,10 @@ export class CoreCourseResourcePrefetchHandlerBase extends CoreCourseModulePrefe /** * Load module contents into module.contents if they aren't loaded already. * - * @param {any} module Module to load the contents. - * @param {number} [courseId] The course ID. Recommended to speed up the process and minimize data usage. - * @param {boolean} [ignoreCache] True if it should ignore cached data (it will always fail in offline or server down). - * @return {Promise} Promise resolved when loaded. + * @param module Module to load the contents. + * @param courseId The course ID. Recommended to speed up the process and minimize data usage. + * @param ignoreCache True if it should ignore cached data (it will always fail in offline or server down). + * @return Promise resolved when loaded. */ loadContents(module: any, courseId: number, ignoreCache?: boolean): Promise { return this.courseProvider.loadModuleContents(module, courseId, undefined, false, ignoreCache); @@ -142,11 +144,11 @@ export class CoreCourseResourcePrefetchHandlerBase extends CoreCourseModulePrefe /** * Prefetch a module. * - * @param {any} module Module. - * @param {number} courseId Course ID the module belongs to. - * @param {boolean} [single] True if we're downloading a single module, false if we're downloading a whole section. - * @param {string} [dirPath] Path of the directory where to store all the content files. - * @return {Promise} Promise resolved when done. + * @param module Module. + * @param courseId Course ID the module belongs to. + * @param single True if we're downloading a single module, false if we're downloading a whole section. + * @param dirPath Path of the directory where to store all the content files. + * @return Promise resolved when done. */ prefetch(module: any, courseId?: number, single?: boolean, dirPath?: string): Promise { return this.downloadOrPrefetch(module, courseId, true, dirPath); diff --git a/src/core/course/components/components.module.ts b/src/core/course/components/components.module.ts index 1f09dcd6f..07af875ec 100644 --- a/src/core/course/components/components.module.ts +++ b/src/core/course/components/components.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/core/course/components/format/core-course-format.html b/src/core/course/components/format/core-course-format.html index c7c3bf6ed..453928f72 100644 --- a/src/core/course/components/format/core-course-format.html +++ b/src/core/course/components/format/core-course-format.html @@ -11,10 +11,10 @@ -
+
@@ -55,13 +55,13 @@
- + @@ -74,13 +74,13 @@
- + - + diff --git a/src/core/course/components/format/format.scss b/src/core/course/components/format/format.scss index 75426f095..dfbbc4033 100644 --- a/src/core/course/components/format/format.scss +++ b/src/core/course/components/format/format.scss @@ -6,13 +6,6 @@ ion-app.app-root ion-badge.core-course-download-section-progress { ion-app.app-root core-course-format { - button.core-button-select .core-section-selector-text { - overflow: hidden; - text-overflow: ellipsis; - line-height: 2em; - white-space: nowrap; - } - .core-format-progress-list { margin-bottom: 0; @@ -71,6 +64,10 @@ ion-app.app-root core-course-format { @include padding(null, 0, null, null); } + .core-button-selector-row { + @include safe-area-padding-start($content-padding !important, $content-padding); + } + .core-course-section-nav-buttons { .button-inner core-format-text { white-space: nowrap; diff --git a/src/core/course/components/format/format.ts b/src/core/course/components/format/format.ts index 8455d4487..a439bc728 100644 --- a/src/core/course/components/format/format.ts +++ b/src/core/course/components/format/format.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -271,7 +271,7 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy { /** * Display the section selector modal. * - * @param {MouseEvent} event Event. + * @param event Event. */ showSectionSelector(event: MouseEvent): void { if (!this.sectionSelectorExpanded) { @@ -296,7 +296,7 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy { /** * Function called when selected section changes. * - * @param {any} newSection The new selected section. + * @param newSection The new selected section. */ sectionChanged(newSection: any): void { const previousValue = this.selectedSection; @@ -351,9 +351,9 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy { /** * Compare if two sections are equal. * - * @param {any} s1 First section. - * @param {any} s2 Second section. - * @return {boolean} Whether they're equal. + * @param s1 First section. + * @param s2 Second section. + * @return Whether they're equal. */ compareSections(s1: any, s2: any): boolean { return s1 && s2 ? s1.id === s2.id : s1 === s2; @@ -362,7 +362,7 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy { /** * Calculate the status of sections. * - * @param {boolean} refresh If refresh or not. + * @param refresh If refresh or not. */ protected calculateSectionsStatus(refresh?: boolean): void { this.courseHelper.calculateSectionsStatus(this.sections, this.course.id, refresh).catch(() => { @@ -373,8 +373,8 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy { /** * Confirm and prefetch a section. If the section is "all sections", prefetch all the sections. * - * @param {any} section Section to download. - * @param {boolean} refresh Refresh clicked (not used). + * @param section Section to download. + * @param refresh Refresh clicked (not used). */ prefetch(section: any, refresh: boolean = false): void { section.isCalculating = true; @@ -393,9 +393,9 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy { /** * Prefetch a section. * - * @param {any} section The section to download. - * @param {boolean} [manual] Whether the prefetch was started manually or it was automatically started because all modules - * are being downloaded. + * @param section The section to download. + * @param manual Whether the prefetch was started manually or it was automatically started because all modules + * are being downloaded. */ protected prefetchSection(section: any, manual?: boolean): void { this.courseHelper.prefetchSection(section, this.course.id, this.sections).catch((error) => { @@ -411,10 +411,10 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy { /** * Refresh the data. * - * @param {any} [refresher] Refresher. - * @param {Function} [done] Function to call when done. - * @param {boolean} [afterCompletionChange] Whether the refresh is due to a completion change. - * @return {Promise} Promise resolved when done. + * @param refresher Refresher. + * @param done Function to call when done. + * @param afterCompletionChange Whether the refresh is due to a completion change. + * @return Promise resolved when done. */ doRefresh(refresher?: any, done?: () => void, afterCompletionChange?: boolean): Promise { const promises = []; @@ -433,7 +433,7 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy { /** * Show more activities (only used when showing all the sections at the same time). * - * @param {any} [infiniteComplete] Infinite scroll complete function. Only used from core-infinite-loading. + * @param infiniteComplete Infinite scroll complete function. Only used from core-infinite-loading. */ showMoreActivities(infiniteComplete?: any): void { this.canLoadMore = false; @@ -508,8 +508,8 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy { /** * Check whether a section can be viewed. * - * @param {any} section The section to check. - * @return {boolean} Whether the section can be viewed. + * @param section The section to check. + * @return Whether the section can be viewed. */ canViewSection(section: any): boolean { return section.uservisible !== false && !section.hiddenbynumsections && @@ -543,7 +543,7 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy { /** * Recalculate the download status of each section, in response to a module being downloaded. * - * @param {any} eventData + * @param eventData */ onModuleStatusChange(eventData: any): void { this.courseHelper.calculateSectionsStatus(this.sections, this.course.id, false, false); diff --git a/src/core/course/components/module-completion/module-completion.ts b/src/core/course/components/module-completion/module-completion.ts index 73efcc86f..08d979f40 100644 --- a/src/core/course/components/module-completion/module-completion.ts +++ b/src/core/course/components/module-completion/module-completion.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -15,9 +15,9 @@ import { Component, Input, Output, EventEmitter, OnChanges, SimpleChange } from '@angular/core'; import { TranslateService } from '@ngx-translate/core'; import { CoreDomUtilsProvider } from '@providers/utils/dom'; -import { CoreTextUtilsProvider } from '@providers/utils/text'; import { CoreUserProvider } from '@core/user/providers/user'; import { CoreCourseProvider } from '../../providers/course'; +import { CoreFilterHelperProvider } from '@core/filter/providers/helper'; /** * Component to handle activity completion. It shows a checkbox with the current status, and allows manually changing @@ -34,13 +34,14 @@ import { CoreCourseProvider } from '../../providers/course'; }) export class CoreCourseModuleCompletionComponent implements OnChanges { @Input() completion: any; // The completion status. + @Input() moduleId?: number; // The name of the module this completion affects. @Input() moduleName?: string; // The name of the module this completion affects. @Output() completionChanged?: EventEmitter; // Will emit an event when the completion changes. completionImage: string; completionDescription: string; - constructor(private textUtils: CoreTextUtilsProvider, private domUtils: CoreDomUtilsProvider, + constructor(private filterHelper: CoreFilterHelperProvider, private domUtils: CoreDomUtilsProvider, private translate: TranslateService, private courseProvider: CoreCourseProvider, private userProvider: CoreUserProvider) { this.completionChanged = new EventEmitter(); @@ -58,7 +59,7 @@ export class CoreCourseModuleCompletionComponent implements OnChanges { /** * Completion clicked. * - * @param {Event} e The click event. + * @param e The click event. */ completionClicked(e: Event): void { if (this.completion) { @@ -137,7 +138,9 @@ export class CoreCourseModuleCompletionComponent implements OnChanges { } if (moduleName) { - this.textUtils.formatText(moduleName, true, true, 50).then((modNameFormatted) => { + this.filterHelper.getFiltersAndFormatText(moduleName, 'module', this.moduleId, + {clean: true, singleLine: true, shortenLength: 50, courseId: this.completion.courseId}).then((result) => { + let promise; if (this.completion.overrideby > 0) { @@ -147,11 +150,11 @@ export class CoreCourseModuleCompletionComponent implements OnChanges { (profile) => { return { overrideuser: profile.fullname, - modname: modNameFormatted + modname: result.text }; }); } else { - promise = Promise.resolve(modNameFormatted); + promise = Promise.resolve(result.text); } return promise.then((translateParams) => { diff --git a/src/core/course/components/module-description/core-course-module-description.html b/src/core/course/components/module-description/core-course-module-description.html index 412d5bf3b..7cb12666b 100644 --- a/src/core/course/components/module-description/core-course-module-description.html +++ b/src/core/course/components/module-description/core-course-module-description.html @@ -1,6 +1,6 @@ - + {{ note }} diff --git a/src/core/course/components/module-description/course-module-description.scss b/src/core/course/components/module-description/course-module-description.scss new file mode 100644 index 000000000..e34a0f102 --- /dev/null +++ b/src/core/course/components/module-description/course-module-description.scss @@ -0,0 +1,16 @@ +ion-app.app-root { + .safe-area-page, + .safe-padding-horizontal { + core-course-module-description { + padding-left: 0 !important; + padding-right: 0 !important; + .item-ios.item-block { + @include safe-area-padding-horizontal($item-ios-padding-end / 2, null); + + .item-inner { + @include safe-area-padding-horizontal(null, $item-ios-padding-end / 2); + } + } + } + } +} \ No newline at end of file diff --git a/src/core/course/components/module-description/module-description.ts b/src/core/course/components/module-description/module-description.ts index 5e650c389..c17f51609 100644 --- a/src/core/course/components/module-description/module-description.ts +++ b/src/core/course/components/module-description/module-description.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -40,6 +40,9 @@ export class CoreCourseModuleDescriptionComponent { @Input() component?: string; // Component for format text directive. @Input() componentId?: string | number; // Component ID to use in conjunction with the component. @Input() showFull?: string | boolean; // Whether to always display the full description. + @Input() contextLevel?: string; // The context level. + @Input() contextInstanceId?: number; // The instance ID related to the context. + @Input() courseId?: number; // Course ID the text belongs to. It can be used to improve performance with filters. constructor() { // Nothing to do. diff --git a/src/core/course/components/module/core-course-module.html b/src/core/course/components/module/core-course-module.html index eae6da52c..39358083f 100644 --- a/src/core/course/components/module/core-course-module.html +++ b/src/core/course/components/module/core-course-module.html @@ -1,13 +1,13 @@ - +
- +
- +
@@ -21,15 +21,20 @@
- + {{ 'core.course.hiddenfromstudents' | translate }} {{ 'core.course.hiddenoncoursepage' | translate }}
{{ 'core.restricted' | translate }} - +
{{ 'core.course.manualcompletionnotsynced' | translate }}
- - \ No newline at end of file + + + + + + + diff --git a/src/core/course/components/module/module.scss b/src/core/course/components/module/module.scss index c08daa491..1f1af0389 100644 --- a/src/core/course/components/module/module.scss +++ b/src/core/course/components/module/module.scss @@ -15,6 +15,20 @@ ion-app.app-root core-course-module { .core-module-icon { align-items: flex-start; } + + cursor: pointer; + &.item-ios:active, + &.item-ios.activated { + background-color: $list-ios-activated-background-color; + } + &.item-md:active, + &.item-md.activated { + background-color: $list-md-activated-background-color; + } + &.item-wp:active, + &.item-wp.activated { + background-color: $list-wp-activated-background-color; + } } .core-module-title { @@ -64,9 +78,42 @@ ion-app.app-root core-course-module { } } - .core-not-clickable:active, - .core-not-clickable.activated { - background-color: $list-background-color; + .core-not-clickable { + cursor: initial; + + &:active, + &.activated { + background-color: $list-background-color; + } + } + + .core-module-loading { + width: 100%; + text-align: center; + padding-top: 10px; + clear: both; + @include darkmode() { + color: $core-dark-text-color; + } + } + + @include darkmode() { + .item.core-course-module-handler { + background: $core-dark-item-bg-color; + &.item-ios:active, + &.item-ios.activated, + &.item-md:active, + &.item-md.activated, + &.item-wp:active, + &.item-wp.activated { + background-color: $core-dark-background-color; + } + } + + .core-not-clickable:active, + .core-not-clickable.activated { + background-color: $core-dark-item-bg-color; + } } } diff --git a/src/core/course/components/module/module.ts b/src/core/course/components/module/module.ts index 3bdb1a898..3ae0be2bc 100644 --- a/src/core/course/components/module/module.ts +++ b/src/core/course/components/module/module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -111,7 +111,7 @@ export class CoreCourseModuleComponent implements OnInit, OnDestroy { /** * Function called when the module is clicked. * - * @param {Event} event Click event. + * @param event Click event. */ moduleClicked(event: Event): void { if (this.module.uservisible !== false && this.module.handlerData.action) { @@ -122,8 +122,8 @@ export class CoreCourseModuleComponent implements OnInit, OnDestroy { /** * Function called when a button is clicked. * - * @param {Event} event Click event. - * @param {CoreCourseModuleHandlerButton} button The clicked button. + * @param event Click event. + * @param button The clicked button. */ buttonClicked(event: Event, button: CoreCourseModuleHandlerButton): void { if (button && button.action) { @@ -137,7 +137,7 @@ export class CoreCourseModuleComponent implements OnInit, OnDestroy { /** * Download the module. * - * @param {boolean} refresh Whether it's refreshing. + * @param refresh Whether it's refreshing. */ download(refresh: boolean): void { if (!this.prefetchHandler) { @@ -169,7 +169,7 @@ export class CoreCourseModuleComponent implements OnInit, OnDestroy { /** * Show download buttons according to module status. * - * @param {string} status Module status. + * @param status Module status. */ protected showStatus(status: string): void { if (status) { diff --git a/src/core/course/components/tag-area/tag-area.ts b/src/core/course/components/tag-area/tag-area.ts index 07d34c21c..e60925d3a 100644 --- a/src/core/course/components/tag-area/tag-area.ts +++ b/src/core/course/components/tag-area/tag-area.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -33,7 +33,7 @@ export class CoreCourseTagAreaComponent { /** * Open a course. * - * @param {number} courseId The course to open. + * @param courseId The course to open. */ openCourse(courseId: number): void { // If this component is inside a split view, use the master nav to open it. diff --git a/src/core/course/components/unsupported-module/core-course-unsupported-module.html b/src/core/course/components/unsupported-module/core-course-unsupported-module.html index 3738ade00..e28fe05c8 100644 --- a/src/core/course/components/unsupported-module/core-course-unsupported-module.html +++ b/src/core/course/components/unsupported-module/core-course-unsupported-module.html @@ -1,5 +1,5 @@
- +

{{ 'core.whoops' | translate }}

{{ 'core.uhoh' | translate }}

diff --git a/src/core/course/components/unsupported-module/unsupported-module.ts b/src/core/course/components/unsupported-module/unsupported-module.ts index 4c655e857..6a0cfdd2c 100644 --- a/src/core/course/components/unsupported-module/unsupported-module.ts +++ b/src/core/course/components/unsupported-module/unsupported-module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -24,7 +24,7 @@ import { CoreCourseModuleDelegate } from '../../providers/module-delegate'; templateUrl: 'core-course-unsupported-module.html', }) export class CoreCourseUnsupportedModuleComponent implements OnInit { - @Input() course: any; // The course to module belongs to. + @Input() courseId: number; // The course to module belongs to. @Input() module: any; // The module to render. isDisabledInSite: boolean; diff --git a/src/core/course/course.module.ts b/src/core/course/course.module.ts index 52a686a40..3ac31d675 100644 --- a/src/core/course/course.module.ts +++ b/src/core/course/course.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/core/course/directives/directives.module.ts b/src/core/course/directives/directives.module.ts index 9b43d41cc..180635253 100644 --- a/src/core/course/directives/directives.module.ts +++ b/src/core/course/directives/directives.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/core/course/directives/download-module-main-file.ts b/src/core/course/directives/download-module-main-file.ts index e562a3216..915e3a771 100644 --- a/src/core/course/directives/download-module-main-file.ts +++ b/src/core/course/directives/download-module-main-file.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/core/course/formats/singleactivity/components/singleactivity.ts b/src/core/course/formats/singleactivity/components/singleactivity.ts index fb2a21924..c0dc391dc 100644 --- a/src/core/course/formats/singleactivity/components/singleactivity.ts +++ b/src/core/course/formats/singleactivity/components/singleactivity.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -64,10 +64,10 @@ export class CoreCourseFormatSingleActivityComponent implements OnChanges { /** * Refresh the data. * - * @param {any} [refresher] Refresher. - * @param {Function} [done] Function to call when done. - * @param {boolean} [afterCompletionChange] Whether the refresh is due to a completion change. - * @return {Promise} Promise resolved when done. + * @param refresher Refresher. + * @param done Function to call when done. + * @param afterCompletionChange Whether the refresh is due to a completion change. + * @return Promise resolved when done. */ doRefresh(refresher?: any, done?: () => void, afterCompletionChange?: boolean): Promise { if (afterCompletionChange) { diff --git a/src/core/course/formats/singleactivity/providers/handler.ts b/src/core/course/formats/singleactivity/providers/handler.ts index 328ae5ee6..c9edd07db 100644 --- a/src/core/course/formats/singleactivity/providers/handler.ts +++ b/src/core/course/formats/singleactivity/providers/handler.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -32,7 +32,7 @@ export class CoreCourseFormatSingleActivityHandler implements CoreCourseFormatHa /** * Whether or not the handler is enabled on a site level. * - * @return {boolean|Promise} True or promise resolved with true if enabled. + * @return True or promise resolved with true if enabled. */ isEnabled(): boolean | Promise { return true; @@ -41,8 +41,8 @@ export class CoreCourseFormatSingleActivityHandler implements CoreCourseFormatHa /** * Whether it allows seeing all sections at the same time. Defaults to true. * - * @param {any} course The course to check. - * @type {boolean} Whether it can view all sections. + * @param course The course to check. + * @return Whether it can view all sections. */ canViewAllSections(course: any): boolean { return false; @@ -52,9 +52,9 @@ export class CoreCourseFormatSingleActivityHandler implements CoreCourseFormatHa * Get the title to use in course page. If not defined, course displayname or fullname. * This function will be called without sections first, and then call it again when the sections are retrieved. * - * @param {any} course The course. - * @param {any[]} [sections] List of sections. - * @return {string} Title. + * @param course The course. + * @param sections List of sections. + * @return Title. */ getCourseTitle(course: any, sections?: any[]): string { if (sections && sections[0] && sections[0].modules && sections[0].modules[0]) { @@ -73,8 +73,8 @@ export class CoreCourseFormatSingleActivityHandler implements CoreCourseFormatHa /** * Whether the option to enable section/module download should be displayed. Defaults to true. * - * @param {any} course The course to check. - * @return {boolean} Whether the option to enable section/module download should be displayed + * @param course The course to check. + * @return Whether the option to enable section/module download should be displayed */ displayEnableDownload(course: any): boolean { return false; @@ -83,8 +83,8 @@ export class CoreCourseFormatSingleActivityHandler implements CoreCourseFormatHa /** * Whether the default section selector should be displayed. Defaults to true. * - * @param {any} course The course to check. - * @type {boolean} Whether the default section selector should be displayed. + * @param course The course to check. + * @return Whether the default section selector should be displayed. */ displaySectionSelector(course: any): boolean { return false; @@ -94,9 +94,9 @@ export class CoreCourseFormatSingleActivityHandler implements CoreCourseFormatHa * Whether the course refresher should be displayed. If it returns false, a refresher must be included in the course format, * and the doRefresh method of CoreCourseSectionPage must be called on refresh. Defaults to true. * - * @param {any} course The course to check. - * @param {any[]} sections List of course sections. - * @return {boolean} Whether the refresher should be displayed. + * @param course The course to check. + * @param sections List of course sections. + * @return Whether the refresher should be displayed. */ displayRefresher(course: any, sections: any[]): boolean { if (sections && sections[0] && sections[0].modules && sections[0].modules[0]) { @@ -112,9 +112,9 @@ export class CoreCourseFormatSingleActivityHandler implements CoreCourseFormatHa * If you want to customize the default format there are several methods to customize parts of it. * It's recommended to return the class of the component, but you can also return an instance of the component. * - * @param {Injector} injector Injector. - * @param {any} course The course to render. - * @return {any|Promise} The component (or promise resolved with component) to use, undefined if not found. + * @param injector Injector. + * @param course The course to render. + * @return The component (or promise resolved with component) to use, undefined if not found. */ getCourseFormatComponent(injector: Injector, course: any): any | Promise { return CoreCourseFormatSingleActivityComponent; @@ -124,8 +124,8 @@ export class CoreCourseFormatSingleActivityHandler implements CoreCourseFormatHa * Whether the view should be refreshed when completion changes. If your course format doesn't display * activity completion then you should return false. * - * @param {any} course The course. - * @return {boolean|Promise} Whether course view should be refreshed when an activity completion changes. + * @param course The course. + * @return Whether course view should be refreshed when an activity completion changes. */ shouldRefreshWhenCompletionChanges(course: any): boolean | Promise { return false; diff --git a/src/core/course/formats/singleactivity/singleactivity.module.ts b/src/core/course/formats/singleactivity/singleactivity.module.ts index a35d4ad93..1c4675d7c 100644 --- a/src/core/course/formats/singleactivity/singleactivity.module.ts +++ b/src/core/course/formats/singleactivity/singleactivity.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/core/course/formats/social/providers/handler.ts b/src/core/course/formats/social/providers/handler.ts index fd4c7f5bb..e46732a2c 100644 --- a/src/core/course/formats/social/providers/handler.ts +++ b/src/core/course/formats/social/providers/handler.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/core/course/formats/social/social.module.ts b/src/core/course/formats/social/social.module.ts index 0ee3677fa..60bbf98be 100644 --- a/src/core/course/formats/social/social.module.ts +++ b/src/core/course/formats/social/social.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/core/course/formats/topics/providers/handler.ts b/src/core/course/formats/topics/providers/handler.ts index 0f6d49e3c..43f8064f7 100644 --- a/src/core/course/formats/topics/providers/handler.ts +++ b/src/core/course/formats/topics/providers/handler.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -30,7 +30,7 @@ export class CoreCourseFormatTopicsHandler implements CoreCourseFormatHandler { /** * Whether or not the handler is enabled on a site level. * - * @return {boolean|Promise} True or promise resolved with true if enabled. + * @return True or promise resolved with true if enabled. */ isEnabled(): boolean | Promise { return true; diff --git a/src/core/course/formats/topics/topics.module.ts b/src/core/course/formats/topics/topics.module.ts index 97cedcc73..7d5c1c78a 100644 --- a/src/core/course/formats/topics/topics.module.ts +++ b/src/core/course/formats/topics/topics.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/core/course/formats/weeks/providers/handler.ts b/src/core/course/formats/weeks/providers/handler.ts index ccaef9d44..bfed9b033 100644 --- a/src/core/course/formats/weeks/providers/handler.ts +++ b/src/core/course/formats/weeks/providers/handler.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -30,7 +30,7 @@ export class CoreCourseFormatWeeksHandler implements CoreCourseFormatHandler { /** * Whether or not the handler is enabled on a site level. * - * @return {boolean|Promise} True or promise resolved with true if enabled. + * @return True or promise resolved with true if enabled. */ isEnabled(): boolean | Promise { return true; @@ -39,9 +39,9 @@ export class CoreCourseFormatWeeksHandler implements CoreCourseFormatHandler { /** * Given a list of sections, get the "current" section that should be displayed first. * - * @param {any} course The course to get the title. - * @param {any[]} sections List of sections. - * @return {any|Promise} Current section (or promise resolved with current section). + * @param course The course to get the title. + * @param sections List of sections. + * @return Current section (or promise resolved with current section). */ getCurrentSection(course: any, sections: any[]): any | Promise { const now = this.timeUtils.timestamp(); @@ -70,9 +70,9 @@ export class CoreCourseFormatWeeksHandler implements CoreCourseFormatHandler { /** * Return the start and end date of a section. * - * @param {any} section The section to treat. - * @param {number} startDate The course start date (in seconds). - * @return {{start: number, end: number}} An object with the start and end date of the section. + * @param section The section to treat. + * @param startDate The course start date (in seconds). + * @return An object with the start and end date of the section. */ protected getSectionDates(section: any, startDate: number): { start: number, end: number } { // Hack alert. We add 2 hours to avoid possible DST problems. (e.g. we go into daylight savings and the date changes). diff --git a/src/core/course/formats/weeks/weeks.module.ts b/src/core/course/formats/weeks/weeks.module.ts index b26e66f44..ea250bea8 100644 --- a/src/core/course/formats/weeks/weeks.module.ts +++ b/src/core/course/formats/weeks/weeks.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/core/course/lang/en.json b/src/core/course/lang/en.json index 2d2d0156f..2fe5518e9 100644 --- a/src/core/course/lang/en.json +++ b/src/core/course/lang/en.json @@ -8,6 +8,7 @@ "confirmdeletemodulefiles": "Are you sure you want to delete these files?", "confirmdownload": "You are about to download {{size}}.{{availableSpace}} Are you sure you want to continue?", "confirmdownloadunknownsize": "It was not possible to calculate the size of the download.{{availableSpace}} Are you sure you want to continue?", + "confirmdownloadzerosize": "You are about to start downloading.{{availableSpace}} Are you sure you want to continue?", "confirmpartialdownloadsize": "You are about to download at least {{size}}.{{availableSpace}} Are you sure you want to continue?", "confirmlimiteddownload": "You are not currently connected to Wi-Fi. ", "contents": "Contents", diff --git a/src/core/course/pages/list-mod-type/list-mod-type.html b/src/core/course/pages/list-mod-type/list-mod-type.html index 7315af183..eeb723d8a 100644 --- a/src/core/course/pages/list-mod-type/list-mod-type.html +++ b/src/core/course/pages/list-mod-type/list-mod-type.html @@ -1,6 +1,6 @@ - + {{ title }} @@ -8,11 +8,13 @@ - + - - + + + + diff --git a/src/core/course/pages/list-mod-type/list-mod-type.module.ts b/src/core/course/pages/list-mod-type/list-mod-type.module.ts index 1de8ec959..9de52733b 100644 --- a/src/core/course/pages/list-mod-type/list-mod-type.module.ts +++ b/src/core/course/pages/list-mod-type/list-mod-type.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/core/course/pages/list-mod-type/list-mod-type.ts b/src/core/course/pages/list-mod-type/list-mod-type.ts index 37220ae4a..aa2c218f7 100644 --- a/src/core/course/pages/list-mod-type/list-mod-type.ts +++ b/src/core/course/pages/list-mod-type/list-mod-type.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -31,7 +31,7 @@ import { CoreConstants } from '@core/constants'; }) export class CoreCourseListModTypePage { - modules = []; + sections = []; title: string; loaded = false; downloadEnabled = false; @@ -63,23 +63,21 @@ export class CoreCourseListModTypePage { /** * Fetches the data. * - * @return {Promise} Resolved when done. + * @return Resolved when done. */ protected fetchData(): Promise { // Get all the modules in the course. return this.courseProvider.getSections(this.courseId, false, true).then((sections) => { - this.modules = []; - - sections.forEach((section) => { + this.sections = sections.filter((section) => { if (!section.modules) { - return; + return false; } - section.modules.forEach((mod) => { + section.modules = section.modules.filter((mod) => { if (mod.uservisible === false || !this.courseProvider.moduleHasView(mod)) { // Ignore this module. - return; + return false; } if (this.modName === 'resources') { @@ -90,21 +88,18 @@ export class CoreCourseListModTypePage { } if (this.archetypes[mod.modname] == CoreConstants.MOD_ARCHETYPE_RESOURCE) { - this.modules.push(mod); + return true; } } else if (mod.modname == this.modName) { - this.modules.push(mod); + return true; } }); + + return section.modules.length > 0; }); - // Get the handler data for the modules. - const fakeSection = { - visible: 1, - modules: this.modules - }; - this.courseHelper.addHandlerDataForModules([fakeSection], this.courseId); + this.courseHelper.addHandlerDataForModules(this.sections, this.courseId); }).catch((error) => { this.domUtils.showErrorModalDefault(error, 'Error getting data'); }); @@ -113,7 +108,7 @@ export class CoreCourseListModTypePage { /** * Refresh the data. * - * @param {any} refresher Refresher. + * @param refresher Refresher. */ refreshData(refresher: any): void { this.courseProvider.invalidateSections(this.courseId).finally(() => { diff --git a/src/core/course/pages/section-selector/section-selector.html b/src/core/course/pages/section-selector/section-selector.html index e160fa1f6..e8af2bc87 100644 --- a/src/core/course/pages/section-selector/section-selector.html +++ b/src/core/course/pages/section-selector/section-selector.html @@ -13,10 +13,10 @@ -

+

{{ 'core.course.hiddenfromstudents' | translate }} - +
diff --git a/src/core/course/pages/section-selector/section-selector.module.ts b/src/core/course/pages/section-selector/section-selector.module.ts index 897696b62..5ceb20e03 100644 --- a/src/core/course/pages/section-selector/section-selector.module.ts +++ b/src/core/course/pages/section-selector/section-selector.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/core/course/pages/section-selector/section-selector.ts b/src/core/course/pages/section-selector/section-selector.ts index 6186fe8ca..d3ce687c3 100644 --- a/src/core/course/pages/section-selector/section-selector.ts +++ b/src/core/course/pages/section-selector/section-selector.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -30,12 +30,15 @@ export class CoreCourseSectionSelectorPage { stealthModulesSectionId = CoreCourseProvider.STEALTH_MODULES_SECTION_ID; sections: any; selected: number; + courseId: number; constructor(navParams: NavParams, courseHelper: CoreCourseHelperProvider, private viewCtrl: ViewController) { this.sections = navParams.get('sections'); this.selected = navParams.get('selected'); const course = navParams.get('course'); + this.courseId = course && course.id; + if (course && course.enablecompletion && course.courseformatoptions && course.courseformatoptions.coursedisplay == 1 && course.completionusertracked !== false) { this.sections.forEach((section) => { @@ -69,7 +72,7 @@ export class CoreCourseSectionSelectorPage { /** * Select a section. * - * @param {any} section Selected section object. + * @param section Selected section object. */ selectSection(section: any): void { if (section.uservisible !== false) { diff --git a/src/core/course/pages/section/section.html b/src/core/course/pages/section/section.html index ea12ab46f..6d2411153 100644 --- a/src/core/course/pages/section/section.html +++ b/src/core/course/pages/section/section.html @@ -1,6 +1,6 @@ - + diff --git a/src/core/course/pages/section/section.module.ts b/src/core/course/pages/section/section.module.ts index db558c4c3..8761e43ff 100644 --- a/src/core/course/pages/section/section.module.ts +++ b/src/core/course/pages/section/section.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/core/course/pages/section/section.ts b/src/core/course/pages/section/section.ts index 85f3ab89e..7ff78fd7e 100644 --- a/src/core/course/pages/section/section.ts +++ b/src/core/course/pages/section/section.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -18,7 +18,6 @@ import { TranslateService } from '@ngx-translate/core'; import { CoreEventsProvider } from '@providers/events'; import { CoreSitesProvider } from '@providers/sites'; import { CoreDomUtilsProvider } from '@providers/utils/dom'; -import { CoreTextUtilsProvider } from '@providers/utils/text'; import { CoreUtilsProvider } from '@providers/utils/utils'; import { CoreTabsComponent } from '@components/tabs/tabs'; import { CoreCoursesProvider } from '@core/courses/providers/courses'; @@ -30,6 +29,7 @@ import { CoreCourseOptionsDelegate, CoreCourseOptionsHandlerToDisplay, CoreCourseOptionsMenuHandlerToDisplay } from '../../providers/options-delegate'; import { CoreCourseSyncProvider } from '../../providers/sync'; import { CoreCourseFormatComponent } from '../../components/format/format'; +import { CoreFilterHelperProvider } from '@core/filter/providers/helper'; /** * Page that displays the list of courses the user is enrolled in. @@ -75,7 +75,7 @@ export class CoreCourseSectionPage implements OnDestroy { constructor(navParams: NavParams, private courseProvider: CoreCourseProvider, private domUtils: CoreDomUtilsProvider, private courseFormatDelegate: CoreCourseFormatDelegate, private courseOptionsDelegate: CoreCourseOptionsDelegate, private translate: TranslateService, private courseHelper: CoreCourseHelperProvider, eventsProvider: CoreEventsProvider, - private textUtils: CoreTextUtilsProvider, private coursesProvider: CoreCoursesProvider, + private coursesProvider: CoreCoursesProvider, private filterHelper: CoreFilterHelperProvider, sitesProvider: CoreSitesProvider, private navCtrl: NavController, private injector: Injector, private prefetchDelegate: CoreCourseModulePrefetchDelegate, private syncProvider: CoreCourseSyncProvider, private utils: CoreUtilsProvider) { @@ -187,9 +187,9 @@ export class CoreCourseSectionPage implements OnDestroy { /** * Fetch and load all the data required for the view. * - * @param {boolean} [refresh] If it's refreshing content. - * @param {boolean} [sync] If it should try to sync. - * @return {Promise} Promise resolved when done. + * @param refresh If it's refreshing content. + * @param sync If it should try to sync. + * @return Promise resolved when done. */ protected loadData(refresh?: boolean, sync?: boolean): Promise { // First of all, get the course because the data might have changed. @@ -259,12 +259,14 @@ export class CoreCourseSectionPage implements OnDestroy { } return promise.then((completionStatus) => { - this.courseHelper.addHandlerDataForModules(sections, this.course.id, completionStatus, this.course.fullname); + this.courseHelper.addHandlerDataForModules(sections, this.course.id, completionStatus, this.course.fullname, + true); // Format the name of each section and check if it has content. this.sections = sections.map((section) => { - this.textUtils.formatText(section.name.trim(), true, true).then((name) => { - section.formattedName = name; + this.filterHelper.getFiltersAndFormatText(section.name.trim(), 'course', this.course.id, + {clean: true, singleLine: true}).then((result) => { + section.formattedName = result.text; }); section.hasContent = this.courseHelper.sectionHasContent(section); @@ -348,8 +350,8 @@ export class CoreCourseSectionPage implements OnDestroy { /** * Refresh the data. * - * @param {any} [refresher] Refresher. - * @return {Promise} Promise resolved when done. + * @param refresher Refresher. + * @return Promise resolved when done. */ doRefresh(refresher?: any): Promise { return this.invalidateData().finally(() => { @@ -402,7 +404,7 @@ export class CoreCourseSectionPage implements OnDestroy { /** * Refresh list after a completion change since there could be new activities. * - * @param {boolean} [sync] If it should try to sync. + * @param sync If it should try to sync. */ protected refreshAfterCompletionChange(sync?: boolean): void { // Save scroll position to restore it once done. @@ -428,7 +430,7 @@ export class CoreCourseSectionPage implements OnDestroy { /** * Determines the prefetch icon of the course. * - * @return {Promise} Promise resolved when done. + * @return Promise resolved when done. */ protected determineCoursePrefetchIcon(): Promise { return this.courseHelper.getCourseStatusIconAndTitle(this.course.id).then((data) => { @@ -460,7 +462,7 @@ export class CoreCourseSectionPage implements OnDestroy { /** * Update the course status icon and title. * - * @param {string} status Status to show. + * @param status Status to show. */ protected updateCourseStatus(status: string): void { const statusData = this.courseHelper.getCourseStatusIconAndTitleFromStatus(status); @@ -479,7 +481,7 @@ export class CoreCourseSectionPage implements OnDestroy { /** * Opens a menu item registered to the delegate. * - * @param {CoreCourseMenuHandlerToDisplay} item Item to open + * @param item Item to open */ openMenuItem(item: CoreCourseOptionsMenuHandlerToDisplay): void { const params = Object.assign({ course: this.course}, item.data.pageParams); diff --git a/src/core/course/pages/unsupported-module/unsupported-module.html b/src/core/course/pages/unsupported-module/unsupported-module.html index 13c933949..21a839390 100644 --- a/src/core/course/pages/unsupported-module/unsupported-module.html +++ b/src/core/course/pages/unsupported-module/unsupported-module.html @@ -1,6 +1,6 @@ - + @@ -11,5 +11,5 @@ - + diff --git a/src/core/course/pages/unsupported-module/unsupported-module.module.ts b/src/core/course/pages/unsupported-module/unsupported-module.module.ts index d3772a0da..41e6dec4d 100644 --- a/src/core/course/pages/unsupported-module/unsupported-module.module.ts +++ b/src/core/course/pages/unsupported-module/unsupported-module.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/core/course/pages/unsupported-module/unsupported-module.ts b/src/core/course/pages/unsupported-module/unsupported-module.ts index e52c44b1f..a28a2dde6 100644 --- a/src/core/course/pages/unsupported-module/unsupported-module.ts +++ b/src/core/course/pages/unsupported-module/unsupported-module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -27,15 +27,18 @@ import { CoreTextUtilsProvider } from '@providers/utils/text'; }) export class CoreCourseUnsupportedModulePage { module: any; + courseId: number; constructor(navParams: NavParams, private translate: TranslateService, private textUtils: CoreTextUtilsProvider) { this.module = navParams.get('module') || {}; + this.courseId = navParams.get('courseId'); } /** * Expand the description. */ expandDescription(): void { - this.textUtils.expandText(this.translate.instant('core.description'), this.module.description); + this.textUtils.expandText(this.translate.instant('core.description'), this.module.description, undefined, undefined, + [], true, 'module', this.module.id, this.courseId); } } diff --git a/src/core/course/providers/course-offline.ts b/src/core/course/providers/course-offline.ts index 4de3ac671..9def3150c 100644 --- a/src/core/course/providers/course-offline.ts +++ b/src/core/course/providers/course-offline.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -63,9 +63,9 @@ export class CoreCourseOfflineProvider { /** * Delete a manual completion stored. * - * @param {number} cmId The module ID to remove the completion. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when deleted, rejected if failure. + * @param cmId The module ID to remove the completion. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when deleted, rejected if failure. */ deleteManualCompletion(cmId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -77,8 +77,8 @@ export class CoreCourseOfflineProvider { /** * Get all offline manual completions for a certain course. * - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with the list of completions. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the list of completions. */ getAllManualCompletions(siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -90,9 +90,9 @@ export class CoreCourseOfflineProvider { /** * Get all offline manual completions for a certain course. * - * @param {number} courseId Course ID the module belongs to. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with the list of completions. + * @param courseId Course ID the module belongs to. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the list of completions. */ getCourseManualCompletions(courseId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -104,9 +104,9 @@ export class CoreCourseOfflineProvider { /** * Get the offline manual completion for a certain module. * - * @param {number} cmId The module ID to remove the completion. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with the completion, rejected if failure or not found. + * @param cmId The module ID to remove the completion. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the completion, rejected if failure or not found. */ getManualCompletion(cmId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -118,12 +118,12 @@ export class CoreCourseOfflineProvider { /** * Offline version for manually marking a module as completed. * - * @param {number} cmId The module ID to store the completion. - * @param {number} completed Whether the module is completed or not. - * @param {number} courseId Course ID the module belongs to. - * @param {string} [courseName] Course name. Recommended, it is used to display a better warning message. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise<{status: boolean, offline: boolean}>} Promise resolved when completion is successfully stored. + * @param cmId The module ID to store the completion. + * @param completed Whether the module is completed or not. + * @param courseId Course ID the module belongs to. + * @param courseName Course name. Recommended, it is used to display a better warning message. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when completion is successfully stored. */ markCompletedManually(cmId: number, completed: number, courseId: number, courseName?: string, siteId?: string) : Promise<{status: boolean, offline: boolean}> { diff --git a/src/core/course/providers/course-tag-area-handler.ts b/src/core/course/providers/course-tag-area-handler.ts index 066754de6..1aaa1343a 100644 --- a/src/core/course/providers/course-tag-area-handler.ts +++ b/src/core/course/providers/course-tag-area-handler.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -29,7 +29,7 @@ export class CoreCourseTagAreaHandler implements CoreTagAreaHandler { /** * Whether or not the handler is enabled on a site level. - * @return {boolean|Promise} Whether or not the handler is enabled on a site level. + * @return Whether or not the handler is enabled on a site level. */ isEnabled(): boolean | Promise { return true; @@ -38,8 +38,8 @@ export class CoreCourseTagAreaHandler implements CoreTagAreaHandler { /** * Parses the rendered content of a tag index and returns the items. * - * @param {string} content Rendered content. - * @return {any[]|Promise} Area items (or promise resolved with the items). + * @param content Rendered content. + * @return Area items (or promise resolved with the items). */ parseContent(content: string): any[] | Promise { const items = []; @@ -65,8 +65,8 @@ export class CoreCourseTagAreaHandler implements CoreTagAreaHandler { /** * Get the component to use to display items. * - * @param {Injector} injector Injector. - * @return {any|Promise} The component (or promise resolved with component) to use, undefined if not found. + * @param injector Injector. + * @return The component (or promise resolved with component) to use, undefined if not found. */ getComponent(injector: Injector): any | Promise { return CoreCourseTagAreaComponent; diff --git a/src/core/course/providers/course.ts b/src/core/course/providers/course.ts index d15793574..c6f99929b 100644 --- a/src/core/course/providers/course.ts +++ b/src/core/course/providers/course.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -113,19 +113,21 @@ export class CoreCourseProvider { /** * Check if the get course blocks WS is available in current site. * - * @return {boolean} Whether it's available. + * @param site Site to check. If not defined, current site. + * @return Whether it's available. * @since 3.7 */ - canGetCourseBlocks(): boolean { - return this.sitesProvider.getCurrentSite().isVersionGreaterEqualThan('3.7') && - this.sitesProvider.wsAvailableInCurrentSite('core_block_get_course_blocks'); + canGetCourseBlocks(site?: CoreSite): boolean { + site = site || this.sitesProvider.getCurrentSite(); + + return site.isVersionGreaterEqualThan('3.7') && site.wsAvailable('core_block_get_course_blocks'); } /** * Check whether the site supports requesting stealth modules. * - * @param {CoreSite} [site] Site. If not defined, current site. - * @return {boolean} Whether the site supports requesting stealth modules. + * @param site Site. If not defined, current site. + * @return Whether the site supports requesting stealth modules. * @since 3.4.6, 3.5.3, 3.6 */ canRequestStealthModules(site?: CoreSite): boolean { @@ -138,8 +140,8 @@ export class CoreCourseProvider { * Check if module completion could have changed. If it could have, trigger event. This function must be used, * for example, after calling a "module_view" WS since it can change the module completion. * - * @param {number} courseId Course ID. - * @param {any} completion Completion status of the module. + * @param courseId Course ID. + * @param completion Completion status of the module. */ checkModuleCompletion(courseId: number, completion: any): void { if (completion && completion.tracking === 2 && completion.state === 0) { @@ -152,8 +154,8 @@ export class CoreCourseProvider { /** * Clear all courses status in a site. * - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when all status are cleared. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when all status are cleared. */ clearAllCoursesStatus(siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -168,9 +170,9 @@ export class CoreCourseProvider { /** * Check if the current view in a NavController is a certain course initial page. * - * @param {NavController} navCtrl NavController. - * @param {number} courseId Course ID. - * @return {boolean} Whether the current view is a certain course. + * @param navCtrl NavController. + * @param courseId Course ID. + * @return Whether the current view is a certain course. */ currentViewIsCourse(navCtrl: NavController, courseId: number): boolean { if (navCtrl) { @@ -185,13 +187,13 @@ export class CoreCourseProvider { /** * Get completion status of all the activities in a course for a certain user. * - * @param {number} courseId Course ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @param {number} [userId] User ID. If not defined, current user. - * @param {boolean} [forceCache] True if it should return cached data. Has priority over ignoreCache. - * @param {boolean} [ignoreCache] True if it should ignore cached data (it will always fail in offline or server down). - * @param {boolean} [includeOffline=true] True if it should load offline data in the completion status. - * @return {Promise} Promise resolved with the completion statuses: object where the key is module ID. + * @param courseId Course ID. + * @param siteId Site ID. If not defined, current site. + * @param userId User ID. If not defined, current user. + * @param forceCache True if it should return cached data. Has priority over ignoreCache. + * @param ignoreCache True if it should ignore cached data (it will always fail in offline or server down). + * @param includeOffline True if it should load offline data in the completion status. + * @return Promise resolved with the completion statuses: object where the key is module ID. */ getActivitiesCompletionStatus(courseId: number, siteId?: string, userId?: number, forceCache: boolean = false, ignoreCache: boolean = false, includeOffline: boolean = true): Promise { @@ -254,9 +256,9 @@ export class CoreCourseProvider { /** * Get cache key for activities completion WS calls. * - * @param {number} courseId Course ID. - * @param {number} userId User ID. - * @return {string} Cache key. + * @param courseId Course ID. + * @param userId User ID. + * @return Cache key. */ protected getActivitiesCompletionCacheKey(courseId: number, userId: number): string { return this.ROOT_CACHE_KEY + 'activitiescompletion:' + courseId + ':' + userId; @@ -265,9 +267,9 @@ export class CoreCourseProvider { /** * Get course blocks. * - * @param {number} courseId Course ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with the list of blocks. + * @param courseId Course ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the list of blocks. * @since 3.7 */ getCourseBlocks(courseId: number, siteId?: string): Promise { @@ -290,8 +292,8 @@ export class CoreCourseProvider { /** * Get cache key for course blocks WS calls. * - * @param {number} courseId Course ID. - * @return {string} Cache key. + * @param courseId Course ID. + * @return Cache key. */ protected getCourseBlocksCacheKey(courseId: number): string { return this.ROOT_CACHE_KEY + 'courseblocks:' + courseId; @@ -300,9 +302,9 @@ export class CoreCourseProvider { /** * Get the data stored for a course. * - * @param {number} courseId Course ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with the data. + * @param courseId Course ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the data. */ getCourseStatusData(courseId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -319,9 +321,9 @@ export class CoreCourseProvider { /** * Get a course status. * - * @param {number} courseId Course ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with the status. + * @param courseId Course ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the status. */ getCourseStatus(courseId: number, siteId?: string): Promise { return this.getCourseStatusData(courseId, siteId).then((entry) => { @@ -334,15 +336,15 @@ export class CoreCourseProvider { /** * Get a module from Moodle. * - * @param {number} moduleId The module ID. - * @param {number} [courseId] The course ID. Recommended to speed up the process and minimize data usage. - * @param {number} [sectionId] The section ID. - * @param {boolean} [preferCache] True if shouldn't call WS if data is cached, false otherwise. - * @param {boolean} [ignoreCache] True if it should ignore cached data (it will always fail in offline or server down). - * @param {string} [siteId] Site ID. If not defined, current site. - * @param {string} [modName] If set, the app will retrieve all modules of this type with a single WS call. This reduces the - * number of WS calls, but it isn't recommended for modules that can return a lot of contents. - * @return {Promise} Promise resolved with the module. + * @param moduleId The module ID. + * @param courseId The course ID. Recommended to speed up the process and minimize data usage. + * @param sectionId The section ID. + * @param preferCache True if shouldn't call WS if data is cached, false otherwise. + * @param ignoreCache True if it should ignore cached data (it will always fail in offline or server down). + * @param siteId Site ID. If not defined, current site. + * @param modName If set, the app will retrieve all modules of this type with a single WS call. This reduces the + * number of WS calls, but it isn't recommended for modules that can return a lot of contents. + * @return Promise resolved with the module. */ getModule(moduleId: number, courseId?: number, sectionId?: number, preferCache?: boolean, ignoreCache?: boolean, siteId?: string, modName?: string): Promise { @@ -457,9 +459,9 @@ export class CoreCourseProvider { /** * Gets a module basic info by module ID. * - * @param {number} moduleId Module ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with the module's info. + * @param moduleId Module ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the module's info. */ getModuleBasicInfo(moduleId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -488,9 +490,9 @@ export class CoreCourseProvider { * * If the user does not have permision to manage the activity false is returned. * - * @param {number} moduleId Module ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with the module's grade info. + * @param moduleId Module ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the module's grade info. */ getModuleBasicGradeInfo(moduleId: number, siteId?: string): Promise { return this.getModuleBasicInfo(moduleId, siteId).then((info) => { @@ -514,10 +516,10 @@ export class CoreCourseProvider { /** * Gets a module basic info by instance. * - * @param {number} id Instance ID. - * @param {string} module Name of the module. E.g. 'glossary'. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with the module's info. + * @param id Instance ID. + * @param module Name of the module. E.g. 'glossary'. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the module's info. */ getModuleBasicInfoByInstance(id: number, module: string, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -545,9 +547,9 @@ export class CoreCourseProvider { /** * Get cache key for get module by instance WS calls. * - * @param {number} id Instance ID. - * @param {string} module Name of the module. E.g. 'glossary'. - * @return {string} Cache key. + * @param id Instance ID. + * @param module Name of the module. E.g. 'glossary'. + * @return Cache key. */ protected getModuleBasicInfoByInstanceCacheKey(id: number, module: string): string { return this.ROOT_CACHE_KEY + 'moduleByInstance:' + module + ':' + id; @@ -556,8 +558,8 @@ export class CoreCourseProvider { /** * Get cache key for module WS calls. * - * @param {number} moduleId Module ID. - * @return {string} Cache key. + * @param moduleId Module ID. + * @return Cache key. */ protected getModuleCacheKey(moduleId: number): string { return this.ROOT_CACHE_KEY + 'module:' + moduleId; @@ -566,8 +568,8 @@ export class CoreCourseProvider { /** * Get cache key for module by modname WS calls. * - * @param {string} modName Name of the module. - * @return {string} Cache key. + * @param modName Name of the module. + * @return Cache key. */ protected getModuleByModNameCacheKey(modName: string): string { return this.ROOT_CACHE_KEY + 'module:modName:' + modName; @@ -576,9 +578,9 @@ export class CoreCourseProvider { /** * Returns the source to a module icon. * - * @param {string} moduleName The module name. - * @param {string} [modicon] The mod icon string to use in case we are not using a core activity. - * @return {string} The IMG src. + * @param moduleName The module name. + * @param modicon The mod icon string to use in case we are not using a core activity. + * @return The IMG src. */ getModuleIconSrc(moduleName: string, modicon?: string): string { // @TODO: Check modicon url theme to apply other theme icons. @@ -598,9 +600,9 @@ export class CoreCourseProvider { /** * Get the section ID a module belongs to. * - * @param {number} moduleId The module ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with the section ID. + * @param moduleId The module ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the section ID. */ getModuleSectionId(moduleId: number, siteId?: string): Promise { // Try to get the section using getModuleBasicInfo. @@ -612,12 +614,12 @@ export class CoreCourseProvider { /** * Return a specific section. * - * @param {number} courseId The course ID. - * @param {number} sectionId The section ID. - * @param {boolean} [excludeModules] Do not return modules, return only the sections structure. - * @param {boolean} [excludeContents] Do not return module contents (i.e: files inside a resource). - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with the section. + * @param courseId The course ID. + * @param sectionId The section ID. + * @param excludeModules Do not return modules, return only the sections structure. + * @param excludeContents Do not return module contents (i.e: files inside a resource). + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the section. */ getSection(courseId: number, sectionId?: number, excludeModules?: boolean, excludeContents?: boolean, siteId?: string) : Promise { @@ -640,13 +642,13 @@ export class CoreCourseProvider { /** * Get the course sections. * - * @param {number} courseId The course ID. - * @param {boolean} [excludeModules] Do not return modules, return only the sections structure. - * @param {boolean} [excludeContents] Do not return module contents (i.e: files inside a resource). - * @param {CoreSiteWSPreSets} [preSets] Presets to use. - * @param {string} [siteId] Site ID. If not defined, current site. - * @param {boolean} [includeStealthModules] Whether to include stealth modules. Defaults to true. - * @return {Promise} The reject contains the error message, else contains the sections. + * @param courseId The course ID. + * @param excludeModules Do not return modules, return only the sections structure. + * @param excludeContents Do not return module contents (i.e: files inside a resource). + * @param preSets Presets to use. + * @param siteId Site ID. If not defined, current site. + * @param includeStealthModules Whether to include stealth modules. Defaults to true. + * @return The reject contains the error message, else contains the sections. */ getSections(courseId?: number, excludeModules?: boolean, excludeContents?: boolean, preSets?: CoreSiteWSPreSets, siteId?: string, includeStealthModules: boolean = true): Promise { @@ -705,8 +707,8 @@ export class CoreCourseProvider { /** * Get cache key for section WS call. * - * @param {number} courseId Course ID. - * @return {string} Cache key. + * @param courseId Course ID. + * @return Cache key. */ protected getSectionsCacheKey(courseId: number): string { return this.ROOT_CACHE_KEY + 'sections:' + courseId; @@ -715,8 +717,8 @@ export class CoreCourseProvider { /** * Given a list of sections, returns the list of modules in the sections. * - * @param {any[]} sections Sections. - * @return {any[]} Modules. + * @param sections Sections. + * @return Modules. */ getSectionsModules(sections: any[]): any[] { if (!sections || !sections.length) { @@ -736,9 +738,9 @@ export class CoreCourseProvider { /** * Invalidates course blocks WS call. * - * @param {number} courseId Course ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the data is invalidated. + * @param courseId Course ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the data is invalidated. */ invalidateCourseBlocks(courseId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -749,10 +751,10 @@ export class CoreCourseProvider { /** * Invalidates module WS call. * - * @param {number} moduleId Module ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @param {string} [modName] Module name. E.g. 'label', 'url', ... - * @return {Promise} Promise resolved when the data is invalidated. + * @param moduleId Module ID. + * @param siteId Site ID. If not defined, current site. + * @param modName Module name. E.g. 'label', 'url', ... + * @return Promise resolved when the data is invalidated. */ invalidateModule(moduleId: number, siteId?: string, modName?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -771,10 +773,10 @@ export class CoreCourseProvider { /** * Invalidates module WS call. * - * @param {number} id Instance ID. - * @param {string} module Name of the module. E.g. 'glossary'. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the data is invalidated. + * @param id Instance ID. + * @param module Name of the module. E.g. 'glossary'. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the data is invalidated. */ invalidateModuleByInstance(id: number, module: string, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -785,10 +787,10 @@ export class CoreCourseProvider { /** * Invalidates sections WS call. * - * @param {number} courseId Course ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @param {number} [userId] User ID. If not defined, current user. - * @return {Promise} Promise resolved when the data is invalidated. + * @param courseId Course ID. + * @param siteId Site ID. If not defined, current site. + * @param userId User ID. If not defined, current user. + * @return Promise resolved when the data is invalidated. */ invalidateSections(courseId: number, siteId?: string, userId?: number): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -810,15 +812,15 @@ export class CoreCourseProvider { /** * Load module contents into module.contents if they aren't loaded already. * - * @param {any} module Module to load the contents. - * @param {number} [courseId] The course ID. Recommended to speed up the process and minimize data usage. - * @param {number} [sectionId] The section ID. - * @param {boolean} [preferCache] True if shouldn't call WS if data is cached, false otherwise. - * @param {boolean} [ignoreCache] True if it should ignore cached data (it will always fail in offline or server down). - * @param {string} [siteId] Site ID. If not defined, current site. - * @param {string} [modName] If set, the app will retrieve all modules of this type with a single WS call. This reduces the - * number of WS calls, but it isn't recommended for modules that can return a lot of contents. - * @return {Promise} Promise resolved when loaded. + * @param module Module to load the contents. + * @param courseId The course ID. Recommended to speed up the process and minimize data usage. + * @param sectionId The section ID. + * @param preferCache True if shouldn't call WS if data is cached, false otherwise. + * @param ignoreCache True if it should ignore cached data (it will always fail in offline or server down). + * @param siteId Site ID. If not defined, current site. + * @param modName If set, the app will retrieve all modules of this type with a single WS call. This reduces the + * number of WS calls, but it isn't recommended for modules that can return a lot of contents. + * @return Promise resolved when loaded. */ loadModuleContents(module: any, courseId?: number, sectionId?: number, preferCache?: boolean, ignoreCache?: boolean, siteId?: string, modName?: string): Promise { @@ -836,11 +838,11 @@ export class CoreCourseProvider { /** * Report a course and section as being viewed. * - * @param {number} courseId Course ID. - * @param {number} [sectionNumber] Section number. - * @param {string} [siteId] Site ID. If not defined, current site. - * @param {string} [name] Name of the course. - * @return {Promise} Promise resolved when the WS call is successful. + * @param courseId Course ID. + * @param sectionNumber Section number. + * @param siteId Site ID. If not defined, current site. + * @param name Name of the course. + * @return Promise resolved when the WS call is successful. */ logView(courseId: number, sectionNumber?: number, siteId?: string, name?: string): Promise { const params: any = { @@ -866,12 +868,12 @@ export class CoreCourseProvider { /** * Offline version for manually marking a module as completed. * - * @param {number} cmId The module ID. - * @param {number} completed Whether the module is completed or not. - * @param {number} courseId Course ID the module belongs to. - * @param {string} [courseName] Course name. Recommended, it is used to display a better warning message. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when completion is successfully sent or stored. + * @param cmId The module ID. + * @param completed Whether the module is completed or not. + * @param courseId Course ID the module belongs to. + * @param courseName Course name. Recommended, it is used to display a better warning message. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when completion is successfully sent or stored. */ markCompletedManually(cmId: number, completed: number, courseId: number, courseName?: string, siteId?: string) : Promise { @@ -911,10 +913,10 @@ export class CoreCourseProvider { /** * Offline version for manually marking a module as completed. * - * @param {number} cmId The module ID. - * @param {number} completed Whether the module is completed or not. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when completion is successfully sent. + * @param cmId The module ID. + * @param completed Whether the module is completed or not. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when completion is successfully sent. */ markCompletedManuallyOnline(cmId: number, completed: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -930,8 +932,8 @@ export class CoreCourseProvider { /** * Check if a module has a view page. E.g. labels don't have a view page. * - * @param {any} module The module object. - * @return {boolean} Whether the module has a view page. + * @param module The module object. + * @return Whether the module has a view page. */ moduleHasView(module: any): boolean { return !!module.url; @@ -947,10 +949,10 @@ export class CoreCourseProvider { * * This function must be in here instead of course helper to prevent circular dependencies. * - * @param {NavController} navCtrl The nav controller to use. If not defined, the course will be opened in main menu. - * @param {any} course Course to open - * @param {any} [params] Other params to pass to the course page. - * @return {Promise} Promise resolved when done. + * @param navCtrl The nav controller to use. If not defined, the course will be opened in main menu. + * @param course Course to open + * @param params Other params to pass to the course page. + * @return Promise resolved when done. */ openCourse(navCtrl: NavController, course: any, params?: any): Promise { const loading = this.domUtils.showModalLoading(); @@ -1000,8 +1002,8 @@ export class CoreCourseProvider { /** * Select a certain tab in the course. Please use currentViewIsCourse() first to verify user is viewing the course. * - * @param {string} [name] Name of the tab. If not provided, course contents. - * @param {any} [params] Other params. + * @param name Name of the tab. If not provided, course contents. + * @param params Other params. */ selectCourseTab(name?: string, params?: any): void { params = params || {}; @@ -1013,9 +1015,9 @@ export class CoreCourseProvider { /** * Change the course status, setting it to the previous status. * - * @param {number} courseId Course ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the status is changed. Resolve param: new status. + * @param courseId Course ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the status is changed. Resolve param: new status. */ setCoursePreviousStatus(courseId: number, siteId?: string): Promise { siteId = siteId || this.sitesProvider.getCurrentSiteId(); @@ -1050,10 +1052,10 @@ export class CoreCourseProvider { /** * Store course status. * - * @param {number} courseId Course ID. - * @param {string} status New course status. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the status is stored. + * @param courseId Course ID. + * @param status New course status. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the status is stored. */ setCourseStatus(courseId: number, status: string, siteId?: string): Promise { siteId = siteId || this.sitesProvider.getCurrentSiteId(); @@ -1107,8 +1109,8 @@ export class CoreCourseProvider { /** * Translate a module name to current language. * - * @param {string} moduleName The module name. - * @return {string} Translated name. + * @param moduleName The module name. + * @return Translated name. */ translateModuleName(moduleName: string): string { if (this.CORE_MODULES.indexOf(moduleName) < 0) { @@ -1124,9 +1126,9 @@ export class CoreCourseProvider { /** * Trigger COURSE_STATUS_CHANGED with the right data. * - * @param {number} courseId Course ID. - * @param {string} status New course status. - * @param {string} [siteId] Site ID. If not defined, current site. + * @param courseId Course ID. + * @param status New course status. + * @param siteId Site ID. If not defined, current site. */ protected triggerCourseStatusChanged(courseId: number, status: string, siteId?: string): void { this.eventsProvider.trigger(CoreEventsProvider.COURSE_STATUS_CHANGED, { @@ -1135,3 +1137,38 @@ export class CoreCourseProvider { }, siteId); } } + +/** + * Data returned by course_summary_exporter. + */ +export type CoreCourseSummary = { + id: number; // Id. + fullname: string; // Fullname. + shortname: string; // Shortname. + idnumber: string; // Idnumber. + summary: string; // @since 3.3. Summary. + summaryformat: number; // @since 3.3. Summary format (1 = HTML, 0 = MOODLE, 2 = PLAIN or 4 = MARKDOWN). + startdate: number; // @since 3.3. Startdate. + enddate: number; // @since 3.3. Enddate. + visible: boolean; // @since 3.8. Visible. + fullnamedisplay: string; // @since 3.3. Fullnamedisplay. + viewurl: string; // Viewurl. + courseimage: string; // @since 3.6. Courseimage. + progress?: number; // @since 3.6. Progress. + hasprogress: boolean; // @since 3.6. Hasprogress. + isfavourite: boolean; // @since 3.6. Isfavourite. + hidden: boolean; // @since 3.6. Hidden. + timeaccess?: number; // @since 3.6. Timeaccess. + showshortname: boolean; // @since 3.6. Showshortname. + coursecategory: string; // @since 3.7. Coursecategory. +}; + +/** + * Data returned by course_module_summary_exporter. + */ +export type CoreCourseModuleSummary = { + id: number; // Id. + name: string; // Name. + url?: string; // Url. + iconurl: string; // Iconurl. +}; diff --git a/src/core/course/providers/default-format.ts b/src/core/course/providers/default-format.ts index e03341376..df3e7f401 100644 --- a/src/core/course/providers/default-format.ts +++ b/src/core/course/providers/default-format.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -33,7 +33,7 @@ export class CoreCourseFormatDefaultHandler implements CoreCourseFormatHandler { /** * Whether or not the handler is enabled on a site level. * - * @return {boolean|Promise} True or promise resolved with true if enabled. + * @return True or promise resolved with true if enabled. */ isEnabled(): boolean | Promise { return true; @@ -42,8 +42,8 @@ export class CoreCourseFormatDefaultHandler implements CoreCourseFormatHandler { /** * Get the title to use in course page. * - * @param {any} course The course. - * @return {string} Title. + * @param course The course. + * @return Title. */ getCourseTitle(course: any): string { if (course.displayname) { @@ -58,8 +58,8 @@ export class CoreCourseFormatDefaultHandler implements CoreCourseFormatHandler { /** * Whether it allows seeing all sections at the same time. Defaults to true. * - * @param {any} course The course to check. - * @type {boolean} Whether it can view all sections. + * @param course The course to check. + * @return Whether it can view all sections. */ canViewAllSections(course: any): boolean { return true; @@ -68,8 +68,8 @@ export class CoreCourseFormatDefaultHandler implements CoreCourseFormatHandler { /** * Whether the option to enable section/module download should be displayed. Defaults to true. * - * @param {any} course The course to check. - * @return {boolean} Whether the option to enable section/module download should be displayed + * @param course The course to check. + * @return Whether the option to enable section/module download should be displayed */ displayEnableDownload(course: any): boolean { return true; @@ -78,8 +78,8 @@ export class CoreCourseFormatDefaultHandler implements CoreCourseFormatHandler { /** * Whether the default section selector should be displayed. Defaults to true. * - * @param {any} course The course to check. - * @type {boolean} Whether the default section selector should be displayed. + * @param course The course to check. + * @return Whether the default section selector should be displayed. */ displaySectionSelector(course: any): boolean { return true; @@ -89,9 +89,9 @@ export class CoreCourseFormatDefaultHandler implements CoreCourseFormatHandler { * Whether the course refresher should be displayed. If it returns false, a refresher must be included in the course format, * and the doRefresh method of CoreCourseSectionPage must be called on refresh. Defaults to true. * - * @param {any} course The course to check. - * @param {any[]} sections List of course sections. - * @return {boolean} Whether the refresher should be displayed. + * @param course The course to check. + * @param sections List of course sections. + * @return Whether the refresher should be displayed. */ displayRefresher?(course: any, sections: any[]): boolean { return true; @@ -100,9 +100,9 @@ export class CoreCourseFormatDefaultHandler implements CoreCourseFormatHandler { /** * Given a list of sections, get the "current" section that should be displayed first. * - * @param {any} course The course to get the title. - * @param {any[]} sections List of sections. - * @return {any|Promise} Current section (or promise resolved with current section). + * @param course The course to get the title. + * @param sections List of sections. + * @return Current section (or promise resolved with current section). */ getCurrentSection(course: any, sections: any[]): any | Promise { let promise; @@ -143,9 +143,9 @@ export class CoreCourseFormatDefaultHandler implements CoreCourseFormatHandler { /** * Invalidate the data required to load the course format. * - * @param {any} course The course to get the title. - * @param {any[]} sections List of sections. - * @return {Promise} Promise resolved when the data is invalidated. + * @param course The course to get the title. + * @param sections List of sections. + * @return Promise resolved when the data is invalidated. */ invalidateData(course: any, sections: any[]): Promise { return this.coursesProvider.invalidateCoursesByField('id', course.id); @@ -157,10 +157,10 @@ export class CoreCourseFormatDefaultHandler implements CoreCourseFormatHandler { * getCourseFormatComponent because it will display the course handlers at the top. * Your page should include the course handlers using CoreCoursesDelegate. * - * @param {NavController} navCtrl The NavController instance to use. If not defined, please use loginHelper.redirect. - * @param {any} course The course to open. It should contain a "format" attribute. - * @param {any} [params] Params to pass to the course page. - * @return {Promise} Promise resolved when done. + * @param navCtrl The NavController instance to use. If not defined, please use loginHelper.redirect. + * @param course The course to open. It should contain a "format" attribute. + * @param params Params to pass to the course page. + * @return Promise resolved when done. */ openCourse(navCtrl: NavController, course: any, params?: any): Promise { params = params || {}; @@ -183,8 +183,8 @@ export class CoreCourseFormatDefaultHandler implements CoreCourseFormatHandler { * Whether the view should be refreshed when completion changes. If your course format doesn't display * activity completion then you should return false. * - * @param {any} course The course. - * @return {boolean|Promise} Whether course view should be refreshed when an activity completion changes. + * @param course The course. + * @return Whether course view should be refreshed when an activity completion changes. */ shouldRefreshWhenCompletionChanges(course: any): boolean | Promise { return true; diff --git a/src/core/course/providers/default-module.ts b/src/core/course/providers/default-module.ts index f82e1b2d9..bfa297f11 100644 --- a/src/core/course/providers/default-module.ts +++ b/src/core/course/providers/default-module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -31,7 +31,7 @@ export class CoreCourseModuleDefaultHandler implements CoreCourseModuleHandler { /** * Whether or not the handler is enabled on a site level. * - * @return {boolean|Promise} True or promise resolved with true if enabled. + * @return True or promise resolved with true if enabled. */ isEnabled(): boolean | Promise { return true; @@ -40,10 +40,10 @@ export class CoreCourseModuleDefaultHandler implements CoreCourseModuleHandler { /** * Get the data required to display the module in the course contents view. * - * @param {any} module The module object. - * @param {number} courseId The course ID. - * @param {number} sectionId The section ID. - * @return {CoreCourseModuleHandlerData} Data to render the module. + * @param module The module object. + * @param courseId The course ID. + * @param sectionId The section ID. + * @return Data to render the module. */ getData(module: any, courseId: number, sectionId: number): CoreCourseModuleHandlerData { // Return the default data. @@ -55,7 +55,7 @@ export class CoreCourseModuleDefaultHandler implements CoreCourseModuleHandler { event.preventDefault(); event.stopPropagation(); - navCtrl.push('CoreCourseUnsupportedModulePage', { module: module }, options); + navCtrl.push('CoreCourseUnsupportedModulePage', { module: module, courseId: courseId }, options); } }; @@ -80,10 +80,10 @@ export class CoreCourseModuleDefaultHandler implements CoreCourseModuleHandler { * The component returned must implement CoreCourseModuleMainComponent. * It's recommended to return the class of the component, but you can also return an instance of the component. * - * @param {Injector} injector Injector. - * @param {any} course The course object. - * @param {any} module The module object. - * @return {any|Promise} The component (or promise resolved with component) to use, undefined if not found. + * @param injector Injector. + * @param course The course object. + * @param module The module object. + * @return The component (or promise resolved with component) to use, undefined if not found. */ getMainComponent(injector: Injector, course: any, module: any): any | Promise { // We can't inject CoreCourseUnsupportedModuleComponent here due to circular dependencies. @@ -94,7 +94,7 @@ export class CoreCourseModuleDefaultHandler implements CoreCourseModuleHandler { * Whether to display the course refresher in single activity course format. If it returns false, a refresher must be * included in the template that calls the doRefresh method of the component. Defaults to true. * - * @return {boolean} Whether the refresher should be displayed. + * @return Whether the refresher should be displayed. */ displayRefresherInSingleActivity(): boolean { return true; diff --git a/src/core/course/providers/format-delegate.ts b/src/core/course/providers/format-delegate.ts index b65560ced..87279b01e 100644 --- a/src/core/course/providers/format-delegate.ts +++ b/src/core/course/providers/format-delegate.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -26,7 +26,6 @@ import { CoreDelegate, CoreDelegateHandler } from '@classes/delegate'; export interface CoreCourseFormatHandler extends CoreDelegateHandler { /** * Name of the format the handler supports. E.g. 'singleactivity'. - * @type {string} */ format: string; @@ -34,33 +33,33 @@ export interface CoreCourseFormatHandler extends CoreDelegateHandler { * Get the title to use in course page. If not defined, course fullname. * This function will be called without sections first, and then call it again when the sections are retrieved. * - * @param {any} course The course. - * @param {any[]} [sections] List of sections. - * @return {string} Title. + * @param course The course. + * @param sections List of sections. + * @return Title. */ getCourseTitle?(course: any, sections?: any[]): string; /** * Whether it allows seeing all sections at the same time. Defaults to true. * - * @param {any} course The course to check. - * @type {boolean} Whether it can view all sections. + * @param course The course to check. + * @return Whether it can view all sections. */ canViewAllSections?(course: any): boolean; /** * Whether the option to enable section/module download should be displayed. Defaults to true. * - * @param {any} course The course to check. - * @type {boolean} Whether the option to enable section/module download should be displayed. + * @param course The course to check. + * @return Whether the option to enable section/module download should be displayed. */ displayEnableDownload?(course: any): boolean; /** * Whether the default section selector should be displayed. Defaults to true. * - * @param {any} course The course to check. - * @type {boolean} Whether the default section selector should be displayed. + * @param course The course to check. + * @return Whether the default section selector should be displayed. */ displaySectionSelector?(course: any): boolean; @@ -68,19 +67,19 @@ export interface CoreCourseFormatHandler extends CoreDelegateHandler { * Whether the course refresher should be displayed. If it returns false, a refresher must be included in the course format, * and the doRefresh method of CoreCourseSectionPage must be called on refresh. Defaults to true. * - * @param {any} course The course to check. - * @param {any[]} sections List of course sections. - * @type {boolean} Whether the refresher should be displayed. + * @param course The course to check. + * @param sections List of course sections. + * @return Whether the refresher should be displayed. */ displayRefresher?(course: any, sections: any[]): boolean; /** * Given a list of sections, get the "current" section that should be displayed first. Defaults to first section. * - * @param {any} course The course to get the title. - * @param {any[]} sections List of sections. - * @return {any|Promise} Current section (or promise resolved with current section). If a promise is returned, it should - * never fail. + * @param course The course to get the title. + * @param sections List of sections. + * @return Current section (or promise resolved with current section). If a promise is returned, it should + * never fail. */ getCurrentSection?(course: any, sections: any[]): any | Promise; @@ -90,10 +89,10 @@ export interface CoreCourseFormatHandler extends CoreDelegateHandler { * getCourseFormatComponent because it will display the course handlers at the top. * Your page should include the course handlers using CoreCoursesDelegate. * - * @param {NavController} navCtrl The NavController instance to use. - * @param {any} course The course to open. It should contain a "format" attribute. - * @param {any} [params] Params to pass to the course page. - * @return {Promise} Promise resolved when done. + * @param navCtrl The NavController instance to use. + * @param course The course to open. It should contain a "format" attribute. + * @param params Params to pass to the course page. + * @return Promise resolved when done. */ openCourse?(navCtrl: NavController, course: any, params?: any): Promise; @@ -103,9 +102,9 @@ export interface CoreCourseFormatHandler extends CoreDelegateHandler { * If you want to customize the default format there are several methods to customize parts of it. * It's recommended to return the class of the component, but you can also return an instance of the component. * - * @param {Injector} injector Injector. - * @param {any} course The course to render. - * @return {any|Promise} The component (or promise resolved with component) to use, undefined if not found. + * @param injector Injector. + * @param course The course to render. + * @return The component (or promise resolved with component) to use, undefined if not found. */ getCourseFormatComponent?(injector: Injector, course: any): any | Promise; @@ -113,9 +112,9 @@ export interface CoreCourseFormatHandler extends CoreDelegateHandler { * Return the Component to use to display the course summary inside the default course format. * It's recommended to return the class of the component, but you can also return an instance of the component. * - * @param {Injector} injector Injector. - * @param {any} course The course to render. - * @return {any|Promise} The component (or promise resolved with component) to use, undefined if not found. + * @param injector Injector. + * @param course The course to render. + * @return The component (or promise resolved with component) to use, undefined if not found. */ getCourseSummaryComponent?(injector: Injector, course: any): any | Promise; @@ -123,9 +122,9 @@ export interface CoreCourseFormatHandler extends CoreDelegateHandler { * Return the Component to use to display the section selector inside the default course format. * It's recommended to return the class of the component, but you can also return an instance of the component. * - * @param {Injector} injector Injector. - * @param {any} course The course to render. - * @return {any|Promise} The component (or promise resolved with component) to use, undefined if not found. + * @param injector Injector. + * @param course The course to render. + * @return The component (or promise resolved with component) to use, undefined if not found. */ getSectionSelectorComponent?(injector: Injector, course: any): any | Promise; @@ -134,9 +133,9 @@ export interface CoreCourseFormatHandler extends CoreDelegateHandler { * single section. If all the sections are displayed at once then it won't be used. * It's recommended to return the class of the component, but you can also return an instance of the component. * - * @param {Injector} injector Injector. - * @param {any} course The course to render. - * @return {any|Promise} The component (or promise resolved with component) to use, undefined if not found. + * @param injector Injector. + * @param course The course to render. + * @return The component (or promise resolved with component) to use, undefined if not found. */ getSingleSectionComponent?(injector: Injector, course: any): any | Promise; @@ -144,18 +143,18 @@ export interface CoreCourseFormatHandler extends CoreDelegateHandler { * Return the Component to use to display all sections in a course. * It's recommended to return the class of the component, but you can also return an instance of the component. * - * @param {Injector} injector Injector. - * @param {any} course The course to render. - * @return {any|Promise} The component (or promise resolved with component) to use, undefined if not found. + * @param injector Injector. + * @param course The course to render. + * @return The component (or promise resolved with component) to use, undefined if not found. */ getAllSectionsComponent?(injector: Injector, course: any): any | Promise; /** * Invalidate the data required to load the course format. * - * @param {any} course The course to get the title. - * @param {any[]} sections List of sections. - * @return {Promise} Promise resolved when the data is invalidated. + * @param course The course to get the title. + * @param sections List of sections. + * @return Promise resolved when the data is invalidated. */ invalidateData?(course: any, sections: any[]): Promise; @@ -163,8 +162,8 @@ export interface CoreCourseFormatHandler extends CoreDelegateHandler { * Whether the view should be refreshed when completion changes. If your course format doesn't display * activity completion then you should return false. * - * @param {any} course The course. - * @return {boolean|Promise} Whether course view should be refreshed when an activity completion changes. + * @param course The course. + * @return Whether course view should be refreshed when an activity completion changes. */ shouldRefreshWhenCompletionChanges?(course: any): boolean | Promise; } @@ -185,8 +184,8 @@ export class CoreCourseFormatDelegate extends CoreDelegate { /** * Whether it allows seeing all sections at the same time. Defaults to true. * - * @param {any} course The course to check. - * @return {boolean} Whether it allows seeing all sections at the same time. + * @param course The course to check. + * @return Whether it allows seeing all sections at the same time. */ canViewAllSections(course: any): boolean { return this.executeFunctionOnEnabled(course.format, 'canViewAllSections', [course]); @@ -195,8 +194,8 @@ export class CoreCourseFormatDelegate extends CoreDelegate { /** * Whether the option to enable section/module download should be displayed. Defaults to true. * - * @param {any} course The course to check. - * @return {boolean} Whether the option to enable section/module download should be displayed + * @param course The course to check. + * @return Whether the option to enable section/module download should be displayed */ displayEnableDownload(course: any): boolean { return this.executeFunctionOnEnabled(course.format, 'displayEnableDownload', [course]); @@ -206,9 +205,9 @@ export class CoreCourseFormatDelegate extends CoreDelegate { * Whether the course refresher should be displayed. If it returns false, a refresher must be included in the course format, * and the doRefresh method of CoreCourseSectionPage must be called on refresh. Defaults to true. * - * @param {any} course The course to check. - * @param {any[]} sections List of course sections. - * @return {boolean} Whether the refresher should be displayed. + * @param course The course to check. + * @param sections List of course sections. + * @return Whether the refresher should be displayed. */ displayRefresher(course: any, sections: any[]): boolean { return this.executeFunctionOnEnabled(course.format, 'displayRefresher', [course, sections]); @@ -217,8 +216,8 @@ export class CoreCourseFormatDelegate extends CoreDelegate { /** * Whether the default section selector should be displayed. Defaults to true. * - * @param {any} course The course to check. - * @return {boolean} Whether the section selector should be displayed. + * @param course The course to check. + * @return Whether the section selector should be displayed. */ displaySectionSelector(course: any): boolean { return this.executeFunctionOnEnabled(course.format, 'displaySectionSelector', [course]); @@ -227,9 +226,9 @@ export class CoreCourseFormatDelegate extends CoreDelegate { /** * Get the component to use to display all sections in a course. * - * @param {Injector} injector Injector. - * @param {any} course The course to render. - * @return {Promise} Promise resolved with component to use, undefined if not found. + * @param injector Injector. + * @param course The course to render. + * @return Promise resolved with component to use, undefined if not found. */ getAllSectionsComponent(injector: Injector, course: any): Promise { return Promise.resolve(this.executeFunctionOnEnabled(course.format, 'getAllSectionsComponent', [injector, course])) @@ -241,9 +240,9 @@ export class CoreCourseFormatDelegate extends CoreDelegate { /** * Get the component to use to display a course format. * - * @param {Injector} injector Injector. - * @param {any} course The course to render. - * @return {Promise} Promise resolved with component to use, undefined if not found. + * @param injector Injector. + * @param course The course to render. + * @return Promise resolved with component to use, undefined if not found. */ getCourseFormatComponent(injector: Injector, course: any): Promise { return Promise.resolve(this.executeFunctionOnEnabled(course.format, 'getCourseFormatComponent', [injector, course])) @@ -255,9 +254,9 @@ export class CoreCourseFormatDelegate extends CoreDelegate { /** * Get the component to use to display the course summary in the default course format. * - * @param {Injector} injector Injector. - * @param {any} course The course to render. - * @return {Promise} Promise resolved with component to use, undefined if not found. + * @param injector Injector. + * @param course The course to render. + * @return Promise resolved with component to use, undefined if not found. */ getCourseSummaryComponent(injector: Injector, course: any): Promise { return Promise.resolve(this.executeFunctionOnEnabled(course.format, 'getCourseSummaryComponent', [injector, course])) @@ -269,9 +268,9 @@ export class CoreCourseFormatDelegate extends CoreDelegate { /** * Given a course, return the title to use in the course page. * - * @param {any} course The course to get the title. - * @param {any[]} [sections] List of sections. - * @return {string} Course title. + * @param course The course to get the title. + * @param sections List of sections. + * @return Course title. */ getCourseTitle(course: any, sections?: any[]): string { return this.executeFunctionOnEnabled(course.format, 'getCourseTitle', [course, sections]); @@ -280,9 +279,9 @@ export class CoreCourseFormatDelegate extends CoreDelegate { /** * Given a course and a list of sections, return the current section that should be displayed first. * - * @param {any} course The course to get the title. - * @param {any[]} sections List of sections. - * @return {Promise} Promise resolved with current section. + * @param course The course to get the title. + * @param sections List of sections. + * @return Promise resolved with current section. */ getCurrentSection(course: any, sections: any[]): Promise { @@ -296,9 +295,9 @@ export class CoreCourseFormatDelegate extends CoreDelegate { /** * Get the component to use to display the section selector inside the default course format. * - * @param {Injector} injector Injector. - * @param {any} course The course to render. - * @return {Promise} Promise resolved with component to use, undefined if not found. + * @param injector Injector. + * @param course The course to render. + * @return Promise resolved with component to use, undefined if not found. */ getSectionSelectorComponent(injector: Injector, course: any): Promise { return Promise.resolve(this.executeFunctionOnEnabled(course.format, 'getSectionSelectorComponent', [injector, course])) @@ -311,9 +310,9 @@ export class CoreCourseFormatDelegate extends CoreDelegate { * Get the component to use to display a single section. This component will only be used if the user is viewing * a single section. If all the sections are displayed at once then it won't be used. * - * @param {Injector} injector Injector. - * @param {any} course The course to render. - * @return {Promise} Promise resolved with component to use, undefined if not found. + * @param injector Injector. + * @param course The course to render. + * @return Promise resolved with component to use, undefined if not found. */ getSingleSectionComponent(injector: Injector, course: any): Promise { return Promise.resolve(this.executeFunctionOnEnabled(course.format, 'getSingleSectionComponent', [injector, course])) @@ -325,9 +324,9 @@ export class CoreCourseFormatDelegate extends CoreDelegate { /** * Invalidate the data required to load the course format. * - * @param {any} course The course to get the title. - * @param {any[]} sections List of sections. - * @return {Promise} Promise resolved when the data is invalidated. + * @param course The course to get the title. + * @param sections List of sections. + * @return Promise resolved when the data is invalidated. */ invalidateData(course: any, sections: any[]): Promise { return this.executeFunctionOnEnabled(course.format, 'invalidateData', [course, sections]); @@ -336,10 +335,10 @@ export class CoreCourseFormatDelegate extends CoreDelegate { /** * Open a course. * - * @param {NavController} navCtrl The NavController instance to use. - * @param {any} course The course to open. It should contain a "format" attribute. - * @param {any} [params] Params to pass to the course page. - * @return {Promise} Promise resolved when done. + * @param navCtrl The NavController instance to use. + * @param course The course to open. It should contain a "format" attribute. + * @param params Params to pass to the course page. + * @return Promise resolved when done. */ openCourse(navCtrl: NavController, course: any, params?: any): Promise { return this.executeFunctionOnEnabled(course.format, 'openCourse', [navCtrl, course, params]); @@ -349,8 +348,8 @@ export class CoreCourseFormatDelegate extends CoreDelegate { * Whether the view should be refreshed when completion changes. If your course format doesn't display * activity completion then you should return false. * - * @param {any} course The course. - * @return {Promise} Whether course view should be refreshed when an activity completion changes. + * @param course The course. + * @return Whether course view should be refreshed when an activity completion changes. */ shouldRefreshWhenCompletionChanges(course: any): Promise { return Promise.resolve(this.executeFunctionOnEnabled(course.format, 'shouldRefreshWhenCompletionChanges', [course])); diff --git a/src/core/course/providers/helper.ts b/src/core/course/providers/helper.ts index 73d30973c..d386f1a58 100644 --- a/src/core/course/providers/helper.ts +++ b/src/core/course/providers/helper.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -38,6 +38,7 @@ import { CoreConstants } from '@core/constants'; import { CoreSite } from '@classes/site'; import { CoreLoggerProvider } from '@providers/logger'; import * as moment from 'moment'; +import { CoreFilterHelperProvider } from '@core/filter/providers/helper'; /** * Prefetch info of a module. @@ -45,37 +46,31 @@ import * as moment from 'moment'; export type CoreCourseModulePrefetchInfo = { /** * Downloaded size. - * @type {number} */ size?: number; /** * Downloadable size in a readable format. - * @type {string} */ sizeReadable?: string; /** * Module status. - * @type {string} */ status?: string; /** * Icon's name of the module status. - * @type {string} */ statusIcon?: string; /** * Time when the module was last downloaded. - * @type {number} */ downloadTime?: number; /** * Download time in a readable format. - * @type {string} */ downloadTimeReadable?: string; }; @@ -86,25 +81,21 @@ export type CoreCourseModulePrefetchInfo = { export type CoreCourseCoursesProgress = { /** * Number of courses downloaded so far. - * @type {number} */ count: number; /** * Toal of courses to download. - * @type {number} */ total: number; /** * Whether the download has been successful so far. - * @type {boolean} */ success: boolean; /** * Last downloaded course. - * @type {number} */ courseId?: number; }; @@ -118,16 +109,28 @@ export class CoreCourseHelperProvider { protected courseDwnPromises: { [s: string]: { [id: number]: Promise } } = {}; protected logger; - constructor(private courseProvider: CoreCourseProvider, private domUtils: CoreDomUtilsProvider, - private moduleDelegate: CoreCourseModuleDelegate, private prefetchDelegate: CoreCourseModulePrefetchDelegate, - private filepoolProvider: CoreFilepoolProvider, private sitesProvider: CoreSitesProvider, - private textUtils: CoreTextUtilsProvider, private timeUtils: CoreTimeUtilsProvider, - private utils: CoreUtilsProvider, private translate: TranslateService, private loginHelper: CoreLoginHelperProvider, - private courseOptionsDelegate: CoreCourseOptionsDelegate, private siteHomeProvider: CoreSiteHomeProvider, - private eventsProvider: CoreEventsProvider, private fileHelper: CoreFileHelperProvider, - private appProvider: CoreAppProvider, private fileProvider: CoreFileProvider, private injector: Injector, - private coursesProvider: CoreCoursesProvider, private courseOffline: CoreCourseOfflineProvider, - loggerProvider: CoreLoggerProvider) { + constructor(private courseProvider: CoreCourseProvider, + private domUtils: CoreDomUtilsProvider, + private moduleDelegate: CoreCourseModuleDelegate, + private prefetchDelegate: CoreCourseModulePrefetchDelegate, + private filepoolProvider: CoreFilepoolProvider, + private sitesProvider: CoreSitesProvider, + private textUtils: CoreTextUtilsProvider, + private timeUtils: CoreTimeUtilsProvider, + private utils: CoreUtilsProvider, + private translate: TranslateService, + private loginHelper: CoreLoginHelperProvider, + private courseOptionsDelegate: CoreCourseOptionsDelegate, + private siteHomeProvider: CoreSiteHomeProvider, + private eventsProvider: CoreEventsProvider, + private fileHelper: CoreFileHelperProvider, + private appProvider: CoreAppProvider, + private fileProvider: CoreFileProvider, + private injector: Injector, + private coursesProvider: CoreCoursesProvider, + private courseOffline: CoreCourseOfflineProvider, + loggerProvider: CoreLoggerProvider, + private filterHelper: CoreFilterHelperProvider) { this.logger = loggerProvider.getInstance('CoreCourseHelperProvider'); } @@ -136,13 +139,16 @@ export class CoreCourseHelperProvider { * This function treats every module on the sections provided to load the handler data, treat completion * and navigate to a module page if required. It also returns if sections has content. * - * @param {any[]} sections List of sections to treat modules. - * @param {number} courseId Course ID of the modules. - * @param {any[]} [completionStatus] List of completion status. - * @param {string} [courseName] Course name. Recommended if completionStatus is supplied. - * @return {boolean} Whether the sections have content. + * @param sections List of sections to treat modules. + * @param courseId Course ID of the modules. + * @param completionStatus List of completion status. + * @param courseName Course name. Recommended if completionStatus is supplied. + * @param forCoursePage Whether the data will be used to render the course page. + * @return Whether the sections have content. */ - addHandlerDataForModules(sections: any[], courseId: number, completionStatus?: any, courseName?: string): boolean { + addHandlerDataForModules(sections: any[], courseId: number, completionStatus?: any, courseName?: string, + forCoursePage?: boolean): boolean { + let hasContent = false; sections.forEach((section) => { @@ -153,7 +159,8 @@ export class CoreCourseHelperProvider { hasContent = true; section.modules.forEach((module) => { - module.handlerData = this.moduleDelegate.getModuleDataFor(module.modname, module, courseId, section.id); + module.handlerData = this.moduleDelegate.getModuleDataFor(module.modname, module, courseId, section.id, + forCoursePage); if (module.completiondata && module.completion > 0) { module.completiondata.courseId = courseId; @@ -184,11 +191,11 @@ export class CoreCourseHelperProvider { /** * Calculate the status of a section. * - * @param {any} section Section to calculate its status. It can't be "All sections". - * @param {number} courseId Course ID the section belongs to. - * @param {boolean} [refresh] True if it shouldn't use module status cache (slower). - * @param {boolean} [checkUpdates=true] Whether to use the WS to check updates. Defaults to true. - * @return {Promise} Promise resolved when the status is calculated. + * @param section Section to calculate its status. It can't be "All sections". + * @param courseId Course ID the section belongs to. + * @param refresh True if it shouldn't use module status cache (slower). + * @param checkUpdates Whether to use the WS to check updates. Defaults to true. + * @return Promise resolved when the status is calculated. */ calculateSectionStatus(section: any, courseId: number, refresh?: boolean, checkUpdates: boolean = true): Promise { @@ -228,11 +235,11 @@ export class CoreCourseHelperProvider { /** * Calculate the status of a list of sections, setting attributes to determine the icons/data to be shown. * - * @param {any[]} sections Sections to calculate their status. - * @param {number} courseId Course ID the sections belong to. - * @param {boolean} [refresh] True if it shouldn't use module status cache (slower). - * @param {boolean} [checkUpdates=true] Whether to use the WS to check updates. Defaults to true. - * @return {Promise} Promise resolved when the states are calculated. + * @param sections Sections to calculate their status. + * @param courseId Course ID the sections belong to. + * @param refresh True if it shouldn't use module status cache (slower). + * @param checkUpdates Whether to use the WS to check updates. Defaults to true. + * @return Promise resolved when the states are calculated. */ calculateSectionsStatus(sections: any[], courseId: number, refresh?: boolean, checkUpdates: boolean = true): Promise { const promises = []; @@ -274,12 +281,12 @@ export class CoreCourseHelperProvider { * This function will set the icon to "spinner" when starting and it will also set it back to the initial icon if the * user cancels. All the other updates of the icon should be made when CoreEventsProvider.COURSE_STATUS_CHANGED is received. * - * @param {any} data An object where to store the course icon and title: "prefetchCourseIcon", "title" and "downloadSucceeded". - * @param {any} course Course to prefetch. - * @param {any[]} [sections] List of course sections. - * @param {CoreCourseOptionsHandlerToDisplay[]} courseHandlers List of course handlers. - * @param {CoreCourseOptionsMenuHandlerToDisplay[]} menuHandlers List of course menu handlers. - * @return {Promise} Promise resolved when the download finishes, rejected if an error occurs or the user cancels. + * @param data An object where to store the course icon and title: "prefetchCourseIcon", "title" and "downloadSucceeded". + * @param course Course to prefetch. + * @param sections List of course sections. + * @param courseHandlers List of course handlers. + * @param menuHandlers List of course menu handlers. + * @return Promise resolved when the download finishes, rejected if an error occurs or the user cancels. */ confirmAndPrefetchCourse(data: any, course: any, sections?: any[], courseHandlers?: CoreCourseOptionsHandlerToDisplay[], menuHandlers?: CoreCourseOptionsMenuHandlerToDisplay[]): Promise { @@ -341,9 +348,9 @@ export class CoreCourseHelperProvider { /** * Confirm and prefetches a list of courses. * - * @param {any[]} courses List of courses to download. - * @param {Function} [onProgress] Function to call everytime a course is downloaded. - * @return {Promise} Resolved when downloaded, rejected if error or canceled. + * @param courses List of courses to download. + * @param onProgress Function to call everytime a course is downloaded. + * @return Resolved when downloaded, rejected if error or canceled. */ confirmAndPrefetchCourses(courses: any[], onProgress?: (data: CoreCourseCoursesProgress) => void): Promise { const siteId = this.sitesProvider.getCurrentSiteId(); @@ -399,12 +406,12 @@ export class CoreCourseHelperProvider { /** * Show confirmation dialog and then remove a module files. * - * @param {any} module Module to remove the files. - * @param {number} courseId Course ID the module belongs to. - * @return {Promise} Promise resolved when done. + * @param module Module to remove the files. + * @param courseId Course ID the module belongs to. + * @return Promise resolved when done. */ confirmAndRemoveFiles(module: any, courseId: number): Promise { - return this.domUtils.showConfirm(this.translate.instant('core.course.confirmdeletemodulefiles')).then(() => { + return this.domUtils.showDeleteConfirm('core.course.confirmdeletemodulefiles').then(() => { return this.prefetchDelegate.removeModuleFiles(module, courseId); }).catch((error) => { if (error) { @@ -416,11 +423,11 @@ export class CoreCourseHelperProvider { /** * Calculate the size to download a section and show a confirm modal if needed. * - * @param {number} courseId Course ID the section belongs to. - * @param {any} [section] Section. If not provided, all sections. - * @param {any[]} [sections] List of sections. Used when downloading all the sections. - * @param {boolean} [alwaysConfirm] True to show a confirm even if the size isn't high, false otherwise. - * @return {Promise} Promise resolved if the user confirms or there's no need to confirm. + * @param courseId Course ID the section belongs to. + * @param section Section. If not provided, all sections. + * @param sections List of sections. Used when downloading all the sections. + * @param alwaysConfirm True to show a confirm even if the size isn't high, false otherwise. + * @return Promise resolved if the user confirms or there's no need to confirm. */ confirmDownloadSizeSection(courseId: number, section?: any, sections?: any[], alwaysConfirm?: boolean): Promise { let sizePromise, @@ -431,7 +438,7 @@ export class CoreCourseHelperProvider { sizePromise = this.prefetchDelegate.getDownloadSize(section.modules, courseId); // Check if the section has embedded files in the description. - haveEmbeddedFiles = this.domUtils.extractDownloadableFilesFromHtml(section.summary).length > 0; + haveEmbeddedFiles = this.filepoolProvider.extractDownloadableFilesFromHtml(section.summary).length > 0; } else { const promises = [], results = { @@ -447,7 +454,7 @@ export class CoreCourseHelperProvider { })); // Check if the section has embedded files in the description. - if (!haveEmbeddedFiles && this.domUtils.extractDownloadableFilesFromHtml(s.summary).length > 0) { + if (!haveEmbeddedFiles && this.filepoolProvider.extractDownloadableFilesFromHtml(s.summary).length > 0) { haveEmbeddedFiles = true; } } @@ -472,11 +479,11 @@ export class CoreCourseHelperProvider { * Helper function to prefetch a module, showing a confirmation modal if the size is big. * This function is meant to be called from a context menu option. It will also modify some data like the prefetch icon. * - * @param {any} instance The component instance that has the context menu. It should have prefetchStatusIcon and isDestroyed. - * @param {any} module Module to be prefetched - * @param {number} courseId Course ID the module belongs to. - * @param {Function} [done] Function to call when done. It will close the context menu. - * @return {Promise} Promise resolved when done. + * @param instance The component instance that has the context menu. It should have prefetchStatusIcon and isDestroyed. + * @param module Module to be prefetched + * @param courseId Course ID the module belongs to. + * @param done Function to call when done. It will close the context menu. + * @return Promise resolved when done. */ contextMenuPrefetch(instance: any, module: any, courseId: number, done?: () => void): Promise { const initialIcon = instance.prefetchStatusIcon; @@ -503,8 +510,8 @@ export class CoreCourseHelperProvider { /** * Determine the status of a list of courses. * - * @param {any[]} courses Courses - * @return {Promise} Promise resolved with the status. + * @param courses Courses + * @return Promise resolved with the status. */ determineCoursesStatus(courses: any[]): Promise { // Get the status of each course. @@ -530,13 +537,13 @@ export class CoreCourseHelperProvider { * Convenience function to open a module main file, downloading the package if needed. * This is meant for modules like mod_resource. * - * @param {any} module The module to download. - * @param {number} courseId The course ID of the module. - * @param {string} [component] The component to link the files to. - * @param {string|number} [componentId] An ID to use in conjunction with the component. - * @param {any[]} [files] List of files of the module. If not provided, use module.contents. - * @param {string} [siteId] The site ID. If not defined, current site. - * @return {Promise} Resolved on success. + * @param module The module to download. + * @param courseId The course ID of the module. + * @param component The component to link the files to. + * @param componentId An ID to use in conjunction with the component. + * @param files List of files of the module. If not provided, use module.contents. + * @param siteId The site ID. If not defined, current site. + * @return Resolved on success. */ downloadModuleAndOpenFile(module: any, courseId: number, component?: string, componentId?: string | number, files?: any[], siteId?: string): Promise { @@ -566,21 +573,22 @@ export class CoreCourseHelperProvider { if (this.fileHelper.shouldOpenInBrowser(mainFile)) { if (this.appProvider.isOnline()) { // Open in browser. - let fixedUrl = site.fixPluginfileURL(fileUrl).replace('&offline=1', ''); - // Remove forcedownload when followed by another param. - fixedUrl = fixedUrl.replace(/forcedownload=\d+&/, ''); - // Remove forcedownload when not followed by any param. - fixedUrl = fixedUrl.replace(/[\?|\&]forcedownload=\d+/, ''); + return site.checkAndFixPluginfileURL(fileUrl).then((fixedUrl) => { + fixedUrl = fixedUrl.replace('&offline=1', ''); + // Remove forcedownload when followed by another param. + fixedUrl = fixedUrl.replace(/forcedownload=\d+&/, ''); + // Remove forcedownload when not followed by any param. + fixedUrl = fixedUrl.replace(/[\?|\&]forcedownload=\d+/, ''); - this.utils.openInBrowser(fixedUrl); + this.utils.openInBrowser(fixedUrl); - if (this.fileProvider.isAvailable()) { - // Download the file if needed (file outdated or not downloaded). - // Download will be in background, don't return the promise. - this.downloadModule(module, courseId, component, componentId, files, siteId); - } + if (this.fileProvider.isAvailable()) { + // Download the file if needed (file outdated or not downloaded). + // Download will be in background, don't return the promise. + this.downloadModule(module, courseId, component, componentId, files, siteId); + } + }); - return; } else { // Not online, get the offline file. It will fail if not found. return this.filepoolProvider.getInternalUrlByUrl(siteId, fileUrl).then((path) => { @@ -633,13 +641,13 @@ export class CoreCourseHelperProvider { * Convenience function to download a module that has a main file and return the local file's path and other info. * This is meant for modules like mod_resource. * - * @param {any} module The module to download. - * @param {number} courseId The course ID of the module. - * @param {string} [component] The component to link the files to. - * @param {string|number} [componentId] An ID to use in conjunction with the component. - * @param {any[]} [files] List of files of the module. If not provided, use module.contents. - * @param {string} [siteId] The site ID. If not defined, current site. - * @return {Promise<{fixedUrl: string, path: string, status: string}>} Promise resolved when done. + * @param module The module to download. + * @param courseId The course ID of the module. + * @param component The component to link the files to. + * @param componentId An ID to use in conjunction with the component. + * @param files List of files of the module. If not provided, use module.contents. + * @param siteId The site ID. If not defined, current site. + * @return Promise resolved when done. */ downloadModuleWithMainFileIfNeeded(module: any, courseId: number, component?: string, componentId?: string | number, files?: any[], siteId?: string): Promise<{fixedUrl: string, path: string, status: string}> { @@ -653,7 +661,6 @@ export class CoreCourseHelperProvider { const mainFile = files[0], fileUrl = this.fileHelper.getFileUrl(mainFile), - timemodified = this.fileHelper.getFileTimemodified(mainFile), result = { fixedUrl: undefined, path: undefined, @@ -661,7 +668,8 @@ export class CoreCourseHelperProvider { }; return this.sitesProvider.getSite(siteId).then((site) => { - const fixedUrl = site.fixPluginfileURL(fileUrl); + return site.checkAndFixPluginfileURL(fileUrl); + }).then((fixedUrl) => { result.fixedUrl = fixedUrl; if (this.fileProvider.isAvailable()) { @@ -669,48 +677,23 @@ export class CoreCourseHelperProvider { return this.filepoolProvider.getPackageStatus(siteId, component, componentId).then((status) => { result.status = status; - const isWifi = this.appProvider.isWifi(), - isOnline = this.appProvider.isOnline(); - if (status === CoreConstants.DOWNLOADED) { // Get the local file URL. return this.filepoolProvider.getInternalUrlByUrl(siteId, fileUrl).catch((error) => { - // File not found, mark the module as not downloaded and reject. + // File not found, mark the module as not downloaded and try again. return this.filepoolProvider.storePackageStatus(siteId, CoreConstants.NOT_DOWNLOADED, component, componentId).then(() => { - return Promise.reject(error); + return this.downloadModuleWithMainFile(module, courseId, fixedUrl, files, status, component, + componentId, siteId); }); }); } else if (status === CoreConstants.DOWNLOADING && !this.appProvider.isDesktop()) { // Return the online URL. return fixedUrl; } else { - if (!isOnline && status === CoreConstants.NOT_DOWNLOADED) { - // Not downloaded and we're offline, reject. - return Promise.reject(this.translate.instant('core.networkerrormsg')); - } - - return this.filepoolProvider.shouldDownloadBeforeOpen(fixedUrl, mainFile.filesize).then(() => { - // Download and then return the local URL. - return this.downloadModule(module, courseId, component, componentId, files, siteId).then(() => { - return this.filepoolProvider.getInternalUrlByUrl(siteId, fileUrl); - }); - }, () => { - // Start the download if in wifi, but return the URL right away so the file is opened. - if (isWifi) { - this.downloadModule(module, courseId, component, componentId, files, siteId); - } - - if (!this.fileHelper.isStateDownloaded(status) || isOnline) { - // Not downloaded or online, return the online URL. - return fixedUrl; - } else { - // Outdated but offline, so we return the local URL. Use getUrlByUrl so it's added to the queue. - return this.filepoolProvider.getUrlByUrl(siteId, fileUrl, component, componentId, timemodified, - false, false, mainFile); - } - }); + return this.downloadModuleWithMainFile(module, courseId, fixedUrl, files, status, component, componentId, + siteId); } }).then((path) => { result.path = path; @@ -726,16 +709,65 @@ export class CoreCourseHelperProvider { }); } + /** + * Convenience function to download a module that has a main file and return the local file's path and other info. + * This is meant for modules like mod_resource. + * + * @param module The module to download. + * @param courseId The course ID of the module. + * @param fixedUrl Main file's fixed URL. + * @param files List of files of the module. + * @param status The package status. + * @param component The component to link the files to. + * @param componentId An ID to use in conjunction with the component. + * @param siteId The site ID. If not defined, current site. + * @return Promise resolved when done. + */ + protected downloadModuleWithMainFile(module: any, courseId: number, fixedUrl: string, files: any[], status: string, + component?: string, componentId?: string | number, siteId?: string): Promise { + + const isOnline = this.appProvider.isOnline(); + const mainFile = files[0]; + const fileUrl = this.fileHelper.getFileUrl(mainFile); + const timemodified = this.fileHelper.getFileTimemodified(mainFile); + + if (!isOnline && status === CoreConstants.NOT_DOWNLOADED) { + // Not downloaded and we're offline, reject. + return Promise.reject(this.translate.instant('core.networkerrormsg')); + } + + return this.filepoolProvider.shouldDownloadBeforeOpen(fixedUrl, mainFile.filesize).then(() => { + // Download and then return the local URL. + return this.downloadModule(module, courseId, component, componentId, files, siteId).then(() => { + return this.filepoolProvider.getInternalUrlByUrl(siteId, fileUrl); + }); + }, () => { + // Start the download if in wifi, but return the URL right away so the file is opened. + if (this.appProvider.isWifi()) { + this.downloadModule(module, courseId, component, componentId, files, siteId); + } + + if (!this.fileHelper.isStateDownloaded(status) || isOnline) { + // Not downloaded or online, return the online URL. + return fixedUrl; + } else { + // Outdated but offline, so we return the local URL. Use getUrlByUrl so it's added to the queue. + return this.filepoolProvider.getUrlByUrl(siteId, fileUrl, component, componentId, timemodified, + false, false, mainFile); + } + }); + } + /** * Convenience function to download a module. * - * @param {any} module The module to download. - * @param {number} courseId The course ID of the module. - * @param {string} [component] The component to link the files to. - * @param {string|number} [componentId] An ID to use in conjunction with the component. - * @param {any[]} [files] List of files of the module. If not provided, use module.contents. - * @param {string} [siteId] The site ID. If not defined, current site. - * @return {Promise} Promise resolved when done. + * @param module The module to download. + * @param courseId The course ID of the module. + * @param component The component to link the files to. + * @param componentId An ID to use in conjunction with the component. + * @param files List of files of the module. If not provided, use module.contents. + * @param siteId The site ID. If not defined, current site. + * @return Promise resolved when done. */ downloadModule(module: any, courseId: number, component?: string, componentId?: string | number, files?: any[], siteId?: string) : Promise { @@ -760,12 +792,12 @@ export class CoreCourseHelperProvider { /** * Fill the Context Menu for a certain module. * - * @param {any} instance The component instance that has the context menu. - * @param {any} module Module to be prefetched - * @param {number} courseId Course ID the module belongs to. - * @param {boolean} [invalidateCache] Invalidates the cache first. - * @param {string} [component] Component of the module. - * @return {Promise} Promise resolved when done. + * @param instance The component instance that has the context menu. + * @param module Module to be prefetched + * @param courseId Course ID the module belongs to. + * @param invalidateCache Invalidates the cache first. + * @param component Component of the module. + * @return Promise resolved when done. */ fillContextMenu(instance: any, module: any, courseId: number, invalidateCache?: boolean, component?: string): Promise { return this.getModulePrefetchInfo(module, courseId, invalidateCache, component).then((moduleInfo) => { @@ -796,9 +828,9 @@ export class CoreCourseHelperProvider { /** * Get a course. It will first check the user courses, and fallback to another WS if not enrolled. * - * @param {number} courseId Course ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise<{enrolled: boolean, course: any}>} Promise resolved with the course. + * @param courseId Course ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the course. */ getCourse(courseId: number, siteId?: string): Promise<{enrolled: boolean, course: any}> { siteId = siteId || this.sitesProvider.getCurrentSiteId(); @@ -824,10 +856,10 @@ export class CoreCourseHelperProvider { * Get a course, wait for any course format plugin to load, and open the course page. It basically chains the functions * getCourse and openCourse. * - * @param {NavController} navCtrl The nav controller to use. If not defined, the course will be opened in main menu. - * @param {number} courseId Course ID. - * @param {any} [params] Other params to pass to the course page. - * @param {string} [siteId] Site ID. If not defined, current site. + * @param navCtrl The nav controller to use. If not defined, the course will be opened in main menu. + * @param courseId Course ID. + * @param params Other params to pass to the course page. + * @param siteId Site ID. If not defined, current site. */ getAndOpenCourse(navCtrl: NavController, courseId: number, params?: any, siteId?: string): Promise { const modal = this.domUtils.showModalLoading(); @@ -847,10 +879,10 @@ export class CoreCourseHelperProvider { /** * Check if the course has a block with that name. * - * @param {number} courseId Course ID. - * @param {string} name Block name to search. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with true if the block exists or false otherwise. + * @param courseId Course ID. + * @param name Block name to search. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with true if the block exists or false otherwise. * @since 3.3 */ hasABlockNamed(courseId: number, name: string, siteId?: string): Promise { @@ -866,10 +898,10 @@ export class CoreCourseHelperProvider { /** * Initialize the prefetch icon for selected courses. * - * @param {any[]} courses Courses array to get info from. - * @param {any} prefetch Prefetch information. - * @param {number} [minCourses=2] Min course to show icon. - * @return {Promise} Resolved with the prefetch information updated when done. + * @param courses Courses array to get info from. + * @param prefetch Prefetch information. + * @param minCourses Min course to show icon. + * @return Resolved with the prefetch information updated when done. */ initPrefetchCoursesIcons(courses: any[], prefetch: any, minCourses: number = 2): Promise { if (!courses || courses.length < minCourses) { @@ -895,10 +927,10 @@ export class CoreCourseHelperProvider { * Load offline completion into a list of sections. * This should be used in 3.6 sites or higher, where the course contents already include the completion. * - * @param {number} courseId The course to get the completion. - * @param {any[]} sections List of sections of the course. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when done. + * @param courseId The course to get the completion. + * @param sections List of sections of the course. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when done. */ loadOfflineCompletion(courseId: number, sections: any[], siteId?: string): Promise { return this.courseOffline.getCourseManualCompletions(courseId, siteId).then((offlineCompletions) => { @@ -910,7 +942,7 @@ export class CoreCourseHelperProvider { const totalOffline = offlineCompletions.length; let loaded = 0; - offlineCompletions = this.utils.arrayToObject(offlineCompletions, 'cmid'); + const offlineCompletionsMap = this.utils.arrayToObject(offlineCompletions, 'cmid'); // Load the offline data in the modules. for (let i = 0; i < sections.length; i++) { @@ -922,7 +954,7 @@ export class CoreCourseHelperProvider { for (let j = 0; j < section.modules.length; j++) { const module = section.modules[j], - offlineCompletion = offlineCompletions[module.id]; + offlineCompletion = offlineCompletionsMap[module.id]; if (offlineCompletion && typeof module.completiondata != 'undefined' && offlineCompletion.timecompleted >= module.completiondata.timecompleted * 1000) { @@ -944,9 +976,9 @@ export class CoreCourseHelperProvider { /** * Prefetch all the courses in the array. * - * @param {any[]} courses Courses array to prefetch. - * @param {any} prefetch Prefetch information to be updated. - * @return {Promise} Promise resolved when done. + * @param courses Courses array to prefetch. + * @param prefetch Prefetch information to be updated. + * @return Promise resolved when done. */ prefetchCourses(courses: any[], prefetch: any): Promise { prefetch.icon = 'spinner'; @@ -964,9 +996,9 @@ export class CoreCourseHelperProvider { /** * Get a course download promise (if any). * - * @param {number} courseId Course ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Download promise, undefined if not found. + * @param courseId Course ID. + * @param siteId Site ID. If not defined, current site. + * @return Download promise, undefined if not found. */ getCourseDownloadPromise(courseId: number, siteId?: string): Promise { siteId = siteId || this.sitesProvider.getCurrentSiteId(); @@ -977,9 +1009,9 @@ export class CoreCourseHelperProvider { /** * Get a course status icon and the langkey to use as a title. * - * @param {number} courseId Course ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise<{icon: string, title: string}>} Promise resolved with the icon name and the title key. + * @param courseId Course ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the icon name and the title key. */ getCourseStatusIconAndTitle(courseId: number, siteId?: string): Promise<{icon: string, title: string}> { return this.courseProvider.getCourseStatus(courseId, siteId).then((status) => { @@ -990,8 +1022,8 @@ export class CoreCourseHelperProvider { /** * Get a course status icon and the langkey to use as a title from status. * - * @param {string} status Course status. - * @return {{icon: string, title: string}} Title and icon name. + * @param status Course status. + * @return Title and icon name. */ getCourseStatusIconAndTitleFromStatus(status: string): {icon: string, title: string} { if (status == CoreConstants.DOWNLOADED) { @@ -1016,10 +1048,10 @@ export class CoreCourseHelperProvider { /** * Get the course ID from a module instance ID, showing an error message if it can't be retrieved. * - * @param {number} id Instance ID. - * @param {string} module Name of the module. E.g. 'glossary'. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with the module's course ID. + * @param id Instance ID. + * @param module Name of the module. E.g. 'glossary'. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the module's course ID. */ getModuleCourseIdByInstance(id: number, module: any, siteId?: string): Promise { return this.courseProvider.getModuleBasicInfoByInstance(id, module, siteId).then((cm) => { @@ -1034,11 +1066,11 @@ export class CoreCourseHelperProvider { /** * Get prefetch info for a module. * - * @param {any} module Module to get the info from. - * @param {number} courseId Course ID the section belongs to. - * @param {boolean} [invalidateCache] Invalidates the cache first. - * @param {string} [component] Component of the module. - * @return {Promise} Promise resolved with the info. + * @param module Module to get the info from. + * @param courseId Course ID the section belongs to. + * @param invalidateCache Invalidates the cache first. + * @param component Component of the module. + * @return Promise resolved with the info. */ getModulePrefetchInfo(module: any, courseId: number, invalidateCache?: boolean, component?: string) : Promise { @@ -1080,7 +1112,7 @@ export class CoreCourseHelperProvider { // Get the time it was downloaded (if it was downloaded). promises.push(this.filepoolProvider.getPackageData(siteId, component, module.id).then((data) => { - if (data && data.downloadTime && (data.status == CoreConstants.OUTDATED || data.status == CoreConstants.DOWNLOADED)) { + if (data && data.downloadTime && this.fileHelper.isStateDownloaded(data.status)) { const now = this.timeUtils.timestamp(); moduleInfo.downloadTime = data.downloadTime; if (now - data.downloadTime < 7 * 86400) { @@ -1102,26 +1134,57 @@ export class CoreCourseHelperProvider { /** * Get the download ID of a section. It's used to interact with CoreCourseModulePrefetchDelegate. * - * @param {any} section Section. - * @return {string} Section download ID. + * @param section Section. + * @return Section download ID. */ getSectionDownloadId(section: any): string { return 'Section-' + section.id; } + /** + * Navigate to a module using instance ID and module name. + * + * @param instanceId Activity instance ID. + * @param modName Module name of the activity. + * @param siteId Site ID. If not defined, current site. + * @param courseId Course ID. If not defined we'll try to retrieve it from the site. + * @param sectionId Section the module belongs to. If not defined we'll try to retrieve it from the site. + * @param useModNameToGetModule If true, the app will retrieve all modules of this type with a single WS call. This reduces the + * number of WS calls, but it isn't recommended for modules that can return a lot of contents. + * @param modParams Params to pass to the module + * @param navCtrl NavController for adding new pages to the current history. Optional for legacy support, but + * generates a warning if omitted. + * @return Promise resolved when done. + */ + navigateToModuleByInstance(instanceId: number, modName: string, siteId?: string, courseId?: number, sectionId?: number, + useModNameToGetModule: boolean = false, modParams?: any, navCtrl?: NavController): Promise { + + const modal = this.domUtils.showModalLoading(); + + return this.courseProvider.getModuleBasicInfoByInstance(instanceId, modName, siteId).then((module) => { + this.navigateToModule(parseInt(module.id, 10), siteId, module.course, sectionId, + useModNameToGetModule ? modName : undefined, modParams, navCtrl); + }).catch((error) => { + this.domUtils.showErrorModalDefault(error, 'core.course.errorgetmodule', true); + }).finally(() => { + // Just in case. In fact we need to dismiss the modal before showing a toast or error message. + modal.dismiss(); + }); + } + /** * Navigate to a module. * - * @param {number} moduleId Module's ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @param {number} [courseId] Course ID. If not defined we'll try to retrieve it from the site. - * @param {number} [sectionId] Section the module belongs to. If not defined we'll try to retrieve it from the site. - * @param {string} [modName] If set, the app will retrieve all modules of this type with a single WS call. This reduces the - * number of WS calls, but it isn't recommended for modules that can return a lot of contents. - * @param {any} [modParams] Params to pass to the module - * @param {NavController} [navCtrl] NavController for adding new pages to the current history. Optional for legacy support, but - * generates a warning if omitted. - * @return {Promise} Promise resolved when done. + * @param moduleId Module's ID. + * @param siteId Site ID. If not defined, current site. + * @param courseId Course ID. If not defined we'll try to retrieve it from the site. + * @param sectionId Section the module belongs to. If not defined we'll try to retrieve it from the site. + * @param modName If set, the app will retrieve all modules of this type with a single WS call. This reduces the + * number of WS calls, but it isn't recommended for modules that can return a lot of contents. + * @param modParams Params to pass to the module + * @param navCtrl NavController for adding new pages to the current history. Optional for legacy support, but + * generates a warning if omitted. + * @return Promise resolved when done. */ navigateToModule(moduleId: number, siteId?: string, courseId?: number, sectionId?: number, modName?: string, modParams?: any, navCtrl?: NavController) @@ -1161,25 +1224,25 @@ export class CoreCourseHelperProvider { // Get the module. return this.courseProvider.getModule(moduleId, courseId, sectionId, false, false, siteId, modName); }).then((module) => { - const params = { - course: { id: courseId }, - module: module, - sectionId: sectionId, - modParams: modParams - }; - - module.handlerData = this.moduleDelegate.getModuleDataFor(module.modname, module, courseId, sectionId); + module.handlerData = this.moduleDelegate.getModuleDataFor(module.modname, module, courseId, sectionId, false); if (navCtrl && module.handlerData && module.handlerData.action) { // If the link handler for this module passed through navCtrl, we can use the module's handler to navigate cleanly. // Otherwise, we will redirect below. modal.dismiss(); - return module.handlerData.action(new Event('click'), navCtrl, module, courseId); + return module.handlerData.action(new Event('click'), navCtrl, module, courseId, undefined, modParams); } this.logger.warn('navCtrl was not passed to navigateToModule by the link handler for ' + module.modname); + const params = { + course: { id: courseId }, + module: module, + sectionId: sectionId, + modParams: modParams + }; + if (courseId == site.getSiteHomeId()) { // Check if site home is available. return this.siteHomeProvider.isAvailable().then(() => { @@ -1201,16 +1264,16 @@ export class CoreCourseHelperProvider { /** * Open a module. * - * @param {NavController} navCtrl The NavController to use. - * @param {any} module The module to open. - * @param {number} courseId The course ID of the module. - * @param {number} [sectionId] The section ID of the module. - * @param {any} [modParams] Params to pass to the module - * @param {boolean} True if module can be opened, false otherwise. + * @param navCtrl The NavController to use. + * @param module The module to open. + * @param courseId The course ID of the module. + * @param sectionId The section ID of the module. + * @param modParams Params to pass to the module + * @param True if module can be opened, false otherwise. */ openModule(navCtrl: NavController, module: any, courseId: number, sectionId?: number, modParams?: any): boolean { if (!module.handlerData) { - module.handlerData = this.moduleDelegate.getModuleDataFor(module.modname, module, courseId, sectionId); + module.handlerData = this.moduleDelegate.getModuleDataFor(module.modname, module, courseId, sectionId, false); } if (module.handlerData && module.handlerData.action) { @@ -1225,12 +1288,12 @@ export class CoreCourseHelperProvider { /** * Prefetch all the activities in a course and also the course addons. * - * @param {any} course The course to prefetch. - * @param {any[]} sections List of course sections. - * @param {CoreCourseOptionsHandlerToDisplay[]} courseHandlers List of course options handlers. - * @param {CoreCourseOptionsMenuHandlerToDisplay[]} courseMenuHandlers List of course menu handlers. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the download finishes. + * @param course The course to prefetch. + * @param sections List of course sections. + * @param courseHandlers List of course options handlers. + * @param courseMenuHandlers List of course menu handlers. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the download finishes. */ prefetchCourse(course: any, sections: any[], courseHandlers: CoreCourseOptionsHandlerToDisplay[], courseMenuHandlers: CoreCourseOptionsMenuHandlerToDisplay[], siteId?: string): Promise { @@ -1280,6 +1343,8 @@ export class CoreCourseHelperProvider { promises.push(this.courseProvider.getActivitiesCompletionStatus(course.id)); } + promises.push(this.filterHelper.getFilters('course', course.id)); + return this.utils.allPromises(promises); }).then(() => { // Download success, mark the course as downloaded. @@ -1300,12 +1365,12 @@ export class CoreCourseHelperProvider { * Helper function to prefetch a module, showing a confirmation modal if the size is big * and invalidating contents if refreshing. * - * @param {handler} handler Prefetch handler to use. Must implement 'prefetch' and 'invalidateContent'. - * @param {any} module Module to download. - * @param {any} size Object containing size to download (in bytes) and a boolean to indicate if its totally calculated. - * @param {number} courseId Course ID of the module. - * @param {boolean} [refresh] True if refreshing, false otherwise. - * @return {Promise} Promise resolved when downloaded. + * @param handler Prefetch handler to use. Must implement 'prefetch' and 'invalidateContent'. + * @param module Module to download. + * @param size Object containing size to download (in bytes) and a boolean to indicate if its totally calculated. + * @param courseId Course ID of the module. + * @param refresh True if refreshing, false otherwise. + * @return Promise resolved when downloaded. */ prefetchModule(handler: any, module: any, size: any, courseId: number, refresh?: boolean): Promise { // Show confirmation if needed. @@ -1325,10 +1390,10 @@ export class CoreCourseHelperProvider { * Prefetch one section or all the sections. * If the section is "All sections" it will prefetch all the sections. * - * @param {any} section Section. - * @param {number} courseId Course ID the section belongs to. - * @param {any[]} [sections] List of sections. Used when downloading all the sections. - * @return {Promise} Promise resolved when the prefetch is finished. + * @param section Section. + * @param courseId Course ID the section belongs to. + * @param sections List of sections. Used when downloading all the sections. + * @return Promise resolved when the prefetch is finished. */ prefetchSection(section: any, courseId: number, sections?: any[]): Promise { if (section.id != CoreCourseProvider.ALL_SECTIONS_ID) { @@ -1370,9 +1435,9 @@ export class CoreCourseHelperProvider { * Prefetch a certain section if it needs to be prefetched. * If the section is "All sections" it will be ignored. * - * @param {any} section Section to prefetch. - * @param {number} courseId Course ID the section belongs to. - * @return {Promise} Promise resolved when the section is prefetched. + * @param section Section to prefetch. + * @param courseId Course ID the section belongs to. + * @return Promise resolved when the section is prefetched. */ protected prefetchSingleSectionIfNeeded(section: any, courseId: number): Promise { if (section.id == CoreCourseProvider.ALL_SECTIONS_ID) { @@ -1407,7 +1472,7 @@ export class CoreCourseHelperProvider { })); // Download the files in the section description. - const introFiles = this.domUtils.extractDownloadableFilesFromHtmlAsFakeFileObjects(section.summary), + const introFiles = this.filepoolProvider.extractDownloadableFilesFromHtmlAsFakeFileObjects(section.summary), siteId = this.sitesProvider.getCurrentSiteId(); promises.push(this.filepoolProvider.addFilesToQueue(siteId, introFiles, CoreCourseProvider.COMPONENT, courseId) @@ -1422,10 +1487,10 @@ export class CoreCourseHelperProvider { * Start or restore the prefetch of a section. * If the section is "All sections" it will be ignored. * - * @param {any} section Section to download. - * @param {any} result Result of CoreCourseModulePrefetchDelegate.getModulesStatus for this section. - * @param {number} courseId Course ID the section belongs to. - * @return {Promise} Promise resolved when the section has been prefetched. + * @param section Section to download. + * @param result Result of CoreCourseModulePrefetchDelegate.getModulesStatus for this section. + * @param courseId Course ID the section belongs to. + * @return Promise resolved when the section has been prefetched. */ protected prefetchSingleSection(section: any, result: any, courseId: number): Promise { if (section.id == CoreCourseProvider.ALL_SECTIONS_ID) { @@ -1454,8 +1519,8 @@ export class CoreCourseHelperProvider { /** * Check if a section has content. * - * @param {any} section Section to check. - * @return {boolean} Whether the section has content. + * @param section Section to check. + * @return Whether the section has content. */ sectionHasContent(section: any): boolean { if (section.hiddenbynumsections) { @@ -1474,11 +1539,11 @@ export class CoreCourseHelperProvider { * will be displayed until it is complete, before the course page is opened. If the promise is already complete, * they will see the result immediately. * - * @param {NavController} navCtrl The nav controller to use. If not defined, the course will be opened in main menu. - * @param {any} course Course to open - * @param {any} [params] Params to pass to the course page. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when done. + * @param navCtrl The nav controller to use. If not defined, the course will be opened in main menu. + * @param course Course to open + * @param params Params to pass to the course page. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when done. */ openCourse(navCtrl: NavController, course: any, params?: any, siteId?: string): Promise { if (!siteId || siteId == this.sitesProvider.getCurrentSiteId()) { diff --git a/src/core/course/providers/log-cron-handler.ts b/src/core/course/providers/log-cron-handler.ts index e708cf056..26ba8a309 100644 --- a/src/core/course/providers/log-cron-handler.ts +++ b/src/core/course/providers/log-cron-handler.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -30,9 +30,9 @@ export class CoreCourseLogCronHandler implements CoreCronHandler { * Execute the process. * Receives the ID of the site affected, undefined for all sites. * - * @param {string} [siteId] ID of the site affected, undefined for all sites. - * @param {boolean} [force] Wether the execution is forced (manual sync). - * @return {Promise} Promise resolved when done, rejected if failure. + * @param siteId ID of the site affected, undefined for all sites. + * @param force Wether the execution is forced (manual sync). + * @return Promise resolved when done, rejected if failure. */ execute(siteId?: string, force?: boolean): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -43,7 +43,7 @@ export class CoreCourseLogCronHandler implements CoreCronHandler { /** * Check whether it's a synchronization process or not. * - * @return {boolean} Whether it's a synchronization process or not. + * @return Whether it's a synchronization process or not. */ isSync(): boolean { return false; @@ -52,7 +52,7 @@ export class CoreCourseLogCronHandler implements CoreCronHandler { /** * Get the time between consecutive executions. * - * @return {number} Time between consecutive executions (in ms). + * @return Time between consecutive executions (in ms). */ getInterval(): number { return 240000; // 4 minutes. By default platform will see the user as online if lastaccess is less than 5 minutes. diff --git a/src/core/course/providers/log-helper.ts b/src/core/course/providers/log-helper.ts index 36ccf4ab8..8792eab81 100644 --- a/src/core/course/providers/log-helper.ts +++ b/src/core/course/providers/log-helper.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -70,10 +70,10 @@ export class CoreCourseLogHelperProvider { /** * Delete the offline saved activity logs. * - * @param {string} component Component name. - * @param {number} componentId Component ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when deleted, rejected if failure. + * @param component Component name. + * @param componentId Component ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when deleted, rejected if failure. */ protected deleteLogs(component: string, componentId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -86,11 +86,11 @@ export class CoreCourseLogHelperProvider { /** * Delete a WS based log. * - * @param {string} component Component name. - * @param {number} componentId Component ID. - * @param {string} ws WS name. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when deleted, rejected if failure. + * @param component Component name. + * @param componentId Component ID. + * @param ws WS name. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when deleted, rejected if failure. */ protected deleteWSLogsByComponent(component: string, componentId: number, ws: string, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -103,10 +103,10 @@ export class CoreCourseLogHelperProvider { /** * Delete the offline saved activity logs using call data. * - * @param {string} ws WS name. - * @param {any} data Data to send to the WS. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when deleted, rejected if failure. + * @param ws WS name. + * @param data Data to send to the WS. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when deleted, rejected if failure. */ protected deleteWSLogs(ws: string, data: any, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -119,8 +119,8 @@ export class CoreCourseLogHelperProvider { /** * Get all the offline saved activity logs. * - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with the list of offline logs. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the list of offline logs. */ protected getAllLogs(siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -132,10 +132,10 @@ export class CoreCourseLogHelperProvider { /** * Get the offline saved activity logs. * - * @param {string} component Component name. - * @param {number} componentId Component ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with the list of offline logs. + * @param component Component name. + * @param componentId Component ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the list of offline logs. */ protected getLogs(component: string, componentId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -148,12 +148,12 @@ export class CoreCourseLogHelperProvider { /** * Perform log online. Data will be saved offline for syncing. * - * @param {string} ws WS name. - * @param {any} data Data to send to the WS. - * @param {string} component Component name. - * @param {number} componentId Component ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when done. + * @param ws WS name. + * @param data Data to send to the WS. + * @param component Component name. + * @param componentId Component ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when done. */ log(ws: string, data: any, component: string, componentId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -177,22 +177,25 @@ export class CoreCourseLogHelperProvider { /** * Perform the log online. * - * @param {string} ws WS name. - * @param {any} data Data to send to the WS. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when log is successfully submitted. Rejected with object containing - * the error message (if any) and a boolean indicating if the error was returned by WS. + * @param ws WS name. + * @param data Data to send to the WS. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when log is successfully submitted. Rejected with object containing + * the error message (if any) and a boolean indicating if the error was returned by WS. */ protected logOnline(ws: string, data: any, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { - return site.write(ws, data).then((response) => { + // Clone to have an unmodified data object. + const wsData = Object.assign({}, data); + + return site.write(ws, wsData).then((response) => { if (!response.status) { return Promise.reject(this.utils.createFakeWSError('')); } // Remove all the logs performed. // TODO: Remove this lines when time is accepted in logs. - return this.deleteWSLogs(ws, data); + return this.deleteWSLogs(ws, data, siteId); }); }); } @@ -201,15 +204,15 @@ export class CoreCourseLogHelperProvider { * Perform log online. Data will be saved offline for syncing. * It also triggers a Firebase view_item event. * - * @param {string} ws WS name. - * @param {any} data Data to send to the WS. - * @param {string} component Component name. - * @param {number} componentId Component ID. - * @param {string} [name] Name of the viewed item. - * @param {string} [category] Category of the viewed item. - * @param {string} [eventData] Data to pass to the Firebase event. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when done. + * @param ws WS name. + * @param data Data to send to the WS. + * @param component Component name. + * @param componentId Component ID. + * @param name Name of the viewed item. + * @param category Category of the viewed item. + * @param eventData Data to pass to the Firebase event. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when done. */ logSingle(ws: string, data: any, component: string, componentId: number, name?: string, category?: string, eventData?: any, siteId?: string): Promise { @@ -222,14 +225,14 @@ export class CoreCourseLogHelperProvider { * Perform log online. Data will be saved offline for syncing. * It also triggers a Firebase view_item_list event. * - * @param {string} ws WS name. - * @param {any} data Data to send to the WS. - * @param {string} component Component name. - * @param {number} componentId Component ID. - * @param {string} category Category of the viewed item. - * @param {string} [eventData] Data to pass to the Firebase event. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when done. + * @param ws WS name. + * @param data Data to send to the WS. + * @param component Component name. + * @param componentId Component ID. + * @param category Category of the viewed item. + * @param eventData Data to pass to the Firebase event. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when done. */ logList(ws: string, data: any, component: string, componentId: number, category: string, eventData?: any, siteId?: string) : Promise { @@ -241,12 +244,12 @@ export class CoreCourseLogHelperProvider { /** * Save activity log for offline sync. * - * @param {string} ws WS name. - * @param {any} data Data to send to the WS. - * @param {string} component Component name. - * @param {number} componentId Component ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Resolved with the inserted rowId field. + * @param ws WS name. + * @param data Data to send to the WS. + * @param component Component name. + * @param componentId Component ID. + * @param siteId Site ID. If not defined, current site. + * @return Resolved with the inserted rowId field. */ protected storeOffline(ws: string, data: any, component: string, componentId: number, siteId?: string): Promise { @@ -266,8 +269,8 @@ export class CoreCourseLogHelperProvider { /** * Sync all the offline saved activity logs. * - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when done. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when done. */ syncSite(siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -297,10 +300,10 @@ export class CoreCourseLogHelperProvider { /** * Sync the offline saved activity logs. * - * @param {string} component Component name. - * @param {number} componentId Component ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when done. + * @param component Component name. + * @param componentId Component ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when done. */ syncIfNeeded(component: string, componentId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -329,14 +332,25 @@ export class CoreCourseLogHelperProvider { /** * Sync and delete given logs. * - * @param {any[]} logs Array of log objects. - * @param {string} siteId Site Id. - * @return {Promise} Promise resolved when done. + * @param logs Array of log objects. + * @param siteId Site Id. + * @return Promise resolved when done. */ protected syncLogs(logs: any[], siteId: string): Promise { return Promise.all(logs.map((log) => { - return this.logOnline(log.ws, this.textUtils.parseJSON(log.data), siteId).then(() => { - return this.deleteWSLogsByComponent(log.component, log.componentid, log.ws); + const data = this.textUtils.parseJSON(log.data); + + return this.logOnline(log.ws, data, siteId).catch((error) => { + const promise = this.utils.isWebServiceError(error) ? this.deleteWSLogs(log.ws, data, siteId) : Promise.resolve(); + + return promise.catch(() => { + // Ignore errors. + }).then(() => { + // The WebService has thrown an error, this means that responses cannot be submitted. + return Promise.reject(error); + }); + }).then(() => { + return this.deleteWSLogsByComponent(log.component, log.componentid, log.ws, siteId); }); })); } diff --git a/src/core/course/providers/module-delegate.ts b/src/core/course/providers/module-delegate.ts index 4a3824784..4b21284e7 100644 --- a/src/core/course/providers/module-delegate.ts +++ b/src/core/course/providers/module-delegate.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -29,7 +29,6 @@ import { CoreDelegate, CoreDelegateHandler } from '@classes/delegate'; export interface CoreCourseModuleHandler extends CoreDelegateHandler { /** * Name of the module. It should match the "modname" of the module returned in core_course_get_contents. - * @type {string} */ modName: string; @@ -37,29 +36,29 @@ export interface CoreCourseModuleHandler extends CoreDelegateHandler { * List of supported features. The keys should be the name of the feature. * This is to replicate the "plugin_supports" function of Moodle. * If you need some dynamic checks please implement the supportsFeature function. - * @type {{[name: string]: any}} */ supportedFeatures?: {[name: string]: any}; /** * Get the data required to display the module in the course contents view. * - * @param {any} module The module object. - * @param {number} courseId The course ID. - * @param {number} sectionId The section ID. - * @return {CoreCourseModuleHandlerData} Data to render the module. + * @param module The module object. + * @param courseId The course ID. + * @param sectionId The section ID. + * @param forCoursePage Whether the data will be used to render the course page. + * @return Data to render the module. */ - getData(module: any, courseId: number, sectionId: number): CoreCourseModuleHandlerData; + getData(module: any, courseId: number, sectionId: number, forCoursePage: boolean): CoreCourseModuleHandlerData; /** * Get the component to render the module. This is needed to support singleactivity course format. * The component returned must implement CoreCourseModuleMainComponent. * It's recommended to return the class of the component, but you can also return an instance of the component. * - * @param {Injector} injector Injector. - * @param {any} course The course object. - * @param {any} module The module object. - * @return {any|Promise} The component (or promise resolved with component) to use, undefined if not found. + * @param injector Injector. + * @param course The course object. + * @param module The module object. + * @return The component (or promise resolved with component) to use, undefined if not found. */ getMainComponent(injector: Injector, course: any, module: any): any | Promise; @@ -67,14 +66,14 @@ export interface CoreCourseModuleHandler extends CoreDelegateHandler { * Whether to display the course refresher in single activity course format. If it returns false, a refresher must be * included in the template that calls the doRefresh method of the component. Defaults to true. * - * @return {boolean} Whether the refresher should be displayed. + * @return Whether the refresher should be displayed. */ displayRefresherInSingleActivity?(): boolean; /** * Get the icon src for the module. * - * @return {string} The icon src. + * @return The icon src. */ getIconSrc?(): string; @@ -82,8 +81,8 @@ export interface CoreCourseModuleHandler extends CoreDelegateHandler { * Check if this type of module supports a certain feature. * If this function is implemented, the supportedFeatures object will be ignored. * - * @param {string} feature The feature to check. - * @return {any} The result of the supports check. + * @param feature The feature to check. + * @return The result of the supports check. */ supportsFeature?(feature: string): any; } @@ -94,37 +93,31 @@ export interface CoreCourseModuleHandler extends CoreDelegateHandler { export interface CoreCourseModuleHandlerData { /** * The title to display in the module. - * @type {string} */ title: string; /** * The accessibility title to use in the module. If not provided, title will be used. - * @type {string} */ a11yTitle?: string; /** * The image to use as icon (path to the image). - * @type {string} */ icon?: string | SafeUrl; /** * The class to assign to the item. - * @type {string} */ class?: string; /** * The text to show in an extra badge. - * @type {string} */ extraBadge?: string; /** * The color of the extra badge. Default: primary. - * @type {string} */ extraBadgeColor?: string; @@ -132,38 +125,40 @@ export interface CoreCourseModuleHandlerData { * Whether to display a button to download/refresh the module if it's downloadable. * If it's set to true, the app will show a download/refresh button when needed and will handle the download of the * module using CoreCourseModulePrefetchDelegate. - * @type {boolean} */ showDownloadButton?: boolean; /** * The buttons to display in the module item. - * @type {CoreCourseModuleHandlerButton[]} */ buttons?: CoreCourseModuleHandlerButton[]; /** - * Whether to display a spinner in the module item. - * @type {boolean} + * Whether to display a spinner where the download button is displayed. The module icon, title, etc. will be displayed. */ spinner?: boolean; + /** + * Whether the data is being loaded. If true, it will display a spinner in the whole module, nothing else will be shown. + */ + loading?: boolean; + /** * Action to perform when the module is clicked. * - * @param {Event} event The click event. - * @param {NavController} navCtrl NavController instance. - * @param {any} module The module object. - * @param {number} courseId The course ID. - * @param {NavOptions} [options] Options for the navigation. - * @param {any} [params] Params for the new page. + * @param event The click event. + * @param navCtrl NavController instance. + * @param module The module object. + * @param courseId The course ID. + * @param options Options for the navigation. + * @param params Params for the new page. */ action?(event: Event, navCtrl: NavController, module: any, courseId: number, options?: NavOptions, params?: any): void; /** * Updates the status of the module. * - * @param {string} status Module status. + * @param status Module status. */ updateStatus?(status: string): void; @@ -180,9 +175,9 @@ export interface CoreCourseModuleMainComponent { /** * Refresh the data. * - * @param {any} [refresher] Refresher. - * @param {Function} [done] Function to call when done. - * @return {Promise} Promise resolved when done. + * @param refresher Refresher. + * @param done Function to call when done. + * @return Promise resolved when done. */ doRefresh(refresher?: any, done?: () => void): Promise; } @@ -193,41 +188,36 @@ export interface CoreCourseModuleMainComponent { export interface CoreCourseModuleHandlerButton { /** * The label to add to the button. - * @type {string} */ label: string; /** * The name of the button icon. - * @type {string} */ icon: string; /** * Whether the button should be hidden. - * @type {boolean} */ hidden?: boolean; /** * The name of the button icon to use in iOS instead of "icon". - * @type {string} */ iosIcon?: string; /** * The name of the button icon to use in MaterialDesign instead of "icon". - * @type {string} */ mdIcon?: string; /** * Action to perform when the button is clicked. * - * @param {Event} event The click event. - * @param {NavController} navCtrl NavController instance. - * @param {any} module The module object. - * @param {number} courseId The course ID. + * @param event The click event. + * @param navCtrl NavController instance. + * @param module The module object. + * @param courseId The course ID. */ action(event: Event, navCtrl: NavController, module: any, courseId: number): void; } @@ -248,10 +238,10 @@ export class CoreCourseModuleDelegate extends CoreDelegate { /** * Get the component to render the module. * - * @param {Injector} injector Injector. - * @param {any} course The course object. - * @param {any} module The module object. - * @return {Promise} Promise resolved with component to use, undefined if not found. + * @param injector Injector. + * @param course The course object. + * @param module The module object. + * @return Promise resolved with component to use, undefined if not found. */ getMainComponent(injector: Injector, course: any, module: any): Promise { return Promise.resolve(this.executeFunctionOnEnabled(module.modname, 'getMainComponent', [injector, course, module])) @@ -263,22 +253,24 @@ export class CoreCourseModuleDelegate extends CoreDelegate { /** * Get the data required to display the module in the course contents view. * - * @param {string} modname The name of the module type. - * @param {any} module The module object. - * @param {number} courseId The course ID. - * @param {number} sectionId The section ID. - * @return {CoreCourseModuleHandlerData} Data to render the module. + * @param modname The name of the module type. + * @param module The module object. + * @param courseId The course ID. + * @param sectionId The section ID. + * @param forCoursePage Whether the data will be used to render the course page. + * @return Data to render the module. */ - getModuleDataFor(modname: string, module: any, courseId: number, sectionId: number): CoreCourseModuleHandlerData { - return this.executeFunctionOnEnabled(modname, 'getData', [module, courseId, sectionId]); + getModuleDataFor(modname: string, module: any, courseId: number, sectionId: number, forCoursePage?: boolean) + : CoreCourseModuleHandlerData { + return this.executeFunctionOnEnabled(modname, 'getData', [module, courseId, sectionId, forCoursePage]); } /** * Check if a certain module type is disabled in a site. * - * @param {string} modname The name of the module type. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with boolean: whether module is disabled. + * @param modname The name of the module type. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with boolean: whether module is disabled. */ isModuleDisabled(modname: string, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -289,12 +281,12 @@ export class CoreCourseModuleDelegate extends CoreDelegate { /** * Check if a certain module type is disabled in a site. * - * @param {string} modname The name of the module type. - * @param {CoreSite} [site] Site. If not defined, use current site. - * @return {boolean} Whether module is disabled. + * @param modname The name of the module type. + * @param site Site. If not defined, use current site. + * @return Whether module is disabled. */ isModuleDisabledInSite(modname: string, site?: CoreSite): boolean { - const handler = this.getHandler(modname, true); + const handler = this.getHandler(modname, false); if (handler) { site = site || this.sitesProvider.getCurrentSite(); @@ -309,8 +301,8 @@ export class CoreCourseModuleDelegate extends CoreDelegate { * Whether to display the course refresher in single activity course format. If it returns false, a refresher must be * included in the template that calls the doRefresh method of the component. Defaults to true. * - * @param {any} modname The name of the module type. - * @return {boolean} Whether the refresher should be displayed. + * @param modname The name of the module type. + * @return Whether the refresher should be displayed. */ displayRefresherInSingleActivity(modname: string): boolean { return this.executeFunctionOnEnabled(modname, 'displayRefresherInSingleActivity'); @@ -319,9 +311,9 @@ export class CoreCourseModuleDelegate extends CoreDelegate { /** * Get the icon src for a certain type of module. * - * @param {any} modname The name of the module type. - * @param {string} [modicon] The mod icon string. - * @return {string} The icon src. + * @param modname The name of the module type. + * @param modicon The mod icon string. + * @return The icon src. */ getModuleIconSrc(modname: string, modicon?: string): string { return this.executeFunctionOnEnabled(modname, 'getIconSrc') || this.courseProvider.getModuleIconSrc(modname, modicon); @@ -330,10 +322,10 @@ export class CoreCourseModuleDelegate extends CoreDelegate { /** * Check if a certain type of module supports a certain feature. * - * @param {string} modname The modname. - * @param {string} feature The feature to check. - * @param {any} defaultValue Value to return if the module is not supported or doesn't know if it's supported. - * @return {any} The result of the supports check. + * @param modname The modname. + * @param feature The feature to check. + * @param defaultValue Value to return if the module is not supported or doesn't know if it's supported. + * @return The result of the supports check. */ supportsFeature(modname: string, feature: string, defaultValue: any): any { const handler = this.enabledHandlers[modname]; diff --git a/src/core/course/providers/module-prefetch-delegate.ts b/src/core/course/providers/module-prefetch-delegate.ts index 267027d84..6e05b283b 100644 --- a/src/core/course/providers/module-prefetch-delegate.ts +++ b/src/core/course/providers/module-prefetch-delegate.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -27,6 +27,7 @@ import { CoreConstants } from '../../constants'; import { Md5 } from 'ts-md5/dist/md5'; import { Subject, BehaviorSubject, Subscription } from 'rxjs'; import { CoreDelegate, CoreDelegateHandler } from '@classes/delegate'; +import { CoreFileHelperProvider } from '@providers/file-helper'; /** * Progress of downloading a list of modules. @@ -34,13 +35,11 @@ import { CoreDelegate, CoreDelegateHandler } from '@classes/delegate'; export type CoreCourseModulesProgress = { /** * Number of modules downloaded so far. - * @type {number} */ count: number; /** * Toal of modules to download. - * @type {number} */ total: number; }; @@ -48,7 +47,7 @@ export type CoreCourseModulesProgress = { /** * Progress function for downloading a list of modules. * - * @param {CoreCourseModulesProgress} data Progress data. + * @param data Progress data. */ export type CoreCourseModulesProgressFunction = (data: CoreCourseModulesProgress) => void; @@ -58,65 +57,60 @@ export type CoreCourseModulesProgressFunction = (data: CoreCourseModulesProgress export interface CoreCourseModulePrefetchHandler extends CoreDelegateHandler { /** * Name of the handler. - * @type {string} */ name: string; /** * Name of the module. It should match the "modname" of the module returned in core_course_get_contents. - * @type {string} */ modName: string; /** * The handler's component. - * @type {string} */ component: string; /** * The RegExp to check updates. If a module has an update whose name matches this RegExp, the module will be marked * as outdated. This RegExp is ignored if hasUpdates function is defined. - * @type {RegExp} */ updatesNames?: RegExp; /** * If true, this module will be treated as not downloadable when determining the status of a list of modules. The module will * still be downloaded when downloading the section/course, it only affects whether the button should be displayed. - * @type {boolean} */ skipListStatus: boolean; /** * Get the download size of a module. * - * @param {any} module Module. - * @param {number} courseId Course ID the module belongs to. - * @param {boolean} [single] True if we're downloading a single module, false if we're downloading a whole section. - * @return {Promise<{size: number, total: boolean}>} Promise resolved with the size and a boolean indicating if it was able - * to calculate the total size. + * @param module Module. + * @param courseId Course ID the module belongs to. + * @param single True if we're downloading a single module, false if we're downloading a whole section. + * @return Promise resolved with the size and a boolean indicating if it was able + * to calculate the total size. */ getDownloadSize(module: any, courseId: number, single?: boolean): Promise<{ size: number, total: boolean }>; /** * Prefetch a module. * - * @param {any} module Module. - * @param {number} courseId Course ID the module belongs to. - * @param {boolean} [single] True if we're downloading a single module, false if we're downloading a whole section. - * @param {string} [dirPath] Path of the directory where to store all the content files. - * @return {Promise} Promise resolved when done. + * @param module Module. + * @param courseId Course ID the module belongs to. + * @param single True if we're downloading a single module, false if we're downloading a whole section. + * @param dirPath Path of the directory where to store all the content files. + * @return Promise resolved when done. */ prefetch(module: any, courseId?: number, single?: boolean, dirPath?: string): Promise; /** * Download the module. * - * @param {any} module The module object returned by WS. - * @param {number} courseId Course ID. - * @param {string} [dirPath] Path of the directory where to store all the content files. - * @return {Promise} Promise resolved when all content is downloaded. + * @param module The module object returned by WS. + * @param courseId Course ID. + * @param dirPath Path of the directory where to store all the content files. + * @return Promise resolved when all content is downloaded. */ download?(module: any, courseId: number, dirPath?: string): Promise; @@ -125,9 +119,9 @@ export interface CoreCourseModulePrefetchHandler extends CoreDelegateHandler { * If not defined, it will assume all modules can be checked. * The modules that return false will always be shown as outdated when they're downloaded. * - * @param {any} module Module. - * @param {number} courseId Course ID the module belongs to. - * @return {boolean|Promise} Whether the module can use check_updates. The promise should never be rejected. + * @param module Module. + * @param courseId Course ID the module belongs to. + * @return Whether the module can use check_updates. The promise should never be rejected. */ canUseCheckUpdates?(module: any, courseId: number): boolean | Promise; @@ -135,38 +129,38 @@ export interface CoreCourseModulePrefetchHandler extends CoreDelegateHandler { * Return the status to show based on current status. E.g. a module might want to show outdated instead of downloaded. * If not implemented, the original status will be returned. * - * @param {any} module Module. - * @param {string} status The current status. - * @param {boolean} canCheck Whether the site allows checking for updates. - * @return {string} Status to display. + * @param module Module. + * @param status The current status. + * @param canCheck Whether the site allows checking for updates. + * @return Status to display. */ determineStatus?(module: any, status: string, canCheck: boolean): string; /** * Get the downloaded size of a module. If not defined, we'll use getFiles to calculate it (it can be slow). * - * @param {any} module Module. - * @param {number} courseId Course ID the module belongs to. - * @return {number|Promise} Size, or promise resolved with the size. + * @param module Module. + * @param courseId Course ID the module belongs to. + * @return Size, or promise resolved with the size. */ getDownloadedSize?(module: any, courseId: number): number | Promise; /** * Get the list of files of the module. If not defined, we'll assume they are in module.contents. * - * @param {any} module Module. - * @param {number} courseId Course ID the module belongs to. - * @return {any[]|Promise} List of files, or promise resolved with the files. + * @param module Module. + * @param courseId Course ID the module belongs to. + * @return List of files, or promise resolved with the files. */ getFiles?(module: any, courseId: number): any[] | Promise; /** * Check if a certain module has updates based on the result of check updates. * - * @param {any} module Module. - * @param {number} courseId Course ID the module belongs to. - * @param {any[]} moduleUpdates List of updates for the module. - * @return {boolean|Promise} Whether the module has updates. The promise should never be rejected. + * @param module Module. + * @param courseId Course ID the module belongs to. + * @param moduleUpdates List of updates for the module. + * @return Whether the module has updates. The promise should never be rejected. */ hasUpdates?(module: any, courseId: number, moduleUpdates: any[]): boolean | Promise; @@ -174,46 +168,46 @@ export interface CoreCourseModulePrefetchHandler extends CoreDelegateHandler { * Invalidate WS calls needed to determine module status (usually, to check if module is downloadable). * It doesn't need to invalidate check updates. It should NOT invalidate files nor all the prefetched data. * - * @param {any} module Module. - * @param {number} courseId Course ID the module belongs to. - * @return {Promise} Promise resolved when invalidated. + * @param module Module. + * @param courseId Course ID the module belongs to. + * @return Promise resolved when invalidated. */ invalidateModule?(module: any, courseId: number): Promise; /** * Check if a module can be downloaded. If the function is not defined, we assume that all modules are downloadable. * - * @param {any} module Module. - * @param {number} courseId Course ID the module belongs to. - * @return {boolean|Promise} Whether the module can be downloaded. The promise should never be rejected. + * @param module Module. + * @param courseId Course ID the module belongs to. + * @return Whether the module can be downloaded. The promise should never be rejected. */ isDownloadable?(module: any, courseId: number): boolean | Promise; /** * Load module contents in module.contents if they aren't loaded already. This is meant for resources. * - * @param {any} module Module. - * @param {number} courseId Course ID the module belongs to. - * @return {Promise} Promise resolved when done. + * @param module Module. + * @param courseId Course ID the module belongs to. + * @return Promise resolved when done. */ loadContents?(module: any, courseId: number): Promise; /** * Remove module downloaded files. If not defined, we'll use getFiles to remove them (slow). * - * @param {any} module Module. - * @param {number} courseId Course ID the module belongs to. - * @return {Promise} Promise resolved when done. + * @param module Module. + * @param courseId Course ID the module belongs to. + * @return Promise resolved when done. */ removeFiles?(module: any, courseId: number): Promise; /** * Sync a module. * - * @param {any} module Module. - * @param {number} courseId Course ID the module belongs to - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when done. + * @param module Module. + * @param courseId Course ID the module belongs to + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when done. */ sync?(module: any, courseId: number, siteId?: any): Promise; } @@ -265,10 +259,15 @@ export class CoreCourseModulePrefetchDelegate extends CoreDelegate { } } = {}; - constructor(loggerProvider: CoreLoggerProvider, protected sitesProvider: CoreSitesProvider, private utils: CoreUtilsProvider, - private courseProvider: CoreCourseProvider, private filepoolProvider: CoreFilepoolProvider, - private timeUtils: CoreTimeUtilsProvider, private fileProvider: CoreFileProvider, - protected eventsProvider: CoreEventsProvider) { + constructor(loggerProvider: CoreLoggerProvider, + protected sitesProvider: CoreSitesProvider, + protected utils: CoreUtilsProvider, + protected courseProvider: CoreCourseProvider, + protected filepoolProvider: CoreFilepoolProvider, + protected timeUtils: CoreTimeUtilsProvider, + protected fileProvider: CoreFileProvider, + protected eventsProvider: CoreEventsProvider, + protected fileHelper: CoreFileHelperProvider) { super('CoreCourseModulePrefetchDelegate', loggerProvider, sitesProvider, eventsProvider); this.sitesProvider.registerSiteSchema(this.siteSchema); @@ -282,7 +281,7 @@ export class CoreCourseModulePrefetchDelegate extends CoreDelegate { /** * Check if current site can check updates using core_course_check_updates. * - * @return {boolean} True if can check updates, false otherwise. + * @return True if can check updates, false otherwise. */ canCheckUpdates(): boolean { return this.sitesProvider.wsAvailableInCurrentSite('core_course_check_updates'); @@ -291,9 +290,9 @@ export class CoreCourseModulePrefetchDelegate extends CoreDelegate { /** * Check if a certain module can use core_course_check_updates. * - * @param {any} module Module. - * @param {number} courseId Course ID the module belongs to. - * @return {Promise} Promise resolved with boolean: whether the module can use check updates WS. + * @param module Module. + * @param courseId Course ID the module belongs to. + * @return Promise resolved with boolean: whether the module can use check updates WS. */ canModuleUseCheckUpdates(module: any, courseId: number): Promise { const handler = this.getPrefetchHandlerFor(module); @@ -321,9 +320,9 @@ export class CoreCourseModulePrefetchDelegate extends CoreDelegate { /** * Creates the list of modules to check for get course updates. * - * @param {any[]} modules List of modules. - * @param {number} courseId Course ID the modules belong to. - * @return {Promise<{toCheck: any[], cannotUse: any[]}>} Promise resolved with the lists. + * @param modules List of modules. + * @param courseId Course ID the modules belong to. + * @return Promise resolved with the lists. */ protected createToCheckList(modules: any[], courseId: number): Promise<{ toCheck: any[], cannotUse: any[] }> { const result = { @@ -368,10 +367,10 @@ export class CoreCourseModulePrefetchDelegate extends CoreDelegate { /** * Determines a module status based on current status, restoring downloads if needed. * - * @param {any} module Module. - * @param {string} status Current status. - * @param {boolean} [canCheck] True if updates can be checked using core_course_check_updates. - * @return {string} Module status. + * @param module Module. + * @param status Current status. + * @param canCheck True if updates can be checked using core_course_check_updates. + * @return Module status. */ determineModuleStatus(module: any, status: string, canCheck?: boolean): string { const handler = this.getPrefetchHandlerFor(module), @@ -399,10 +398,10 @@ export class CoreCourseModulePrefetchDelegate extends CoreDelegate { /** * Check for updates in a course. * - * @param {any[]} modules List of modules. - * @param {number} courseId Course ID the modules belong to. - * @return {Promise} Promise resolved with the updates. If a module is set to false, it means updates cannot be - * checked for that module in the current site. + * @param modules List of modules. + * @param courseId Course ID the modules belong to. + * @return Promise resolved with the updates. If a module is set to false, it means updates cannot be + * checked for that module in the current site. */ getCourseUpdates(modules: any[], courseId: number): Promise { if (!this.canCheckUpdates()) { @@ -491,8 +490,8 @@ export class CoreCourseModulePrefetchDelegate extends CoreDelegate { /** * Check for updates in a course. * - * @param {number} courseId Course ID the modules belong to. - * @return {Promise} Promise resolved with the updates. + * @param courseId Course ID the modules belong to. + * @return Promise resolved with the updates. */ getCourseUpdatesByCourseId(courseId: number): Promise { if (!this.canCheckUpdates()) { @@ -508,8 +507,8 @@ export class CoreCourseModulePrefetchDelegate extends CoreDelegate { /** * Get cache key for course updates WS calls. * - * @param {number} courseId Course ID. - * @return {string} Cache key. + * @param courseId Course ID. + * @return Cache key. */ protected getCourseUpdatesCacheKey(courseId: number): string { return this.ROOT_CACHE_KEY + 'courseUpdates:' + courseId; @@ -518,10 +517,10 @@ export class CoreCourseModulePrefetchDelegate extends CoreDelegate { /** * Get modules download size. Only treat the modules with status not downloaded or outdated. * - * @param {any[]} modules List of modules. - * @param {number} courseId Course ID the modules belong to. - * @return {Promise<{size: number, total: boolean}>} Promise resolved with the size and a boolean indicating if it was able - * to calculate the total size. + * @param modules List of modules. + * @param courseId Course ID the modules belong to. + * @return Promise resolved with the size and a boolean indicating if it was able + * to calculate the total size. */ getDownloadSize(modules: any[], courseId: number): Promise<{ size: number, total: boolean }> { // Get the status of each module. @@ -549,11 +548,11 @@ export class CoreCourseModulePrefetchDelegate extends CoreDelegate { /** * Get the download size of a module. * - * @param {any} module Module to get size. - * @param {Number} courseId Course ID the module belongs to. - * @param {boolean} [single] True if we're downloading a single module, false if we're downloading a whole section. - * @return {Promise<{size: number, total: boolean}>} Promise resolved with the size and a boolean indicating if it was able - * to calculate the total size. + * @param module Module to get size. + * @param courseId Course ID the module belongs to. + * @param single True if we're downloading a single module, false if we're downloading a whole section. + * @return Promise resolved with the size and a boolean indicating if it was able + * to calculate the total size. */ getModuleDownloadSize(module: any, courseId: number, single?: boolean): Promise<{ size: number, total: boolean }> { const handler = this.getPrefetchHandlerFor(module); @@ -592,9 +591,9 @@ export class CoreCourseModulePrefetchDelegate extends CoreDelegate { /** * Get the download size of a module. * - * @param {any} module Module to get size. - * @param {number} courseId Course ID the module belongs to. - * @return {Promise} Promise resolved with the size. + * @param module Module to get size. + * @param courseId Course ID the module belongs to. + * @return Promise resolved with the size. */ getModuleDownloadedSize(module: any, courseId: number): Promise { const handler = this.getPrefetchHandlerFor(module); @@ -664,9 +663,9 @@ export class CoreCourseModulePrefetchDelegate extends CoreDelegate { /** * Get module files. * - * @param {any} module Module to get the files. - * @param {number} courseId Course ID the module belongs to. - * @return {Promise} Promise resolved with the list of files. + * @param module Module to get the files. + * @param courseId Course ID the module belongs to. + * @return Promise resolved with the list of files. */ getModuleFiles(module: any, courseId: number): Promise { const handler = this.getPrefetchHandlerFor(module); @@ -687,13 +686,13 @@ export class CoreCourseModulePrefetchDelegate extends CoreDelegate { /** * Get the module status. * - * @param {any} module Module. - * @param {number} courseId Course ID the module belongs to. - * @param {any} [updates] Result of getCourseUpdates for all modules in the course. If not provided, it will be - * calculated (slower). If it's false it means the site doesn't support check updates. - * @param {boolean} [refresh] True if it should ignore the cache. - * @param {number} [sectionId] ID of the section the module belongs to. - * @return {Promise} Promise resolved with the status. + * @param module Module. + * @param courseId Course ID the module belongs to. + * @param updates Result of getCourseUpdates for all modules in the course. If not provided, it will be + * calculated (slower). If it's false it means the site doesn't support check updates. + * @param refresh True if it should ignore the cache. + * @param sectionId ID of the section the module belongs to. + * @return Promise resolved with the status. */ getModuleStatus(module: any, courseId: number, updates?: any, refresh?: boolean, sectionId?: number): Promise { const handler = this.getPrefetchHandlerFor(module), @@ -790,19 +789,19 @@ export class CoreCourseModulePrefetchDelegate extends CoreDelegate { * Get the status of a list of modules, along with the lists of modules for each status. * @see {@link CoreFilepoolProvider.determinePackagesStatus} * - * @param {any[]} modules List of modules to prefetch. - * @param {number} courseId Course ID the modules belong to. - * @param {number} [sectionId] ID of the section the modules belong to. - * @param {boolean} [refresh] True if it should always check the DB (slower). - * @param {boolean} [onlyToDisplay] True if the status will only be used to determine which button should be displayed. - * @param {boolean} [checkUpdates=true] Whether to use the WS to check updates. Defaults to true. - * @return {Promise} Promise resolved with an object with the following properties: - * - status (string) Status of the module. - * - total (number) Number of modules. - * - CoreConstants.NOT_DOWNLOADED (any[]) Modules with state NOT_DOWNLOADED. - * - CoreConstants.DOWNLOADED (any[]) Modules with state DOWNLOADED. - * - CoreConstants.DOWNLOADING (any[]) Modules with state DOWNLOADING. - * - CoreConstants.OUTDATED (any[]) Modules with state OUTDATED. + * @param modules List of modules to prefetch. + * @param courseId Course ID the modules belong to. + * @param sectionId ID of the section the modules belong to. + * @param refresh True if it should always check the DB (slower). + * @param onlyToDisplay True if the status will only be used to determine which button should be displayed. + * @param checkUpdates Whether to use the WS to check updates. Defaults to true. + * @return Promise resolved with an object with the following properties: + * - status (string) Status of the module. + * - total (number) Number of modules. + * - CoreConstants.NOT_DOWNLOADED (any[]) Modules with state NOT_DOWNLOADED. + * - CoreConstants.DOWNLOADED (any[]) Modules with state DOWNLOADED. + * - CoreConstants.DOWNLOADING (any[]) Modules with state DOWNLOADING. + * - CoreConstants.OUTDATED (any[]) Modules with state OUTDATED. */ getModulesStatus(modules: any[], courseId: number, sectionId?: number, refresh?: boolean, onlyToDisplay?: boolean, checkUpdates: boolean = true): any { @@ -875,9 +874,9 @@ export class CoreCourseModulePrefetchDelegate extends CoreDelegate { /** * Get a module status and download time. It will only return the download time if the module is downloaded or outdated. * - * @param {any} module Module. - * @param {number} courseId Course ID the module belongs to. - * @return {Promise<{status: string, downloadTime?: number}>} Promise resolved with the data. + * @param module Module. + * @param courseId Course ID the module belongs to. + * @return Promise resolved with the data. */ protected getModuleStatusAndDownloadTime(module: any, courseId: number): Promise<{ status: string, downloadTime?: number }> { const handler = this.getPrefetchHandlerFor(module), @@ -888,7 +887,7 @@ export class CoreCourseModulePrefetchDelegate extends CoreDelegate { const packageId = this.filepoolProvider.getPackageId(handler.component, module.id), status = this.statusCache.getValue(packageId, 'status'); - if (typeof status != 'undefined' && status != CoreConstants.DOWNLOADED && status != CoreConstants.OUTDATED) { + if (typeof status != 'undefined' && !this.fileHelper.isStateDownloaded(status)) { // Module isn't downloaded, just return the status. return Promise.resolve({ status: status @@ -923,18 +922,18 @@ export class CoreCourseModulePrefetchDelegate extends CoreDelegate { * Get updates for a certain module. * It will only return the updates if the module can use check updates and it's downloaded or outdated. * - * @param {any} module Module to check. - * @param {number} courseId Course the module belongs to. - * @param {boolean} [ignoreCache] True if it should ignore cached data (it will always fail in offline or server down). - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with the updates. + * @param module Module to check. + * @param courseId Course the module belongs to. + * @param ignoreCache True if it should ignore cached data (it will always fail in offline or server down). + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the updates. */ getModuleUpdates(module: any, courseId: number, ignoreCache?: boolean, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { // Get the status and download time of the module. return this.getModuleStatusAndDownloadTime(module, courseId).then((data) => { - if (data.status != CoreConstants.DOWNLOADED && data.status != CoreConstants.OUTDATED) { + if (!this.fileHelper.isStateDownloaded(data.status)) { // Not downloaded, no updates. return {}; } @@ -980,9 +979,9 @@ export class CoreCourseModulePrefetchDelegate extends CoreDelegate { /** * Get cache key for module updates WS calls. * - * @param {number} courseId Course ID. - * @param {number} moduleId Module ID. - * @return {string} Cache key. + * @param courseId Course ID. + * @param moduleId Module ID. + * @return Cache key. */ protected getModuleUpdatesCacheKey(courseId: number, moduleId: number): string { return this.getCourseUpdatesCacheKey(courseId) + ':' + moduleId; @@ -991,8 +990,8 @@ export class CoreCourseModulePrefetchDelegate extends CoreDelegate { /** * Get a prefetch handler. * - * @param {any} module The module to work on. - * @return {CoreCourseModulePrefetchHandler} Prefetch handler. + * @param module The module to work on. + * @return Prefetch handler. */ getPrefetchHandlerFor(module: any): CoreCourseModulePrefetchHandler { return this.getHandler(module.modname, true); @@ -1001,8 +1000,8 @@ export class CoreCourseModulePrefetchDelegate extends CoreDelegate { /** * Invalidate check updates WS call. * - * @param {number} courseId Course ID. - * @return {Promise} Promise resolved when data is invalidated. + * @param courseId Course ID. + * @return Promise resolved when data is invalidated. */ invalidateCourseUpdates(courseId: number): Promise { return this.sitesProvider.getCurrentSite().invalidateWsCacheForKey(this.getCourseUpdatesCacheKey(courseId)); @@ -1011,9 +1010,9 @@ export class CoreCourseModulePrefetchDelegate extends CoreDelegate { /** * Invalidate a list of modules in a course. This should only invalidate WS calls, not downloaded files. * - * @param {any[]} modules List of modules. - * @param {number} courseId Course ID. - * @return {Promise} Promise resolved when modules are invalidated. + * @param modules List of modules. + * @param courseId Course ID. + * @return Promise resolved when modules are invalidated. */ invalidateModules(modules: any[], courseId: number): Promise { const promises = []; @@ -1040,7 +1039,7 @@ export class CoreCourseModulePrefetchDelegate extends CoreDelegate { /** * Invalidates the cache for a given module. * - * @param {any} module Module to be invalidated. + * @param module Module to be invalidated. */ invalidateModuleStatusCache(module: any): void { const handler = this.getPrefetchHandlerFor(module); @@ -1052,10 +1051,10 @@ export class CoreCourseModulePrefetchDelegate extends CoreDelegate { /** * Invalidate check updates WS call for a certain module. * - * @param {number} courseId Course ID. - * @param {number} moduleId Module ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when data is invalidated. + * @param courseId Course ID. + * @param moduleId Module ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when data is invalidated. */ invalidateModuleUpdates(courseId: number, moduleId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -1066,8 +1065,8 @@ export class CoreCourseModulePrefetchDelegate extends CoreDelegate { /** * Check if a list of modules is being downloaded. * - * @param {string} id An ID to identify the download. - * @return {boolean} True if it's being downloaded, false otherwise. + * @param id An ID to identify the download. + * @return True if it's being downloaded, false otherwise. */ isBeingDownloaded(id: string): boolean { const siteId = this.sitesProvider.getCurrentSiteId(); @@ -1078,9 +1077,9 @@ export class CoreCourseModulePrefetchDelegate extends CoreDelegate { /** * Check if a module is downloadable. * - * @param {any} module Module. - * @param {Number} courseId Course ID the module belongs to. - * @return {Promise} Promise resolved with true if downloadable, false otherwise. + * @param module Module. + * @param courseId Course ID the module belongs to. + * @return Promise resolved with true if downloadable, false otherwise. */ isModuleDownloadable(module: any, courseId: number): Promise { if (module.uservisible === false) { @@ -1118,10 +1117,10 @@ export class CoreCourseModulePrefetchDelegate extends CoreDelegate { /** * Check if a module has updates based on the result of getCourseUpdates. * - * @param {any} module Module. - * @param {number} courseId Course ID the module belongs to. - * @param {any} updates Result of getCourseUpdates. - * @return {Promise} Promise resolved with boolean: whether the module has updates. + * @param module Module. + * @param courseId Course ID the module belongs to. + * @param updates Result of getCourseUpdates. + * @return Promise resolved with boolean: whether the module has updates. */ moduleHasUpdates(module: any, courseId: number, updates: any): Promise { const handler = this.getPrefetchHandlerFor(module), @@ -1151,10 +1150,10 @@ export class CoreCourseModulePrefetchDelegate extends CoreDelegate { /** * Prefetch a module. * - * @param {any} module Module to prefetch. - * @param {number} courseId Course ID the module belongs to. - * @param {boolean} [single] True if we're downloading a single module, false if we're downloading a whole section. - * @return {Promise} Promise resolved when finished. + * @param module Module to prefetch. + * @param courseId Course ID the module belongs to. + * @param single True if we're downloading a single module, false if we're downloading a whole section. + * @return Promise resolved when finished. */ prefetchModule(module: any, courseId: number, single?: boolean): Promise { const handler = this.getPrefetchHandlerFor(module); @@ -1172,9 +1171,9 @@ export class CoreCourseModulePrefetchDelegate extends CoreDelegate { /** * Sync a group of modules. * - * @param {any[]} modules Array of modules to sync. - * @param {number} courseId Course ID the module belongs to. - * @return {Promise} Promise resolved when finished. + * @param modules Array of modules to sync. + * @param courseId Course ID the module belongs to. + * @return Promise resolved when finished. */ syncModules(modules: any[], courseId: number): Promise { return Promise.all(modules.map((module) => { @@ -1190,9 +1189,9 @@ export class CoreCourseModulePrefetchDelegate extends CoreDelegate { /** * Sync a module. * - * @param {any} module Module to sync. - * @param {number} courseId Course ID the module belongs to. - * @return {Promise} Promise resolved when finished. + * @param module Module to sync. + * @param courseId Course ID the module belongs to. + * @return Promise resolved when finished. */ syncModule(module: any, courseId: number): Promise { const handler = this.getPrefetchHandlerFor(module); @@ -1215,11 +1214,11 @@ export class CoreCourseModulePrefetchDelegate extends CoreDelegate { * Prefetches a list of modules using their prefetch handlers. * If a prefetch already exists for this site and id, returns the current promise. * - * @param {string} id An ID to identify the download. It can be used to retrieve the download promise. - * @param {any[]} modules List of modules to prefetch. - * @param {number} courseId Course ID the modules belong to. - * @param {CoreCourseModulesProgressFunction} [onProgress] Function to call everytime a module is downloaded. - * @return {Promise} Promise resolved when all modules have been prefetched. + * @param id An ID to identify the download. It can be used to retrieve the download promise. + * @param modules List of modules to prefetch. + * @param courseId Course ID the modules belong to. + * @param onProgress Function to call everytime a module is downloaded. + * @return Promise resolved when all modules have been prefetched. */ prefetchModules(id: string, modules: any[], courseId: number, onProgress?: CoreCourseModulesProgressFunction): Promise { @@ -1294,9 +1293,9 @@ export class CoreCourseModulePrefetchDelegate extends CoreDelegate { /** * Remove module Files from handler. * - * @param {any} module Module to remove the files. - * @param {number} courseId Course ID the module belongs to. - * @return {Promise} Promise resolved when done. + * @param module Module to remove the files. + * @param courseId Course ID the module belongs to. + * @return Promise resolved when done. */ removeModuleFiles(module: any, courseId: number): Promise { const handler = this.getPrefetchHandlerFor(module), @@ -1334,8 +1333,8 @@ export class CoreCourseModulePrefetchDelegate extends CoreDelegate { /** * Set an on progress function for the download of a list of modules. * - * @param {string} id An ID to identify the download. - * @param {CoreCourseModulesProgressFunction} onProgress Function to call everytime a module is downloaded. + * @param id An ID to identify the download. + * @param onProgress Function to call everytime a module is downloaded. */ setOnProgress(id: string, onProgress: CoreCourseModulesProgressFunction): void { const siteId = this.sitesProvider.getCurrentSiteId(), @@ -1350,9 +1349,9 @@ export class CoreCourseModulePrefetchDelegate extends CoreDelegate { /** * If courseId or sectionId is set, save them in the cache. * - * @param {string} packageId The package ID. - * @param {number} [courseId] Course ID. - * @param {number} [sectionId] Section ID. + * @param packageId The package ID. + * @param courseId Course ID. + * @param sectionId Section ID. */ storeCourseAndSection(packageId: string, courseId?: number, sectionId?: number): void { if (courseId) { @@ -1366,12 +1365,12 @@ export class CoreCourseModulePrefetchDelegate extends CoreDelegate { /** * Treat the result of the check updates WS call. * - * @param {any[]} toCheckList List of modules to check (from createToCheckList). - * @param {any} response WS call response. - * @param {any} result Object where to store the result. - * @param {number} [previousTime] Time of the previous check updates execution. If set, modules downloaded - * after this time will be ignored. - * @return {any} Result. + * @param toCheckList List of modules to check (from createToCheckList). + * @param response WS call response. + * @param result Object where to store the result. + * @param previousTime Time of the previous check updates execution. If set, modules downloaded + * after this time will be ignored. + * @return Result. */ protected treatCheckUpdatesResult(toCheckList: any[], response: any, result: any, previousTime?: number): any { // Format the response to index it by module ID. @@ -1399,11 +1398,11 @@ export class CoreCourseModulePrefetchDelegate extends CoreDelegate { /** * Update the status of a module in the "cache". * - * @param {string} status New status. - * @param {string} component Package's component. - * @param {string|number} [componentId] An ID to use in conjunction with the component. - * @param {number} [courseId] Course ID of the module. - * @param {number} [sectionId] Section ID of the module. + * @param status New status. + * @param component Package's component. + * @param componentId An ID to use in conjunction with the component. + * @param courseId Course ID of the module. + * @param sectionId Section ID of the module. */ updateStatusCache(status: string, component: string, componentId?: string | number, courseId?: number, sectionId?: number) : void { diff --git a/src/core/course/providers/modules-tag-area-handler.ts b/src/core/course/providers/modules-tag-area-handler.ts index 4b7dcfc0a..fb8ebf6d1 100644 --- a/src/core/course/providers/modules-tag-area-handler.ts +++ b/src/core/course/providers/modules-tag-area-handler.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -29,7 +29,7 @@ export class CoreCourseModulesTagAreaHandler implements CoreTagAreaHandler { /** * Whether or not the handler is enabled on a site level. - * @return {boolean|Promise} Whether or not the handler is enabled on a site level. + * @return Whether or not the handler is enabled on a site level. */ isEnabled(): boolean | Promise { return true; @@ -38,8 +38,8 @@ export class CoreCourseModulesTagAreaHandler implements CoreTagAreaHandler { /** * Parses the rendered content of a tag index and returns the items. * - * @param {string} content Rendered content. - * @return {any[]|Promise} Area items (or promise resolved with the items). + * @param content Rendered content. + * @return Area items (or promise resolved with the items). */ parseContent(content: string): any[] | Promise { return this.tagHelper.parseFeedContent(content); @@ -48,8 +48,8 @@ export class CoreCourseModulesTagAreaHandler implements CoreTagAreaHandler { /** * Get the component to use to display items. * - * @param {Injector} injector Injector. - * @return {any|Promise} The component (or promise resolved with component) to use, undefined if not found. + * @param injector Injector. + * @return The component (or promise resolved with component) to use, undefined if not found. */ getComponent(injector: Injector): any | Promise { return CoreTagFeedComponent; diff --git a/src/core/course/providers/options-delegate.ts b/src/core/course/providers/options-delegate.ts index 8f0ac6124..9d2745056 100644 --- a/src/core/course/providers/options-delegate.ts +++ b/src/core/course/providers/options-delegate.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -27,51 +27,49 @@ import { CoreCourseProvider } from './course'; export interface CoreCourseOptionsHandler extends CoreDelegateHandler { /** * The highest priority is displayed first. - * @type {number} */ priority: number; /** * True if this handler should appear in menu rather than as a tab. - * @type {boolean} */ isMenuHandler?: boolean; /** * Whether or not the handler is enabled for a certain course. * - * @param {number} courseId The course ID. - * @param {any} accessData Access type and data. Default, guest, ... - * @param {any} [navOptions] Course navigation options for current user. See CoreCoursesProvider.getUserNavigationOptions. - * @param {any} [admOptions] Course admin options for current user. See CoreCoursesProvider.getUserAdministrationOptions. - * @return {boolean|Promise} True or promise resolved with true if enabled. + * @param courseId The course ID. + * @param accessData Access type and data. Default, guest, ... + * @param navOptions Course navigation options for current user. See CoreCoursesProvider.getUserNavigationOptions. + * @param admOptions Course admin options for current user. See CoreCoursesProvider.getUserAdministrationOptions. + * @return True or promise resolved with true if enabled. */ isEnabledForCourse(courseId: number, accessData: any, navOptions?: any, admOptions?: any): boolean | Promise; /** * Returns the data needed to render the handler. * - * @param {Injector} injector Injector. - * @param {number} course The course. - * @return {CoreCourseOptionsHandlerData|Promise} Data or promise resolved with the data. + * @param injector Injector. + * @param course The course. + * @return Data or promise resolved with the data. */ getDisplayData?(injector: Injector, course: any): CoreCourseOptionsHandlerData | Promise; /** * Should invalidate the data to determine if the handler is enabled for a certain course. * - * @param {number} courseId The course ID. - * @param {any} [navOptions] Course navigation options for current user. See CoreCoursesProvider.getUserNavigationOptions. - * @param {any} [admOptions] Course admin options for current user. See CoreCoursesProvider.getUserAdministrationOptions. - * @return {Promise} Promise resolved when done. + * @param courseId The course ID. + * @param navOptions Course navigation options for current user. See CoreCoursesProvider.getUserNavigationOptions. + * @param admOptions Course admin options for current user. See CoreCoursesProvider.getUserAdministrationOptions. + * @return Promise resolved when done. */ invalidateEnabledForCourse?(courseId: number, navOptions?: any, admOptions?: any): Promise; /** * Called when a course is downloaded. It should prefetch all the data to be able to see the addon in offline. * - * @param {any} course The course. - * @return {Promise} Promise resolved when done. + * @param course The course. + * @return Promise resolved when done. */ prefetch?(course: any): Promise; } @@ -83,9 +81,9 @@ export interface CoreCourseOptionsMenuHandler extends CoreCourseOptionsHandler { /** * Returns the data needed to render the handler. * - * @param {Injector} injector Injector. - * @param {number} course The course. - * @return {CoreCourseOptionsMenuHandlerData|Promise} Data or promise resolved with data. + * @param injector Injector. + * @param course The course. + * @return Data or promise resolved with data. */ getMenuDisplayData(injector: Injector, course: any): CoreCourseOptionsMenuHandlerData | Promise; @@ -97,13 +95,11 @@ export interface CoreCourseOptionsMenuHandler extends CoreCourseOptionsHandler { export interface CoreCourseOptionsHandlerData { /** * Title to display for the handler. - * @type {string} */ title: string; /** * Class to add to the displayed handler. - * @type {string} */ class?: string; @@ -115,7 +111,6 @@ export interface CoreCourseOptionsHandlerData { /** * Data to pass to the component. All the properties in this object will be passed to the component as inputs. - * @type {any} */ componentData?: any; } @@ -126,31 +121,26 @@ export interface CoreCourseOptionsHandlerData { export interface CoreCourseOptionsMenuHandlerData { /** * Title to display for the handler. - * @type {string} */ title: string; /** * Class to add to the displayed handler. - * @type {string} */ class?: string; /** * Name of the page to load for the handler. - * @type {string} */ page: string; /** * Params to pass to the page (other than 'course' which is always sent). - * @type {any} */ pageParams?: any; /** * Name of the icon to display for the handler. - * @type {string} */ icon: string; // Name of the icon to display in the tab. } @@ -161,27 +151,24 @@ export interface CoreCourseOptionsMenuHandlerData { export interface CoreCourseOptionsHandlerToDisplay { /** * Data to display. - * @type {CoreCourseOptionsHandlerData} */ data: CoreCourseOptionsHandlerData; /** * Name of the handler, or name and sub context (AddonMessages, AddonMessages:blockContact, ...). - * @type {string} */ name: string; /** * The highest priority is displayed first. - * @type {number} */ priority?: number; /** * Called when a course is downloaded. It should prefetch all the data to be able to see the addon in offline. * - * @param {any} course The course. - * @return {Promise} Promise resolved when done. + * @param course The course. + * @return Promise resolved when done. */ prefetch?(course: any): Promise; } @@ -192,27 +179,24 @@ export interface CoreCourseOptionsHandlerToDisplay { export interface CoreCourseOptionsMenuHandlerToDisplay { /** * Data to display. - * @type {CoreCourseOptionsMenuHandlerData} */ data: CoreCourseOptionsMenuHandlerData; /** * Name of the handler, or name and sub context (AddonMessages, AddonMessages:blockContact, ...). - * @type {string} */ name: string; /** * The highest priority is displayed first. - * @type {number} */ priority?: number; /** * Called when a course is downloaded. It should prefetch all the data to be able to see the addon in offline. * - * @param {any} course The course. - * @return {Promise} Promise resolved when done. + * @param course The course. + * @return Promise resolved when done. */ prefetch?(course: any): Promise; } @@ -245,8 +229,8 @@ export class CoreCourseOptionsDelegate extends CoreDelegate { /** * Check if handlers are loaded for a certain course. * - * @param {number} courseId The course ID to check. - * @return {boolean} True if handlers are loaded, false otherwise. + * @param courseId The course ID to check. + * @return True if handlers are loaded, false otherwise. */ areHandlersLoaded(courseId: number): boolean { return !!this.loaded[courseId]; @@ -255,7 +239,7 @@ export class CoreCourseOptionsDelegate extends CoreDelegate { /** * Clear all course options handlers. * - * @param {number} [courseId] The course ID. If not defined, all handlers will be cleared. + * @param courseId The course ID. If not defined, all handlers will be cleared. */ protected clearCoursesHandlers(courseId?: number): void { if (courseId) { @@ -270,8 +254,8 @@ export class CoreCourseOptionsDelegate extends CoreDelegate { /** * Clear all courses handlers and invalidate its options. * - * @param {number} [courseId] The course ID. If not defined, all handlers will be cleared. - * @return {Promise} Promise resolved when done. + * @param courseId The course ID. If not defined, all handlers will be cleared. + * @return Promise resolved when done. */ clearAndInvalidateCoursesOptions(courseId?: number): Promise { const promises = []; @@ -301,12 +285,12 @@ export class CoreCourseOptionsDelegate extends CoreDelegate { /** * Get the handlers for a course using a certain access type. * - * @param {number} courseId The course ID. - * @param {boolean} refresh True if it should refresh the list. - * @param {any} accessData Access type and data. Default, guest, ... - * @param {any} [navOptions] Course navigation options for current user. See CoreCoursesProvider.getUserNavigationOptions. - * @param {any} [admOptions] Course admin options for current user. See CoreCoursesProvider.getUserAdministrationOptions. - * @return {Promise} Promise resolved with array of handlers. + * @param courseId The course ID. + * @param refresh True if it should refresh the list. + * @param accessData Access type and data. Default, guest, ... + * @param navOptions Course navigation options for current user. See CoreCoursesProvider.getUserNavigationOptions. + * @param admOptions Course admin options for current user. See CoreCoursesProvider.getUserAdministrationOptions. + * @return Promise resolved with array of handlers. */ protected getHandlersForAccess(courseId: number, refresh: boolean, accessData: any, navOptions?: any, admOptions?: any): Promise { @@ -336,13 +320,13 @@ export class CoreCourseOptionsDelegate extends CoreDelegate { * Get the list of handlers that should be displayed for a course. * This function should be called only when the handlers need to be displayed, since it can call several WebServices. * - * @param {Injector} injector Injector. - * @param {any} course The course object. - * @param {boolean} [refresh] True if it should refresh the list. - * @param {boolean} [isGuest] Whether it's guest. - * @param {any} [navOptions] Course navigation options for current user. See CoreCoursesProvider.getUserNavigationOptions. - * @param {any} [admOptions] Course admin options for current user. See CoreCoursesProvider.getUserAdministrationOptions. - * @return {Promise} Promise resolved with array of handlers. + * @param injector Injector. + * @param course The course object. + * @param refresh True if it should refresh the list. + * @param isGuest Whether it's guest. + * @param navOptions Course navigation options for current user. See CoreCoursesProvider.getUserNavigationOptions. + * @param admOptions Course admin options for current user. See CoreCoursesProvider.getUserAdministrationOptions. + * @return Promise resolved with array of handlers. */ getHandlersToDisplay(injector: Injector, course: any, refresh?: boolean, isGuest?: boolean, navOptions?: any, admOptions?: any): Promise { @@ -354,13 +338,13 @@ export class CoreCourseOptionsDelegate extends CoreDelegate { * Get the list of menu handlers that should be displayed for a course. * This function should be called only when the handlers need to be displayed, since it can call several WebServices. * - * @param {Injector} injector Injector. - * @param {any} course The course object. - * @param {boolean} [refresh] True if it should refresh the list. - * @param {boolean} [isGuest] Whether it's guest. - * @param {any} [navOptions] Course navigation options for current user. See CoreCoursesProvider.getUserNavigationOptions. - * @param {any} [admOptions] Course admin options for current user. See CoreCoursesProvider.getUserAdministrationOptions. - * @return {Promise} Promise resolved with array of handlers. + * @param injector Injector. + * @param course The course object. + * @param refresh True if it should refresh the list. + * @param isGuest Whether it's guest. + * @param navOptions Course navigation options for current user. See CoreCoursesProvider.getUserNavigationOptions. + * @param admOptions Course admin options for current user. See CoreCoursesProvider.getUserAdministrationOptions. + * @return Promise resolved with array of handlers. */ getMenuHandlersToDisplay(injector: Injector, course: any, refresh?: boolean, isGuest?: boolean, navOptions?: any, admOptions?: any): Promise { @@ -372,14 +356,14 @@ export class CoreCourseOptionsDelegate extends CoreDelegate { * Get the list of menu handlers that should be displayed for a course. * This function should be called only when the handlers need to be displayed, since it can call several WebServices. * - * @param {boolean} menu If true, gets menu handlers; false, gets tab handlers - * @param {Injector} injector Injector. - * @param {any} course The course object. - * @param {boolean} refresh True if it should refresh the list. - * @param {boolean} isGuest Whether it's guest. - * @param {any} navOptions Course navigation options for current user. See CoreCoursesProvider.getUserNavigationOptions. - * @param {any} admOptions Course admin options for current user. See CoreCoursesProvider.getUserAdministrationOptions. - * @return {Promise} Promise resolved with array of handlers. + * @param menu If true, gets menu handlers; false, gets tab handlers + * @param injector Injector. + * @param course The course object. + * @param refresh True if it should refresh the list. + * @param isGuest Whether it's guest. + * @param navOptions Course navigation options for current user. See CoreCoursesProvider.getUserNavigationOptions. + * @param admOptions Course admin options for current user. See CoreCoursesProvider.getUserAdministrationOptions. + * @return Promise resolved with array of handlers. */ protected getHandlersToDisplayInternal(menu: boolean, injector: Injector, course: any, refresh: boolean, isGuest: boolean, navOptions: any, admOptions: any): Promise { @@ -439,9 +423,9 @@ export class CoreCourseOptionsDelegate extends CoreDelegate { /** * Check if a course has any handler enabled for default access, using course object. * - * @param {any} course The course object. - * @param {boolean} [refresh] True if it should refresh the list. - * @return {Promise} Promise resolved with boolean: true if it has handlers, false otherwise. + * @param course The course object. + * @param refresh True if it should refresh the list. + * @return Promise resolved with boolean: true if it has handlers, false otherwise. */ hasHandlersForCourse(course: any, refresh?: boolean): Promise { // Load course options if missing. @@ -453,11 +437,11 @@ export class CoreCourseOptionsDelegate extends CoreDelegate { /** * Check if a course has any handler enabled for default access. * - * @param {number} courseId The course ID. - * @param {boolean} [refresh] True if it should refresh the list. - * @param {any} [navOptions] Course navigation options for current user. See CoreCoursesProvider.getUserNavigationOptions. - * @param {any} [admOptions] Course admin options for current user. See CoreCoursesProvider.getUserAdministrationOptions. - * @return {Promise} Promise resolved with boolean: true if it has handlers, false otherwise. + * @param courseId The course ID. + * @param refresh True if it should refresh the list. + * @param navOptions Course navigation options for current user. See CoreCoursesProvider.getUserNavigationOptions. + * @param admOptions Course admin options for current user. See CoreCoursesProvider.getUserAdministrationOptions. + * @return Promise resolved with boolean: true if it has handlers, false otherwise. */ hasHandlersForDefault(courseId: number, refresh?: boolean, navOptions?: any, admOptions?: any): Promise { // Default access. @@ -473,11 +457,11 @@ export class CoreCourseOptionsDelegate extends CoreDelegate { /** * Check if a course has any handler enabled for guest access. * - * @param {number} courseId The course ID. - * @param {boolean} [refresh] True if it should refresh the list. - * @param {any} [navOptions] Course navigation options for current user. See CoreCoursesProvider.getUserNavigationOptions. - * @param {any} [admOptions] Course admin options for current user. See CoreCoursesProvider.getUserAdministrationOptions. - * @return {Promise} Promise resolved with boolean: true if it has handlers, false otherwise. + * @param courseId The course ID. + * @param refresh True if it should refresh the list. + * @param navOptions Course navigation options for current user. See CoreCoursesProvider.getUserNavigationOptions. + * @param admOptions Course admin options for current user. See CoreCoursesProvider.getUserAdministrationOptions. + * @return Promise resolved with boolean: true if it has handlers, false otherwise. */ hasHandlersForGuest(courseId: number, refresh?: boolean, navOptions?: any, admOptions?: any): Promise { // Guest access. @@ -493,8 +477,8 @@ export class CoreCourseOptionsDelegate extends CoreDelegate { /** * Invalidate the data to be able to determine if handlers are enabled for a certain course. * - * @param {number} courseId Course ID. - * @return {Promise} Promise resolved when done. + * @param courseId Course ID. + * @return Promise resolved when done. */ invalidateCourseHandlers(courseId: number): Promise { const promises = [], @@ -518,9 +502,9 @@ export class CoreCourseOptionsDelegate extends CoreDelegate { * Check if a time belongs to the last update handlers for course call. * This is to handle the cases where updateHandlersForCourse don't finish in the same order as they're called. * - * @param {number} courseId Course ID. - * @param {number} time Time to check. - * @return {boolean} Whether it's the last call. + * @param courseId Course ID. + * @param time Time to check. + * @return Whether it's the last call. */ isLastUpdateCourseCall(courseId: number, time: number): boolean { if (!this.lastUpdateHandlersForCoursesStart[courseId]) { @@ -533,9 +517,9 @@ export class CoreCourseOptionsDelegate extends CoreDelegate { /** * Load course options if missing. * - * @param {any} course The course object. - * @param {boolean} [refresh] True if it should refresh the list. - * @return {Promise} Promise resolved when done. + * @param course The course object. + * @param refresh True if it should refresh the list. + * @return Promise resolved when done. */ protected loadCourseOptions(course: any, refresh?: boolean): Promise { if (this.coursesProvider.canGetAdminAndNavOptions() && @@ -564,11 +548,11 @@ export class CoreCourseOptionsDelegate extends CoreDelegate { /** * Update the handlers for a certain course. * - * @param {number} courseId The course ID. - * @param {any} accessData Access type and data. Default, guest, ... - * @param {any} [navOptions] Course navigation options for current user. See CoreCoursesProvider.getUserNavigationOptions. - * @param {any} [admOptions] Course admin options for current user. See CoreCoursesProvider.getUserAdministrationOptions. - * @return {Promise} Resolved when updated. + * @param courseId The course ID. + * @param accessData Access type and data. Default, guest, ... + * @param navOptions Course navigation options for current user. See CoreCoursesProvider.getUserNavigationOptions. + * @param admOptions Course admin options for current user. See CoreCoursesProvider.getUserAdministrationOptions. + * @return Resolved when updated. */ updateHandlersForCourse(courseId: number, accessData: any, navOptions?: any, admOptions?: any): Promise { const promises = [], diff --git a/src/core/course/providers/sync-cron-handler.ts b/src/core/course/providers/sync-cron-handler.ts index 8f3adc3a6..6dc8c17e4 100644 --- a/src/core/course/providers/sync-cron-handler.ts +++ b/src/core/course/providers/sync-cron-handler.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -29,9 +29,9 @@ export class CoreCourseSyncCronHandler implements CoreCronHandler { * Execute the process. * Receives the ID of the site affected, undefined for all sites. * - * @param {string} [siteId] ID of the site affected, undefined for all sites. - * @param {boolean} [force] Wether the execution is forced (manual sync). - * @return {Promise} Promise resolved when done, rejected if failure. + * @param siteId ID of the site affected, undefined for all sites. + * @param force Wether the execution is forced (manual sync). + * @return Promise resolved when done, rejected if failure. */ execute(siteId?: string, force?: boolean): Promise { return this.courseSync.syncAllCourses(siteId); @@ -40,7 +40,7 @@ export class CoreCourseSyncCronHandler implements CoreCronHandler { /** * Get the time between consecutive executions. * - * @return {number} Time between consecutive executions (in ms). + * @return Time between consecutive executions (in ms). */ getInterval(): number { return this.courseSync.syncInterval; diff --git a/src/core/course/providers/sync.ts b/src/core/course/providers/sync.ts index 2c091a863..ef5ea7462 100644 --- a/src/core/course/providers/sync.ts +++ b/src/core/course/providers/sync.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -48,9 +48,9 @@ export class CoreCourseSyncProvider extends CoreSyncBaseProvider { /** * Try to synchronize all the courses in a certain site or in all sites. * - * @param {string} [siteId] Site ID to sync. If not defined, sync all sites. - * @param {boolean} [force] Wether the execution is forced (manual sync). - * @return {Promise} Promise resolved if sync is successful, rejected if sync fails. + * @param siteId Site ID to sync. If not defined, sync all sites. + * @param force Wether the execution is forced (manual sync). + * @return Promise resolved if sync is successful, rejected if sync fails. */ syncAllCourses(siteId?: string, force?: boolean): Promise { return this.syncOnSites('courses', this.syncAllCoursesFunc.bind(this), [force], siteId); @@ -59,9 +59,9 @@ export class CoreCourseSyncProvider extends CoreSyncBaseProvider { /** * Sync all courses on a site. * - * @param {string} siteId Site ID to sync. If not defined, sync all sites. - * @param {boolean} force Wether the execution is forced (manual sync). - * @return {Promise} Promise resolved if sync is successful, rejected if sync fails. + * @param siteId Site ID to sync. If not defined, sync all sites. + * @param force Wether the execution is forced (manual sync). + * @return Promise resolved if sync is successful, rejected if sync fails. */ protected syncAllCoursesFunc(siteId: string, force: boolean): Promise { const p1 = []; @@ -94,9 +94,9 @@ export class CoreCourseSyncProvider extends CoreSyncBaseProvider { /** * Sync a course if it's needed. * - * @param {number} courseId Course ID to be synced. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the course is synced or it doesn't need to be synced. + * @param courseId Course ID to be synced. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the course is synced or it doesn't need to be synced. */ syncCourseIfNeeded(courseId: number, siteId?: string): Promise { // Usually we call isSyncNeeded to check if a certain time has passed. @@ -107,9 +107,9 @@ export class CoreCourseSyncProvider extends CoreSyncBaseProvider { /** * Synchronize a course. * - * @param {number} courseId Course ID to be synced. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved if sync is successful, rejected otherwise. + * @param courseId Course ID to be synced. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved if sync is successful, rejected otherwise. */ syncCourse(courseId: number, siteId?: string): Promise { siteId = siteId || this.sitesProvider.getCurrentSiteId(); diff --git a/src/core/courses/components/components.module.ts b/src/core/courses/components/components.module.ts index ccb458973..bdd0965ca 100644 --- a/src/core/courses/components/components.module.ts +++ b/src/core/courses/components/components.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/core/courses/components/course-list-item/core-courses-course-list-item.html b/src/core/courses/components/course-list-item/core-courses-course-list-item.html index a56a02b9b..e594ff06b 100644 --- a/src/core/courses/components/course-list-item/core-courses-course-list-item.html +++ b/src/core/courses/components/course-list-item/core-courses-course-list-item.html @@ -1,6 +1,6 @@
-

+

diff --git a/src/core/courses/components/course-list-item/course-list-item.ts b/src/core/courses/components/course-list-item/course-list-item.ts index c11f17f69..776a13a5d 100644 --- a/src/core/courses/components/course-list-item/course-list-item.ts +++ b/src/core/courses/components/course-list-item/course-list-item.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -78,7 +78,7 @@ export class CoreCoursesCourseListItemComponent implements OnInit { /** * Open a course. * - * @param {any} course The course to open. + * @param course The course to open. */ openCourse(course: any): void { if (course.isEnrolled) { diff --git a/src/core/courses/components/course-options-menu/course-options-menu.ts b/src/core/courses/components/course-options-menu/course-options-menu.ts index a63dbedf5..02aae3630 100644 --- a/src/core/courses/components/course-options-menu/course-options-menu.ts +++ b/src/core/courses/components/course-options-menu/course-options-menu.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -43,7 +43,7 @@ export class CoreCoursesCourseOptionsMenuComponent implements OnInit { /** * Do an action over the course. - * @param {string} action Action name to take. + * @param action Action name to take. */ action(action: string): void { this.viewCtrl.dismiss(action); diff --git a/src/core/courses/components/course-progress/core-courses-course-progress.html b/src/core/courses/components/course-progress/core-courses-course-progress.html index 985d92008..646d9edcb 100644 --- a/src/core/courses/components/course-progress/core-courses-course-progress.html +++ b/src/core/courses/components/course-progress/core-courses-course-progress.html @@ -1,13 +1,17 @@ - -
+ +
-

+

+ + | + +

- +

diff --git a/src/core/courses/components/course-progress/course-progress.scss b/src/core/courses/components/course-progress/course-progress.scss index 230838b1f..a5f0f750a 100644 --- a/src/core/courses/components/course-progress/course-progress.scss +++ b/src/core/courses/components/course-progress/course-progress.scss @@ -21,6 +21,7 @@ ion-app.app-root core-courses-course-progress { position: relative; background-position: center; background-size: cover; + @include core-transition(all, 50ms); &.core-course-color-img { background: white; @@ -34,7 +35,7 @@ ion-app.app-root core-courses-course-progress { } } - .core-course-shortname { + .core-course-additional-info { margin-bottom: 8px; } @@ -113,7 +114,7 @@ ion-app.app-root .core-horizontal-scroll core-courses-course-progress { .core-course-link { @include padding(4px, 0px, 4px, 8px); - .core-course-shortname { + .core-course-additional-info { font-size: 1.2rem; } diff --git a/src/core/courses/components/course-progress/course-progress.ts b/src/core/courses/components/course-progress/course-progress.ts index 814b00c55..60dc67d30 100644 --- a/src/core/courses/components/course-progress/course-progress.ts +++ b/src/core/courses/components/course-progress/course-progress.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -130,7 +130,7 @@ export class CoreCoursesCourseProgressComponent implements OnInit, OnDestroy { /** * Open a course. * - * @param {any} course The course to open. + * @param course The course to open. */ openCourse(course: any): void { this.courseHelper.openCourse(this.navCtrl, course); @@ -139,7 +139,7 @@ export class CoreCoursesCourseProgressComponent implements OnInit, OnDestroy { /** * Prefetch the course. * - * @param {Event} e Click event. + * @param e Click event. */ prefetchCourse(e: Event): void { e.preventDefault(); @@ -155,7 +155,7 @@ export class CoreCoursesCourseProgressComponent implements OnInit, OnDestroy { /** * Update the course status icon and title. * - * @param {string} status Status to show. + * @param status Status to show. */ protected updateCourseStatus(status: string): void { const statusData = this.courseHelper.getCourseStatusIconAndTitleFromStatus(status); @@ -167,7 +167,7 @@ export class CoreCoursesCourseProgressComponent implements OnInit, OnDestroy { /** * Show the context menu. * - * @param {Event} e Click Event. + * @param e Click Event. */ showCourseOptionsMenu(e: Event): void { e.preventDefault(); @@ -210,7 +210,7 @@ export class CoreCoursesCourseProgressComponent implements OnInit, OnDestroy { /** * Hide/Unhide the course from the course list. * - * @param {boolean} hide True to hide and false to show. + * @param hide True to hide and false to show. */ protected setCourseHidden(hide: boolean): void { this.showSpinner = true; @@ -232,7 +232,7 @@ export class CoreCoursesCourseProgressComponent implements OnInit, OnDestroy { /** * Favourite/Unfavourite the course from the course list. * - * @param {boolean} favourite True to favourite and false to unfavourite. + * @param favourite True to favourite and false to unfavourite. */ protected setCourseFavourite(favourite: boolean): void { this.showSpinner = true; diff --git a/src/core/courses/components/my-courses/my-courses.html b/src/core/courses/components/my-courses/my-courses.html index 06ca202b8..f6133e350 100644 --- a/src/core/courses/components/my-courses/my-courses.html +++ b/src/core/courses/components/my-courses/my-courses.html @@ -1,7 +1,7 @@ - + diff --git a/src/core/courses/components/my-courses/my-courses.ts b/src/core/courses/components/my-courses/my-courses.ts index fc0817350..1c51b5b31 100644 --- a/src/core/courses/components/my-courses/my-courses.ts +++ b/src/core/courses/components/my-courses/my-courses.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -84,7 +84,7 @@ export class CoreCoursesMyCoursesComponent implements OnInit, OnDestroy { /** * Fetch the user courses. * - * @return {Promise} Promise resolved when done. + * @return Promise resolved when done. */ protected fetchCourses(): Promise { return this.coursesProvider.getUserCourses().then((courses) => { @@ -121,7 +121,7 @@ export class CoreCoursesMyCoursesComponent implements OnInit, OnDestroy { /** * Refresh the courses. * - * @param {any} refresher Refresher. + * @param refresher Refresher. */ refreshCourses(refresher: any): void { const promises = []; @@ -158,7 +158,7 @@ export class CoreCoursesMyCoursesComponent implements OnInit, OnDestroy { /** * The filter has changed. * - * @param {any} Received Event. + * @param Received Event. */ filterChanged(event: any): void { const newValue = event.target.value && event.target.value.trim().toLowerCase(); @@ -181,7 +181,7 @@ export class CoreCoursesMyCoursesComponent implements OnInit, OnDestroy { /** * Prefetch all the courses. * - * @return {Promise} Promise resolved when done. + * @return Promise resolved when done. */ prefetchCourses(): Promise { const initialIcon = this.prefetchCoursesData.icon; diff --git a/src/core/courses/courses.module.ts b/src/core/courses/courses.module.ts index a166ad5db..3d8ad776c 100644 --- a/src/core/courses/courses.module.ts +++ b/src/core/courses/courses.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/core/courses/pages/available-courses/available-courses.module.ts b/src/core/courses/pages/available-courses/available-courses.module.ts index 3decb3196..ea3cff2bf 100644 --- a/src/core/courses/pages/available-courses/available-courses.module.ts +++ b/src/core/courses/pages/available-courses/available-courses.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/core/courses/pages/available-courses/available-courses.ts b/src/core/courses/pages/available-courses/available-courses.ts index 5e244e7cb..f029b4880 100644 --- a/src/core/courses/pages/available-courses/available-courses.ts +++ b/src/core/courses/pages/available-courses/available-courses.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -45,7 +45,7 @@ export class CoreCoursesAvailableCoursesPage { /** * Load the courses. * - * @return {Promise} Promise resolved when done. + * @return Promise resolved when done. */ protected loadCourses(): Promise { const frontpageCourseId = this.sitesProvider.getCurrentSite().getSiteHomeId(); @@ -62,7 +62,7 @@ export class CoreCoursesAvailableCoursesPage { /** * Refresh the courses. * - * @param {any} refresher Refresher. + * @param refresher Refresher. */ refreshCourses(refresher: any): void { const promises = []; diff --git a/src/core/courses/pages/categories/categories.html b/src/core/courses/pages/categories/categories.html index e24c372bb..0952bb4ef 100644 --- a/src/core/courses/pages/categories/categories.html +++ b/src/core/courses/pages/categories/categories.html @@ -1,6 +1,6 @@ - + @@ -10,10 +10,10 @@ -

+

- +
@@ -21,7 +21,7 @@
-

+

{{category.coursecount}}
diff --git a/src/core/courses/pages/categories/categories.module.ts b/src/core/courses/pages/categories/categories.module.ts index c60b37cc3..b5b4162da 100644 --- a/src/core/courses/pages/categories/categories.module.ts +++ b/src/core/courses/pages/categories/categories.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/core/courses/pages/categories/categories.ts b/src/core/courses/pages/categories/categories.ts index ab9a35cb1..596eef44c 100644 --- a/src/core/courses/pages/categories/categories.ts +++ b/src/core/courses/pages/categories/categories.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -56,7 +56,7 @@ export class CoreCoursesCategoriesPage { /** * Fetch the categories. * - * @return {Promise} Promise resolved when done. + * @return Promise resolved when done. */ protected fetchCategories(): Promise { return this.coursesProvider.getCategories(this.categoryId, true).then((cats) => { @@ -98,7 +98,7 @@ export class CoreCoursesCategoriesPage { /** * Refresh the categories. * - * @param {any} refresher Refresher. + * @param refresher Refresher. */ refreshCategories(refresher: any): void { const promises = []; @@ -117,7 +117,7 @@ export class CoreCoursesCategoriesPage { /** * Open a category. * - * @param {number} categoryId The category ID. + * @param categoryId The category ID. */ openCategory(categoryId: number): void { this.navCtrl.push('CoreCoursesCategoriesPage', { categoryId: categoryId }); diff --git a/src/core/courses/pages/course-preview/course-preview.html b/src/core/courses/pages/course-preview/course-preview.html index e379c0b21..f84814ab6 100644 --- a/src/core/courses/pages/course-preview/course-preview.html +++ b/src/core/courses/pages/course-preview/course-preview.html @@ -1,6 +1,6 @@ - + @@ -15,13 +15,13 @@
-

-

+

+

{{course.startdate * 1000 | coreFormatDate:"strftimedatefullshort" }} - {{course.enddate * 1000 | coreFormatDate:"strftimedatefullshort" }}

- + @@ -36,8 +36,8 @@
- : - + : +
diff --git a/src/core/courses/pages/course-preview/course-preview.module.ts b/src/core/courses/pages/course-preview/course-preview.module.ts index 2a34ddf92..60c0102a2 100644 --- a/src/core/courses/pages/course-preview/course-preview.module.ts +++ b/src/core/courses/pages/course-preview/course-preview.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/core/courses/pages/course-preview/course-preview.ts b/src/core/courses/pages/course-preview/course-preview.ts index 6e1569c2d..639b4d52f 100644 --- a/src/core/courses/pages/course-preview/course-preview.ts +++ b/src/core/courses/pages/course-preview/course-preview.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -157,8 +157,8 @@ export class CoreCoursesCoursePreviewPage implements OnDestroy { /** * Check if the user can access as guest. * - * @return {Promise} Promise resolved if can access as guest, rejected otherwise. Resolve param indicates if - * password is required for guest access. + * @return Promise resolved if can access as guest, rejected otherwise. Resolve param indicates if + * password is required for guest access. */ protected canAccessAsGuest(): Promise { if (!this.isGuestEnabled) { @@ -192,7 +192,7 @@ export class CoreCoursesCoursePreviewPage implements OnDestroy { /** * Convenience function to get course. We use this to determine if a user can see the course or not. * - * @param {boolean} refresh Whether the user is refreshing the data. + * @param refresh Whether the user is refreshing the data. */ protected getCourse(refresh?: boolean): Promise { // Get course enrolment methods. @@ -336,7 +336,7 @@ export class CoreCoursesCoursePreviewPage implements OnDestroy { /** * User clicked in a self enrol button. * - * @param {number} instanceId The instance ID of the enrolment method. + * @param instanceId The instance ID of the enrolment method. */ selfEnrolClicked(instanceId: number): void { this.domUtils.showConfirm(this.translate.instant('core.courses.confirmselfenrol')).then(() => { @@ -349,9 +349,9 @@ export class CoreCoursesCoursePreviewPage implements OnDestroy { /** * Self enrol in a course. * - * @param {string} password Password to use. - * @param {number} instanceId The instance ID. - * @return {Promise} Promise resolved when self enrolled. + * @param password Password to use. + * @param instanceId The instance ID. + * @return Promise resolved when self enrolled. */ selfEnrolInCourse(password: string, instanceId: number): Promise { const modal = this.domUtils.showModalLoading('core.loading', true); @@ -390,7 +390,7 @@ export class CoreCoursesCoursePreviewPage implements OnDestroy { /** * Refresh the data. * - * @param {any} [refresher] The refresher if this was triggered by a Pull To Refresh. + * @param refresher The refresher if this was triggered by a Pull To Refresh. */ refreshData(refresher?: any): Promise { const promises = []; @@ -418,7 +418,7 @@ export class CoreCoursesCoursePreviewPage implements OnDestroy { /** * Update the course status icon and title. * - * @param {string} status Status to show. + * @param status Status to show. */ protected updateCourseStatus(status: string): void { const statusData = this.courseHelper.getCourseStatusIconAndTitleFromStatus(status); @@ -430,8 +430,8 @@ export class CoreCoursesCoursePreviewPage implements OnDestroy { /** * Wait for the user to be enrolled in the course. * - * @param {boolean} first If it's the first call (true) or it's a recursive call (false). - * @return {Promise} Promise resolved when enrolled or timeout. + * @param first If it's the first call (true) or it's a recursive call (false). + * @return Promise resolved when enrolled or timeout. */ protected waitForEnrolled(first?: boolean): Promise { if (first) { diff --git a/src/core/courses/pages/dashboard/dashboard.html b/src/core/courses/pages/dashboard/dashboard.html index d94fcdd0a..4ad0dcd61 100644 --- a/src/core/courses/pages/dashboard/dashboard.html +++ b/src/core/courses/pages/dashboard/dashboard.html @@ -1,6 +1,9 @@ - + - + + + + ').appendTo($container).click(function () { + var $spinner = $('
', {class: 'h5p-spinner'}).replaceAll($button); + var parts = ['|', '/', '-', '\\']; + var current = 0; + var spinning = setInterval(function () { + $spinner.text(parts[current]); + current++; + if (current === parts.length) current = 0; + }, 100); + + var $counter = $container.find('.progress'); + var build = function () { + $.post(notCached.url, function (left) { + if (left === '0') { + clearInterval(spinning); + $container.remove(); + location.reload(); + } + else { + var counter = $counter.text().split(' '); + counter[0] = left; + $counter.text(counter.join(' ')); + build(); + } + }); + }; + build(); + }); + + return $container; + }; + + /** + * Generic table class with useful helpers. + * + * @class + * @param {Object} classes + * Custom html classes to use on elements. + * e.g. {tableClass: 'fixed'}. + */ + H5PUtils.Table = function (classes) { + var numCols; + var sortByCol; + var $sortCol; + var sortCol; + var sortDir; + + // Create basic table + var tableOptions = {}; + if (classes.table !== undefined) { + tableOptions['class'] = classes.table; + } + var $table = $('', tableOptions); + var $thead = $('').appendTo($table); + var $tfoot = $('').appendTo($table); + var $tbody = $('').appendTo($table); + + /** + * Add columns to given table row. + * + * @private + * @param {jQuery} $tr Table row + * @param {(String|Object)} col Column properties + * @param {Number} id Used to seperate the columns + */ + var addCol = function ($tr, col, id) { + var options = { + on: {} + }; + + if (!(col instanceof Object)) { + options.text = col; + } + else { + if (col.text !== undefined) { + options.text = col.text; + } + if (col.class !== undefined) { + options.class = col.class; + } + + if (sortByCol !== undefined && col.sortable === true) { + // Make sortable + options.role = 'button'; + options.tabIndex = 0; + + // This is the first sortable column, use as default sort + if (sortCol === undefined) { + sortCol = id; + sortDir = 0; + } + + // This is the sort column + if (sortCol === id) { + options['class'] = 'h5p-sort'; + if (sortDir === 1) { + options['class'] += ' h5p-reverse'; + } + } + + options.on.click = function () { + sort($th, id); + }; + options.on.keypress = function (event) { + if ((event.charCode || event.keyCode) === 32) { // Space + sort($th, id); + } + }; + } + } + + // Append + var $th = $(''); + var $tr = $('').appendTo($newThead); + for (var i = 0; i < cols.length; i++) { + addCol($tr, cols[i], i); + } + + // Update DOM + $thead.replaceWith($newThead); + $thead = $newThead; + }; + + /** + * Set table rows. + * + * @public + * @param {Array} rows Table rows with cols: [[1,'hello',3],[2,'asd',6]] + */ + this.setRows = function (rows) { + var $newTbody = $(''); + + for (var i = 0; i < rows.length; i++) { + var $tr = $('').appendTo($newTbody); + + for (var j = 0; j < rows[i].length; j++) { + $(''); + var $tr = $('').appendTo($newTbody); + $(''); + var $tr = $('').appendTo($newTfoot); + $('\s*$/g,At={option:[1,""],legend:[1,"
","
"],area:[1,"",""],param:[1,"",""],thead:[1,"
', options).appendTo($tr); + if (sortCol === id) { + $sortCol = $th; // Default sort column + } + }; + + /** + * Updates the UI when a column header has been clicked. + * Triggers sorting callback. + * + * @private + * @param {jQuery} $th Table header + * @param {Number} id Used to seperate the columns + */ + var sort = function ($th, id) { + if (id === sortCol) { + // Change sorting direction + if (sortDir === 0) { + sortDir = 1; + $th.addClass('h5p-reverse'); + } + else { + sortDir = 0; + $th.removeClass('h5p-reverse'); + } + } + else { + // Change sorting column + $sortCol.removeClass('h5p-sort').removeClass('h5p-reverse'); + $sortCol = $th.addClass('h5p-sort'); + sortCol = id; + sortDir = 0; + } + + sortByCol({ + by: sortCol, + dir: sortDir + }); + }; + + /** + * Set table headers. + * + * @public + * @param {Array} cols + * Table header data. Can be strings or objects with options like + * "text" and "sortable". E.g. + * [{text: 'Col 1', sortable: true}, 'Col 2', 'Col 3'] + * @param {Function} sort Callback which is runned when sorting changes + * @param {Object} [order] + */ + this.setHeaders = function (cols, sort, order) { + numCols = cols.length; + sortByCol = sort; + + if (order) { + sortCol = order.by; + sortDir = order.dir; + } + + // Create new head + var $newThead = $('
', { + html: rows[i][j] + }).appendTo($tr); + } + } + + $tbody.replaceWith($newTbody); + $tbody = $newTbody; + + return $tbody; + }; + + /** + * Set custom table body content. This can be a message or a throbber. + * Will cover all table columns. + * + * @public + * @param {jQuery} $content Custom content + */ + this.setBody = function ($content) { + var $newTbody = $('
', { + colspan: numCols + }).append($content).appendTo($tr); + $tbody.replaceWith($newTbody); + $tbody = $newTbody; + }; + + /** + * Set custom table foot content. This can be a pagination widget. + * Will cover all table columns. + * + * @public + * @param {jQuery} $content Custom content + */ + this.setFoot = function ($content) { + var $newTfoot = $('
', { + colspan: numCols + }).append($content).appendTo($tr); + $tfoot.replaceWith($newTfoot); + }; + + + /** + * Appends the table to the given container. + * + * @public + * @param {jQuery} $container + */ + this.appendTo = function ($container) { + $table.appendTo($container); + }; + }; + + /** + * Generic pagination class. Creates a useful pagination widget. + * + * @class + * @param {Number} num Total number of items to pagiate. + * @param {Number} limit Number of items to dispaly per page. + * @param {Function} goneTo + * Callback which is fired when the user wants to go to another page. + * @param {Object} l10n + * Localization / translations. e.g. + * { + * currentPage: 'Page $current of $total', + * nextPage: 'Next page', + * previousPage: 'Previous page' + * } + */ + H5PUtils.Pagination = function (num, limit, goneTo, l10n) { + var current = 0; + var pages = Math.ceil(num / limit); + + // Create components + + // Previous button + var $left = $(''; + } + if (contentData.displayOptions.export && contentData.displayOptions.copy) { + html += '
or
'; + } + if (contentData.displayOptions.copy) { + html += ''; + } + + const dialog = new H5P.Dialog('reuse', H5P.t('reuseContent'), html, $element); + + // Selecting embed code when dialog is opened + H5P.jQuery(dialog).on('dialog-opened', function (e, $dialog) { + H5P.jQuery('More Info').click(function (e) { + e.stopPropagation(); + }).appendTo($dialog.find('h2')); + $dialog.find('.h5p-download-button').click(function () { + window.location.href = contentData.exportUrl; + instance.triggerXAPI('downloaded'); + dialog.close(); + }); + $dialog.find('.h5p-copy-button').click(function () { + const item = new H5P.ClipboardItem(library); + item.contentId = contentId; + H5P.setClipboard(item); + instance.triggerXAPI('copied'); + dialog.close(); + H5P.attachToastTo( + H5P.jQuery('.h5p-content:first')[0], + H5P.t('contentCopied'), + { + position: { + horizontal: 'centered', + vertical: 'centered', + noOverflowX: true + } + } + ); + }); + H5P.trigger(instance, 'resize'); + }).on('dialog-closed', function () { + H5P.trigger(instance, 'resize'); + }); + + dialog.open(); +}; + +/** + * Display a dialog containing the embed code. + * + * @param {H5P.jQuery} $element + * Element to insert dialog after. + * @param {string} embedCode + * The embed code. + * @param {string} resizeCode + * The advanced resize code + * @param {Object} size + * The content's size. + * @param {number} size.width + * @param {number} size.height + */ +H5P.openEmbedDialog = function ($element, embedCode, resizeCode, size, instance) { + var fullEmbedCode = embedCode + resizeCode; + var dialog = new H5P.Dialog('embed', H5P.t('embed'), '' + H5P.t('size') + ': × px
' + H5P.t('showAdvanced') + '

' + H5P.t('advancedHelp') + '

', $element); + + // Selecting embed code when dialog is opened + H5P.jQuery(dialog).on('dialog-opened', function (event, $dialog) { + var $inner = $dialog.find('.h5p-inner'); + var $scroll = $inner.find('.h5p-scroll-content'); + var diff = $scroll.outerHeight() - $scroll.innerHeight(); + var positionInner = function () { + H5P.trigger(instance, 'resize'); + }; + + // Handle changing of width/height + var $w = $dialog.find('.h5p-embed-size:eq(0)'); + var $h = $dialog.find('.h5p-embed-size:eq(1)'); + var getNum = function ($e, d) { + var num = parseFloat($e.val()); + if (isNaN(num)) { + return d; + } + return Math.ceil(num); + }; + var updateEmbed = function () { + $dialog.find('.h5p-embed-code-container:first').val(fullEmbedCode.replace(':w', getNum($w, size.width)).replace(':h', getNum($h, size.height))); + }; + + $w.change(updateEmbed); + $h.change(updateEmbed); + updateEmbed(); + + // Select text and expand textareas + $dialog.find('.h5p-embed-code-container').each(function () { + H5P.jQuery(this).css('height', this.scrollHeight + 'px').focus(function () { + H5P.jQuery(this).select(); + }); + }); + $dialog.find('.h5p-embed-code-container').eq(0).select(); + positionInner(); + + // Expand advanced embed + var expand = function () { + var $expander = H5P.jQuery(this); + var $content = $expander.next(); + if ($content.is(':visible')) { + $expander.removeClass('h5p-open').text(H5P.t('showAdvanced')); + $content.hide(); + } + else { + $expander.addClass('h5p-open').text(H5P.t('hideAdvanced')); + $content.show(); + } + $dialog.find('.h5p-embed-code-container').each(function () { + H5P.jQuery(this).css('height', this.scrollHeight + 'px'); + }); + positionInner(); + }; + $dialog.find('.h5p-expander').click(expand).keypress(function (event) { + if (event.keyCode === 32) { + expand.apply(this); + } + }); + }).on('dialog-closed', function () { + H5P.trigger(instance, 'resize'); + }); + + dialog.open(); +}; + +/** + * Show a toast message. + * + * The reference element could be dom elements the toast should be attached to, + * or e.g. the document body for general toast messages. + * + * @param {DOM} element Reference element to show toast message for. + * @param {string} message Message to show. + * @param {object} [config] Configuration. + * @param {string} [config.style=h5p-toast] Style name for the tooltip. + * @param {number} [config.duration=3000] Toast message length in ms. + * @param {object} [config.position] Relative positioning of the toast. + * @param {string} [config.position.horizontal=centered] [before|left|centered|right|after]. + * @param {string} [config.position.vertical=below] [above|top|centered|bottom|below]. + * @param {number} [config.position.offsetHorizontal=0] Extra horizontal offset. + * @param {number} [config.position.offsetVertical=0] Extra vetical offset. + * @param {boolean} [config.position.noOverflowLeft=false] True to prevent overflow left. + * @param {boolean} [config.position.noOverflowRight=false] True to prevent overflow right. + * @param {boolean} [config.position.noOverflowTop=false] True to prevent overflow top. + * @param {boolean} [config.position.noOverflowBottom=false] True to prevent overflow bottom. + * @param {boolean} [config.position.noOverflowX=false] True to prevent overflow left and right. + * @param {boolean} [config.position.noOverflowY=false] True to prevent overflow top and bottom. + * @param {object} [config.position.overflowReference=document.body] DOM reference for overflow. + */ +H5P.attachToastTo = function (element, message, config) { + if (element === undefined || message === undefined) { + return; + } + + const eventPath = function (evt) { + var path = (evt.composedPath && evt.composedPath()) || evt.path; + var target = evt.target; + + if (path != null) { + // Safari doesn't include Window, but it should. + return (path.indexOf(window) < 0) ? path.concat(window) : path; + } + + if (target === window) { + return [window]; + } + + function getParents(node, memo) { + memo = memo || []; + var parentNode = node.parentNode; + + if (!parentNode) { + return memo; + } + else { + return getParents(parentNode, memo.concat(parentNode)); + } + } + + return [target].concat(getParents(target), window); + }; + + /** + * Handle click while toast is showing. + */ + const clickHandler = function (event) { + /* + * A common use case will be to attach toasts to buttons that are clicked. + * The click would remove the toast message instantly without this check. + * Children of the clicked element are also ignored. + */ + var path = eventPath(event); + if (path.indexOf(element) !== -1) { + return; + } + clearTimeout(timer); + removeToast(); + }; + + + + /** + * Remove the toast message. + */ + const removeToast = function () { + document.removeEventListener('click', clickHandler); + if (toast.parentNode) { + toast.parentNode.removeChild(toast); + } + }; + + /** + * Get absolute coordinates for the toast. + * + * @param {DOM} element Reference element to show toast message for. + * @param {DOM} toast Toast element. + * @param {object} [position={}] Relative positioning of the toast message. + * @param {string} [position.horizontal=centered] [before|left|centered|right|after]. + * @param {string} [position.vertical=below] [above|top|centered|bottom|below]. + * @param {number} [position.offsetHorizontal=0] Extra horizontal offset. + * @param {number} [position.offsetVertical=0] Extra vetical offset. + * @param {boolean} [position.noOverflowLeft=false] True to prevent overflow left. + * @param {boolean} [position.noOverflowRight=false] True to prevent overflow right. + * @param {boolean} [position.noOverflowTop=false] True to prevent overflow top. + * @param {boolean} [position.noOverflowBottom=false] True to prevent overflow bottom. + * @param {boolean} [position.noOverflowX=false] True to prevent overflow left and right. + * @param {boolean} [position.noOverflowY=false] True to prevent overflow top and bottom. + * @return {object} + */ + const getToastCoordinates = function (element, toast, position) { + position = position || {}; + position.offsetHorizontal = position.offsetHorizontal || 0; + position.offsetVertical = position.offsetVertical || 0; + + const toastRect = toast.getBoundingClientRect(); + const elementRect = element.getBoundingClientRect(); + + let left = 0; + let top = 0; + + // Compute horizontal position + switch (position.horizontal) { + case 'before': + left = elementRect.left - toastRect.width - position.offsetHorizontal; + break; + case 'after': + left = elementRect.left + elementRect.width + position.offsetHorizontal; + break; + case 'left': + left = elementRect.left + position.offsetHorizontal; + break; + case 'right': + left = elementRect.left + elementRect.width - toastRect.width - position.offsetHorizontal; + break; + case 'centered': + left = elementRect.left + elementRect.width / 2 - toastRect.width / 2 + position.offsetHorizontal; + break; + default: + left = elementRect.left + elementRect.width / 2 - toastRect.width / 2 + position.offsetHorizontal; + } + + // Compute vertical position + switch (position.vertical) { + case 'above': + top = elementRect.top - toastRect.height - position.offsetVertical; + break; + case 'below': + top = elementRect.top + elementRect.height + position.offsetVertical; + break; + case 'top': + top = elementRect.top + position.offsetVertical; + break; + case 'bottom': + top = elementRect.top + elementRect.height - toastRect.height - position.offsetVertical; + break; + case 'centered': + top = elementRect.top + elementRect.height / 2 - toastRect.height / 2 + position.offsetVertical; + break; + default: + top = elementRect.top + elementRect.height + position.offsetVertical; + } + + // Prevent overflow + const overflowElement = document.body; + const bounds = overflowElement.getBoundingClientRect(); + if ((position.noOverflowLeft || position.noOverflowX) && (left < bounds.x)) { + left = bounds.x; + } + if ((position.noOverflowRight || position.noOverflowX) && ((left + toastRect.width) > (bounds.x + bounds.width))) { + left = bounds.x + bounds.width - toastRect.width; + } + if ((position.noOverflowTop || position.noOverflowY) && (top < bounds.y)) { + top = bounds.y; + } + if ((position.noOverflowBottom || position.noOverflowY) && ((top + toastRect.height) > (bounds.y + bounds.height))) { + left = bounds.y + bounds.height - toastRect.height; + } + + return {left: left, top: top}; + }; + + // Sanitization + config = config || {}; + config.style = config.style || 'h5p-toast'; + config.duration = config.duration || 3000; + + // Build toast + const toast = document.createElement('div'); + toast.setAttribute('id', config.style); + toast.classList.add('h5p-toast-disabled'); + toast.classList.add(config.style); + + const msg = document.createElement('span'); + msg.innerHTML = message; + toast.appendChild(msg); + + document.body.appendChild(toast); + + // The message has to be set before getting the coordinates + const coordinates = getToastCoordinates(element, toast, config.position); + toast.style.left = Math.round(coordinates.left) + 'px'; + toast.style.top = Math.round(coordinates.top) + 'px'; + + toast.classList.remove('h5p-toast-disabled'); + const timer = setTimeout(removeToast, config.duration); + + // The toast can also be removed by clicking somewhere + document.addEventListener('click', clickHandler); +}; + +/** + * Copyrights for a H5P Content Library. + * + * @class + */ +H5P.ContentCopyrights = function () { + var label; + var media = []; + var content = []; + + /** + * Set label. + * + * @param {string} newLabel + */ + this.setLabel = function (newLabel) { + label = newLabel; + }; + + /** + * Add sub content. + * + * @param {H5P.MediaCopyright} newMedia + */ + this.addMedia = function (newMedia) { + if (newMedia !== undefined) { + media.push(newMedia); + } + }; + + /** + * Add sub content in front. + * + * @param {H5P.MediaCopyright} newMedia + */ + this.addMediaInFront = function (newMedia) { + if (newMedia !== undefined) { + media.unshift(newMedia); + } + }; + + /** + * Add sub content. + * + * @param {H5P.ContentCopyrights} newContent + */ + this.addContent = function (newContent) { + if (newContent !== undefined) { + content.push(newContent); + } + }; + + /** + * Print content copyright. + * + * @returns {string} HTML. + */ + this.toString = function () { + var html = ''; + + // Add media rights + for (var i = 0; i < media.length; i++) { + html += media[i]; + } + + // Add sub content rights + for (i = 0; i < content.length; i++) { + html += content[i]; + } + + + if (html !== '') { + // Add a label to this info + if (label !== undefined) { + html = '

' + label + '

' + html; + } + + // Add wrapper + html = '
' + html + '
'; + } + + return html; + }; +}; + +/** + * A ordered list of copyright fields for media. + * + * @class + * @param {Object} copyright + * Copyright information fields. + * @param {Object} [labels] + * Translation of labels. + * @param {Array} [order] + * Order of the fields. + * @param {Object} [extraFields] + * Add extra copyright fields. + */ +H5P.MediaCopyright = function (copyright, labels, order, extraFields) { + var thumbnail; + var list = new H5P.DefinitionList(); + + /** + * Get translated label for field. + * + * @private + * @param {string} fieldName + * @returns {string} + */ + var getLabel = function (fieldName) { + if (labels === undefined || labels[fieldName] === undefined) { + return H5P.t(fieldName); + } + + return labels[fieldName]; + }; + + /** + * Get humanized value for the license field. + * + * @private + * @param {string} license + * @param {string} [version] + * @returns {string} + */ + var humanizeLicense = function (license, version) { + var copyrightLicense = H5P.copyrightLicenses[license]; + + // Build license string + var value = ''; + if (!(license === 'PD' && version)) { + // Add license label + value += (copyrightLicense.hasOwnProperty('label') ? copyrightLicense.label : copyrightLicense); + } + + // Check for version info + var versionInfo; + if (copyrightLicense.versions) { + if (copyrightLicense.versions.default && (!version || !copyrightLicense.versions[version])) { + version = copyrightLicense.versions.default; + } + if (version && copyrightLicense.versions[version]) { + versionInfo = copyrightLicense.versions[version]; + } + } + + if (versionInfo) { + // Add license version + if (value) { + value += ' '; + } + value += (versionInfo.hasOwnProperty('label') ? versionInfo.label : versionInfo); + } + + // Add link if specified + var link; + if (copyrightLicense.hasOwnProperty('link')) { + link = copyrightLicense.link.replace(':version', copyrightLicense.linkVersions ? copyrightLicense.linkVersions[version] : version); + } + else if (versionInfo && copyrightLicense.hasOwnProperty('link')) { + link = versionInfo.link; + } + if (link) { + value = '' + value + ''; + } + + // Generate parenthesis + var parenthesis = ''; + if (license !== 'PD' && license !== 'C') { + parenthesis += license; + } + if (version && version !== 'CC0 1.0') { + if (parenthesis && license !== 'GNU GPL') { + parenthesis += ' '; + } + parenthesis += version; + } + if (parenthesis) { + value += ' (' + parenthesis + ')'; + } + if (license === 'C') { + value += ' ©'; + } + + return value; + }; + + if (copyright !== undefined) { + // Add the extra fields + for (var field in extraFields) { + if (extraFields.hasOwnProperty(field)) { + copyright[field] = extraFields[field]; + } + } + + if (order === undefined) { + // Set default order + order = ['contentType', 'title', 'license', 'author', 'year', 'source', 'licenseExtras', 'changes']; + } + + for (var i = 0; i < order.length; i++) { + var fieldName = order[i]; + if (copyright[fieldName] !== undefined && copyright[fieldName] !== '') { + var humanValue = copyright[fieldName]; + if (fieldName === 'license') { + humanValue = humanizeLicense(copyright.license, copyright.version); + } + if (fieldName === 'source') { + humanValue = (humanValue) ? '' + humanValue + '' : undefined; + } + list.add(new H5P.Field(getLabel(fieldName), humanValue)); + } + } + } + + /** + * Set thumbnail. + * + * @param {H5P.Thumbnail} newThumbnail + */ + this.setThumbnail = function (newThumbnail) { + thumbnail = newThumbnail; + }; + + /** + * Checks if this copyright is undisclosed. + * I.e. only has the license attribute set, and it's undisclosed. + * + * @returns {boolean} + */ + this.undisclosed = function () { + if (list.size() === 1) { + var field = list.get(0); + if (field.getLabel() === getLabel('license') && field.getValue() === humanizeLicense('U')) { + return true; + } + } + return false; + }; + + /** + * Print media copyright. + * + * @returns {string} HTML. + */ + this.toString = function () { + var html = ''; + + if (this.undisclosed()) { + return html; // No need to print a copyright with a single undisclosed license. + } + + if (thumbnail !== undefined) { + html += thumbnail; + } + html += list; + + if (html !== '') { + html = ''; + } + + return html; + }; +}; + +/** + * A simple and elegant class for creating thumbnails of images. + * + * @class + * @param {string} source + * @param {number} width + * @param {number} height + */ +H5P.Thumbnail = function (source, width, height) { + var thumbWidth, thumbHeight = 100; + if (width !== undefined) { + thumbWidth = Math.round(thumbHeight * (width / height)); + } + + /** + * Print thumbnail. + * + * @returns {string} HTML. + */ + this.toString = function () { + return '' + H5P.t('thumbnail') + ''; + }; +}; + +/** + * Simple data structure class for storing a single field. + * + * @class + * @param {string} label + * @param {string} value + */ +H5P.Field = function (label, value) { + /** + * Public. Get field label. + * + * @returns {String} + */ + this.getLabel = function () { + return label; + }; + + /** + * Public. Get field value. + * + * @returns {String} + */ + this.getValue = function () { + return value; + }; +}; + +/** + * Simple class for creating a definition list. + * + * @class + */ +H5P.DefinitionList = function () { + var fields = []; + + /** + * Add field to list. + * + * @param {H5P.Field} field + */ + this.add = function (field) { + fields.push(field); + }; + + /** + * Get Number of fields. + * + * @returns {number} + */ + this.size = function () { + return fields.length; + }; + + /** + * Get field at given index. + * + * @param {number} index + * @returns {H5P.Field} + */ + this.get = function (index) { + return fields[index]; + }; + + /** + * Print definition list. + * + * @returns {string} HTML. + */ + this.toString = function () { + var html = ''; + for (var i = 0; i < fields.length; i++) { + var field = fields[i]; + html += '
' + field.getLabel() + '
' + field.getValue() + '
'; + } + return (html === '' ? html : '
' + html + '
'); + }; +}; + +/** + * THIS FUNCTION/CLASS IS DEPRECATED AND WILL BE REMOVED. + * + * Helper object for keeping coordinates in the same format all over. + * + * @deprecated + * Will be removed march 2016. + * @class + * @param {number} x + * @param {number} y + * @param {number} w + * @param {number} h + */ +H5P.Coords = function (x, y, w, h) { + if ( !(this instanceof H5P.Coords) ) + return new H5P.Coords(x, y, w, h); + + /** @member {number} */ + this.x = 0; + /** @member {number} */ + this.y = 0; + /** @member {number} */ + this.w = 1; + /** @member {number} */ + this.h = 1; + + if (typeof(x) === 'object') { + this.x = x.x; + this.y = x.y; + this.w = x.w; + this.h = x.h; + } + else { + if (x !== undefined) { + this.x = x; + } + if (y !== undefined) { + this.y = y; + } + if (w !== undefined) { + this.w = w; + } + if (h !== undefined) { + this.h = h; + } + } + return this; +}; + +/** + * Parse library string into values. + * + * @param {string} library + * library in the format "machineName majorVersion.minorVersion" + * @returns {Object} + * library as an object with machineName, majorVersion and minorVersion properties + * return false if the library parameter is invalid + */ +H5P.libraryFromString = function (library) { + var regExp = /(.+)\s(\d+)\.(\d+)$/g; + var res = regExp.exec(library); + if (res !== null) { + return { + 'machineName': res[1], + 'majorVersion': parseInt(res[2]), + 'minorVersion': parseInt(res[3]) + }; + } + else { + return false; + } +}; + +/** + * Get the path to the library + * + * @param {string} library + * The library identifier in the format "machineName-majorVersion.minorVersion". + * @returns {string} + * The full path to the library. + */ +H5P.getLibraryPath = function (library) { + if (H5PIntegration.urlLibraries !== undefined) { + // This is an override for those implementations that has a different libraries URL, e.g. Moodle + return H5PIntegration.urlLibraries + '/' + library; + } + else { + return H5PIntegration.url + '/libraries/' + library; + } +}; + +/** + * Recursivly clone the given object. + * + * @param {Object|Array} object + * Object to clone. + * @param {boolean} [recursive] + * @returns {Object|Array} + * A clone of object. + */ +H5P.cloneObject = function (object, recursive) { + // TODO: Consider if this needs to be in core. Doesn't $.extend do the same? + var clone = object instanceof Array ? [] : {}; + + for (var i in object) { + if (object.hasOwnProperty(i)) { + if (recursive !== undefined && recursive && typeof object[i] === 'object') { + clone[i] = H5P.cloneObject(object[i], recursive); + } + else { + clone[i] = object[i]; + } + } + } + + return clone; +}; + +/** + * Remove all empty spaces before and after the value. + * + * @param {string} value + * @returns {string} + */ +H5P.trim = function (value) { + return value.replace(/^\s+|\s+$/g, ''); + + // TODO: Only include this or String.trim(). What is best? + // I'm leaning towards implementing the missing ones: http://kangax.github.io/compat-table/es5/ + // So should we make this function deprecated? +}; + +/** + * Check if JavaScript path/key is loaded. + * + * @param {string} path + * @returns {boolean} + */ +H5P.jsLoaded = function (path) { + H5PIntegration.loadedJs = H5PIntegration.loadedJs || []; + return H5P.jQuery.inArray(path, H5PIntegration.loadedJs) !== -1; +}; + +/** + * Check if styles path/key is loaded. + * + * @param {string} path + * @returns {boolean} + */ +H5P.cssLoaded = function (path) { + H5PIntegration.loadedCss = H5PIntegration.loadedCss || []; + return H5P.jQuery.inArray(path, H5PIntegration.loadedCss) !== -1; +}; + +/** + * Shuffle an array in place. + * + * @param {Array} array + * Array to shuffle + * @returns {Array} + * The passed array is returned for chaining. + */ +H5P.shuffleArray = function (array) { + // TODO: Consider if this should be a part of core. I'm guessing very few libraries are going to use it. + if (!(array instanceof Array)) { + return; + } + + var i = array.length, j, tempi, tempj; + if ( i === 0 ) return false; + while ( --i ) { + j = Math.floor( Math.random() * ( i + 1 ) ); + tempi = array[i]; + tempj = array[j]; + array[i] = tempj; + array[j] = tempi; + } + return array; +}; + +/** + * Post finished results for user. + * + * @deprecated + * Do not use this function directly, trigger the finish event instead. + * Will be removed march 2016 + * @param {number} contentId + * Identifies the content + * @param {number} score + * Achieved score/points + * @param {number} maxScore + * The maximum score/points that can be achieved + * @param {number} [time] + * Reported time consumption/usage + */ +H5P.setFinished = function (contentId, score, maxScore, time) { + var validScore = typeof score === 'number' || score instanceof Number; + if (validScore && H5PIntegration.postUserStatistics === true) { + /** + * Return unix timestamp for the given JS Date. + * + * @private + * @param {Date} date + * @returns {Number} + */ + var toUnix = function (date) { + return Math.round(date.getTime() / 1000); + }; + + // Post the results + const data = { + contentId: contentId, + score: score, + maxScore: maxScore, + opened: toUnix(H5P.opened[contentId]), + finished: toUnix(new Date()), + time: time + }; + H5P.jQuery.post(H5PIntegration.ajax.setFinished, data) + .fail(function () { + H5P.offlineRequestQueue.add(H5PIntegration.ajax.setFinished, data); + }); + } +}; + +// Add indexOf to browsers that lack them. (IEs) +if (!Array.prototype.indexOf) { + Array.prototype.indexOf = function (needle) { + for (var i = 0; i < this.length; i++) { + if (this[i] === needle) { + return i; + } + } + return -1; + }; +} + +// Need to define trim() since this is not available on older IEs, +// and trim is used in several libs +if (String.prototype.trim === undefined) { + String.prototype.trim = function () { + return H5P.trim(this); + }; +} + +/** + * Trigger an event on an instance + * + * Helper function that triggers an event if the instance supports event handling + * + * @param {Object} instance + * Instance of H5P content + * @param {string} eventType + * Type of event to trigger + * @param {*} data + * @param {Object} extras + */ +H5P.trigger = function (instance, eventType, data, extras) { + // Try new event system first + if (instance.trigger !== undefined) { + instance.trigger(eventType, data, extras); + } + // Try deprecated event system + else if (instance.$ !== undefined && instance.$.trigger !== undefined) { + instance.$.trigger(eventType); + } +}; + +/** + * Register an event handler + * + * Helper function that registers an event handler for an event type if + * the instance supports event handling + * + * @param {Object} instance + * Instance of H5P content + * @param {string} eventType + * Type of event to listen for + * @param {H5P.EventCallback} handler + * Callback that gets triggered for events of the specified type + */ +H5P.on = function (instance, eventType, handler) { + // Try new event system first + if (instance.on !== undefined) { + instance.on(eventType, handler); + } + // Try deprecated event system + else if (instance.$ !== undefined && instance.$.on !== undefined) { + instance.$.on(eventType, handler); + } +}; + +/** + * Generate random UUID + * + * @returns {string} UUID + */ +H5P.createUUID = function () { + return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (char) { + var random = Math.random()*16|0, newChar = char === 'x' ? random : (random&0x3|0x8); + return newChar.toString(16); + }); +}; + +/** + * Create title + * + * @param {string} rawTitle + * @param {number} maxLength + * @returns {string} + */ +H5P.createTitle = function (rawTitle, maxLength) { + if (!rawTitle) { + return ''; + } + if (maxLength === undefined) { + maxLength = 60; + } + var title = H5P.jQuery('
') + .text( + // Strip tags + rawTitle.replace(/(<([^>]+)>)/ig,"") + // Escape + ).text(); + if (title.length > maxLength) { + title = title.substr(0, maxLength - 3) + '...'; + } + return title; +}; + +// Wrap in privates +(function ($) { + + /** + * Creates ajax requests for inserting, updateing and deleteing + * content user data. + * + * @private + * @param {number} contentId What content to store the data for. + * @param {string} dataType Identifies the set of data for this content. + * @param {string} subContentId Identifies sub content + * @param {function} [done] Callback when ajax is done. + * @param {object} [data] To be stored for future use. + * @param {boolean} [preload=false] Data is loaded when content is loaded. + * @param {boolean} [invalidate=false] Data is invalidated when content changes. + * @param {boolean} [async=true] + */ + function contentUserDataAjax(contentId, dataType, subContentId, done, data, preload, invalidate, async) { + if (H5PIntegration.user === undefined) { + // Not logged in, no use in saving. + done('Not signed in.'); + return; + } + + var options = { + url: H5PIntegration.ajax.contentUserData.replace(':contentId', contentId).replace(':dataType', dataType).replace(':subContentId', subContentId ? subContentId : 0), + dataType: 'json', + async: async === undefined ? true : async + }; + if (data !== undefined) { + options.type = 'POST'; + options.data = { + data: (data === null ? 0 : data), + preload: (preload ? 1 : 0), + invalidate: (invalidate ? 1 : 0) + }; + } + else { + options.type = 'GET'; + } + if (done !== undefined) { + options.error = function (xhr, error) { + done(error); + }; + options.success = function (response) { + if (!response.success) { + done(response.message); + return; + } + + if (response.data === false || response.data === undefined) { + done(); + return; + } + + done(undefined, response.data); + }; + } + + $.ajax(options); + } + + /** + * Get user data for given content. + * + * @param {number} contentId + * What content to get data for. + * @param {string} dataId + * Identifies the set of data for this content. + * @param {function} done + * Callback with error and data parameters. + * @param {string} [subContentId] + * Identifies which data belongs to sub content. + */ + H5P.getUserData = function (contentId, dataId, done, subContentId) { + if (!subContentId) { + subContentId = 0; // Default + } + + H5PIntegration.contents = H5PIntegration.contents || {}; + var content = H5PIntegration.contents['cid-' + contentId] || {}; + var preloadedData = content.contentUserData; + if (preloadedData && preloadedData[subContentId] && preloadedData[subContentId][dataId] !== undefined) { + if (preloadedData[subContentId][dataId] === 'RESET') { + done(undefined, null); + return; + } + try { + done(undefined, JSON.parse(preloadedData[subContentId][dataId])); + } + catch (err) { + done(err); + } + } + else { + contentUserDataAjax(contentId, dataId, subContentId, function (err, data) { + if (err || data === undefined) { + done(err, data); + return; // Error or no data + } + + // Cache in preloaded + if (content.contentUserData === undefined) { + content.contentUserData = preloadedData = {}; + } + if (preloadedData[subContentId] === undefined) { + preloadedData[subContentId] = {}; + } + preloadedData[subContentId][dataId] = data; + + // Done. Try to decode JSON + try { + done(undefined, JSON.parse(data)); + } + catch (e) { + done(e); + } + }); + } + }; + + /** + * Async error handling. + * + * @callback H5P.ErrorCallback + * @param {*} error + */ + + /** + * Set user data for given content. + * + * @param {number} contentId + * What content to get data for. + * @param {string} dataId + * Identifies the set of data for this content. + * @param {Object} data + * The data that is to be stored. + * @param {Object} [extras] + * Extra properties + * @param {string} [extras.subContentId] + * Identifies which data belongs to sub content. + * @param {boolean} [extras.preloaded=true] + * If the data should be loaded when content is loaded. + * @param {boolean} [extras.deleteOnChange=false] + * If the data should be invalidated when the content changes. + * @param {H5P.ErrorCallback} [extras.errorCallback] + * Callback with error as parameters. + * @param {boolean} [extras.async=true] + */ + H5P.setUserData = function (contentId, dataId, data, extras) { + var options = H5P.jQuery.extend(true, {}, { + subContentId: 0, + preloaded: true, + deleteOnChange: false, + async: true + }, extras); + + try { + data = JSON.stringify(data); + } + catch (err) { + if (options.errorCallback) { + options.errorCallback(err); + } + return; // Failed to serialize. + } + + var content = H5PIntegration.contents['cid-' + contentId]; + if (content === undefined) { + content = H5PIntegration.contents['cid-' + contentId] = {}; + } + if (!content.contentUserData) { + content.contentUserData = {}; + } + var preloadedData = content.contentUserData; + if (preloadedData[options.subContentId] === undefined) { + preloadedData[options.subContentId] = {}; + } + if (data === preloadedData[options.subContentId][dataId]) { + return; // No need to save this twice. + } + + preloadedData[options.subContentId][dataId] = data; + contentUserDataAjax(contentId, dataId, options.subContentId, function (error) { + if (options.errorCallback && error) { + options.errorCallback(error); + } + }, data, options.preloaded, options.deleteOnChange, options.async); + }; + + /** + * Delete user data for given content. + * + * @param {number} contentId + * What content to remove data for. + * @param {string} dataId + * Identifies the set of data for this content. + * @param {string} [subContentId] + * Identifies which data belongs to sub content. + */ + H5P.deleteUserData = function (contentId, dataId, subContentId) { + if (!subContentId) { + subContentId = 0; // Default + } + + // Remove from preloaded/cache + var preloadedData = H5PIntegration.contents['cid-' + contentId].contentUserData; + if (preloadedData && preloadedData[subContentId] && preloadedData[subContentId][dataId]) { + delete preloadedData[subContentId][dataId]; + } + + contentUserDataAjax(contentId, dataId, subContentId, undefined, null); + }; + + /** + * Function for getting content for a certain ID + * + * @param {number} contentId + * @return {Object} + */ + H5P.getContentForInstance = function (contentId) { + var key = 'cid-' + contentId; + var exists = H5PIntegration && H5PIntegration.contents && + H5PIntegration.contents[key]; + + return exists ? H5PIntegration.contents[key] : undefined; + }; + + /** + * Prepares the content parameters for storing in the clipboard. + * + * @class + * @param {Object} parameters The parameters for the content to store + * @param {string} [genericProperty] If only part of the parameters are generic, which part + * @param {string} [specificKey] If the parameters are specific, what content type does it fit + * @returns {Object} Ready for the clipboard + */ + H5P.ClipboardItem = function (parameters, genericProperty, specificKey) { + var self = this; + + /** + * Set relative dimensions when params contains a file with a width and a height. + * Very useful to be compatible with wysiwyg editors. + * + * @private + */ + var setDimensionsFromFile = function () { + if (!self.generic) { + return; + } + var params = self.specific[self.generic]; + if (!params.params.file || !params.params.file.width || !params.params.file.height) { + return; + } + + self.width = 20; // % + self.height = (params.params.file.height / params.params.file.width) * self.width; + }; + + if (!genericProperty) { + genericProperty = 'action'; + parameters = { + action: parameters + }; + } + + self.specific = parameters; + + if (genericProperty && parameters[genericProperty]) { + self.generic = genericProperty; + } + if (specificKey) { + self.from = specificKey; + } + + if (window.H5PEditor && H5PEditor.contentId) { + self.contentId = H5PEditor.contentId; + } + + if (!self.specific.width && !self.specific.height) { + setDimensionsFromFile(); + } + }; + + /** + * Store item in the H5P Clipboard. + * + * @param {H5P.ClipboardItem|*} clipboardItem + */ + H5P.clipboardify = function (clipboardItem) { + if (!(clipboardItem instanceof H5P.ClipboardItem)) { + clipboardItem = new H5P.ClipboardItem(clipboardItem); + } + H5P.setClipboard(clipboardItem); + }; + + /** + * Retrieve parsed clipboard data. + * + * @return {Object} + */ + H5P.getClipboard = function () { + return parseClipboard(); + }; + + /** + * Set item in the H5P Clipboard. + * + * @param {H5P.ClipboardItem|object} clipboardItem - Data to be set. + */ + H5P.setClipboard = function (clipboardItem) { + localStorage.setItem('h5pClipboard', JSON.stringify(clipboardItem)); + + // Trigger an event so all 'Paste' buttons may be enabled. + H5P.externalDispatcher.trigger('datainclipboard', {reset: false}); + }; + + /** + * Get config for a library + * + * @param string machineName + * @return Object + */ + H5P.getLibraryConfig = function (machineName) { + var hasConfig = H5PIntegration.libraryConfig && H5PIntegration.libraryConfig[machineName]; + return hasConfig ? H5PIntegration.libraryConfig[machineName] : {}; + }; + + /** + * Get item from the H5P Clipboard. + * + * @private + * @return {Object} + */ + var parseClipboard = function () { + var clipboardData = localStorage.getItem('h5pClipboard'); + if (!clipboardData) { + return; + } + + // Try to parse clipboard dat + try { + clipboardData = JSON.parse(clipboardData); + } + catch (err) { + console.error('Unable to parse JSON from clipboard.', err); + return; + } + + // Update file URLs and reset content Ids + recursiveUpdate(clipboardData.specific, function (path) { + var isTmpFile = (path.substr(-4, 4) === '#tmp'); + if (!isTmpFile && clipboardData.contentId && !path.match(/^https?:\/\//i)) { + // Comes from existing content + + if (H5PEditor.contentId) { + // .. to existing content + return '../' + clipboardData.contentId + '/' + path; + } + else { + // .. to new content + return (H5PEditor.contentRelUrl ? H5PEditor.contentRelUrl : '../content/') + clipboardData.contentId + '/' + path; + } + } + return path; // Will automatically be looked for in tmp folder + }); + + + if (clipboardData.generic) { + // Use reference instead of key + clipboardData.generic = clipboardData.specific[clipboardData.generic]; + } + + return clipboardData; + }; + + /** + * Update file URLs and reset content IDs. + * Useful when copying content. + * + * @private + * @param {object} params Reference + * @param {function} handler Modifies the path to work when pasted + */ + var recursiveUpdate = function (params, handler) { + for (var prop in params) { + if (params.hasOwnProperty(prop) && params[prop] instanceof Object) { + var obj = params[prop]; + if (obj.path !== undefined && obj.mime !== undefined) { + obj.path = handler(obj.path); + } + else { + if (obj.library !== undefined && obj.subContentId !== undefined) { + // Avoid multiple content with same ID + delete obj.subContentId; + } + recursiveUpdate(obj, handler); + } + } + } + }; + + // Init H5P when page is fully loadded + $(document).ready(function () { + + window.addEventListener('storage', function (event) { + // Pick up clipboard changes from other tabs + if (event.key === 'h5pClipboard') { + // Trigger an event so all 'Paste' buttons may be enabled. + H5P.externalDispatcher.trigger('datainclipboard', {reset: event.newValue === null}); + } + }); + + var ccVersions = { + 'default': '4.0', + '4.0': H5P.t('licenseCC40'), + '3.0': H5P.t('licenseCC30'), + '2.5': H5P.t('licenseCC25'), + '2.0': H5P.t('licenseCC20'), + '1.0': H5P.t('licenseCC10'), + }; + + /** + * Maps copyright license codes to their human readable counterpart. + * + * @type {Object} + */ + H5P.copyrightLicenses = { + 'U': H5P.t('licenseU'), + 'CC BY': { + label: H5P.t('licenseCCBY'), + link: 'http://creativecommons.org/licenses/by/:version', + versions: ccVersions + }, + 'CC BY-SA': { + label: H5P.t('licenseCCBYSA'), + link: 'http://creativecommons.org/licenses/by-sa/:version', + versions: ccVersions + }, + 'CC BY-ND': { + label: H5P.t('licenseCCBYND'), + link: 'http://creativecommons.org/licenses/by-nd/:version', + versions: ccVersions + }, + 'CC BY-NC': { + label: H5P.t('licenseCCBYNC'), + link: 'http://creativecommons.org/licenses/by-nc/:version', + versions: ccVersions + }, + 'CC BY-NC-SA': { + label: H5P.t('licenseCCBYNCSA'), + link: 'http://creativecommons.org/licenses/by-nc-sa/:version', + versions: ccVersions + }, + 'CC BY-NC-ND': { + label: H5P.t('licenseCCBYNCND'), + link: 'http://creativecommons.org/licenses/by-nc-nd/:version', + versions: ccVersions + }, + 'CC0 1.0': { + label: H5P.t('licenseCC010'), + link: 'https://creativecommons.org/publicdomain/zero/1.0/' + }, + 'GNU GPL': { + label: H5P.t('licenseGPL'), + link: 'http://www.gnu.org/licenses/gpl-:version-standalone.html', + linkVersions: { + 'v3': '3.0', + 'v2': '2.0', + 'v1': '1.0' + }, + versions: { + 'default': 'v3', + 'v3': H5P.t('licenseV3'), + 'v2': H5P.t('licenseV2'), + 'v1': H5P.t('licenseV1') + } + }, + 'PD': { + label: H5P.t('licensePD'), + versions: { + 'CC0 1.0': { + label: H5P.t('licenseCC010'), + link: 'https://creativecommons.org/publicdomain/zero/1.0/' + }, + 'CC PDM': { + label: H5P.t('licensePDM'), + link: 'https://creativecommons.org/publicdomain/mark/1.0/' + } + } + }, + 'ODC PDDL': 'Public Domain Dedication and Licence', + 'CC PDM': { + label: H5P.t('licensePDM'), + link: 'https://creativecommons.org/publicdomain/mark/1.0/' + }, + 'C': H5P.t('licenseC'), + }; + + /** + * Indicates if H5P is embedded on an external page using iframe. + * @member {boolean} H5P.externalEmbed + */ + + // Relay events to top window. This must be done before H5P.init + // since events may be fired on initialization. + if (H5P.isFramed && H5P.externalEmbed === false) { + H5P.externalDispatcher.on('*', function (event) { + window.parent.H5P.externalDispatcher.trigger.call(this, event); + }); + } + + /** + * Prevent H5P Core from initializing. Must be overriden before document ready. + * @member {boolean} H5P.preventInit + */ + if (!H5P.preventInit) { + // Note that this start script has to be an external resource for it to + // load in correct order in IE9. + H5P.init(document.body); + } + + if (H5PIntegration.saveFreq !== false) { + // When was the last state stored + var lastStoredOn = 0; + // Store the current state of the H5P when leaving the page. + var storeCurrentState = function () { + // Make sure at least 250 ms has passed since last save + var currentTime = new Date().getTime(); + if (currentTime - lastStoredOn > 250) { + lastStoredOn = currentTime; + for (var i = 0; i < H5P.instances.length; i++) { + var instance = H5P.instances[i]; + if (instance.getCurrentState instanceof Function || + typeof instance.getCurrentState === 'function') { + var state = instance.getCurrentState(); + if (state !== undefined) { + // Async is not used to prevent the request from being cancelled. + H5P.setUserData(instance.contentId, 'state', state, {deleteOnChange: true, async: false}); + } + } + } + } + }; + // iPad does not support beforeunload, therefore using unload + H5P.$window.one('beforeunload unload', function () { + // Only want to do this once + H5P.$window.off('pagehide beforeunload unload'); + storeCurrentState(); + }); + // pagehide is used on iPad when tabs are switched + H5P.$window.on('pagehide', storeCurrentState); + } + }); + +})(H5P.jQuery); diff --git a/src/core/h5p/assets/js/jquery.js b/src/core/h5p/assets/js/jquery.js new file mode 100644 index 000000000..a05d5568b --- /dev/null +++ b/src/core/h5p/assets/js/jquery.js @@ -0,0 +1,20 @@ +/*! jQuery v1.9.1 | (c) 2005, 2012 jQuery Foundation, Inc. | jquery.org/license +*/(function(e,t){var n,r,i=typeof t,o=e.document,a=e.location,s=e.jQuery,u=e.$,l={},c=[],p="1.9.1",f=c.concat,d=c.push,h=c.slice,g=c.indexOf,m=l.toString,y=l.hasOwnProperty,v=p.trim,b=function(e,t){return new b.fn.init(e,t,r)},x=/[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/.source,w=/\S+/g,T=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,N=/^(?:(<[\w\W]+>)[^>]*|#([\w-]*))$/,C=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,k=/^[\],:{}\s]*$/,E=/(?:^|:|,)(?:\s*\[)+/g,S=/\\(?:["\\\/bfnrt]|u[\da-fA-F]{4})/g,A=/"[^"\\\r\n]*"|true|false|null|-?(?:\d+\.|)\d+(?:[eE][+-]?\d+|)/g,j=/^-ms-/,D=/-([\da-z])/gi,L=function(e,t){return t.toUpperCase()},H=function(e){(o.addEventListener||"load"===e.type||"complete"===o.readyState)&&(q(),b.ready())},q=function(){o.addEventListener?(o.removeEventListener("DOMContentLoaded",H,!1),e.removeEventListener("load",H,!1)):(o.detachEvent("onreadystatechange",H),e.detachEvent("onload",H))};b.fn=b.prototype={jquery:p,constructor:b,init:function(e,n,r){var i,a;if(!e)return this;if("string"==typeof e){if(i="<"===e.charAt(0)&&">"===e.charAt(e.length-1)&&e.length>=3?[null,e,null]:N.exec(e),!i||!i[1]&&n)return!n||n.jquery?(n||r).find(e):this.constructor(n).find(e);if(i[1]){if(n=n instanceof b?n[0]:n,b.merge(this,b.parseHTML(i[1],n&&n.nodeType?n.ownerDocument||n:o,!0)),C.test(i[1])&&b.isPlainObject(n))for(i in n)b.isFunction(this[i])?this[i](n[i]):this.attr(i,n[i]);return this}if(a=o.getElementById(i[2]),a&&a.parentNode){if(a.id!==i[2])return r.find(e);this.length=1,this[0]=a}return this.context=o,this.selector=e,this}return e.nodeType?(this.context=this[0]=e,this.length=1,this):b.isFunction(e)?r.ready(e):(e.selector!==t&&(this.selector=e.selector,this.context=e.context),b.makeArray(e,this))},selector:"",length:0,size:function(){return this.length},toArray:function(){return h.call(this)},get:function(e){return null==e?this.toArray():0>e?this[this.length+e]:this[e]},pushStack:function(e){var t=b.merge(this.constructor(),e);return t.prevObject=this,t.context=this.context,t},each:function(e,t){return b.each(this,e,t)},ready:function(e){return b.ready.promise().done(e),this},slice:function(){return this.pushStack(h.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(e){var t=this.length,n=+e+(0>e?t:0);return this.pushStack(n>=0&&t>n?[this[n]]:[])},map:function(e){return this.pushStack(b.map(this,function(t,n){return e.call(t,n,t)}))},end:function(){return this.prevObject||this.constructor(null)},push:d,sort:[].sort,splice:[].splice},b.fn.init.prototype=b.fn,b.extend=b.fn.extend=function(){var e,n,r,i,o,a,s=arguments[0]||{},u=1,l=arguments.length,c=!1;for("boolean"==typeof s&&(c=s,s=arguments[1]||{},u=2),"object"==typeof s||b.isFunction(s)||(s={}),l===u&&(s=this,--u);l>u;u++)if(null!=(o=arguments[u]))for(i in o)e=s[i],r=o[i],s!==r&&(c&&r&&(b.isPlainObject(r)||(n=b.isArray(r)))?(n?(n=!1,a=e&&b.isArray(e)?e:[]):a=e&&b.isPlainObject(e)?e:{},s[i]=b.extend(c,a,r)):r!==t&&(s[i]=r));return s},b.extend({noConflict:function(t){return e.$===b&&(e.$=u),t&&e.jQuery===b&&(e.jQuery=s),b},isReady:!1,readyWait:1,holdReady:function(e){e?b.readyWait++:b.ready(!0)},ready:function(e){if(e===!0?!--b.readyWait:!b.isReady){if(!o.body)return setTimeout(b.ready);b.isReady=!0,e!==!0&&--b.readyWait>0||(n.resolveWith(o,[b]),b.fn.trigger&&b(o).trigger("ready").off("ready"))}},isFunction:function(e){return"function"===b.type(e)},isArray:Array.isArray||function(e){return"array"===b.type(e)},isWindow:function(e){return null!=e&&e==e.window},isNumeric:function(e){return!isNaN(parseFloat(e))&&isFinite(e)},type:function(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?l[m.call(e)]||"object":typeof e},isPlainObject:function(e){if(!e||"object"!==b.type(e)||e.nodeType||b.isWindow(e))return!1;try{if(e.constructor&&!y.call(e,"constructor")&&!y.call(e.constructor.prototype,"isPrototypeOf"))return!1}catch(n){return!1}var r;for(r in e);return r===t||y.call(e,r)},isEmptyObject:function(e){var t;for(t in e)return!1;return!0},error:function(e){throw Error(e)},parseHTML:function(e,t,n){if(!e||"string"!=typeof e)return null;"boolean"==typeof t&&(n=t,t=!1),t=t||o;var r=C.exec(e),i=!n&&[];return r?[t.createElement(r[1])]:(r=b.buildFragment([e],t,i),i&&b(i).remove(),b.merge([],r.childNodes))},parseJSON:function(n){return e.JSON&&e.JSON.parse?e.JSON.parse(n):null===n?n:"string"==typeof n&&(n=b.trim(n),n&&k.test(n.replace(S,"@").replace(A,"]").replace(E,"")))?Function("return "+n)():(b.error("Invalid JSON: "+n),t)},parseXML:function(n){var r,i;if(!n||"string"!=typeof n)return null;try{e.DOMParser?(i=new DOMParser,r=i.parseFromString(n,"text/xml")):(r=new ActiveXObject("Microsoft.XMLDOM"),r.async="false",r.loadXML(n))}catch(o){r=t}return r&&r.documentElement&&!r.getElementsByTagName("parsererror").length||b.error("Invalid XML: "+n),r},noop:function(){},globalEval:function(t){t&&b.trim(t)&&(e.execScript||function(t){e.eval.call(e,t)})(t)},camelCase:function(e){return e.replace(j,"ms-").replace(D,L)},nodeName:function(e,t){return e.nodeName&&e.nodeName.toLowerCase()===t.toLowerCase()},each:function(e,t,n){var r,i=0,o=e.length,a=M(e);if(n){if(a){for(;o>i;i++)if(r=t.apply(e[i],n),r===!1)break}else for(i in e)if(r=t.apply(e[i],n),r===!1)break}else if(a){for(;o>i;i++)if(r=t.call(e[i],i,e[i]),r===!1)break}else for(i in e)if(r=t.call(e[i],i,e[i]),r===!1)break;return e},trim:v&&!v.call("\ufeff\u00a0")?function(e){return null==e?"":v.call(e)}:function(e){return null==e?"":(e+"").replace(T,"")},makeArray:function(e,t){var n=t||[];return null!=e&&(M(Object(e))?b.merge(n,"string"==typeof e?[e]:e):d.call(n,e)),n},inArray:function(e,t,n){var r;if(t){if(g)return g.call(t,e,n);for(r=t.length,n=n?0>n?Math.max(0,r+n):n:0;r>n;n++)if(n in t&&t[n]===e)return n}return-1},merge:function(e,n){var r=n.length,i=e.length,o=0;if("number"==typeof r)for(;r>o;o++)e[i++]=n[o];else while(n[o]!==t)e[i++]=n[o++];return e.length=i,e},grep:function(e,t,n){var r,i=[],o=0,a=e.length;for(n=!!n;a>o;o++)r=!!t(e[o],o),n!==r&&i.push(e[o]);return i},map:function(e,t,n){var r,i=0,o=e.length,a=M(e),s=[];if(a)for(;o>i;i++)r=t(e[i],i,n),null!=r&&(s[s.length]=r);else for(i in e)r=t(e[i],i,n),null!=r&&(s[s.length]=r);return f.apply([],s)},guid:1,proxy:function(e,n){var r,i,o;return"string"==typeof n&&(o=e[n],n=e,e=o),b.isFunction(e)?(r=h.call(arguments,2),i=function(){return e.apply(n||this,r.concat(h.call(arguments)))},i.guid=e.guid=e.guid||b.guid++,i):t},access:function(e,n,r,i,o,a,s){var u=0,l=e.length,c=null==r;if("object"===b.type(r)){o=!0;for(u in r)b.access(e,n,u,r[u],!0,a,s)}else if(i!==t&&(o=!0,b.isFunction(i)||(s=!0),c&&(s?(n.call(e,i),n=null):(c=n,n=function(e,t,n){return c.call(b(e),n)})),n))for(;l>u;u++)n(e[u],r,s?i:i.call(e[u],u,n(e[u],r)));return o?e:c?n.call(e):l?n(e[0],r):a},now:function(){return(new Date).getTime()}}),b.ready.promise=function(t){if(!n)if(n=b.Deferred(),"complete"===o.readyState)setTimeout(b.ready);else if(o.addEventListener)o.addEventListener("DOMContentLoaded",H,!1),e.addEventListener("load",H,!1);else{o.attachEvent("onreadystatechange",H),e.attachEvent("onload",H);var r=!1;try{r=null==e.frameElement&&o.documentElement}catch(i){}r&&r.doScroll&&function a(){if(!b.isReady){try{r.doScroll("left")}catch(e){return setTimeout(a,50)}q(),b.ready()}}()}return n.promise(t)},b.each("Boolean Number String Function Array Date RegExp Object Error".split(" "),function(e,t){l["[object "+t+"]"]=t.toLowerCase()});function M(e){var t=e.length,n=b.type(e);return b.isWindow(e)?!1:1===e.nodeType&&t?!0:"array"===n||"function"!==n&&(0===t||"number"==typeof t&&t>0&&t-1 in e)}r=b(o);var _={};function F(e){var t=_[e]={};return b.each(e.match(w)||[],function(e,n){t[n]=!0}),t}b.Callbacks=function(e){e="string"==typeof e?_[e]||F(e):b.extend({},e);var n,r,i,o,a,s,u=[],l=!e.once&&[],c=function(t){for(r=e.memory&&t,i=!0,a=s||0,s=0,o=u.length,n=!0;u&&o>a;a++)if(u[a].apply(t[0],t[1])===!1&&e.stopOnFalse){r=!1;break}n=!1,u&&(l?l.length&&c(l.shift()):r?u=[]:p.disable())},p={add:function(){if(u){var t=u.length;(function i(t){b.each(t,function(t,n){var r=b.type(n);"function"===r?e.unique&&p.has(n)||u.push(n):n&&n.length&&"string"!==r&&i(n)})})(arguments),n?o=u.length:r&&(s=t,c(r))}return this},remove:function(){return u&&b.each(arguments,function(e,t){var r;while((r=b.inArray(t,u,r))>-1)u.splice(r,1),n&&(o>=r&&o--,a>=r&&a--)}),this},has:function(e){return e?b.inArray(e,u)>-1:!(!u||!u.length)},empty:function(){return u=[],this},disable:function(){return u=l=r=t,this},disabled:function(){return!u},lock:function(){return l=t,r||p.disable(),this},locked:function(){return!l},fireWith:function(e,t){return t=t||[],t=[e,t.slice?t.slice():t],!u||i&&!l||(n?l.push(t):c(t)),this},fire:function(){return p.fireWith(this,arguments),this},fired:function(){return!!i}};return p},b.extend({Deferred:function(e){var t=[["resolve","done",b.Callbacks("once memory"),"resolved"],["reject","fail",b.Callbacks("once memory"),"rejected"],["notify","progress",b.Callbacks("memory")]],n="pending",r={state:function(){return n},always:function(){return i.done(arguments).fail(arguments),this},then:function(){var e=arguments;return b.Deferred(function(n){b.each(t,function(t,o){var a=o[0],s=b.isFunction(e[t])&&e[t];i[o[1]](function(){var e=s&&s.apply(this,arguments);e&&b.isFunction(e.promise)?e.promise().done(n.resolve).fail(n.reject).progress(n.notify):n[a+"With"](this===r?n.promise():this,s?[e]:arguments)})}),e=null}).promise()},promise:function(e){return null!=e?b.extend(e,r):r}},i={};return r.pipe=r.then,b.each(t,function(e,o){var a=o[2],s=o[3];r[o[1]]=a.add,s&&a.add(function(){n=s},t[1^e][2].disable,t[2][2].lock),i[o[0]]=function(){return i[o[0]+"With"](this===i?r:this,arguments),this},i[o[0]+"With"]=a.fireWith}),r.promise(i),e&&e.call(i,i),i},when:function(e){var t=0,n=h.call(arguments),r=n.length,i=1!==r||e&&b.isFunction(e.promise)?r:0,o=1===i?e:b.Deferred(),a=function(e,t,n){return function(r){t[e]=this,n[e]=arguments.length>1?h.call(arguments):r,n===s?o.notifyWith(t,n):--i||o.resolveWith(t,n)}},s,u,l;if(r>1)for(s=Array(r),u=Array(r),l=Array(r);r>t;t++)n[t]&&b.isFunction(n[t].promise)?n[t].promise().done(a(t,l,n)).fail(o.reject).progress(a(t,u,s)):--i;return i||o.resolveWith(l,n),o.promise()}}),b.support=function(){var t,n,r,a,s,u,l,c,p,f,d=o.createElement("div");if(d.setAttribute("className","t"),d.innerHTML="
a",n=d.getElementsByTagName("*"),r=d.getElementsByTagName("a")[0],!n||!r||!n.length)return{};s=o.createElement("select"),l=s.appendChild(o.createElement("option")),a=d.getElementsByTagName("input")[0],r.style.cssText="top:1px;float:left;opacity:.5",t={getSetAttribute:"t"!==d.className,leadingWhitespace:3===d.firstChild.nodeType,tbody:!d.getElementsByTagName("tbody").length,htmlSerialize:!!d.getElementsByTagName("link").length,style:/top/.test(r.getAttribute("style")),hrefNormalized:"/a"===r.getAttribute("href"),opacity:/^0.5/.test(r.style.opacity),cssFloat:!!r.style.cssFloat,checkOn:!!a.value,optSelected:l.selected,enctype:!!o.createElement("form").enctype,html5Clone:"<:nav>"!==o.createElement("nav").cloneNode(!0).outerHTML,boxModel:"CSS1Compat"===o.compatMode,deleteExpando:!0,noCloneEvent:!0,inlineBlockNeedsLayout:!1,shrinkWrapBlocks:!1,reliableMarginRight:!0,boxSizingReliable:!0,pixelPosition:!1},a.checked=!0,t.noCloneChecked=a.cloneNode(!0).checked,s.disabled=!0,t.optDisabled=!l.disabled;try{delete d.test}catch(h){t.deleteExpando=!1}a=o.createElement("input"),a.setAttribute("value",""),t.input=""===a.getAttribute("value"),a.value="t",a.setAttribute("type","radio"),t.radioValue="t"===a.value,a.setAttribute("checked","t"),a.setAttribute("name","t"),u=o.createDocumentFragment(),u.appendChild(a),t.appendChecked=a.checked,t.checkClone=u.cloneNode(!0).cloneNode(!0).lastChild.checked,d.attachEvent&&(d.attachEvent("onclick",function(){t.noCloneEvent=!1}),d.cloneNode(!0).click());for(f in{submit:!0,change:!0,focusin:!0})d.setAttribute(c="on"+f,"t"),t[f+"Bubbles"]=c in e||d.attributes[c].expando===!1;return d.style.backgroundClip="content-box",d.cloneNode(!0).style.backgroundClip="",t.clearCloneStyle="content-box"===d.style.backgroundClip,b(function(){var n,r,a,s="padding:0;margin:0;border:0;display:block;box-sizing:content-box;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;",u=o.getElementsByTagName("body")[0];u&&(n=o.createElement("div"),n.style.cssText="border:0;width:0;height:0;position:absolute;top:0;left:-9999px;margin-top:1px",u.appendChild(n).appendChild(d),d.innerHTML="
t
",a=d.getElementsByTagName("td"),a[0].style.cssText="padding:0;margin:0;border:0;display:none",p=0===a[0].offsetHeight,a[0].style.display="",a[1].style.display="none",t.reliableHiddenOffsets=p&&0===a[0].offsetHeight,d.innerHTML="",d.style.cssText="box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;padding:1px;border:1px;display:block;width:4px;margin-top:1%;position:absolute;top:1%;",t.boxSizing=4===d.offsetWidth,t.doesNotIncludeMarginInBodyOffset=1!==u.offsetTop,e.getComputedStyle&&(t.pixelPosition="1%"!==(e.getComputedStyle(d,null)||{}).top,t.boxSizingReliable="4px"===(e.getComputedStyle(d,null)||{width:"4px"}).width,r=d.appendChild(o.createElement("div")),r.style.cssText=d.style.cssText=s,r.style.marginRight=r.style.width="0",d.style.width="1px",t.reliableMarginRight=!parseFloat((e.getComputedStyle(r,null)||{}).marginRight)),typeof d.style.zoom!==i&&(d.innerHTML="",d.style.cssText=s+"width:1px;padding:1px;display:inline;zoom:1",t.inlineBlockNeedsLayout=3===d.offsetWidth,d.style.display="block",d.innerHTML="
",d.firstChild.style.width="5px",t.shrinkWrapBlocks=3!==d.offsetWidth,t.inlineBlockNeedsLayout&&(u.style.zoom=1)),u.removeChild(n),n=d=a=r=null)}),n=s=u=l=r=a=null,t}();var O=/(?:\{[\s\S]*\}|\[[\s\S]*\])$/,B=/([A-Z])/g;function P(e,n,r,i){if(b.acceptData(e)){var o,a,s=b.expando,u="string"==typeof n,l=e.nodeType,p=l?b.cache:e,f=l?e[s]:e[s]&&s;if(f&&p[f]&&(i||p[f].data)||!u||r!==t)return f||(l?e[s]=f=c.pop()||b.guid++:f=s),p[f]||(p[f]={},l||(p[f].toJSON=b.noop)),("object"==typeof n||"function"==typeof n)&&(i?p[f]=b.extend(p[f],n):p[f].data=b.extend(p[f].data,n)),o=p[f],i||(o.data||(o.data={}),o=o.data),r!==t&&(o[b.camelCase(n)]=r),u?(a=o[n],null==a&&(a=o[b.camelCase(n)])):a=o,a}}function R(e,t,n){if(b.acceptData(e)){var r,i,o,a=e.nodeType,s=a?b.cache:e,u=a?e[b.expando]:b.expando;if(s[u]){if(t&&(o=n?s[u]:s[u].data)){b.isArray(t)?t=t.concat(b.map(t,b.camelCase)):t in o?t=[t]:(t=b.camelCase(t),t=t in o?[t]:t.split(" "));for(r=0,i=t.length;i>r;r++)delete o[t[r]];if(!(n?$:b.isEmptyObject)(o))return}(n||(delete s[u].data,$(s[u])))&&(a?b.cleanData([e],!0):b.support.deleteExpando||s!=s.window?delete s[u]:s[u]=null)}}}b.extend({cache:{},expando:"jQuery"+(p+Math.random()).replace(/\D/g,""),noData:{embed:!0,object:"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",applet:!0},hasData:function(e){return e=e.nodeType?b.cache[e[b.expando]]:e[b.expando],!!e&&!$(e)},data:function(e,t,n){return P(e,t,n)},removeData:function(e,t){return R(e,t)},_data:function(e,t,n){return P(e,t,n,!0)},_removeData:function(e,t){return R(e,t,!0)},acceptData:function(e){if(e.nodeType&&1!==e.nodeType&&9!==e.nodeType)return!1;var t=e.nodeName&&b.noData[e.nodeName.toLowerCase()];return!t||t!==!0&&e.getAttribute("classid")===t}}),b.fn.extend({data:function(e,n){var r,i,o=this[0],a=0,s=null;if(e===t){if(this.length&&(s=b.data(o),1===o.nodeType&&!b._data(o,"parsedAttrs"))){for(r=o.attributes;r.length>a;a++)i=r[a].name,i.indexOf("data-")||(i=b.camelCase(i.slice(5)),W(o,i,s[i]));b._data(o,"parsedAttrs",!0)}return s}return"object"==typeof e?this.each(function(){b.data(this,e)}):b.access(this,function(n){return n===t?o?W(o,e,b.data(o,e)):null:(this.each(function(){b.data(this,e,n)}),t)},null,n,arguments.length>1,null,!0)},removeData:function(e){return this.each(function(){b.removeData(this,e)})}});function W(e,n,r){if(r===t&&1===e.nodeType){var i="data-"+n.replace(B,"-$1").toLowerCase();if(r=e.getAttribute(i),"string"==typeof r){try{r="true"===r?!0:"false"===r?!1:"null"===r?null:+r+""===r?+r:O.test(r)?b.parseJSON(r):r}catch(o){}b.data(e,n,r)}else r=t}return r}function $(e){var t;for(t in e)if(("data"!==t||!b.isEmptyObject(e[t]))&&"toJSON"!==t)return!1;return!0}b.extend({queue:function(e,n,r){var i;return e?(n=(n||"fx")+"queue",i=b._data(e,n),r&&(!i||b.isArray(r)?i=b._data(e,n,b.makeArray(r)):i.push(r)),i||[]):t},dequeue:function(e,t){t=t||"fx";var n=b.queue(e,t),r=n.length,i=n.shift(),o=b._queueHooks(e,t),a=function(){b.dequeue(e,t)};"inprogress"===i&&(i=n.shift(),r--),o.cur=i,i&&("fx"===t&&n.unshift("inprogress"),delete o.stop,i.call(e,a,o)),!r&&o&&o.empty.fire()},_queueHooks:function(e,t){var n=t+"queueHooks";return b._data(e,n)||b._data(e,n,{empty:b.Callbacks("once memory").add(function(){b._removeData(e,t+"queue"),b._removeData(e,n)})})}}),b.fn.extend({queue:function(e,n){var r=2;return"string"!=typeof e&&(n=e,e="fx",r--),r>arguments.length?b.queue(this[0],e):n===t?this:this.each(function(){var t=b.queue(this,e,n);b._queueHooks(this,e),"fx"===e&&"inprogress"!==t[0]&&b.dequeue(this,e)})},dequeue:function(e){return this.each(function(){b.dequeue(this,e)})},delay:function(e,t){return e=b.fx?b.fx.speeds[e]||e:e,t=t||"fx",this.queue(t,function(t,n){var r=setTimeout(t,e);n.stop=function(){clearTimeout(r)}})},clearQueue:function(e){return this.queue(e||"fx",[])},promise:function(e,n){var r,i=1,o=b.Deferred(),a=this,s=this.length,u=function(){--i||o.resolveWith(a,[a])};"string"!=typeof e&&(n=e,e=t),e=e||"fx";while(s--)r=b._data(a[s],e+"queueHooks"),r&&r.empty&&(i++,r.empty.add(u));return u(),o.promise(n)}});var I,z,X=/[\t\r\n]/g,U=/\r/g,V=/^(?:input|select|textarea|button|object)$/i,Y=/^(?:a|area)$/i,J=/^(?:checked|selected|autofocus|autoplay|async|controls|defer|disabled|hidden|loop|multiple|open|readonly|required|scoped)$/i,G=/^(?:checked|selected)$/i,Q=b.support.getSetAttribute,K=b.support.input;b.fn.extend({attr:function(e,t){return b.access(this,b.attr,e,t,arguments.length>1)},removeAttr:function(e){return this.each(function(){b.removeAttr(this,e)})},prop:function(e,t){return b.access(this,b.prop,e,t,arguments.length>1)},removeProp:function(e){return e=b.propFix[e]||e,this.each(function(){try{this[e]=t,delete this[e]}catch(n){}})},addClass:function(e){var t,n,r,i,o,a=0,s=this.length,u="string"==typeof e&&e;if(b.isFunction(e))return this.each(function(t){b(this).addClass(e.call(this,t,this.className))});if(u)for(t=(e||"").match(w)||[];s>a;a++)if(n=this[a],r=1===n.nodeType&&(n.className?(" "+n.className+" ").replace(X," "):" ")){o=0;while(i=t[o++])0>r.indexOf(" "+i+" ")&&(r+=i+" ");n.className=b.trim(r)}return this},removeClass:function(e){var t,n,r,i,o,a=0,s=this.length,u=0===arguments.length||"string"==typeof e&&e;if(b.isFunction(e))return this.each(function(t){b(this).removeClass(e.call(this,t,this.className))});if(u)for(t=(e||"").match(w)||[];s>a;a++)if(n=this[a],r=1===n.nodeType&&(n.className?(" "+n.className+" ").replace(X," "):"")){o=0;while(i=t[o++])while(r.indexOf(" "+i+" ")>=0)r=r.replace(" "+i+" "," ");n.className=e?b.trim(r):""}return this},toggleClass:function(e,t){var n=typeof e,r="boolean"==typeof t;return b.isFunction(e)?this.each(function(n){b(this).toggleClass(e.call(this,n,this.className,t),t)}):this.each(function(){if("string"===n){var o,a=0,s=b(this),u=t,l=e.match(w)||[];while(o=l[a++])u=r?u:!s.hasClass(o),s[u?"addClass":"removeClass"](o)}else(n===i||"boolean"===n)&&(this.className&&b._data(this,"__className__",this.className),this.className=this.className||e===!1?"":b._data(this,"__className__")||"")})},hasClass:function(e){var t=" "+e+" ",n=0,r=this.length;for(;r>n;n++)if(1===this[n].nodeType&&(" "+this[n].className+" ").replace(X," ").indexOf(t)>=0)return!0;return!1},val:function(e){var n,r,i,o=this[0];{if(arguments.length)return i=b.isFunction(e),this.each(function(n){var o,a=b(this);1===this.nodeType&&(o=i?e.call(this,n,a.val()):e,null==o?o="":"number"==typeof o?o+="":b.isArray(o)&&(o=b.map(o,function(e){return null==e?"":e+""})),r=b.valHooks[this.type]||b.valHooks[this.nodeName.toLowerCase()],r&&"set"in r&&r.set(this,o,"value")!==t||(this.value=o))});if(o)return r=b.valHooks[o.type]||b.valHooks[o.nodeName.toLowerCase()],r&&"get"in r&&(n=r.get(o,"value"))!==t?n:(n=o.value,"string"==typeof n?n.replace(U,""):null==n?"":n)}}}),b.extend({valHooks:{option:{get:function(e){var t=e.attributes.value;return!t||t.specified?e.value:e.text}},select:{get:function(e){var t,n,r=e.options,i=e.selectedIndex,o="select-one"===e.type||0>i,a=o?null:[],s=o?i+1:r.length,u=0>i?s:o?i:0;for(;s>u;u++)if(n=r[u],!(!n.selected&&u!==i||(b.support.optDisabled?n.disabled:null!==n.getAttribute("disabled"))||n.parentNode.disabled&&b.nodeName(n.parentNode,"optgroup"))){if(t=b(n).val(),o)return t;a.push(t)}return a},set:function(e,t){var n=b.makeArray(t);return b(e).find("option").each(function(){this.selected=b.inArray(b(this).val(),n)>=0}),n.length||(e.selectedIndex=-1),n}}},attr:function(e,n,r){var o,a,s,u=e.nodeType;if(e&&3!==u&&8!==u&&2!==u)return typeof e.getAttribute===i?b.prop(e,n,r):(a=1!==u||!b.isXMLDoc(e),a&&(n=n.toLowerCase(),o=b.attrHooks[n]||(J.test(n)?z:I)),r===t?o&&a&&"get"in o&&null!==(s=o.get(e,n))?s:(typeof e.getAttribute!==i&&(s=e.getAttribute(n)),null==s?t:s):null!==r?o&&a&&"set"in o&&(s=o.set(e,r,n))!==t?s:(e.setAttribute(n,r+""),r):(b.removeAttr(e,n),t))},removeAttr:function(e,t){var n,r,i=0,o=t&&t.match(w);if(o&&1===e.nodeType)while(n=o[i++])r=b.propFix[n]||n,J.test(n)?!Q&&G.test(n)?e[b.camelCase("default-"+n)]=e[r]=!1:e[r]=!1:b.attr(e,n,""),e.removeAttribute(Q?n:r)},attrHooks:{type:{set:function(e,t){if(!b.support.radioValue&&"radio"===t&&b.nodeName(e,"input")){var n=e.value;return e.setAttribute("type",t),n&&(e.value=n),t}}}},propFix:{tabindex:"tabIndex",readonly:"readOnly","for":"htmlFor","class":"className",maxlength:"maxLength",cellspacing:"cellSpacing",cellpadding:"cellPadding",rowspan:"rowSpan",colspan:"colSpan",usemap:"useMap",frameborder:"frameBorder",contenteditable:"contentEditable"},prop:function(e,n,r){var i,o,a,s=e.nodeType;if(e&&3!==s&&8!==s&&2!==s)return a=1!==s||!b.isXMLDoc(e),a&&(n=b.propFix[n]||n,o=b.propHooks[n]),r!==t?o&&"set"in o&&(i=o.set(e,r,n))!==t?i:e[n]=r:o&&"get"in o&&null!==(i=o.get(e,n))?i:e[n]},propHooks:{tabIndex:{get:function(e){var n=e.getAttributeNode("tabindex");return n&&n.specified?parseInt(n.value,10):V.test(e.nodeName)||Y.test(e.nodeName)&&e.href?0:t}}}}),z={get:function(e,n){var r=b.prop(e,n),i="boolean"==typeof r&&e.getAttribute(n),o="boolean"==typeof r?K&&Q?null!=i:G.test(n)?e[b.camelCase("default-"+n)]:!!i:e.getAttributeNode(n);return o&&o.value!==!1?n.toLowerCase():t},set:function(e,t,n){return t===!1?b.removeAttr(e,n):K&&Q||!G.test(n)?e.setAttribute(!Q&&b.propFix[n]||n,n):e[b.camelCase("default-"+n)]=e[n]=!0,n}},K&&Q||(b.attrHooks.value={get:function(e,n){var r=e.getAttributeNode(n);return b.nodeName(e,"input")?e.defaultValue:r&&r.specified?r.value:t},set:function(e,n,r){return b.nodeName(e,"input")?(e.defaultValue=n,t):I&&I.set(e,n,r)}}),Q||(I=b.valHooks.button={get:function(e,n){var r=e.getAttributeNode(n);return r&&("id"===n||"name"===n||"coords"===n?""!==r.value:r.specified)?r.value:t},set:function(e,n,r){var i=e.getAttributeNode(r);return i||e.setAttributeNode(i=e.ownerDocument.createAttribute(r)),i.value=n+="","value"===r||n===e.getAttribute(r)?n:t}},b.attrHooks.contenteditable={get:I.get,set:function(e,t,n){I.set(e,""===t?!1:t,n)}},b.each(["width","height"],function(e,n){b.attrHooks[n]=b.extend(b.attrHooks[n],{set:function(e,r){return""===r?(e.setAttribute(n,"auto"),r):t}})})),b.support.hrefNormalized||(b.each(["href","src","width","height"],function(e,n){b.attrHooks[n]=b.extend(b.attrHooks[n],{get:function(e){var r=e.getAttribute(n,2);return null==r?t:r}})}),b.each(["href","src"],function(e,t){b.propHooks[t]={get:function(e){return e.getAttribute(t,4)}}})),b.support.style||(b.attrHooks.style={get:function(e){return e.style.cssText||t},set:function(e,t){return e.style.cssText=t+""}}),b.support.optSelected||(b.propHooks.selected=b.extend(b.propHooks.selected,{get:function(e){var t=e.parentNode;return t&&(t.selectedIndex,t.parentNode&&t.parentNode.selectedIndex),null}})),b.support.enctype||(b.propFix.enctype="encoding"),b.support.checkOn||b.each(["radio","checkbox"],function(){b.valHooks[this]={get:function(e){return null===e.getAttribute("value")?"on":e.value}}}),b.each(["radio","checkbox"],function(){b.valHooks[this]=b.extend(b.valHooks[this],{set:function(e,n){return b.isArray(n)?e.checked=b.inArray(b(e).val(),n)>=0:t}})});var Z=/^(?:input|select|textarea)$/i,et=/^key/,tt=/^(?:mouse|contextmenu)|click/,nt=/^(?:focusinfocus|focusoutblur)$/,rt=/^([^.]*)(?:\.(.+)|)$/;function it(){return!0}function ot(){return!1}b.event={global:{},add:function(e,n,r,o,a){var s,u,l,c,p,f,d,h,g,m,y,v=b._data(e);if(v){r.handler&&(c=r,r=c.handler,a=c.selector),r.guid||(r.guid=b.guid++),(u=v.events)||(u=v.events={}),(f=v.handle)||(f=v.handle=function(e){return typeof b===i||e&&b.event.triggered===e.type?t:b.event.dispatch.apply(f.elem,arguments)},f.elem=e),n=(n||"").match(w)||[""],l=n.length;while(l--)s=rt.exec(n[l])||[],g=y=s[1],m=(s[2]||"").split(".").sort(),p=b.event.special[g]||{},g=(a?p.delegateType:p.bindType)||g,p=b.event.special[g]||{},d=b.extend({type:g,origType:y,data:o,handler:r,guid:r.guid,selector:a,needsContext:a&&b.expr.match.needsContext.test(a),namespace:m.join(".")},c),(h=u[g])||(h=u[g]=[],h.delegateCount=0,p.setup&&p.setup.call(e,o,m,f)!==!1||(e.addEventListener?e.addEventListener(g,f,!1):e.attachEvent&&e.attachEvent("on"+g,f))),p.add&&(p.add.call(e,d),d.handler.guid||(d.handler.guid=r.guid)),a?h.splice(h.delegateCount++,0,d):h.push(d),b.event.global[g]=!0;e=null}},remove:function(e,t,n,r,i){var o,a,s,u,l,c,p,f,d,h,g,m=b.hasData(e)&&b._data(e);if(m&&(c=m.events)){t=(t||"").match(w)||[""],l=t.length;while(l--)if(s=rt.exec(t[l])||[],d=g=s[1],h=(s[2]||"").split(".").sort(),d){p=b.event.special[d]||{},d=(r?p.delegateType:p.bindType)||d,f=c[d]||[],s=s[2]&&RegExp("(^|\\.)"+h.join("\\.(?:.*\\.|)")+"(\\.|$)"),u=o=f.length;while(o--)a=f[o],!i&&g!==a.origType||n&&n.guid!==a.guid||s&&!s.test(a.namespace)||r&&r!==a.selector&&("**"!==r||!a.selector)||(f.splice(o,1),a.selector&&f.delegateCount--,p.remove&&p.remove.call(e,a));u&&!f.length&&(p.teardown&&p.teardown.call(e,h,m.handle)!==!1||b.removeEvent(e,d,m.handle),delete c[d])}else for(d in c)b.event.remove(e,d+t[l],n,r,!0);b.isEmptyObject(c)&&(delete m.handle,b._removeData(e,"events"))}},trigger:function(n,r,i,a){var s,u,l,c,p,f,d,h=[i||o],g=y.call(n,"type")?n.type:n,m=y.call(n,"namespace")?n.namespace.split("."):[];if(l=f=i=i||o,3!==i.nodeType&&8!==i.nodeType&&!nt.test(g+b.event.triggered)&&(g.indexOf(".")>=0&&(m=g.split("."),g=m.shift(),m.sort()),u=0>g.indexOf(":")&&"on"+g,n=n[b.expando]?n:new b.Event(g,"object"==typeof n&&n),n.isTrigger=!0,n.namespace=m.join("."),n.namespace_re=n.namespace?RegExp("(^|\\.)"+m.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,n.result=t,n.target||(n.target=i),r=null==r?[n]:b.makeArray(r,[n]),p=b.event.special[g]||{},a||!p.trigger||p.trigger.apply(i,r)!==!1)){if(!a&&!p.noBubble&&!b.isWindow(i)){for(c=p.delegateType||g,nt.test(c+g)||(l=l.parentNode);l;l=l.parentNode)h.push(l),f=l;f===(i.ownerDocument||o)&&h.push(f.defaultView||f.parentWindow||e)}d=0;while((l=h[d++])&&!n.isPropagationStopped())n.type=d>1?c:p.bindType||g,s=(b._data(l,"events")||{})[n.type]&&b._data(l,"handle"),s&&s.apply(l,r),s=u&&l[u],s&&b.acceptData(l)&&s.apply&&s.apply(l,r)===!1&&n.preventDefault();if(n.type=g,!(a||n.isDefaultPrevented()||p._default&&p._default.apply(i.ownerDocument,r)!==!1||"click"===g&&b.nodeName(i,"a")||!b.acceptData(i)||!u||!i[g]||b.isWindow(i))){f=i[u],f&&(i[u]=null),b.event.triggered=g;try{i[g]()}catch(v){}b.event.triggered=t,f&&(i[u]=f)}return n.result}},dispatch:function(e){e=b.event.fix(e);var n,r,i,o,a,s=[],u=h.call(arguments),l=(b._data(this,"events")||{})[e.type]||[],c=b.event.special[e.type]||{};if(u[0]=e,e.delegateTarget=this,!c.preDispatch||c.preDispatch.call(this,e)!==!1){s=b.event.handlers.call(this,e,l),n=0;while((o=s[n++])&&!e.isPropagationStopped()){e.currentTarget=o.elem,a=0;while((i=o.handlers[a++])&&!e.isImmediatePropagationStopped())(!e.namespace_re||e.namespace_re.test(i.namespace))&&(e.handleObj=i,e.data=i.data,r=((b.event.special[i.origType]||{}).handle||i.handler).apply(o.elem,u),r!==t&&(e.result=r)===!1&&(e.preventDefault(),e.stopPropagation()))}return c.postDispatch&&c.postDispatch.call(this,e),e.result}},handlers:function(e,n){var r,i,o,a,s=[],u=n.delegateCount,l=e.target;if(u&&l.nodeType&&(!e.button||"click"!==e.type))for(;l!=this;l=l.parentNode||this)if(1===l.nodeType&&(l.disabled!==!0||"click"!==e.type)){for(o=[],a=0;u>a;a++)i=n[a],r=i.selector+" ",o[r]===t&&(o[r]=i.needsContext?b(r,this).index(l)>=0:b.find(r,this,null,[l]).length),o[r]&&o.push(i);o.length&&s.push({elem:l,handlers:o})}return n.length>u&&s.push({elem:this,handlers:n.slice(u)}),s},fix:function(e){if(e[b.expando])return e;var t,n,r,i=e.type,a=e,s=this.fixHooks[i];s||(this.fixHooks[i]=s=tt.test(i)?this.mouseHooks:et.test(i)?this.keyHooks:{}),r=s.props?this.props.concat(s.props):this.props,e=new b.Event(a),t=r.length;while(t--)n=r[t],e[n]=a[n];return e.target||(e.target=a.srcElement||o),3===e.target.nodeType&&(e.target=e.target.parentNode),e.metaKey=!!e.metaKey,s.filter?s.filter(e,a):e},props:"altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "),fixHooks:{},keyHooks:{props:"char charCode key keyCode".split(" "),filter:function(e,t){return null==e.which&&(e.which=null!=t.charCode?t.charCode:t.keyCode),e}},mouseHooks:{props:"button buttons clientX clientY fromElement offsetX offsetY pageX pageY screenX screenY toElement".split(" "),filter:function(e,n){var r,i,a,s=n.button,u=n.fromElement;return null==e.pageX&&null!=n.clientX&&(i=e.target.ownerDocument||o,a=i.documentElement,r=i.body,e.pageX=n.clientX+(a&&a.scrollLeft||r&&r.scrollLeft||0)-(a&&a.clientLeft||r&&r.clientLeft||0),e.pageY=n.clientY+(a&&a.scrollTop||r&&r.scrollTop||0)-(a&&a.clientTop||r&&r.clientTop||0)),!e.relatedTarget&&u&&(e.relatedTarget=u===e.target?n.toElement:u),e.which||s===t||(e.which=1&s?1:2&s?3:4&s?2:0),e}},special:{load:{noBubble:!0},click:{trigger:function(){return b.nodeName(this,"input")&&"checkbox"===this.type&&this.click?(this.click(),!1):t}},focus:{trigger:function(){if(this!==o.activeElement&&this.focus)try{return this.focus(),!1}catch(e){}},delegateType:"focusin"},blur:{trigger:function(){return this===o.activeElement&&this.blur?(this.blur(),!1):t},delegateType:"focusout"},beforeunload:{postDispatch:function(e){e.result!==t&&(e.originalEvent.returnValue=e.result)}}},simulate:function(e,t,n,r){var i=b.extend(new b.Event,n,{type:e,isSimulated:!0,originalEvent:{}});r?b.event.trigger(i,null,t):b.event.dispatch.call(t,i),i.isDefaultPrevented()&&n.preventDefault()}},b.removeEvent=o.removeEventListener?function(e,t,n){e.removeEventListener&&e.removeEventListener(t,n,!1)}:function(e,t,n){var r="on"+t;e.detachEvent&&(typeof e[r]===i&&(e[r]=null),e.detachEvent(r,n))},b.Event=function(e,n){return this instanceof b.Event?(e&&e.type?(this.originalEvent=e,this.type=e.type,this.isDefaultPrevented=e.defaultPrevented||e.returnValue===!1||e.getPreventDefault&&e.getPreventDefault()?it:ot):this.type=e,n&&b.extend(this,n),this.timeStamp=e&&e.timeStamp||b.now(),this[b.expando]=!0,t):new b.Event(e,n)},b.Event.prototype={isDefaultPrevented:ot,isPropagationStopped:ot,isImmediatePropagationStopped:ot,preventDefault:function(){var e=this.originalEvent;this.isDefaultPrevented=it,e&&(e.preventDefault?e.preventDefault():e.returnValue=!1)},stopPropagation:function(){var e=this.originalEvent;this.isPropagationStopped=it,e&&(e.stopPropagation&&e.stopPropagation(),e.cancelBubble=!0)},stopImmediatePropagation:function(){this.isImmediatePropagationStopped=it,this.stopPropagation()}},b.each({mouseenter:"mouseover",mouseleave:"mouseout"},function(e,t){b.event.special[e]={delegateType:t,bindType:t,handle:function(e){var n,r=this,i=e.relatedTarget,o=e.handleObj; +return(!i||i!==r&&!b.contains(r,i))&&(e.type=o.origType,n=o.handler.apply(this,arguments),e.type=t),n}}}),b.support.submitBubbles||(b.event.special.submit={setup:function(){return b.nodeName(this,"form")?!1:(b.event.add(this,"click._submit keypress._submit",function(e){var n=e.target,r=b.nodeName(n,"input")||b.nodeName(n,"button")?n.form:t;r&&!b._data(r,"submitBubbles")&&(b.event.add(r,"submit._submit",function(e){e._submit_bubble=!0}),b._data(r,"submitBubbles",!0))}),t)},postDispatch:function(e){e._submit_bubble&&(delete e._submit_bubble,this.parentNode&&!e.isTrigger&&b.event.simulate("submit",this.parentNode,e,!0))},teardown:function(){return b.nodeName(this,"form")?!1:(b.event.remove(this,"._submit"),t)}}),b.support.changeBubbles||(b.event.special.change={setup:function(){return Z.test(this.nodeName)?(("checkbox"===this.type||"radio"===this.type)&&(b.event.add(this,"propertychange._change",function(e){"checked"===e.originalEvent.propertyName&&(this._just_changed=!0)}),b.event.add(this,"click._change",function(e){this._just_changed&&!e.isTrigger&&(this._just_changed=!1),b.event.simulate("change",this,e,!0)})),!1):(b.event.add(this,"beforeactivate._change",function(e){var t=e.target;Z.test(t.nodeName)&&!b._data(t,"changeBubbles")&&(b.event.add(t,"change._change",function(e){!this.parentNode||e.isSimulated||e.isTrigger||b.event.simulate("change",this.parentNode,e,!0)}),b._data(t,"changeBubbles",!0))}),t)},handle:function(e){var n=e.target;return this!==n||e.isSimulated||e.isTrigger||"radio"!==n.type&&"checkbox"!==n.type?e.handleObj.handler.apply(this,arguments):t},teardown:function(){return b.event.remove(this,"._change"),!Z.test(this.nodeName)}}),b.support.focusinBubbles||b.each({focus:"focusin",blur:"focusout"},function(e,t){var n=0,r=function(e){b.event.simulate(t,e.target,b.event.fix(e),!0)};b.event.special[t]={setup:function(){0===n++&&o.addEventListener(e,r,!0)},teardown:function(){0===--n&&o.removeEventListener(e,r,!0)}}}),b.fn.extend({on:function(e,n,r,i,o){var a,s;if("object"==typeof e){"string"!=typeof n&&(r=r||n,n=t);for(a in e)this.on(a,n,r,e[a],o);return this}if(null==r&&null==i?(i=n,r=n=t):null==i&&("string"==typeof n?(i=r,r=t):(i=r,r=n,n=t)),i===!1)i=ot;else if(!i)return this;return 1===o&&(s=i,i=function(e){return b().off(e),s.apply(this,arguments)},i.guid=s.guid||(s.guid=b.guid++)),this.each(function(){b.event.add(this,e,i,r,n)})},one:function(e,t,n,r){return this.on(e,t,n,r,1)},off:function(e,n,r){var i,o;if(e&&e.preventDefault&&e.handleObj)return i=e.handleObj,b(e.delegateTarget).off(i.namespace?i.origType+"."+i.namespace:i.origType,i.selector,i.handler),this;if("object"==typeof e){for(o in e)this.off(o,n,e[o]);return this}return(n===!1||"function"==typeof n)&&(r=n,n=t),r===!1&&(r=ot),this.each(function(){b.event.remove(this,e,r,n)})},bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,"**"):this.off(t,e||"**",n)},trigger:function(e,t){return this.each(function(){b.event.trigger(e,t,this)})},triggerHandler:function(e,n){var r=this[0];return r?b.event.trigger(e,n,r,!0):t}}),function(e,t){var n,r,i,o,a,s,u,l,c,p,f,d,h,g,m,y,v,x="sizzle"+-new Date,w=e.document,T={},N=0,C=0,k=it(),E=it(),S=it(),A=typeof t,j=1<<31,D=[],L=D.pop,H=D.push,q=D.slice,M=D.indexOf||function(e){var t=0,n=this.length;for(;n>t;t++)if(this[t]===e)return t;return-1},_="[\\x20\\t\\r\\n\\f]",F="(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",O=F.replace("w","w#"),B="([*^$|!~]?=)",P="\\["+_+"*("+F+")"+_+"*(?:"+B+_+"*(?:(['\"])((?:\\\\.|[^\\\\])*?)\\3|("+O+")|)|)"+_+"*\\]",R=":("+F+")(?:\\(((['\"])((?:\\\\.|[^\\\\])*?)\\3|((?:\\\\.|[^\\\\()[\\]]|"+P.replace(3,8)+")*)|.*)\\)|)",W=RegExp("^"+_+"+|((?:^|[^\\\\])(?:\\\\.)*)"+_+"+$","g"),$=RegExp("^"+_+"*,"+_+"*"),I=RegExp("^"+_+"*([\\x20\\t\\r\\n\\f>+~])"+_+"*"),z=RegExp(R),X=RegExp("^"+O+"$"),U={ID:RegExp("^#("+F+")"),CLASS:RegExp("^\\.("+F+")"),NAME:RegExp("^\\[name=['\"]?("+F+")['\"]?\\]"),TAG:RegExp("^("+F.replace("w","w*")+")"),ATTR:RegExp("^"+P),PSEUDO:RegExp("^"+R),CHILD:RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+_+"*(even|odd|(([+-]|)(\\d*)n|)"+_+"*(?:([+-]|)"+_+"*(\\d+)|))"+_+"*\\)|)","i"),needsContext:RegExp("^"+_+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+_+"*((?:-\\d)?\\d*)"+_+"*\\)|)(?=[^-]|$)","i")},V=/[\x20\t\r\n\f]*[+~]/,Y=/^[^{]+\{\s*\[native code/,J=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,G=/^(?:input|select|textarea|button)$/i,Q=/^h\d$/i,K=/'|\\/g,Z=/\=[\x20\t\r\n\f]*([^'"\]]*)[\x20\t\r\n\f]*\]/g,et=/\\([\da-fA-F]{1,6}[\x20\t\r\n\f]?|.)/g,tt=function(e,t){var n="0x"+t-65536;return n!==n?t:0>n?String.fromCharCode(n+65536):String.fromCharCode(55296|n>>10,56320|1023&n)};try{q.call(w.documentElement.childNodes,0)[0].nodeType}catch(nt){q=function(e){var t,n=[];while(t=this[e++])n.push(t);return n}}function rt(e){return Y.test(e+"")}function it(){var e,t=[];return e=function(n,r){return t.push(n+=" ")>i.cacheLength&&delete e[t.shift()],e[n]=r}}function ot(e){return e[x]=!0,e}function at(e){var t=p.createElement("div");try{return e(t)}catch(n){return!1}finally{t=null}}function st(e,t,n,r){var i,o,a,s,u,l,f,g,m,v;if((t?t.ownerDocument||t:w)!==p&&c(t),t=t||p,n=n||[],!e||"string"!=typeof e)return n;if(1!==(s=t.nodeType)&&9!==s)return[];if(!d&&!r){if(i=J.exec(e))if(a=i[1]){if(9===s){if(o=t.getElementById(a),!o||!o.parentNode)return n;if(o.id===a)return n.push(o),n}else if(t.ownerDocument&&(o=t.ownerDocument.getElementById(a))&&y(t,o)&&o.id===a)return n.push(o),n}else{if(i[2])return H.apply(n,q.call(t.getElementsByTagName(e),0)),n;if((a=i[3])&&T.getByClassName&&t.getElementsByClassName)return H.apply(n,q.call(t.getElementsByClassName(a),0)),n}if(T.qsa&&!h.test(e)){if(f=!0,g=x,m=t,v=9===s&&e,1===s&&"object"!==t.nodeName.toLowerCase()){l=ft(e),(f=t.getAttribute("id"))?g=f.replace(K,"\\$&"):t.setAttribute("id",g),g="[id='"+g+"'] ",u=l.length;while(u--)l[u]=g+dt(l[u]);m=V.test(e)&&t.parentNode||t,v=l.join(",")}if(v)try{return H.apply(n,q.call(m.querySelectorAll(v),0)),n}catch(b){}finally{f||t.removeAttribute("id")}}}return wt(e.replace(W,"$1"),t,n,r)}a=st.isXML=function(e){var t=e&&(e.ownerDocument||e).documentElement;return t?"HTML"!==t.nodeName:!1},c=st.setDocument=function(e){var n=e?e.ownerDocument||e:w;return n!==p&&9===n.nodeType&&n.documentElement?(p=n,f=n.documentElement,d=a(n),T.tagNameNoComments=at(function(e){return e.appendChild(n.createComment("")),!e.getElementsByTagName("*").length}),T.attributes=at(function(e){e.innerHTML="";var t=typeof e.lastChild.getAttribute("multiple");return"boolean"!==t&&"string"!==t}),T.getByClassName=at(function(e){return e.innerHTML="",e.getElementsByClassName&&e.getElementsByClassName("e").length?(e.lastChild.className="e",2===e.getElementsByClassName("e").length):!1}),T.getByName=at(function(e){e.id=x+0,e.innerHTML="
",f.insertBefore(e,f.firstChild);var t=n.getElementsByName&&n.getElementsByName(x).length===2+n.getElementsByName(x+0).length;return T.getIdNotName=!n.getElementById(x),f.removeChild(e),t}),i.attrHandle=at(function(e){return e.innerHTML="",e.firstChild&&typeof e.firstChild.getAttribute!==A&&"#"===e.firstChild.getAttribute("href")})?{}:{href:function(e){return e.getAttribute("href",2)},type:function(e){return e.getAttribute("type")}},T.getIdNotName?(i.find.ID=function(e,t){if(typeof t.getElementById!==A&&!d){var n=t.getElementById(e);return n&&n.parentNode?[n]:[]}},i.filter.ID=function(e){var t=e.replace(et,tt);return function(e){return e.getAttribute("id")===t}}):(i.find.ID=function(e,n){if(typeof n.getElementById!==A&&!d){var r=n.getElementById(e);return r?r.id===e||typeof r.getAttributeNode!==A&&r.getAttributeNode("id").value===e?[r]:t:[]}},i.filter.ID=function(e){var t=e.replace(et,tt);return function(e){var n=typeof e.getAttributeNode!==A&&e.getAttributeNode("id");return n&&n.value===t}}),i.find.TAG=T.tagNameNoComments?function(e,n){return typeof n.getElementsByTagName!==A?n.getElementsByTagName(e):t}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},i.find.NAME=T.getByName&&function(e,n){return typeof n.getElementsByName!==A?n.getElementsByName(name):t},i.find.CLASS=T.getByClassName&&function(e,n){return typeof n.getElementsByClassName===A||d?t:n.getElementsByClassName(e)},g=[],h=[":focus"],(T.qsa=rt(n.querySelectorAll))&&(at(function(e){e.innerHTML="",e.querySelectorAll("[selected]").length||h.push("\\["+_+"*(?:checked|disabled|ismap|multiple|readonly|selected|value)"),e.querySelectorAll(":checked").length||h.push(":checked")}),at(function(e){e.innerHTML="",e.querySelectorAll("[i^='']").length&&h.push("[*^$]="+_+"*(?:\"\"|'')"),e.querySelectorAll(":enabled").length||h.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),h.push(",.*:")})),(T.matchesSelector=rt(m=f.matchesSelector||f.mozMatchesSelector||f.webkitMatchesSelector||f.oMatchesSelector||f.msMatchesSelector))&&at(function(e){T.disconnectedMatch=m.call(e,"div"),m.call(e,"[s!='']:x"),g.push("!=",R)}),h=RegExp(h.join("|")),g=RegExp(g.join("|")),y=rt(f.contains)||f.compareDocumentPosition?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},v=f.compareDocumentPosition?function(e,t){var r;return e===t?(u=!0,0):(r=t.compareDocumentPosition&&e.compareDocumentPosition&&e.compareDocumentPosition(t))?1&r||e.parentNode&&11===e.parentNode.nodeType?e===n||y(w,e)?-1:t===n||y(w,t)?1:0:4&r?-1:1:e.compareDocumentPosition?-1:1}:function(e,t){var r,i=0,o=e.parentNode,a=t.parentNode,s=[e],l=[t];if(e===t)return u=!0,0;if(!o||!a)return e===n?-1:t===n?1:o?-1:a?1:0;if(o===a)return ut(e,t);r=e;while(r=r.parentNode)s.unshift(r);r=t;while(r=r.parentNode)l.unshift(r);while(s[i]===l[i])i++;return i?ut(s[i],l[i]):s[i]===w?-1:l[i]===w?1:0},u=!1,[0,0].sort(v),T.detectDuplicates=u,p):p},st.matches=function(e,t){return st(e,null,null,t)},st.matchesSelector=function(e,t){if((e.ownerDocument||e)!==p&&c(e),t=t.replace(Z,"='$1']"),!(!T.matchesSelector||d||g&&g.test(t)||h.test(t)))try{var n=m.call(e,t);if(n||T.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(r){}return st(t,p,null,[e]).length>0},st.contains=function(e,t){return(e.ownerDocument||e)!==p&&c(e),y(e,t)},st.attr=function(e,t){var n;return(e.ownerDocument||e)!==p&&c(e),d||(t=t.toLowerCase()),(n=i.attrHandle[t])?n(e):d||T.attributes?e.getAttribute(t):((n=e.getAttributeNode(t))||e.getAttribute(t))&&e[t]===!0?t:n&&n.specified?n.value:null},st.error=function(e){throw Error("Syntax error, unrecognized expression: "+e)},st.uniqueSort=function(e){var t,n=[],r=1,i=0;if(u=!T.detectDuplicates,e.sort(v),u){for(;t=e[r];r++)t===e[r-1]&&(i=n.push(r));while(i--)e.splice(n[i],1)}return e};function ut(e,t){var n=t&&e,r=n&&(~t.sourceIndex||j)-(~e.sourceIndex||j);if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function lt(e){return function(t){var n=t.nodeName.toLowerCase();return"input"===n&&t.type===e}}function ct(e){return function(t){var n=t.nodeName.toLowerCase();return("input"===n||"button"===n)&&t.type===e}}function pt(e){return ot(function(t){return t=+t,ot(function(n,r){var i,o=e([],n.length,t),a=o.length;while(a--)n[i=o[a]]&&(n[i]=!(r[i]=n[i]))})})}o=st.getText=function(e){var t,n="",r=0,i=e.nodeType;if(i){if(1===i||9===i||11===i){if("string"==typeof e.textContent)return e.textContent;for(e=e.firstChild;e;e=e.nextSibling)n+=o(e)}else if(3===i||4===i)return e.nodeValue}else for(;t=e[r];r++)n+=o(t);return n},i=st.selectors={cacheLength:50,createPseudo:ot,match:U,find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(et,tt),e[3]=(e[4]||e[5]||"").replace(et,tt),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||st.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&st.error(e[0]),e},PSEUDO:function(e){var t,n=!e[5]&&e[2];return U.CHILD.test(e[0])?null:(e[4]?e[2]=e[4]:n&&z.test(n)&&(t=ft(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){return"*"===e?function(){return!0}:(e=e.replace(et,tt).toLowerCase(),function(t){return t.nodeName&&t.nodeName.toLowerCase()===e})},CLASS:function(e){var t=k[e+" "];return t||(t=RegExp("(^|"+_+")"+e+"("+_+"|$)"))&&k(e,function(e){return t.test(e.className||typeof e.getAttribute!==A&&e.getAttribute("class")||"")})},ATTR:function(e,t,n){return function(r){var i=st.attr(r,e);return null==i?"!="===t:t?(i+="","="===t?i===n:"!="===t?i!==n:"^="===t?n&&0===i.indexOf(n):"*="===t?n&&i.indexOf(n)>-1:"$="===t?n&&i.slice(-n.length)===n:"~="===t?(" "+i+" ").indexOf(n)>-1:"|="===t?i===n||i.slice(0,n.length+1)===n+"-":!1):!0}},CHILD:function(e,t,n,r,i){var o="nth"!==e.slice(0,3),a="last"!==e.slice(-4),s="of-type"===t;return 1===r&&0===i?function(e){return!!e.parentNode}:function(t,n,u){var l,c,p,f,d,h,g=o!==a?"nextSibling":"previousSibling",m=t.parentNode,y=s&&t.nodeName.toLowerCase(),v=!u&&!s;if(m){if(o){while(g){p=t;while(p=p[g])if(s?p.nodeName.toLowerCase()===y:1===p.nodeType)return!1;h=g="only"===e&&!h&&"nextSibling"}return!0}if(h=[a?m.firstChild:m.lastChild],a&&v){c=m[x]||(m[x]={}),l=c[e]||[],d=l[0]===N&&l[1],f=l[0]===N&&l[2],p=d&&m.childNodes[d];while(p=++d&&p&&p[g]||(f=d=0)||h.pop())if(1===p.nodeType&&++f&&p===t){c[e]=[N,d,f];break}}else if(v&&(l=(t[x]||(t[x]={}))[e])&&l[0]===N)f=l[1];else while(p=++d&&p&&p[g]||(f=d=0)||h.pop())if((s?p.nodeName.toLowerCase()===y:1===p.nodeType)&&++f&&(v&&((p[x]||(p[x]={}))[e]=[N,f]),p===t))break;return f-=i,f===r||0===f%r&&f/r>=0}}},PSEUDO:function(e,t){var n,r=i.pseudos[e]||i.setFilters[e.toLowerCase()]||st.error("unsupported pseudo: "+e);return r[x]?r(t):r.length>1?(n=[e,e,"",t],i.setFilters.hasOwnProperty(e.toLowerCase())?ot(function(e,n){var i,o=r(e,t),a=o.length;while(a--)i=M.call(e,o[a]),e[i]=!(n[i]=o[a])}):function(e){return r(e,0,n)}):r}},pseudos:{not:ot(function(e){var t=[],n=[],r=s(e.replace(W,"$1"));return r[x]?ot(function(e,t,n,i){var o,a=r(e,null,i,[]),s=e.length;while(s--)(o=a[s])&&(e[s]=!(t[s]=o))}):function(e,i,o){return t[0]=e,r(t,null,o,n),!n.pop()}}),has:ot(function(e){return function(t){return st(e,t).length>0}}),contains:ot(function(e){return function(t){return(t.textContent||t.innerText||o(t)).indexOf(e)>-1}}),lang:ot(function(e){return X.test(e||"")||st.error("unsupported lang: "+e),e=e.replace(et,tt).toLowerCase(),function(t){var n;do if(n=d?t.getAttribute("xml:lang")||t.getAttribute("lang"):t.lang)return n=n.toLowerCase(),n===e||0===n.indexOf(e+"-");while((t=t.parentNode)&&1===t.nodeType);return!1}}),target:function(t){var n=e.location&&e.location.hash;return n&&n.slice(1)===t.id},root:function(e){return e===f},focus:function(e){return e===p.activeElement&&(!p.hasFocus||p.hasFocus())&&!!(e.type||e.href||~e.tabIndex)},enabled:function(e){return e.disabled===!1},disabled:function(e){return e.disabled===!0},checked:function(e){var t=e.nodeName.toLowerCase();return"input"===t&&!!e.checked||"option"===t&&!!e.selected},selected:function(e){return e.parentNode&&e.parentNode.selectedIndex,e.selected===!0},empty:function(e){for(e=e.firstChild;e;e=e.nextSibling)if(e.nodeName>"@"||3===e.nodeType||4===e.nodeType)return!1;return!0},parent:function(e){return!i.pseudos.empty(e)},header:function(e){return Q.test(e.nodeName)},input:function(e){return G.test(e.nodeName)},button:function(e){var t=e.nodeName.toLowerCase();return"input"===t&&"button"===e.type||"button"===t},text:function(e){var t;return"input"===e.nodeName.toLowerCase()&&"text"===e.type&&(null==(t=e.getAttribute("type"))||t.toLowerCase()===e.type)},first:pt(function(){return[0]}),last:pt(function(e,t){return[t-1]}),eq:pt(function(e,t,n){return[0>n?n+t:n]}),even:pt(function(e,t){var n=0;for(;t>n;n+=2)e.push(n);return e}),odd:pt(function(e,t){var n=1;for(;t>n;n+=2)e.push(n);return e}),lt:pt(function(e,t,n){var r=0>n?n+t:n;for(;--r>=0;)e.push(r);return e}),gt:pt(function(e,t,n){var r=0>n?n+t:n;for(;t>++r;)e.push(r);return e})}};for(n in{radio:!0,checkbox:!0,file:!0,password:!0,image:!0})i.pseudos[n]=lt(n);for(n in{submit:!0,reset:!0})i.pseudos[n]=ct(n);function ft(e,t){var n,r,o,a,s,u,l,c=E[e+" "];if(c)return t?0:c.slice(0);s=e,u=[],l=i.preFilter;while(s){(!n||(r=$.exec(s)))&&(r&&(s=s.slice(r[0].length)||s),u.push(o=[])),n=!1,(r=I.exec(s))&&(n=r.shift(),o.push({value:n,type:r[0].replace(W," ")}),s=s.slice(n.length));for(a in i.filter)!(r=U[a].exec(s))||l[a]&&!(r=l[a](r))||(n=r.shift(),o.push({value:n,type:a,matches:r}),s=s.slice(n.length));if(!n)break}return t?s.length:s?st.error(e):E(e,u).slice(0)}function dt(e){var t=0,n=e.length,r="";for(;n>t;t++)r+=e[t].value;return r}function ht(e,t,n){var i=t.dir,o=n&&"parentNode"===i,a=C++;return t.first?function(t,n,r){while(t=t[i])if(1===t.nodeType||o)return e(t,n,r)}:function(t,n,s){var u,l,c,p=N+" "+a;if(s){while(t=t[i])if((1===t.nodeType||o)&&e(t,n,s))return!0}else while(t=t[i])if(1===t.nodeType||o)if(c=t[x]||(t[x]={}),(l=c[i])&&l[0]===p){if((u=l[1])===!0||u===r)return u===!0}else if(l=c[i]=[p],l[1]=e(t,n,s)||r,l[1]===!0)return!0}}function gt(e){return e.length>1?function(t,n,r){var i=e.length;while(i--)if(!e[i](t,n,r))return!1;return!0}:e[0]}function mt(e,t,n,r,i){var o,a=[],s=0,u=e.length,l=null!=t;for(;u>s;s++)(o=e[s])&&(!n||n(o,r,i))&&(a.push(o),l&&t.push(s));return a}function yt(e,t,n,r,i,o){return r&&!r[x]&&(r=yt(r)),i&&!i[x]&&(i=yt(i,o)),ot(function(o,a,s,u){var l,c,p,f=[],d=[],h=a.length,g=o||xt(t||"*",s.nodeType?[s]:s,[]),m=!e||!o&&t?g:mt(g,f,e,s,u),y=n?i||(o?e:h||r)?[]:a:m;if(n&&n(m,y,s,u),r){l=mt(y,d),r(l,[],s,u),c=l.length;while(c--)(p=l[c])&&(y[d[c]]=!(m[d[c]]=p))}if(o){if(i||e){if(i){l=[],c=y.length;while(c--)(p=y[c])&&l.push(m[c]=p);i(null,y=[],l,u)}c=y.length;while(c--)(p=y[c])&&(l=i?M.call(o,p):f[c])>-1&&(o[l]=!(a[l]=p))}}else y=mt(y===a?y.splice(h,y.length):y),i?i(null,a,y,u):H.apply(a,y)})}function vt(e){var t,n,r,o=e.length,a=i.relative[e[0].type],s=a||i.relative[" "],u=a?1:0,c=ht(function(e){return e===t},s,!0),p=ht(function(e){return M.call(t,e)>-1},s,!0),f=[function(e,n,r){return!a&&(r||n!==l)||((t=n).nodeType?c(e,n,r):p(e,n,r))}];for(;o>u;u++)if(n=i.relative[e[u].type])f=[ht(gt(f),n)];else{if(n=i.filter[e[u].type].apply(null,e[u].matches),n[x]){for(r=++u;o>r;r++)if(i.relative[e[r].type])break;return yt(u>1&>(f),u>1&&dt(e.slice(0,u-1)).replace(W,"$1"),n,r>u&&vt(e.slice(u,r)),o>r&&vt(e=e.slice(r)),o>r&&dt(e))}f.push(n)}return gt(f)}function bt(e,t){var n=0,o=t.length>0,a=e.length>0,s=function(s,u,c,f,d){var h,g,m,y=[],v=0,b="0",x=s&&[],w=null!=d,T=l,C=s||a&&i.find.TAG("*",d&&u.parentNode||u),k=N+=null==T?1:Math.random()||.1;for(w&&(l=u!==p&&u,r=n);null!=(h=C[b]);b++){if(a&&h){g=0;while(m=e[g++])if(m(h,u,c)){f.push(h);break}w&&(N=k,r=++n)}o&&((h=!m&&h)&&v--,s&&x.push(h))}if(v+=b,o&&b!==v){g=0;while(m=t[g++])m(x,y,u,c);if(s){if(v>0)while(b--)x[b]||y[b]||(y[b]=L.call(f));y=mt(y)}H.apply(f,y),w&&!s&&y.length>0&&v+t.length>1&&st.uniqueSort(f)}return w&&(N=k,l=T),x};return o?ot(s):s}s=st.compile=function(e,t){var n,r=[],i=[],o=S[e+" "];if(!o){t||(t=ft(e)),n=t.length;while(n--)o=vt(t[n]),o[x]?r.push(o):i.push(o);o=S(e,bt(i,r))}return o};function xt(e,t,n){var r=0,i=t.length;for(;i>r;r++)st(e,t[r],n);return n}function wt(e,t,n,r){var o,a,u,l,c,p=ft(e);if(!r&&1===p.length){if(a=p[0]=p[0].slice(0),a.length>2&&"ID"===(u=a[0]).type&&9===t.nodeType&&!d&&i.relative[a[1].type]){if(t=i.find.ID(u.matches[0].replace(et,tt),t)[0],!t)return n;e=e.slice(a.shift().value.length)}o=U.needsContext.test(e)?0:a.length;while(o--){if(u=a[o],i.relative[l=u.type])break;if((c=i.find[l])&&(r=c(u.matches[0].replace(et,tt),V.test(a[0].type)&&t.parentNode||t))){if(a.splice(o,1),e=r.length&&dt(a),!e)return H.apply(n,q.call(r,0)),n;break}}}return s(e,p)(r,t,d,n,V.test(e)),n}i.pseudos.nth=i.pseudos.eq;function Tt(){}i.filters=Tt.prototype=i.pseudos,i.setFilters=new Tt,c(),st.attr=b.attr,b.find=st,b.expr=st.selectors,b.expr[":"]=b.expr.pseudos,b.unique=st.uniqueSort,b.text=st.getText,b.isXMLDoc=st.isXML,b.contains=st.contains}(e);var at=/Until$/,st=/^(?:parents|prev(?:Until|All))/,ut=/^.[^:#\[\.,]*$/,lt=b.expr.match.needsContext,ct={children:!0,contents:!0,next:!0,prev:!0};b.fn.extend({find:function(e){var t,n,r,i=this.length;if("string"!=typeof e)return r=this,this.pushStack(b(e).filter(function(){for(t=0;i>t;t++)if(b.contains(r[t],this))return!0}));for(n=[],t=0;i>t;t++)b.find(e,this[t],n);return n=this.pushStack(i>1?b.unique(n):n),n.selector=(this.selector?this.selector+" ":"")+e,n},has:function(e){var t,n=b(e,this),r=n.length;return this.filter(function(){for(t=0;r>t;t++)if(b.contains(this,n[t]))return!0})},not:function(e){return this.pushStack(ft(this,e,!1))},filter:function(e){return this.pushStack(ft(this,e,!0))},is:function(e){return!!e&&("string"==typeof e?lt.test(e)?b(e,this.context).index(this[0])>=0:b.filter(e,this).length>0:this.filter(e).length>0)},closest:function(e,t){var n,r=0,i=this.length,o=[],a=lt.test(e)||"string"!=typeof e?b(e,t||this.context):0;for(;i>r;r++){n=this[r];while(n&&n.ownerDocument&&n!==t&&11!==n.nodeType){if(a?a.index(n)>-1:b.find.matchesSelector(n,e)){o.push(n);break}n=n.parentNode}}return this.pushStack(o.length>1?b.unique(o):o)},index:function(e){return e?"string"==typeof e?b.inArray(this[0],b(e)):b.inArray(e.jquery?e[0]:e,this):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(e,t){var n="string"==typeof e?b(e,t):b.makeArray(e&&e.nodeType?[e]:e),r=b.merge(this.get(),n);return this.pushStack(b.unique(r))},addBack:function(e){return this.add(null==e?this.prevObject:this.prevObject.filter(e))}}),b.fn.andSelf=b.fn.addBack;function pt(e,t){do e=e[t];while(e&&1!==e.nodeType);return e}b.each({parent:function(e){var t=e.parentNode;return t&&11!==t.nodeType?t:null},parents:function(e){return b.dir(e,"parentNode")},parentsUntil:function(e,t,n){return b.dir(e,"parentNode",n)},next:function(e){return pt(e,"nextSibling")},prev:function(e){return pt(e,"previousSibling")},nextAll:function(e){return b.dir(e,"nextSibling")},prevAll:function(e){return b.dir(e,"previousSibling")},nextUntil:function(e,t,n){return b.dir(e,"nextSibling",n)},prevUntil:function(e,t,n){return b.dir(e,"previousSibling",n)},siblings:function(e){return b.sibling((e.parentNode||{}).firstChild,e)},children:function(e){return b.sibling(e.firstChild)},contents:function(e){return b.nodeName(e,"iframe")?e.contentDocument||e.contentWindow.document:b.merge([],e.childNodes)}},function(e,t){b.fn[e]=function(n,r){var i=b.map(this,t,n);return at.test(e)||(r=n),r&&"string"==typeof r&&(i=b.filter(r,i)),i=this.length>1&&!ct[e]?b.unique(i):i,this.length>1&&st.test(e)&&(i=i.reverse()),this.pushStack(i)}}),b.extend({filter:function(e,t,n){return n&&(e=":not("+e+")"),1===t.length?b.find.matchesSelector(t[0],e)?[t[0]]:[]:b.find.matches(e,t)},dir:function(e,n,r){var i=[],o=e[n];while(o&&9!==o.nodeType&&(r===t||1!==o.nodeType||!b(o).is(r)))1===o.nodeType&&i.push(o),o=o[n];return i},sibling:function(e,t){var n=[];for(;e;e=e.nextSibling)1===e.nodeType&&e!==t&&n.push(e);return n}});function ft(e,t,n){if(t=t||0,b.isFunction(t))return b.grep(e,function(e,r){var i=!!t.call(e,r,e);return i===n});if(t.nodeType)return b.grep(e,function(e){return e===t===n});if("string"==typeof t){var r=b.grep(e,function(e){return 1===e.nodeType});if(ut.test(t))return b.filter(t,r,!n);t=b.filter(t,r)}return b.grep(e,function(e){return b.inArray(e,t)>=0===n})}function dt(e){var t=ht.split("|"),n=e.createDocumentFragment();if(n.createElement)while(t.length)n.createElement(t.pop());return n}var ht="abbr|article|aside|audio|bdi|canvas|data|datalist|details|figcaption|figure|footer|header|hgroup|mark|meter|nav|output|progress|section|summary|time|video",gt=/ jQuery\d+="(?:null|\d+)"/g,mt=RegExp("<(?:"+ht+")[\\s/>]","i"),yt=/^\s+/,vt=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,bt=/<([\w:]+)/,xt=/
","
"],tr:[2,"","
"],col:[2,"","
"],td:[3,"","
"],_default:b.support.htmlSerialize?[0,"",""]:[1,"X
","
"]},jt=dt(o),Dt=jt.appendChild(o.createElement("div"));At.optgroup=At.option,At.tbody=At.tfoot=At.colgroup=At.caption=At.thead,At.th=At.td,b.fn.extend({text:function(e){return b.access(this,function(e){return e===t?b.text(this):this.empty().append((this[0]&&this[0].ownerDocument||o).createTextNode(e))},null,e,arguments.length)},wrapAll:function(e){if(b.isFunction(e))return this.each(function(t){b(this).wrapAll(e.call(this,t))});if(this[0]){var t=b(e,this[0].ownerDocument).eq(0).clone(!0);this[0].parentNode&&t.insertBefore(this[0]),t.map(function(){var e=this;while(e.firstChild&&1===e.firstChild.nodeType)e=e.firstChild;return e}).append(this)}return this},wrapInner:function(e){return b.isFunction(e)?this.each(function(t){b(this).wrapInner(e.call(this,t))}):this.each(function(){var t=b(this),n=t.contents();n.length?n.wrapAll(e):t.append(e)})},wrap:function(e){var t=b.isFunction(e);return this.each(function(n){b(this).wrapAll(t?e.call(this,n):e)})},unwrap:function(){return this.parent().each(function(){b.nodeName(this,"body")||b(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,!0,function(e){(1===this.nodeType||11===this.nodeType||9===this.nodeType)&&this.appendChild(e)})},prepend:function(){return this.domManip(arguments,!0,function(e){(1===this.nodeType||11===this.nodeType||9===this.nodeType)&&this.insertBefore(e,this.firstChild)})},before:function(){return this.domManip(arguments,!1,function(e){this.parentNode&&this.parentNode.insertBefore(e,this)})},after:function(){return this.domManip(arguments,!1,function(e){this.parentNode&&this.parentNode.insertBefore(e,this.nextSibling)})},remove:function(e,t){var n,r=0;for(;null!=(n=this[r]);r++)(!e||b.filter(e,[n]).length>0)&&(t||1!==n.nodeType||b.cleanData(Ot(n)),n.parentNode&&(t&&b.contains(n.ownerDocument,n)&&Mt(Ot(n,"script")),n.parentNode.removeChild(n)));return this},empty:function(){var e,t=0;for(;null!=(e=this[t]);t++){1===e.nodeType&&b.cleanData(Ot(e,!1));while(e.firstChild)e.removeChild(e.firstChild);e.options&&b.nodeName(e,"select")&&(e.options.length=0)}return this},clone:function(e,t){return e=null==e?!1:e,t=null==t?e:t,this.map(function(){return b.clone(this,e,t)})},html:function(e){return b.access(this,function(e){var n=this[0]||{},r=0,i=this.length;if(e===t)return 1===n.nodeType?n.innerHTML.replace(gt,""):t;if(!("string"!=typeof e||Tt.test(e)||!b.support.htmlSerialize&&mt.test(e)||!b.support.leadingWhitespace&&yt.test(e)||At[(bt.exec(e)||["",""])[1].toLowerCase()])){e=e.replace(vt,"<$1>");try{for(;i>r;r++)n=this[r]||{},1===n.nodeType&&(b.cleanData(Ot(n,!1)),n.innerHTML=e);n=0}catch(o){}}n&&this.empty().append(e)},null,e,arguments.length)},replaceWith:function(e){var t=b.isFunction(e);return t||"string"==typeof e||(e=b(e).not(this).detach()),this.domManip([e],!0,function(e){var t=this.nextSibling,n=this.parentNode;n&&(b(this).remove(),n.insertBefore(e,t))})},detach:function(e){return this.remove(e,!0)},domManip:function(e,n,r){e=f.apply([],e);var i,o,a,s,u,l,c=0,p=this.length,d=this,h=p-1,g=e[0],m=b.isFunction(g);if(m||!(1>=p||"string"!=typeof g||b.support.checkClone)&&Ct.test(g))return this.each(function(i){var o=d.eq(i);m&&(e[0]=g.call(this,i,n?o.html():t)),o.domManip(e,n,r)});if(p&&(l=b.buildFragment(e,this[0].ownerDocument,!1,this),i=l.firstChild,1===l.childNodes.length&&(l=i),i)){for(n=n&&b.nodeName(i,"tr"),s=b.map(Ot(l,"script"),Ht),a=s.length;p>c;c++)o=l,c!==h&&(o=b.clone(o,!0,!0),a&&b.merge(s,Ot(o,"script"))),r.call(n&&b.nodeName(this[c],"table")?Lt(this[c],"tbody"):this[c],o,c);if(a)for(u=s[s.length-1].ownerDocument,b.map(s,qt),c=0;a>c;c++)o=s[c],kt.test(o.type||"")&&!b._data(o,"globalEval")&&b.contains(u,o)&&(o.src?b.ajax({url:o.src,type:"GET",dataType:"script",async:!1,global:!1,"throws":!0}):b.globalEval((o.text||o.textContent||o.innerHTML||"").replace(St,"")));l=i=null}return this}});function Lt(e,t){return e.getElementsByTagName(t)[0]||e.appendChild(e.ownerDocument.createElement(t))}function Ht(e){var t=e.getAttributeNode("type");return e.type=(t&&t.specified)+"/"+e.type,e}function qt(e){var t=Et.exec(e.type);return t?e.type=t[1]:e.removeAttribute("type"),e}function Mt(e,t){var n,r=0;for(;null!=(n=e[r]);r++)b._data(n,"globalEval",!t||b._data(t[r],"globalEval"))}function _t(e,t){if(1===t.nodeType&&b.hasData(e)){var n,r,i,o=b._data(e),a=b._data(t,o),s=o.events;if(s){delete a.handle,a.events={};for(n in s)for(r=0,i=s[n].length;i>r;r++)b.event.add(t,n,s[n][r])}a.data&&(a.data=b.extend({},a.data))}}function Ft(e,t){var n,r,i;if(1===t.nodeType){if(n=t.nodeName.toLowerCase(),!b.support.noCloneEvent&&t[b.expando]){i=b._data(t);for(r in i.events)b.removeEvent(t,r,i.handle);t.removeAttribute(b.expando)}"script"===n&&t.text!==e.text?(Ht(t).text=e.text,qt(t)):"object"===n?(t.parentNode&&(t.outerHTML=e.outerHTML),b.support.html5Clone&&e.innerHTML&&!b.trim(t.innerHTML)&&(t.innerHTML=e.innerHTML)):"input"===n&&Nt.test(e.type)?(t.defaultChecked=t.checked=e.checked,t.value!==e.value&&(t.value=e.value)):"option"===n?t.defaultSelected=t.selected=e.defaultSelected:("input"===n||"textarea"===n)&&(t.defaultValue=e.defaultValue)}}b.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(e,t){b.fn[e]=function(e){var n,r=0,i=[],o=b(e),a=o.length-1;for(;a>=r;r++)n=r===a?this:this.clone(!0),b(o[r])[t](n),d.apply(i,n.get());return this.pushStack(i)}});function Ot(e,n){var r,o,a=0,s=typeof e.getElementsByTagName!==i?e.getElementsByTagName(n||"*"):typeof e.querySelectorAll!==i?e.querySelectorAll(n||"*"):t;if(!s)for(s=[],r=e.childNodes||e;null!=(o=r[a]);a++)!n||b.nodeName(o,n)?s.push(o):b.merge(s,Ot(o,n));return n===t||n&&b.nodeName(e,n)?b.merge([e],s):s}function Bt(e){Nt.test(e.type)&&(e.defaultChecked=e.checked)}b.extend({clone:function(e,t,n){var r,i,o,a,s,u=b.contains(e.ownerDocument,e);if(b.support.html5Clone||b.isXMLDoc(e)||!mt.test("<"+e.nodeName+">")?o=e.cloneNode(!0):(Dt.innerHTML=e.outerHTML,Dt.removeChild(o=Dt.firstChild)),!(b.support.noCloneEvent&&b.support.noCloneChecked||1!==e.nodeType&&11!==e.nodeType||b.isXMLDoc(e)))for(r=Ot(o),s=Ot(e),a=0;null!=(i=s[a]);++a)r[a]&&Ft(i,r[a]);if(t)if(n)for(s=s||Ot(e),r=r||Ot(o),a=0;null!=(i=s[a]);a++)_t(i,r[a]);else _t(e,o);return r=Ot(o,"script"),r.length>0&&Mt(r,!u&&Ot(e,"script")),r=s=i=null,o},buildFragment:function(e,t,n,r){var i,o,a,s,u,l,c,p=e.length,f=dt(t),d=[],h=0;for(;p>h;h++)if(o=e[h],o||0===o)if("object"===b.type(o))b.merge(d,o.nodeType?[o]:o);else if(wt.test(o)){s=s||f.appendChild(t.createElement("div")),u=(bt.exec(o)||["",""])[1].toLowerCase(),c=At[u]||At._default,s.innerHTML=c[1]+o.replace(vt,"<$1>")+c[2],i=c[0];while(i--)s=s.lastChild;if(!b.support.leadingWhitespace&&yt.test(o)&&d.push(t.createTextNode(yt.exec(o)[0])),!b.support.tbody){o="table"!==u||xt.test(o)?""!==c[1]||xt.test(o)?0:s:s.firstChild,i=o&&o.childNodes.length;while(i--)b.nodeName(l=o.childNodes[i],"tbody")&&!l.childNodes.length&&o.removeChild(l) +}b.merge(d,s.childNodes),s.textContent="";while(s.firstChild)s.removeChild(s.firstChild);s=f.lastChild}else d.push(t.createTextNode(o));s&&f.removeChild(s),b.support.appendChecked||b.grep(Ot(d,"input"),Bt),h=0;while(o=d[h++])if((!r||-1===b.inArray(o,r))&&(a=b.contains(o.ownerDocument,o),s=Ot(f.appendChild(o),"script"),a&&Mt(s),n)){i=0;while(o=s[i++])kt.test(o.type||"")&&n.push(o)}return s=null,f},cleanData:function(e,t){var n,r,o,a,s=0,u=b.expando,l=b.cache,p=b.support.deleteExpando,f=b.event.special;for(;null!=(n=e[s]);s++)if((t||b.acceptData(n))&&(o=n[u],a=o&&l[o])){if(a.events)for(r in a.events)f[r]?b.event.remove(n,r):b.removeEvent(n,r,a.handle);l[o]&&(delete l[o],p?delete n[u]:typeof n.removeAttribute!==i?n.removeAttribute(u):n[u]=null,c.push(o))}}});var Pt,Rt,Wt,$t=/alpha\([^)]*\)/i,It=/opacity\s*=\s*([^)]*)/,zt=/^(top|right|bottom|left)$/,Xt=/^(none|table(?!-c[ea]).+)/,Ut=/^margin/,Vt=RegExp("^("+x+")(.*)$","i"),Yt=RegExp("^("+x+")(?!px)[a-z%]+$","i"),Jt=RegExp("^([+-])=("+x+")","i"),Gt={BODY:"block"},Qt={position:"absolute",visibility:"hidden",display:"block"},Kt={letterSpacing:0,fontWeight:400},Zt=["Top","Right","Bottom","Left"],en=["Webkit","O","Moz","ms"];function tn(e,t){if(t in e)return t;var n=t.charAt(0).toUpperCase()+t.slice(1),r=t,i=en.length;while(i--)if(t=en[i]+n,t in e)return t;return r}function nn(e,t){return e=t||e,"none"===b.css(e,"display")||!b.contains(e.ownerDocument,e)}function rn(e,t){var n,r,i,o=[],a=0,s=e.length;for(;s>a;a++)r=e[a],r.style&&(o[a]=b._data(r,"olddisplay"),n=r.style.display,t?(o[a]||"none"!==n||(r.style.display=""),""===r.style.display&&nn(r)&&(o[a]=b._data(r,"olddisplay",un(r.nodeName)))):o[a]||(i=nn(r),(n&&"none"!==n||!i)&&b._data(r,"olddisplay",i?n:b.css(r,"display"))));for(a=0;s>a;a++)r=e[a],r.style&&(t&&"none"!==r.style.display&&""!==r.style.display||(r.style.display=t?o[a]||"":"none"));return e}b.fn.extend({css:function(e,n){return b.access(this,function(e,n,r){var i,o,a={},s=0;if(b.isArray(n)){for(o=Rt(e),i=n.length;i>s;s++)a[n[s]]=b.css(e,n[s],!1,o);return a}return r!==t?b.style(e,n,r):b.css(e,n)},e,n,arguments.length>1)},show:function(){return rn(this,!0)},hide:function(){return rn(this)},toggle:function(e){var t="boolean"==typeof e;return this.each(function(){(t?e:nn(this))?b(this).show():b(this).hide()})}}),b.extend({cssHooks:{opacity:{get:function(e,t){if(t){var n=Wt(e,"opacity");return""===n?"1":n}}}},cssNumber:{columnCount:!0,fillOpacity:!0,fontWeight:!0,lineHeight:!0,opacity:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{"float":b.support.cssFloat?"cssFloat":"styleFloat"},style:function(e,n,r,i){if(e&&3!==e.nodeType&&8!==e.nodeType&&e.style){var o,a,s,u=b.camelCase(n),l=e.style;if(n=b.cssProps[u]||(b.cssProps[u]=tn(l,u)),s=b.cssHooks[n]||b.cssHooks[u],r===t)return s&&"get"in s&&(o=s.get(e,!1,i))!==t?o:l[n];if(a=typeof r,"string"===a&&(o=Jt.exec(r))&&(r=(o[1]+1)*o[2]+parseFloat(b.css(e,n)),a="number"),!(null==r||"number"===a&&isNaN(r)||("number"!==a||b.cssNumber[u]||(r+="px"),b.support.clearCloneStyle||""!==r||0!==n.indexOf("background")||(l[n]="inherit"),s&&"set"in s&&(r=s.set(e,r,i))===t)))try{l[n]=r}catch(c){}}},css:function(e,n,r,i){var o,a,s,u=b.camelCase(n);return n=b.cssProps[u]||(b.cssProps[u]=tn(e.style,u)),s=b.cssHooks[n]||b.cssHooks[u],s&&"get"in s&&(a=s.get(e,!0,r)),a===t&&(a=Wt(e,n,i)),"normal"===a&&n in Kt&&(a=Kt[n]),""===r||r?(o=parseFloat(a),r===!0||b.isNumeric(o)?o||0:a):a},swap:function(e,t,n,r){var i,o,a={};for(o in t)a[o]=e.style[o],e.style[o]=t[o];i=n.apply(e,r||[]);for(o in t)e.style[o]=a[o];return i}}),e.getComputedStyle?(Rt=function(t){return e.getComputedStyle(t,null)},Wt=function(e,n,r){var i,o,a,s=r||Rt(e),u=s?s.getPropertyValue(n)||s[n]:t,l=e.style;return s&&(""!==u||b.contains(e.ownerDocument,e)||(u=b.style(e,n)),Yt.test(u)&&Ut.test(n)&&(i=l.width,o=l.minWidth,a=l.maxWidth,l.minWidth=l.maxWidth=l.width=u,u=s.width,l.width=i,l.minWidth=o,l.maxWidth=a)),u}):o.documentElement.currentStyle&&(Rt=function(e){return e.currentStyle},Wt=function(e,n,r){var i,o,a,s=r||Rt(e),u=s?s[n]:t,l=e.style;return null==u&&l&&l[n]&&(u=l[n]),Yt.test(u)&&!zt.test(n)&&(i=l.left,o=e.runtimeStyle,a=o&&o.left,a&&(o.left=e.currentStyle.left),l.left="fontSize"===n?"1em":u,u=l.pixelLeft+"px",l.left=i,a&&(o.left=a)),""===u?"auto":u});function on(e,t,n){var r=Vt.exec(t);return r?Math.max(0,r[1]-(n||0))+(r[2]||"px"):t}function an(e,t,n,r,i){var o=n===(r?"border":"content")?4:"width"===t?1:0,a=0;for(;4>o;o+=2)"margin"===n&&(a+=b.css(e,n+Zt[o],!0,i)),r?("content"===n&&(a-=b.css(e,"padding"+Zt[o],!0,i)),"margin"!==n&&(a-=b.css(e,"border"+Zt[o]+"Width",!0,i))):(a+=b.css(e,"padding"+Zt[o],!0,i),"padding"!==n&&(a+=b.css(e,"border"+Zt[o]+"Width",!0,i)));return a}function sn(e,t,n){var r=!0,i="width"===t?e.offsetWidth:e.offsetHeight,o=Rt(e),a=b.support.boxSizing&&"border-box"===b.css(e,"boxSizing",!1,o);if(0>=i||null==i){if(i=Wt(e,t,o),(0>i||null==i)&&(i=e.style[t]),Yt.test(i))return i;r=a&&(b.support.boxSizingReliable||i===e.style[t]),i=parseFloat(i)||0}return i+an(e,t,n||(a?"border":"content"),r,o)+"px"}function un(e){var t=o,n=Gt[e];return n||(n=ln(e,t),"none"!==n&&n||(Pt=(Pt||b("' + + ''; + + return this.fileProvider.writeFile(indexPath, html); + }).then((fileEntry) => { + return fileEntry.toURL(); + }); + }); + } + + /** + * Delete cached assets from DB and filesystem. + * + * @param libraryId Library identifier. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when done. + */ + protected deleteCachedAssets(libraryId: number, siteId?: string): Promise { + + return this.sitesProvider.getSite(siteId).then((site) => { + const db = site.getDb(); + + // Get all the hashes that use this library. + return db.getRecords(this.LIBRARIES_CACHEDASSETS_TABLE, {libraryid: libraryId}).then((entries) => { + // Delete the files with these hashes. + const promises = [], + hashes = []; + + entries.forEach((entry) => { + hashes.push(entry.hash); + + const cachedAssetsFolder = this.getCachedAssetsFolderPath(entry.foldername, site.getId()); + + ['js', 'css'].forEach((type) => { + const path = this.textUtils.concatenatePaths(cachedAssetsFolder, entry.hash + '.' + type); + + promises.push(this.fileProvider.removeFile(path).catch(() => { + // Ignore errors, maybe there's no cached asset of this type. + })); + }); + }); + + return Promise.all(promises).then(() => { + return db.deleteRecordsList(this.LIBRARIES_CACHEDASSETS_TABLE, 'hash', hashes); + }); + }); + }); + } + + /** + * Delete all package content data. + * + * @param fileUrl File URL. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when done. + */ + deleteContentByUrl(fileUrl: string, siteId?: string): Promise { + siteId = siteId || this.sitesProvider.getCurrentSiteId(); + + return this.getContentDataByUrl(fileUrl, siteId).then((data) => { + const promises = []; + + promises.push(this.deleteContentData(data.id, siteId)); + + promises.push(this.deleteContentFolder(data.foldername, siteId)); + + return this.utils.allPromises(promises); + }); + } + + /** + * Delete content data from DB. + * + * @param id Content ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when done. + */ + deleteContentData(id: number, siteId?: string): Promise { + const promises = []; + + // Delete the content data. + promises.push(this.sitesProvider.getSiteDb(siteId).then((db) => { + return db.deleteRecords(this.CONTENT_TABLE, {id: id}); + })); + + // Remove content library dependencies. + promises.push(this.deleteLibraryUsage(id, siteId)); + + return Promise.all(promises); + } + + /** + * Deletes a content folder from the file system. + * + * @param folderName Folder name of the content. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when done. + */ + deleteContentFolder(folderName: string, siteId?: string): Promise { + return this.fileProvider.removeDir(this.getContentFolderPath(folderName, siteId)); + } + + /** + * Delete content indexes from filesystem. + * + * @param libraryId Library identifier. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when done. + */ + protected deleteContentIndexesForLibrary(libraryId: number, siteId?: string): Promise { + + return this.sitesProvider.getSite(siteId).then((site) => { + const db = site.getDb(); + + // Get the folder names of all the packages that use this library. + const query = 'SELECT DISTINCT hc.foldername ' + + 'FROM ' + this.CONTENTS_LIBRARIES_TABLE + ' hcl ' + + 'JOIN ' + this.CONTENT_TABLE + ' hc ON hcl.h5pid = hc.id ' + + 'WHERE hcl.libraryid = ?', + queryArgs = []; + + queryArgs.push(libraryId); + + return db.execute(query, queryArgs).then((result) => { + const promises = []; + + for (let i = 0; i < result.rows.length; i++) { + const entry = result.rows.item(i); + + // Delete the index.html file. + promises.push(this.fileProvider.removeFile(this.getContentIndexPath(entry.foldername, site.getId())) + .catch(() => { + // Ignore errors. + })); + } + + return Promise.all(promises); + }); + }); + } + + /** + * Delete library data from DB. + * + * @param id Library ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when done. + */ + deleteLibraryData(id: number, siteId?: string): Promise { + return this.sitesProvider.getSiteDb(siteId).then((db) => { + return db.deleteRecords(this.LIBRARIES_TABLE, {id: id}); + }); + } + + /** + * Delete all dependencies belonging to given library. + * + * @param libraryId Library ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when done. + */ + deleteLibraryDependencies(libraryId: number, siteId?: string): Promise { + return this.sitesProvider.getSiteDb(siteId).then((db) => { + return db.deleteRecords(this.LIBRARY_DEPENDENCIES_TABLE, {libraryid: libraryId}); + }); + } + + /** + * Deletes a library from the file system. + * + * @param libraryData The library data. + * @param folderName Folder name. If not provided, it will be calculated. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when done. + */ + deleteLibraryFolder(libraryData: any, folderName?: string, siteId?: string): Promise { + return this.fileProvider.removeDir(this.getLibraryFolderPath(libraryData, siteId, folderName)); + } + + /** + * Delete what libraries a content item is using. + * + * @param id Package ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when done. + */ + deleteLibraryUsage(id: number, siteId?: string): Promise { + return this.sitesProvider.getSiteDb(siteId).then((db) => { + return db.deleteRecords(this.CONTENTS_LIBRARIES_TABLE, {h5pid: id}); + }); + } + + /** + * Extract an H5P file. Some of this code was copied from the isValidPackage function in Moodle's H5PValidator. + * This function won't validate most things because it should've been done by the server already. + * + * @param fileUrl The file URL used to download the file. + * @param file The file entry of the downloaded file. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when done. + */ + extractH5PFile(fileUrl: string, file: FileEntry, siteId?: string): Promise { + siteId = siteId || this.sitesProvider.getCurrentSiteId(); + + // Unzip the file. + const folderName = this.mimeUtils.removeExtension(file.name), + destFolder = this.textUtils.concatenatePaths(CoreFileProvider.TMPFOLDER, 'h5p/' + folderName); + + // Unzip the file. + return this.fileProvider.unzipFile(file.toURL(), destFolder).then(() => { + // Read the contents of the unzipped dir. + return this.fileProvider.getDirectoryContents(destFolder); + }).then((contents) => { + return this.processH5PFiles(destFolder, contents).then((data) => { + const content: any = {}; + + // Save the libraries that were processed. + return this.saveLibraries(data.librariesJsonData, folderName, siteId).then(() => { + // Now treat contents. + + // Find main library version + for (const i in data.mainJsonData.preloadedDependencies) { + const dependency = data.mainJsonData.preloadedDependencies[i]; + + if (dependency.machineName === data.mainJsonData.mainLibrary) { + return this.getLibraryIdByData(dependency).then((id) => { + dependency.libraryId = id; + content.library = dependency; + }); + } + } + }).then(() => { + // Save the content data in DB. + content.params = JSON.stringify(data.contentJsonData); + + return this.saveContentData(content, folderName, fileUrl, siteId); + }).then(() => { + // Save the content files in their right place. + const contentPath = this.textUtils.concatenatePaths(destFolder, 'content'); + + return this.saveContentInFS(contentPath, folderName, siteId).catch((error) => { + // An error occurred, delete the DB data because the content data has been deleted. + return this.deleteContentData(content.id, siteId).catch(() => { + // Ignore errors. + }).then(() => { + return Promise.reject(error); + }); + }); + }).then(() => { + // Create the content player. + + return this.loadContentData(content.id, undefined, siteId).then((contentData) => { + const embedType = this.h5pUtils.determineEmbedType(contentData.embedType, contentData.library.embedTypes); + + return this.createContentIndex(content.id, fileUrl, contentData, embedType, siteId); + }); + }).finally(() => { + // Remove tmp folder. + return this.fileProvider.removeDir(destFolder).catch(() => { + // Ignore errors, it will be deleted eventually. + }); + }); + }); + }); + } + + /** + * Filter content run parameters and rebuild content dependency cache. + * + * @param content Content data. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the filtered params, resolved with null if error. + */ + filterParameters(content: CoreH5PContentData, siteId?: string): Promise { + siteId = siteId || this.sitesProvider.getCurrentSiteId(); + + if (content.filtered) { + return Promise.resolve(content.filtered); + } + + if (typeof content.library == 'undefined' || typeof content.params == 'undefined') { + return Promise.resolve(null); + } + + const params = { + library: this.libraryToString(content.library), + params: this.textUtils.parseJSON(content.params, false) + }; + + if (!params.params) { + return null; + } + + const validator = new CoreH5PContentValidator(this, this.h5pUtils, this.textUtils, this.utils, this.translate, siteId); + + // Validate the main library and its dependencies. + return validator.validateLibrary(params, {options: [params.library]}).then(() => { + + // Handle addons. + return this.loadAddons(siteId); + }).then((addons) => { + // Validate addons. Use a chain of promises to calculate the weight properly. + let promise = Promise.resolve(); + + addons.forEach((addon) => { + const addTo = addon.addTo; + + if (addTo && addTo.content && addTo.content.types && addTo.content.types.length) { + for (let i = 0; i < addTo.content.types.length; i++) { + const type = addTo.content.types[i]; + + if (type && type.text && type.text.regex && + this.h5pUtils.textAddonMatches(params.params, type.text.regex)) { + + promise = promise.then(() => { + return validator.addon(addon); + }); + + // An addon shall only be added once. + break; + } + } + } + }); + + return promise; + }).then(() => { + // Update content dependencies. + content.dependencies = validator.getDependencies(); + + const paramsStr = JSON.stringify(params.params); + + // Sometimes the parameters are filtered before content has been created + if (content.id) { + // Update library usage. + return this.deleteLibraryUsage(content.id, siteId).catch(() => { + // Ignore errors. + }).then(() => { + return this.saveLibraryUsage(content.id, content.dependencies, siteId); + }).then(() => { + if (!content.slug) { + content.slug = this.h5pUtils.slugify(content.title); + } + + // Cache. + return this.updateContentFields(content.id, {filtered: paramsStr}, siteId).then(() => { + return paramsStr; + }); + }); + } + + return paramsStr; + }).catch(() => { + return null; + }); + } + + /** + * Recursive. Goes through the dependency tree for the given library and + * adds all the dependencies to the given array in a flat format. + * + * @param dependencies Object where to save the dependencies. + * @param library The library to find all dependencies for. + * @param nextWeight An integer determining the order of the libraries when they are loaded. + * @param editor Used internally to force all preloaded sub dependencies of an editor dependency to be editor dependencies. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the next weight. + */ + findLibraryDependencies(dependencies: {[key: string]: CoreH5PContentDepsTreeDependency}, + library: CoreH5PLibraryData | CoreH5PLibraryAddonData, nextWeight: number = 1, editor: boolean = false, + siteId?: string): Promise { + + siteId = siteId || this.sitesProvider.getCurrentSiteId(); + + let promise = Promise.resolve(); // We need to create a chain of promises to calculate the weight properly. + + ['dynamic', 'preloaded', 'editor'].forEach((type) => { + const property = type + 'Dependencies'; + + if (!library[property]) { + return; // Skip, no such dependencies. + } + + if (type === 'preloaded' && editor) { + // All preloaded dependencies of an editor library is set to editor. + type = 'editor'; + } + + library[property].forEach((dependency: CoreH5PLibraryBasicData) => { + + promise = promise.then(() => { + const dependencyKey = type + '-' + dependency.machineName; + if (dependencies[dependencyKey]) { + return; // Skip, already have this. + } + + // Get the dependency library data and its subdependencies. + return this.loadLibrary(dependency.machineName, dependency.majorVersion, dependency.minorVersion, siteId) + .then((dependencyLibrary) => { + + dependencies[dependencyKey] = { + library: dependencyLibrary, + type: type + }; + + // Get all its subdependencies. + return this.findLibraryDependencies(dependencies, dependencyLibrary, nextWeight, type === 'editor', siteId); + }).then((weight) => { + nextWeight = weight; + dependencies[dependencyKey].weight = nextWeight++; + }); + }); + }); + }); + + return promise.then(() => { + return nextWeight; + }); + } + + /** + * Validate and fix display options, updating them if needed. + * + * @param displayOptions The display options to validate. + * @param id Package ID. + */ + fixDisplayOptions(displayOptions: CoreH5PDisplayOptions, id: number): CoreH5PDisplayOptions { + + // Never allow downloading in the app. + displayOptions[CoreH5PProvider.DISPLAY_OPTION_DOWNLOAD] = false; + + // Embed - force setting it if always on or always off. In web, this is done when storing in DB. + const embed = this.getOption(CoreH5PProvider.DISPLAY_OPTION_EMBED, CoreH5PDisplayOptionBehaviour.ALWAYS_SHOW); + if (embed == CoreH5PDisplayOptionBehaviour.ALWAYS_SHOW || embed == CoreH5PDisplayOptionBehaviour.NEVER_SHOW) { + displayOptions[CoreH5PProvider.DISPLAY_OPTION_EMBED] = (embed == CoreH5PDisplayOptionBehaviour.ALWAYS_SHOW); + } + + if (!this.getOption(CoreH5PProvider.DISPLAY_OPTION_FRAME, true)) { + displayOptions[CoreH5PProvider.DISPLAY_OPTION_FRAME] = false; + } else { + displayOptions[CoreH5PProvider.DISPLAY_OPTION_EMBED] = this.setDisplayOptionOverrides( + CoreH5PProvider.DISPLAY_OPTION_EMBED, CoreH5PPermission.EMBED_H5P, id, + displayOptions[CoreH5PProvider.DISPLAY_OPTION_EMBED]); + + if (this.getOption(CoreH5PProvider.DISPLAY_OPTION_COPYRIGHT, true) == false) { + displayOptions[CoreH5PProvider.DISPLAY_OPTION_COPYRIGHT] = false; + } + } + + displayOptions[CoreH5PProvider.DISPLAY_OPTION_COPY] = this.hasPermission(CoreH5PPermission.COPY_H5P, id); + + return displayOptions; + } + + /** + * Get the assets of a package. + * + * @param id Content id. + * @param content Content data. + * @param embedType Embed type. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the assets. + */ + protected getAssets(id: number, content: CoreH5PContentData, embedType: string, siteId?: string) + : Promise<{settings: any, cssRequires: string[], jsRequires: string[]}> { + + siteId = siteId || this.sitesProvider.getCurrentSiteId(); + + const cssRequires = [], + jsRequires = [], + contentId = this.getContentId(id); + let settings; + + return this.getCoreSettings(id, siteId).then((coreSettings) => { + settings = coreSettings; + + settings.core = { + styles: [], + scripts: [] + }; + settings.loadedJs = []; + settings.loadedCss = []; + + const libUrl = this.getCoreH5PPath(), + relPath = this.urlUtils.removeProtocolAndWWW(libUrl); + + // Add core stylesheets. + CoreH5PProvider.STYLES.forEach((style) => { + settings.core.styles.push(relPath + style); + cssRequires.push(libUrl + style); + }); + + // Add core JavaScript. + this.getScripts().forEach((script) => { + settings.core.scripts.push(script); + jsRequires.push(script); + }); + + /* The filterParameters function should be called before getting the dependency files because it rebuilds content + dependency cache. */ + return this.filterParameters(content, siteId); + }).then((params) => { + settings.contents = settings.contents || {}; + settings.contents[contentId] = settings.contents[contentId] || {}; + settings.contents[contentId].jsonContent = params; + + return this.getContentDependencyFiles(id, content.folderName, siteId); + }).then((files) => { + + // H5P checks the embedType in here, but we'll always use iframe so there's no need to do it. + // JavaScripts and stylesheets will be loaded through h5p.js. + settings.contents[contentId].scripts = this.h5pUtils.getAssetsUrls(files.scripts); + settings.contents[contentId].styles = this.h5pUtils.getAssetsUrls(files.styles); + + return { + settings: settings, + cssRequires: cssRequires, + jsRequires: jsRequires + }; + }); + } + + /** + * Will check if there are cache assets available for content. + * + * @param key Hashed key for cached asset + * @param folderName Name of the folder of the H5P package. + * @param siteId The site ID. + * @return Promise resolved with the files. + */ + getCachedAssets(key: string, folderName: string, siteId: string) + : Promise<{scripts?: CoreH5PDependencyAsset[], styles?: CoreH5PDependencyAsset[]}> { + + const files: {scripts?: CoreH5PDependencyAsset[], styles?: CoreH5PDependencyAsset[]} = {}, + promises = [], + cachedAssetsName = this.getCachedAssetsFolderName(), + jsPath = this.textUtils.concatenatePaths(cachedAssetsName, key + '.js'), + cssPath = this.textUtils.concatenatePaths(cachedAssetsName, key + '.css'); + let found = false; + + promises.push(this.fileProvider.getFileSize(jsPath).then((size) => { + if (size > 0) { + found = true; + files.scripts = [ + { + path: jsPath, + version: '' + } + ]; + } + }).catch(() => { + // Not found. + })); + + promises.push(this.fileProvider.getFileSize(cssPath).then((size) => { + if (size > 0) { + found = true; + files.styles = [ + { + path: cssPath, + version: '' + } + ]; + } + }).catch(() => { + // Not found. + })); + + return Promise.all(promises).then(() => { + return found ? files : null; + }); + } + + /** + * Get folder name of the content cached assets. + * + * @return Name. + */ + getCachedAssetsFolderName(): string { + return 'cachedassets'; + } + + /** + * Get relative path to a content cached assets. + * + * @param folderName Name of the folder of the content the assets belong to. + * @param siteId Site ID. + * @return Path. + */ + getCachedAssetsFolderPath(folderName: string, siteId: string): string { + return this.textUtils.concatenatePaths(this.getContentFolderPath(folderName, siteId), this.getCachedAssetsFolderName()); + } + + /** + * Get the identifier for the H5P content. This identifier is different than the ID stored in the DB. + * + * @param id Package ID. + * @return Content identifier. + */ + protected getContentId(id: number): string { + return 'cid-' + id; + } + + /** + * Get conent data from DB. + * + * @param id Content ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the content data. + */ + protected getContentData(id: number, siteId?: string): Promise { + return this.sitesProvider.getSiteDb(siteId).then((db) => { + return db.getRecord(this.CONTENT_TABLE, {id: id}); + }); + } + + /** + * Get conent data from DB. + * + * @param fileUrl H5P file URL. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the content data. + */ + protected getContentDataByUrl(fileUrl: string, siteId?: string): Promise { + return this.sitesProvider.getSite(siteId).then((site) => { + const db = site.getDb(); + + // Try to use the folder name, it should be more reliable than the URL. + return this.getContentFolderNameByUrl(fileUrl, site.getId()).then((folderName) => { + + return db.getRecord(this.CONTENT_TABLE, {foldername: folderName}); + }, () => { + // Cannot get folder name, the h5p file was probably deleted. Just use the URL. + return db.getRecord(this.CONTENT_TABLE, {fileurl: fileUrl}); + }); + }); + } + + /** + * Get a package content path. + * + * @param folderName Name of the folder of the H5P package. + * @param siteId The site ID. + * @return Folder path. + */ + getContentFolderPath(folderName: string, siteId: string): string { + return this.textUtils.concatenatePaths(this.getExternalH5PFolderPath(siteId), 'packages/' + folderName + '/content'); + } + + /** + * Get the content index file. + * + * @param fileUrl URL of the H5P package. + * @param urlParams URL params. + * @param siteId The site ID. If not defined, current site. + * @return Promise resolved with the file URL if exists, rejected otherwise. + */ + getContentIndexFileUrl(fileUrl: string, urlParams?: {[name: string]: string}, siteId?: string): Promise { + siteId = siteId || this.sitesProvider.getCurrentSiteId(); + + return this.getContentFolderNameByUrl(fileUrl, siteId).then((folderName) => { + return this.fileProvider.getFile(this.getContentIndexPath(folderName, siteId)); + }).then((file) => { + return file.toURL(); + }).then((url) => { + // Add display options to the URL. + return this.getContentDataByUrl(fileUrl, siteId).then((data) => { + const options = this.fixDisplayOptions(this.getDisplayOptionsFromUrlParams(urlParams), data.id); + + return this.urlUtils.addParamsToUrl(url, options, undefined, true); + }); + }); + + } + + /** + * Get the path to a content index. + * + * @param folderName Name of the folder of the H5P package. + * @param siteId The site ID. + * @return Folder path. + */ + getContentIndexPath(folderName: string, siteId: string): string { + return this.textUtils.concatenatePaths(this.getContentFolderPath(folderName, siteId), 'index.html'); + } + + /** + * Get a content folder name given the package URL. + * + * @param fileUrl Package URL. + * @param siteId Site ID. + * @return Promise resolved with the folder name. + */ + getContentFolderNameByUrl(fileUrl: string, siteId: string): Promise { + return this.filepoolProvider.getFilePathByUrl(siteId, fileUrl).then((path) => { + + const fileAndDir = this.fileProvider.getFileAndDirectoryFromPath(path); + + return this.mimeUtils.removeExtension(fileAndDir.name); + }); + } + + /** + * Get the path to the folder that contains the H5P core libraries. + * + * @return Folder path. + */ + getCoreH5PPath(): string { + return this.textUtils.concatenatePaths(this.fileProvider.getWWWPath(), '/h5p/'); + } + + /** + * Get the settings needed by the H5P library. + * + * @param id The H5P content ID. + * @param siteId The site ID. If not defined, current site. + * @return Promise resolved with the settings. + */ + getCoreSettings(id: number, siteId?: string): Promise { + + return this.sitesProvider.getSite(siteId).then((site) => { + + const basePath = this.fileProvider.getBasePathInstant(), + ajaxPaths: any = {}; + ajaxPaths.xAPIResult = ''; + ajaxPaths.contentUserData = ''; + + return { + baseUrl: this.fileProvider.getWWWPath(), + url: this.textUtils.concatenatePaths(basePath, this.getExternalH5PFolderPath(site.getId())), + urlLibraries: this.textUtils.concatenatePaths(basePath, this.getLibrariesFolderPath(site.getId())), + postUserStatistics: false, + ajax: ajaxPaths, + saveFreq: false, + siteUrl: site.getURL(), + l10n: { + H5P: this.h5pUtils.getLocalization() + }, + user: [], + hubIsEnabled: false, + reportingIsEnabled: false, + crossorigin: null, + libraryConfig: null, + pluginCacheBuster: '', + libraryUrl: this.textUtils.concatenatePaths(this.getCoreH5PPath(), 'js') + }; + }); + } + + /** + * Finds library dependencies files of a certain package. + * + * @param id Content id. + * @param folderName Name of the folder of the content. + * @param siteId The site ID. If not defined, current site. + * @return Promise resolved with the files. + */ + protected getContentDependencyFiles(id: number, folderName: string, siteId?: string) + : Promise<{scripts: CoreH5PDependencyAsset[], styles: CoreH5PDependencyAsset[]}> { + + return this.loadContentDependencies(id, 'preloaded', siteId).then((dependencies) => { + return this.getDependenciesFiles(dependencies, folderName, this.getExternalH5PFolderPath(siteId), siteId); + }); + } + + /** + * Get all dependency assets of the given type. + * + * @param dependency The dependency. + * @param type Type of assets to get. + * @param assets Array where to store the assets. + * @param prefix Make paths relative to another dir. + */ + protected getDependencyAssets(dependency: CoreH5PContentDependencyData, type: string, assets: CoreH5PDependencyAsset[], + prefix: string = ''): void { + + // Check if dependency has any files of this type + if (!dependency[type] || dependency[type][0] === '') { + return; + } + + // Check if we should skip CSS. + if (type === 'preloadedCss' && this.utils.isTrueOrOne(dependency.dropCss)) { + return; + } + + for (const key in dependency[type]) { + const file = dependency[type][key]; + + assets.push({ + path: prefix + '/' + dependency.path + '/' + (typeof file != 'string' ? file.path : file).trim(), + version: dependency.version + }); + } + } + + /** + * Return file paths for all dependencies files. + * + * @param dependencies The dependencies to get the files. + * @param folderName Name of the folder of the content. + * @param prefix Make paths relative to another dir. + * @param siteId The site ID. If not defined, current site. + * @return Promise resolved with the files. + */ + protected getDependenciesFiles(dependencies: {[machineName: string]: CoreH5PContentDependencyData}, folderName: string, + prefix: string = '', siteId?: string): Promise<{scripts: CoreH5PDependencyAsset[], styles: CoreH5PDependencyAsset[]}> { + + // Build files list for assets. + const files = { + scripts: [], + styles: [] + }; + + // Avoid caching empty files. + if (!Object.keys(dependencies).length) { + return Promise.resolve(files); + } + + let promise, + cachedAssetsHash; + + if (this.aggregateAssets) { + // Get aggregated files for assets. + cachedAssetsHash = this.h5pUtils.getDependenciesHash(dependencies); + + promise = this.getCachedAssets(cachedAssetsHash, folderName, siteId); + } else { + promise = Promise.resolve(null); + } + + return promise.then((cachedAssets) => { + if (cachedAssets) { + // Cached assets found, return them. + return Object.assign(files, cachedAssets); + } + + // No cached assets, use content dependencies. + for (const key in dependencies) { + const dependency = dependencies[key]; + + if (!dependency.path) { + dependency.path = this.getDependencyPath(dependency); + dependency.preloadedJs = ( dependency.preloadedJs).split(','); + dependency.preloadedCss = ( dependency.preloadedCss).split(','); + } + + dependency.version = '?ver=' + dependency.majorVersion + '.' + dependency.minorVersion + '.' + + dependency.patchVersion; + + this.getDependencyAssets(dependency, 'preloadedJs', files.scripts, prefix); + this.getDependencyAssets(dependency, 'preloadedCss', files.styles, prefix); + } + + if (this.aggregateAssets) { + // Aggregate and store assets. + return this.cacheAssets(files, cachedAssetsHash, folderName, siteId).then(() => { + // Keep track of which libraries have been cached in case they are updated. + return this.saveCachedAssets(cachedAssetsHash, dependencies, folderName, siteId); + }).then(() => { + return files; + }); + } + + return files; + }); + } + + /** + * Get the path to the dependency. + * + * @param dependency Dependency library. + * @return The path to the dependency library + */ + protected getDependencyPath(dependency: CoreH5PContentDependencyData): string { + return 'libraries/' + dependency.machineName + '-' + dependency.majorVersion + '.' + dependency.minorVersion; + } + + /** + * Get the paths to the content dependencies. + * + * @param id The H5P content ID. + * @param siteId The site ID. If not defined, current site. + * @return Promise resolved with an object containing the path of each content dependency. + */ + getDependencyRoots(id: number, siteId?: string): Promise<{[libString: string]: string}> { + siteId = siteId || this.sitesProvider.getCurrentSiteId(); + + const roots = {}; + + return this.loadContentDependencies(id, undefined, siteId).then((dependencies) => { + + for (const machineName in dependencies) { + const dependency = dependencies[machineName], + folderName = this.libraryToString(dependency, true); + + roots[folderName] = this.getLibraryFolderPath(dependency, siteId, folderName); + } + + return roots; + }); + } + + /** + * Convert display options to an object. + * + * @param disable Display options as a number. + * @return Display options as object. + */ + getDisplayOptionsAsObject(disable: number): CoreH5PDisplayOptions { + const displayOptions: CoreH5PDisplayOptions = {}; + + // tslint:disable: no-bitwise + displayOptions[CoreH5PProvider.DISPLAY_OPTION_FRAME] = !(disable & CoreH5PProvider.DISABLE_FRAME); + displayOptions[CoreH5PProvider.DISPLAY_OPTION_DOWNLOAD] = !(disable & CoreH5PProvider.DISABLE_DOWNLOAD); + displayOptions[CoreH5PProvider.DISPLAY_OPTION_EMBED] = !(disable & CoreH5PProvider.DISABLE_EMBED); + displayOptions[CoreH5PProvider.DISPLAY_OPTION_COPYRIGHT] = !(disable & CoreH5PProvider.DISABLE_COPYRIGHT); + displayOptions[CoreH5PProvider.DISPLAY_OPTION_ABOUT] = !!this.getOption(CoreH5PProvider.DISPLAY_OPTION_ABOUT, true); + + return displayOptions; + } + + /** + * Determine display option visibility when viewing H5P + * + * @param disable The display options as a number. + * @param id Package ID. + * @return Display options as object. + */ + getDisplayOptionsForView(disable: number, id: number): CoreH5PDisplayOptions { + return this.fixDisplayOptions(this.getDisplayOptionsAsObject(disable), id); + } + + /** + * Get display options from a URL params. + * + * @param params URL params. + * @return Display options as object. + */ + getDisplayOptionsFromUrlParams(params: {[name: string]: string}): CoreH5PDisplayOptions { + const displayOptions: CoreH5PDisplayOptions = {}; + + if (!params) { + return displayOptions; + } + + displayOptions[CoreH5PProvider.DISPLAY_OPTION_DOWNLOAD] = + this.utils.isTrueOrOne(params[CoreH5PProvider.DISPLAY_OPTION_DOWNLOAD]); + displayOptions[CoreH5PProvider.DISPLAY_OPTION_EMBED] = + this.utils.isTrueOrOne(params[CoreH5PProvider.DISPLAY_OPTION_EMBED]); + displayOptions[CoreH5PProvider.DISPLAY_OPTION_COPYRIGHT] = + this.utils.isTrueOrOne(params[CoreH5PProvider.DISPLAY_OPTION_COPYRIGHT]); + displayOptions[CoreH5PProvider.DISPLAY_OPTION_FRAME] = displayOptions[CoreH5PProvider.DISPLAY_OPTION_DOWNLOAD] || + displayOptions[CoreH5PProvider.DISPLAY_OPTION_EMBED] || displayOptions[CoreH5PProvider.DISPLAY_OPTION_COPYRIGHT]; + displayOptions[CoreH5PProvider.DISPLAY_OPTION_ABOUT] = !!this.getOption(CoreH5PProvider.DISPLAY_OPTION_ABOUT, true); + + return displayOptions; + } + + /** + * Embed code for settings. + * + * @param siteUrl The site URL. + * @param h5pUrl The URL of the .h5p file. + * @param embedEnabled Whether the option to embed the H5P content is enabled. + * @return The HTML code to reuse this H5P content in a different place. + */ + protected getEmbedCode(siteUrl: string, h5pUrl: string, embedEnabled?: boolean): string { + if (!embedEnabled) { + return ''; + } + + return ''; + } + + /** + * Get the encoded URL for embeding an H5P content. + * + * @param siteUrl The site URL. + * @param h5pUrl The URL of the .h5p file. + * @return The embed URL. + */ + protected getEmbedUrl(siteUrl: string, h5pUrl: string): string { + return this.textUtils.concatenatePaths(siteUrl, '/h5p/embed.php') + '?url=' + h5pUrl; + } + + /** + * Get path to the folder containing H5P files extracted from packages. + * + * @param siteId The site ID. + * @return Folder path. + */ + getExternalH5PFolderPath(siteId: string): string { + return this.textUtils.concatenatePaths(this.fileProvider.getSiteFolder(siteId), 'h5p'); + } + + /** + * Get library data. This code is based on the getLibraryData from Moodle's H5PValidator. + * This function won't validate most things because it should've been done by the server already. + * + * @param libDir Directory where the library files are. + * @param libPath Path to the directory where the library files are. + * @param h5pDir Path to the directory where this h5p files are. + * @return Library data. + */ + protected getLibraryData(libDir: DirectoryEntry, libPath: string, h5pDir: string): any { + const libraryJsonPath = this.textUtils.concatenatePaths(libPath, 'library.json'), + semanticsPath = this.textUtils.concatenatePaths(libPath, 'semantics.json'), + langPath = this.textUtils.concatenatePaths(libPath, 'language'), + iconPath = this.textUtils.concatenatePaths(libPath, 'icon.svg'), + promises = []; + let h5pData, + semanticsData, + langData, + hasIcon; + + // Read the library json file. + promises.push(this.fileProvider.readFile(libraryJsonPath, CoreFileProvider.FORMATJSON).then((data) => { + h5pData = data; + })); + + // Get library semantics if it exists. + promises.push(this.fileProvider.readFile(semanticsPath, CoreFileProvider.FORMATJSON).then((data) => { + semanticsData = data; + }).catch(() => { + // Probably doesn't exist, ignore. + })); + + // Get language data if it exists. + promises.push(this.fileProvider.getDirectoryContents(langPath).then((entries) => { + const subPromises = []; + langData = {}; + + entries.forEach((entry) => { + const langFilePath = this.textUtils.concatenatePaths(langPath, entry.name); + + subPromises.push(this.fileProvider.readFile(langFilePath, CoreFileProvider.FORMATJSON).then((data) => { + const parts = entry.name.split('.'); // The language code is in parts[0]. + langData[parts[0]] = data; + })); + }); + }).catch(() => { + // Probably doesn't exist, ignore. + })); + + // Check if it has icon. + promises.push(this.fileProvider.getFile(iconPath).then(() => { + hasIcon = true; + }).catch(() => { + hasIcon = false; + })); + + return Promise.all(promises).then(() => { + h5pData.semantics = semanticsData; + h5pData.language = langData; + h5pData.hasIcon = hasIcon; + + return h5pData; + }); + } + + /** + * Get a library data stored in DB. + * + * @param machineName Machine name. + * @param majorVersion Major version number. + * @param minorVersion Minor version number. + * @param siteId The site ID. If not defined, current site. + * @return Promise resolved with the library data, rejected if not found. + */ + protected getLibrary(machineName: string, majorVersion?: string | number, minorVersion?: string | number, siteId?: string) + : Promise { + + return this.sitesProvider.getSiteDb(siteId).then((db) => { + const conditions: any = { + machinename: machineName + }; + + if (typeof majorVersion != 'undefined') { + conditions.majorversion = majorVersion; + } + if (typeof minorVersion != 'undefined') { + conditions.minorversion = minorVersion; + } + + return db.getRecords(this.LIBRARIES_TABLE, conditions); + }).then((libraries): any => { + if (!libraries.length) { + return Promise.reject(null); + } + + return this.parseLibDBData(libraries[0]); + }); + } + + /** + * Get a library data stored in DB. + * + * @param libraryData Library data. + * @param siteId The site ID. If not defined, current site. + * @return Promise resolved with the library data, rejected if not found. + */ + protected getLibraryByData(libraryData: any, siteId?: string): Promise { + return this.getLibrary(libraryData.machineName, libraryData.majorVersion, libraryData.minorVersion, siteId); + } + + /** + * Get a library data stored in DB by ID. + * + * @param id Library ID. + * @param siteId The site ID. If not defined, current site. + * @return Promise resolved with the library data, rejected if not found. + */ + protected getLibraryById(id: number, siteId?: string): Promise { + return this.sitesProvider.getSiteDb(siteId).then((db) => { + return db.getRecord(this.LIBRARIES_TABLE, {id: id}).then((library) => { + return this.parseLibDBData(library); + }); + }); + } + + /** + * Get a library ID. If not found, return null. + * + * @param machineName Machine name. + * @param majorVersion Major version number. + * @param minorVersion Minor version number. + * @param siteId The site ID. If not defined, current site. + * @return Promise resolved with the library ID, null if not found. + */ + protected getLibraryId(machineName: string, majorVersion?: string | number, minorVersion?: string | number, siteId?: string) + : Promise { + + return this.getLibrary(machineName, majorVersion, minorVersion, siteId).then((library) => { + return (library && library.id) || null; + }).catch(() => { + return null; + }); + } + + /** + * Get a library ID. If not found, return null. + * + * @param libraryData Library data. + * @param siteId The site ID. If not defined, current site. + * @return Promise resolved with the library ID, null if not found. + */ + protected getLibraryIdByData(libraryData: any, siteId?: string): Promise { + return this.getLibraryId(libraryData.machineName, libraryData.majorVersion, libraryData.minorVersion, siteId); + } + + /** + * Get libraries folder path. + * + * @param siteId The site ID. + * @return Folder path. + */ + getLibrariesFolderPath(siteId: string): string { + return this.textUtils.concatenatePaths(this.getExternalH5PFolderPath(siteId), 'libraries'); + } + + /** + * Get a library's folder path. + * + * @param libraryData The library data. + * @param siteId The site ID. + * @param folderName Folder name. If not provided, it will be calculated. + * @return Folder path. + */ + getLibraryFolderPath(libraryData: any, siteId: string, folderName?: string): string { + if (!folderName) { + folderName = this.libraryToString(libraryData, true); + } + + return this.textUtils.concatenatePaths(this.getLibrariesFolderPath(siteId), folderName); + } + + /** + * Get the default behaviour for the display option defined. + * + * @param name Identifier for the setting. + * @param defaultValue Optional default value if settings is not set. + * @return Return the value for this display option. + */ + getOption(name: string, defaultValue: any = false): any { + // For now, all them are disabled by default, so only will be rendered when defined in the display options. + return CoreH5PDisplayOptionBehaviour.CONTROLLED_BY_AUTHOR_DEFAULT_OFF; + } + + /** + * Resizing script for settings. + * + * @return The HTML code with the resize script. + */ + protected getResizeCode(): string { + return ''; + } + + /** + * Get the URL to the resizer script. + * + * @return URL. + */ + getResizerScriptUrl(): string { + return this.textUtils.concatenatePaths(this.getCoreH5PPath(), 'js/h5p-resizer.js'); + } + + /** + * Get core JavaScript files. + * + * @return array The array containg urls of the core JavaScript files: + */ + getScripts(): string[] { + const libUrl = this.getCoreH5PPath(), + urls = []; + + CoreH5PProvider.SCRIPTS.forEach((script) => { + urls.push(libUrl + script); + }); + + return urls; + } + + /** + * Get a trusted H5P file. + * + * @param url The file URL. + * @param options Options. + * @param ignoreCache Whether to ignore cache. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the file data. + */ + getTrustedH5PFile(url: string, options?: CoreH5PGetTrustedFileOptions, ignoreCache?: boolean, siteId?: string) + : Promise { + + options = options || {}; + + return this.sitesProvider.getSite(siteId).then((site) => { + + const data = { + url: this.treatH5PUrl(url, site.getURL()), + frame: options.frame ? 1 : 0, + export: options.export ? 1 : 0, + embed: options.embed ? 1 : 0, + copyright: options.copyright ? 1 : 0, + }, + preSets: CoreSiteWSPreSets = { + cacheKey: this.getTrustedH5PFileCacheKey(url), + updateFrequency: CoreSite.FREQUENCY_RARELY + }; + + if (ignoreCache) { + preSets.getFromCache = false; + preSets.emergencyCache = false; + } + + return site.read('core_h5p_get_trusted_h5p_file', data, preSets).then((result: CoreH5PGetTrustedH5PFileResult): any => { + if (result.warnings && result.warnings.length) { + return Promise.reject(result.warnings[0]); + } + + if (result.files && result.files.length) { + return result.files[0]; + } + + return Promise.reject(null); + }); + }); + } + + /** + * Get cache key for trusted H5P file WS calls. + * + * @param url The file URL. + * @return Cache key. + */ + protected getTrustedH5PFileCacheKey(url: string): string { + return this.getTrustedH5PFilePrefixCacheKey() + url; + } + + /** + * Get prefixed cache key for trusted H5P file WS calls. + * + * @return Cache key. + */ + protected getTrustedH5PFilePrefixCacheKey(): string { + return this.ROOT_CACHE_KEY + 'trustedH5PFile:'; + } + + /** + * Check whether the user has permission to execute an action. + * + * @param permission Permission to check. + * @param id H5P package id. + * @return Whether the user has permission to execute an action. + */ + hasPermission(permission: number, id: number): boolean { + // H5P capabilities have not been introduced. + return null; + } + + /** + * Invalidates all trusted H5P file WS calls. + * + * @param siteId Site ID (empty for current site). + * @return Promise resolved when the data is invalidated. + */ + invalidateAllGetTrustedH5PFile(siteId?: string): Promise { + return this.sitesProvider.getSite(siteId).then((site) => { + return site.invalidateWsCacheForKeyStartingWith(this.getTrustedH5PFilePrefixCacheKey()); + }); + } + + /** + * Invalidates get trusted H5P file WS call. + * + * @param url The URL of the file. + * @param siteId Site ID (empty for current site). + * @return Promise resolved when the data is invalidated. + */ + invalidateAvailableInContexts(url: string, siteId?: string): Promise { + return this.sitesProvider.getSite(siteId).then((site) => { + return site.invalidateWsCacheForKey(this.getTrustedH5PFileCacheKey(url)); + }); + } + + /** + * Performs actions required when a library has been installed. + * + * @param libraryId ID of library that was installed. + * @param siteId Site ID. + * @return Promise resolved when done. + */ + protected libraryInstalled(libraryId: number, siteId: string): Promise { + const promises = []; + + // Remove all indexes of contents that use this library. + promises.push(this.deleteContentIndexesForLibrary(libraryId, siteId)); + + if (this.aggregateAssets) { + // Remove cached assets that use this library. + promises.push(this.deleteCachedAssets(libraryId, siteId)); + } + + return this.utils.allPromises(promises); + } + + /** + * Writes library data as string on the form {machineName} {majorVersion}.{minorVersion}. + * + * @param libraryData Library data. + * @param folderName Use hyphen instead of space in returned string. + * @return String on the form {machineName} {majorVersion}.{minorVersion}. + */ + protected libraryToString(libraryData: any, folderName?: boolean): string { + return (libraryData.machineName ? libraryData.machineName : libraryData.name) + (folderName ? '-' : ' ') + + libraryData.majorVersion + '.' + libraryData.minorVersion; + } + + /** + * Load addon libraries. + * + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the addon libraries. + */ + loadAddons(siteId?: string): Promise { + return this.sitesProvider.getSiteDb(siteId).then((db) => { + + const query = 'SELECT l1.id AS libraryId, l1.machinename AS machineName, ' + + 'l1.majorversion AS majorVersion, l1.minorversion AS minorVersion, ' + + 'l1.patchversion AS patchVersion, l1.addto AS addTo, ' + + 'l1.preloadedjs AS preloadedJs, l1.preloadedcss AS preloadedCss ' + + 'FROM ' + this.LIBRARIES_TABLE + ' l1 ' + + 'JOIN ' + this.LIBRARIES_TABLE + ' l2 ON l1.machinename = l2.machinename AND (' + + 'l1.majorversion < l2.majorversion OR (l1.majorversion = l2.majorversion AND ' + + 'l1.minorversion < l2.minorversion)) ' + + 'WHERE l1.addto IS NOT NULL AND l2.machinename IS NULL'; + + return db.execute(query).then((result) => { + const addons = []; + + for (let i = 0; i < result.rows.length; i++) { + addons.push(this.parseLibAddonData(result.rows.item(i))); + } + + return addons; + }); + }); + } + + /** + * Load content data from DB. + * + * @param id Content ID. + * @param fileUrl H5P file URL. Required if id is not provided. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the content data. + */ + protected loadContentData(id?: number, fileUrl?: string, siteId?: string): Promise { + siteId = siteId || this.sitesProvider.getCurrentSiteId(); + + let promise: Promise; + + if (id) { + promise = this.getContentData(id, siteId); + } else if (fileUrl) { + promise = this.getContentDataByUrl(fileUrl, siteId); + } else { + promise = Promise.reject(null); + } + + return promise.then((contentData) => { + + // Load the main library data. + return this.getLibraryById(contentData.mainlibraryid, siteId).then((libData) => { + + // Validate metadata. + const validator = new CoreH5PContentValidator(this, this.h5pUtils, this.textUtils, this.utils, this.translate, + siteId); + + // Validate empty metadata, like Moodle web does. + return validator.validateMetadata({}).then((metadata) => { + // Map the values to the names used by the H5P core (it's the same Moodle web does). + return { + id: contentData.id, + params: contentData.jsoncontent, + embedType: 'iframe', // Always use iframe. + disable: null, + folderName: contentData.foldername, + title: libData.title, + slug: this.h5pUtils.slugify(libData.title) + '-' + contentData.id, + filtered: contentData.filtered, + libraryMajorVersion: libData.majorversion, + libraryMinorVersion: libData.minorversion, + metadata: metadata, + library: { + id: libData.id, + name: libData.machinename, + majorVersion: libData.majorversion, + minorVersion: libData.minorversion, + embedTypes: libData.embedtypes, + fullscreen: libData.fullscreen + } + }; + }); + }); + }); + } + + /** + * Load dependencies for the given content of the given type. + * + * @param id Content ID. + * @param type The dependency type. + * @return Content dependencies, indexed by machine name. + */ + loadContentDependencies(id: number, type?: string, siteId?: string) + : Promise<{[machineName: string]: CoreH5PContentDependencyData}> { + + return this.sitesProvider.getSiteDb(siteId).then((db) => { + let query = 'SELECT hl.id AS libraryId, hl.machinename AS machineName, ' + + 'hl.majorversion AS majorVersion, hl.minorversion AS minorVersion, ' + + 'hl.patchversion AS patchVersion, hl.preloadedcss AS preloadedCss, ' + + 'hl.preloadedjs AS preloadedJs, hcl.dropcss AS dropCss, ' + + 'hcl.dependencytype as dependencyType ' + + 'FROM ' + this.CONTENTS_LIBRARIES_TABLE + ' hcl ' + + 'JOIN ' + this.LIBRARIES_TABLE + ' hl ON hcl.libraryid = hl.id ' + + 'WHERE hcl.h5pid = ?'; + const queryArgs = []; + queryArgs.push(id); + + if (type) { + query += ' AND hcl.dependencytype = ?'; + queryArgs.push(type); + } + + query += ' ORDER BY hcl.weight'; + + return db.execute(query, queryArgs).then((result) => { + const dependencies = {}; + + for (let i = 0; i < result.rows.length; i++) { + const dependency = result.rows.item(i); + + dependencies[dependency.machineName] = dependency; + } + + return dependencies; + }); + }); + } + + /** + * Loads a library and its dependencies. + * + * @param machineName The library's machine name. + * @param majorVersion The library's major version. + * @param minorVersion The library's minor version. + * @param siteId The site ID. If not defined, current site. + * @return Promise resolved with the library data. + */ + loadLibrary(machineName: string, majorVersion: number, minorVersion: number, siteId?: string): Promise { + + // First get the library data from DB. + return this.getLibrary(machineName, majorVersion, minorVersion, siteId).then((library) => { + const libraryData: CoreH5PLibraryData = { + libraryId: library.id, + title: library.title, + machineName: library.machinename, + majorVersion: library.majorversion, + minorVersion: library.minorversion, + patchVersion: library.patchversion, + runnable: library.runnable, + fullscreen: library.fullscreen, + embedTypes: library.embedtypes, + preloadedJs: library.preloadedjs, + preloadedCss: library.preloadedcss, + dropLibraryCss: library.droplibrarycss, + semantics: library.semantics, + preloadedDependencies: [], + dynamicDependencies: [], + editorDependencies: [] + }; + + // Now get the dependencies. + const sql = 'SELECT hl.id, hl.machinename, hl.majorversion, hl.minorversion, hll.dependencytype ' + + 'FROM ' + this.LIBRARY_DEPENDENCIES_TABLE + ' hll ' + + 'JOIN ' + this.LIBRARIES_TABLE + ' hl ON hll.requiredlibraryid = hl.id ' + + 'WHERE hll.libraryid = ? ' + + 'ORDER BY hl.id ASC'; + + const sqlParams = [ + library.id + ]; + + return this.sitesProvider.getSiteDb(siteId).then((db) => { + return db.execute(sql, sqlParams).then((result) => { + + for (let i = 0; i < result.rows.length; i++) { + const dependency = result.rows.item(i), + key = dependency.dependencytype + 'Dependencies'; + + libraryData[key].push({ + machineName: dependency.machinename, + majorVersion: dependency.majorversion, + minorVersion: dependency.minorversion + }); + } + + return libraryData; + }); + }); + }); + } + + /** + * Parse library addon data. + * + * @param library Library addon data. + * @return Parsed library. + */ + parseLibAddonData(library: any): CoreH5PLibraryAddonData { + library.addto = this.textUtils.parseJSON(library.addto, null); + + return library; + } + + /** + * Parse library DB data. + * + * @param library Library DB data. + * @return Parsed library. + */ + parseLibDBData(library: any): CoreH5PLibraryDBData { + library.semantics = this.textUtils.parseJSON(library.semantics, null); + library.addto = this.textUtils.parseJSON(library.addto, null); + + return library; + } + + /** + * Process libraries from an H5P library, getting the required data to save them. + * This code was copied from the isValidPackage function in Moodle's H5PValidator. + * This function won't validate most things because it should've been done by the server already. + * + * @param fileUrl The file URL used to download the file. + * @param file The file entry of the downloaded file. + * @return Promise resolved when done. + */ + protected processH5PFiles(destFolder: string, entries: (DirectoryEntry | FileEntry)[]) + : Promise<{librariesJsonData: any, mainJsonData: any, contentJsonData: any}> { + + const promises = [], + libraries: any = {}; + let contentJsonData, + mainH5PData; + + // Read the h5p.json file. + const h5pJsonPath = this.textUtils.concatenatePaths(destFolder, 'h5p.json'); + promises.push(this.fileProvider.readFile(h5pJsonPath, CoreFileProvider.FORMATJSON).then((data) => { + mainH5PData = data; + })); + + // Read the content.json file. + const contentJsonPath = this.textUtils.concatenatePaths(destFolder, 'content/content.json'); + promises.push(this.fileProvider.readFile(contentJsonPath, CoreFileProvider.FORMATJSON).then((data) => { + contentJsonData = data; + })); + + // Treat libraries. + entries.forEach((entry) => { + if (entry.name[0] == '.' || entry.name[0] == '_' || entry.name == 'content' || entry.isFile) { + // Skip files, the content folder and any folder starting with a . or _. + return; + } + + const libDirPath = this.textUtils.concatenatePaths(destFolder, entry.name); + + promises.push(this.getLibraryData( entry, libDirPath, destFolder).then((libraryH5PData) => { + libraryH5PData.uploadDirectory = libDirPath; + libraries[this.libraryToString(libraryH5PData)] = libraryH5PData; + })); + }); + + return Promise.all(promises).then(() => { + return { + librariesJsonData: libraries, + mainJsonData: mainH5PData, + contentJsonData: contentJsonData + }; + }); + } + + /** + * Stores hash keys for cached assets, aggregated JavaScripts and stylesheets, and connects it to libraries so that we + * know which cache file to delete when a library is updated. + * + * @param key Hash key for the given libraries. + * @param libraries List of dependencies used to create the key. + * @param folderName The name of the folder that contains the H5P. + * @param siteId The site ID. + * @return Promise resolved when done. + */ + protected saveCachedAssets(hash: string, dependencies: {[machineName: string]: CoreH5PContentDependencyData}, + folderName: string, siteId?: string): Promise { + + return this.sitesProvider.getSiteDb(siteId).then((db) => { + const promises = []; + + for (const key in dependencies) { + const data = { + hash: key, + libraryid: dependencies[key].libraryId, + foldername: folderName + }; + + promises.push(db.insertRecord(this.LIBRARIES_CACHEDASSETS_TABLE, data)); + } + + return Promise.all(promises); + }); + } + + /** + * Save content data in DB and clear cache. + * + * @param content Content to save. + * @param folderName The name of the folder that contains the H5P. + * @param fileUrl The online URL of the package. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with content ID. + */ + protected saveContentData(content: any, folderName: string, fileUrl: string, siteId?: string): Promise { + // Save in DB. + return this.sitesProvider.getSiteDb(siteId).then((db) => { + + const data: any = { + jsoncontent: content.params, + mainlibraryid: content.library.libraryId, + timemodified: Date.now(), + filtered: null, + foldername: folderName, + fileurl: fileUrl + }; + + if (typeof content.id != 'undefined') { + data.id = content.id; + } else { + data.timecreated = data.timemodified; + } + + return db.insertRecord(this.CONTENT_TABLE, data).then(() => { + if (!data.id) { + // New content. Get its ID. + return db.getRecord(this.CONTENT_TABLE, data).then((entry) => { + content.id = entry.id; + }); + } + }); + }).then(() => { + // If resetContentUserData is implemented in the future, it should be called in here. + return content.id; + }); + } + + /** + * Save the content in filesystem. + * + * @param contentPath Path to the current content folder (tmp). + * @param folderName Name to put to the content folder. + * @param siteId Site ID. + * @return Promise resolved when done. + */ + protected saveContentInFS(contentPath: string, folderName: string, siteId: string): Promise { + const folderPath = this.getContentFolderPath(folderName, siteId); + + // Delete existing content for this package. + return this.fileProvider.removeDir(folderPath).catch(() => { + // Ignore errors, maybe it doesn't exist. + }).then(() => { + // Copy the new one. + return this.fileProvider.moveDir(contentPath, folderPath); + }); + } + + /** + * Save libraries. This code is based on the saveLibraries function from Moodle's H5PStorage. + * + * @param librariesJsonData Data about libraries. + * @param folderName Name of the folder of the H5P package. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when done. + */ + protected saveLibraries(librariesJsonData: any, folderName: string, siteId?: string): Promise { + siteId = siteId || this.sitesProvider.getCurrentSiteId(); + + const libraryIds = []; + + // First of all, try to create the dir where the libraries are stored. This way we don't have to do it for each lib. + return this.fileProvider.createDir(this.getLibrariesFolderPath(siteId)).then(() => { + const promises = []; + + // Go through libraries that came with this package. + for (const libString in librariesJsonData) { + const libraryData = librariesJsonData[libString]; + + // Find local library identifier. + promises.push(this.getLibraryByData(libraryData).catch(() => { + // Not found. + }).then((dbData) => { + if (dbData) { + // Library already installed. + libraryData.libraryId = dbData.id; + + if (libraryData.patchVersion <= dbData.patchversion) { + // Same or older version, no need to save. + libraryData.saveDependencies = false; + + return; + } + } + + libraryData.saveDependencies = true; + + // Convert metadataSettings values to boolean and json_encode it before saving. + libraryData.metadataSettings = libraryData.metadataSettings ? + this.h5pUtils.boolifyAndEncodeMetadataSettings(libraryData.metadataSettings) : null; + + // Save the library data in DB. + return this.saveLibraryData(libraryData, siteId).then(() => { + // Now save it in FS. + return this.saveLibraryInFS(libraryData, siteId).catch((error) => { + // An error occurred, delete the DB data because the lib FS data has been deleted. + return this.deleteLibraryData(libraryData.libraryId, siteId).catch(() => { + // Ignore errors. + }).then(() => { + return Promise.reject(error); + }); + }); + }).then(() => { + if (typeof libraryData.libraryId != 'undefined') { + return this.libraryInstalled(libraryData.libraryId, siteId); + } + }); + })); + } + + return Promise.all(promises); + }).then(() => { + // Go through the libraries again to save dependencies. + const promises = []; + + for (const libString in librariesJsonData) { + const libraryData = librariesJsonData[libString]; + if (!libraryData.saveDependencies) { + continue; + } + + libraryIds.push(libraryData.libraryId); + + // Remove any old dependencies. + promises.push(this.deleteLibraryDependencies(libraryData.libraryId).then(() => { + // Insert the different new ones. + const subPromises = []; + + if (typeof libraryData.preloadedDependencies != 'undefined') { + subPromises.push(this.saveLibraryDependencies(libraryData.libraryId, libraryData.preloadedDependencies, + 'preloaded')); + } + if (typeof libraryData.dynamicDependencies != 'undefined') { + subPromises.push(this.saveLibraryDependencies(libraryData.libraryId, libraryData.dynamicDependencies, + 'dynamic')); + } + if (typeof libraryData.editorDependencies != 'undefined') { + subPromises.push(this.saveLibraryDependencies(libraryData.libraryId, libraryData.editorDependencies, + 'editor')); + } + + return Promise.all(subPromises); + })); + } + + return Promise.all(promises); + }).then(() => { + // Make sure dependencies, parameter filtering and export files get regenerated for content who uses these libraries. + if (libraryIds.length) { + return this.clearFilteredParameters(libraryIds, siteId); + } + }); + } + + /** + * Save a library in filesystem. + * + * @param libraryData Library data. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when done. + */ + protected saveLibraryInFS(libraryData: any, siteId?: string): Promise { + const folderPath = this.getLibraryFolderPath(libraryData, siteId); + + // Delete existing library version. + return this.fileProvider.removeDir(folderPath).catch(() => { + // Ignore errors, maybe it doesn't exist. + }).then(() => { + // Copy the new one. + return this.fileProvider.moveDir(libraryData.uploadDirectory, folderPath, true); + }); + } + + /** + * Save library data in DB. + * + * @param libraryData Library data to save. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when done. + */ + protected saveLibraryData(libraryData: any, siteId?: string): Promise { + // Some special properties needs some checking and converting before they can be saved. + const preloadedJS = this.h5pUtils.libraryParameterValuesToCsv(libraryData, 'preloadedJs', 'path'), + preloadedCSS = this.h5pUtils.libraryParameterValuesToCsv(libraryData, 'preloadedCss', 'path'), + dropLibraryCSS = this.h5pUtils.libraryParameterValuesToCsv(libraryData, 'dropLibraryCss', 'machineName'); + + if (typeof libraryData.semantics == 'undefined') { + libraryData.semantics = ''; + } + if (typeof libraryData.fullscreen == 'undefined') { + libraryData.fullscreen = 0; + } + + let embedTypes = ''; + if (typeof libraryData.embedTypes != 'undefined') { + embedTypes = libraryData.embedTypes.join(', '); + } + + return this.sitesProvider.getSite(siteId).then((site) => { + const db = site.getDb(), + data: any = { + title: libraryData.title, + machinename: libraryData.machineName, + majorversion: libraryData.majorVersion, + minorversion: libraryData.minorVersion, + patchversion: libraryData.patchVersion, + runnable: libraryData.runnable, + fullscreen: libraryData.fullscreen, + embedtypes: embedTypes, + preloadedjs: preloadedJS, + preloadedcss: preloadedCSS, + droplibrarycss: dropLibraryCSS, + semantics: typeof libraryData.semantics != 'undefined' ? JSON.stringify(libraryData.semantics) : null, + addto: typeof libraryData.addTo != 'undefined' ? JSON.stringify(libraryData.addTo) : null, + }; + + if (libraryData.libraryId) { + data.id = libraryData.libraryId; + } + + return db.insertRecord(this.LIBRARIES_TABLE, data).then(() => { + if (!data.id) { + // New library. Get its ID. + return db.getRecord(this.LIBRARIES_TABLE, data).then((entry) => { + libraryData.libraryId = entry.id; + }); + } else { + // Updated libary. Remove old dependencies. + return this.deleteLibraryDependencies(data.id, site.getId()); + } + }); + }); + } + + /** + * Save what libraries a library is depending on. + * + * @param libraryId Library Id for the library we're saving dependencies for. + * @param dependencies List of dependencies as associative arrays containing machineName, majorVersion, minorVersion. + * @param dependencytype The type of dependency. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when done. + */ + protected saveLibraryDependencies(libraryId: number, dependencies: any[], dependencyType: string, siteId?: string) + : Promise { + + return this.sitesProvider.getSiteDb(siteId).then((db) => { + + const promises = []; + + dependencies.forEach((dependency) => { + // Get the ID of the library. + promises.push(this.getLibraryIdByData(dependency, siteId).then((dependencyId) => { + // Create the relation. + const entry = { + libraryid: libraryId, + requiredlibraryid: dependencyId, + dependencytype: dependencyType + }; + + return db.insertRecord(this.LIBRARY_DEPENDENCIES_TABLE, entry); + })); + }); + + return Promise.all(promises); + }); + } + + /** + * Saves what libraries the content uses. + * + * @param id Id identifying the package. + * @param librariesInUse List of libraries the content uses. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when done. + */ + saveLibraryUsage(id: number, librariesInUse: {[key: string]: CoreH5PContentDepsTreeDependency}, siteId?: string) + : Promise { + + return this.sitesProvider.getSiteDb(siteId).then((db) => { + // Calculate the CSS to drop. + const dropLibraryCssList = {}, + promises = []; + + for (const key in librariesInUse) { + const dependency = librariesInUse[key]; + + if (( dependency.library).dropLibraryCss) { + const split = ( dependency.library).dropLibraryCss.split(', '); + + split.forEach((css) => { + dropLibraryCssList[css] = css; + }); + } + } + + for (const key in librariesInUse) { + const dependency = librariesInUse[key], + data = { + h5pid: id, + libraryId: dependency.library.libraryId, + dependencytype: dependency.type, + dropcss: dropLibraryCssList[dependency.library.machineName] ? 1 : 0, + weight: dependency.weight + }; + + promises.push(db.insertRecord(this.CONTENTS_LIBRARIES_TABLE, data)); + } + + return Promise.all(promises); + }); + + } + + /** + * Helper function used to figure out embed and download behaviour. + * + * @param optionName The option name. + * @param permission The permission. + * @param id The package ID. + * @param value Default value. + * @return The value to use. + */ + setDisplayOptionOverrides(optionName: string, permission: number, id: number, value: boolean): boolean { + const behaviour = this.getOption(optionName, CoreH5PDisplayOptionBehaviour.ALWAYS_SHOW); + + // If never show globally, force hide + if (behaviour == CoreH5PDisplayOptionBehaviour.NEVER_SHOW) { + value = false; + } else if (behaviour == CoreH5PDisplayOptionBehaviour.ALWAYS_SHOW) { + // If always show or permissions say so, force show + value = true; + } else if (behaviour == CoreH5PDisplayOptionBehaviour.CONTROLLED_BY_PERMISSIONS) { + value = this.hasPermission(permission, id); + } + + return value; + } + + /** + * Treat an H5P url before sending it to WS. + * + * @param url H5P file URL. + * @param siteUrl Site URL. + * @return Treated url. + */ + protected treatH5PUrl(url: string, siteUrl: string): string { + if (url.indexOf(this.textUtils.concatenatePaths(siteUrl, '/webservice/pluginfile.php')) === 0) { + url = url.replace('/webservice/pluginfile', '/pluginfile'); + } + + return url; + } + + /** + * This will update selected fields on the given content. + * + * @param id Content identifier. + * @param fields Object with the fields to update. + * @param siteId Site ID. If not defined, current site. + */ + protected updateContentFields(id: number, fields: any, siteId?: string): Promise { + + return this.sitesProvider.getSiteDb(siteId).then((db) => { + const data = Object.assign(fields); + delete data.slug; // Slug isn't stored in DB. + + return db.updateRecords(this.CONTENT_TABLE, data, {id: id}); + }); + } +} + +/** + * Display options behaviour constants. + */ +export class CoreH5PDisplayOptionBehaviour { + static NEVER_SHOW = 0; + static CONTROLLED_BY_AUTHOR_DEFAULT_ON = 1; + static CONTROLLED_BY_AUTHOR_DEFAULT_OFF = 2; + static ALWAYS_SHOW = 3; + static CONTROLLED_BY_PERMISSIONS = 4; +} + +/** + * Permission constants. + */ +export class CoreH5PPermission { + static DOWNLOAD_H5P = 0; + static EMBED_H5P = 1; + static CREATE_RESTRICTED = 2; + static UPDATE_LIBRARIES = 3; + static INSTALL_RECOMMENDED = 4; + static COPY_H5P = 4; +} + +/** + * Display options as object. + */ +export type CoreH5PDisplayOptions = { + frame?: boolean; + export?: boolean; + embed?: boolean; + copyright?: boolean; + icon?: boolean; + copy?: boolean; +}; + +/** + * Options for core_h5p_get_trusted_h5p_file. + */ +export type CoreH5PGetTrustedFileOptions = { + frame?: boolean; // Whether to show the bar options below the content. + export?: boolean; // Whether to allow to download the package. + embed?: boolean; // Whether to allow to copy the code to your site. + copyright?: boolean; // The copyright option. +}; + +/** + * Result of core_h5p_get_trusted_h5p_file. + */ +export type CoreH5PGetTrustedH5PFileResult = { + files: CoreWSExternalFile[]; // Files. + warnings: CoreWSExternalWarning[]; // List of warnings. +}; + +/** + * Dependency asset. + */ +export type CoreH5PDependencyAsset = { + path: string; // Path to the asset. + version: string; // Dependency version. +}; + +/** + * Content data stored in DB. + */ +export type CoreH5PContentDBData = { + id: number; // The id of the content. + jsoncontent: string; // The content in json format. + mainlibraryid: number; // The library we first instantiate for this node. + foldername: string; // Name of the folder that contains the contents. + fileurl: string; // The online URL of the H5P package. + filtered: string; // Filtered version of json_content. + timecreated: number; // Time created. + timemodified: number; // Time modified. +}; + +/** + * Content data, including main library data. + */ +export type CoreH5PContentData = { + id: number; // The id of the content. + params: string; // The content in json format. + embedType: string; // Embed type to use. + disable: number; // H5P Button display options. + folderName: string; // Name of the folder that contains the contents. + title: string; // Main library's title. + slug: string; // Lib title and ID slugified. + filtered: string; // Filtered version of json_content. + libraryMajorVersion: number; // Main library's major version. + libraryMinorVersion: number; // Main library's minor version. + metadata: any; // Content metadata. + library: { // Main library data. + id: number; // The id of the library. + name: string; // The library machine name. + majorVersion: number; // Major version. + minorVersion: number; // Minor version. + embedTypes: string; // List of supported embed types. + fullscreen: number; // Display fullscreen button. + }; + dependencies?: {[key: string]: CoreH5PContentDepsTreeDependency}; // Dependencies. Calculated in filterParameters. +}; + +/** + * Content dependency data. + */ +export type CoreH5PContentDependencyData = { + libraryId: number; // The id of the library if it is an existing library. + machineName: string; // The library machineName. + majorVersion: number; // The The library's majorVersion. + minorVersion: number; // The The library's minorVersion. + patchVersion: number; // The The library's patchVersion. + preloadedJs?: string | string[]; // Comma separated string with js file paths. If already parsed, list of paths. + preloadedCss?: string | string[]; // Comma separated string with css file paths. If already parsed, list of paths. + dropCss?: string; // CSV of machine names. + dependencyType: string; // The dependency type. + path?: string; // Path to the dependency. Calculated in getDependenciesFiles. + version?: string; // Version of the dependency. Calculated in getDependenciesFiles. +}; + +/** + * Data for each content dependency in the dependency tree. + */ +export type CoreH5PContentDepsTreeDependency = { + library: CoreH5PLibraryData | CoreH5PLibraryAddonData; // Library data. + type: string; // Dependency type. + weight?: number; // An integer determining the order of the libraries when they are loaded. +}; + +/** + * Library data. + */ +export type CoreH5PLibraryData = { + libraryId: number; // The id of the library. + title: string; // The human readable name of this library. + machineName: string; // The library machine name. + majorVersion: number; // Major version. + minorVersion: number; // Minor version. + patchVersion: number; // Patch version. + runnable: number; // Can this library be started by the module? I.e. not a dependency. + fullscreen: number; // Display fullscreen button. + embedTypes: string; // List of supported embed types. + preloadedJs?: string; // Comma separated list of scripts to load. + preloadedCss?: string; // Comma separated list of stylesheets to load. + dropLibraryCss?: string; // List of libraries that should not have CSS included if this library is used. Comma separated list. + semantics?: any; // The semantics definition. If it's a string, it's in json format. + preloadedDependencies: CoreH5PLibraryBasicData[]; // Dependencies. + dynamicDependencies: CoreH5PLibraryBasicData[]; // Dependencies. + editorDependencies: CoreH5PLibraryBasicData[]; // Dependencies. +}; + +/** + * Library basic data. + */ +export type CoreH5PLibraryBasicData = { + machineName: string; // The library machine name. + majorVersion: number; // Major version. + minorVersion: number; // Minor version. +}; + +/** + * "Addon" data (library). + */ +export type CoreH5PLibraryAddonData = { + libraryId: number; // The id of the library. + machineName: string; // The library machine name. + majorVersion: number; // Major version. + minorVersion: number; // Minor version. + patchVersion: number; // Patch version. + preloadedJs?: string; // Comma separated list of scripts to load. + preloadedCss?: string; // Comma separated list of stylesheets to load. + addTo?: any; // Plugin configuration data. +}; + +/** + * Library data stored in DB. + */ +export type CoreH5PLibraryDBData = { + id: number; // The id of the library. + machinename: string; // The library machine name. + title: string; // The human readable name of this library. + majorversion: number; // Major version. + minorversion: number; // Minor version. + patchversion: number; // Patch version. + runnable: number; // Can this library be started by the module? I.e. not a dependency. + fullscreen: number; // Display fullscreen button. + embedtypes: string; // List of supported embed types. + preloadedjs?: string; // Comma separated list of scripts to load. + preloadedcss?: string; // Comma separated list of stylesheets to load. + droplibrarycss?: string; // List of libraries that should not have CSS included if this library is used. Comma separated list. + semantics?: any; // The semantics definition. + addto?: any; // Plugin configuration data. +}; + +/** + * Library dependencies stored in DB. + */ +export type CoreH5PLibraryDependenciesDBData = { + id: number; // Id. + libraryid: number; // The id of an H5P library. + requiredlibraryid: number; // The dependent library to load. + dependencytype: string; // Type: preloaded, dynamic, or editor. +}; diff --git a/src/core/h5p/providers/pluginfile-handler.ts b/src/core/h5p/providers/pluginfile-handler.ts new file mode 100644 index 000000000..3a38e6381 --- /dev/null +++ b/src/core/h5p/providers/pluginfile-handler.ts @@ -0,0 +1,136 @@ +// (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 { Injectable } from '@angular/core'; +import { CoreFileProvider } from '@providers/file'; +import { CorePluginFileHandler } from '@providers/plugin-file-delegate'; +import { CoreMimetypeUtilsProvider } from '@providers/utils/mimetype'; +import { CoreTextUtilsProvider } from '@providers/utils/text'; +import { CoreUrlUtilsProvider } from '@providers/utils/url'; +import { CoreUtilsProvider } from '@providers/utils/utils'; +import { CoreH5PProvider } from './h5p'; +import { CoreWSExternalFile } from '@providers/ws'; +import { FileEntry } from '@ionic-native/file'; + +/** + * Handler to treat H5P files. + */ +@Injectable() +export class CoreH5PPluginFileHandler implements CorePluginFileHandler { + name = 'CoreH5PPluginFileHandler'; + + constructor(protected urlUtils: CoreUrlUtilsProvider, + protected mimeUtils: CoreMimetypeUtilsProvider, + protected textUtils: CoreTextUtilsProvider, + protected utils: CoreUtilsProvider, + protected fileProvider: CoreFileProvider, + protected h5pProvider: CoreH5PProvider) { } + + /** + * React to a file being deleted. + * + * @param fileUrl The file URL used to download the file. + * @param path The path of the deleted file. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when done. + */ + fileDeleted(fileUrl: string, path: string, siteId?: string): Promise { + // If an h5p file is deleted, remove the contents folder. + return this.h5pProvider.deleteContentByUrl(fileUrl, siteId); + } + + /** + * Check whether a file can be downloaded. If so, return the file to download. + * + * @param file The file data. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the file to use. Rejected if cannot download. + */ + getDownloadableFile(file: CoreWSExternalFile, siteId?: string): Promise { + return this.h5pProvider.getTrustedH5PFile(file.fileurl, {}, false, siteId); + } + + /** + * Given an HTML element, get the URLs of the files that should be downloaded and weren't treated by + * CoreFilepoolProvider.extractDownloadableFilesFromHtml. + * + * @param container Container where to get the URLs from. + * @return List of URLs. + */ + getDownloadableFilesFromHTML(container: HTMLElement): string[] { + const iframes = Array.from(container.querySelectorAll('iframe.h5p-iframe')); + const urls = []; + + for (let i = 0; i < iframes.length; i++) { + const params = this.urlUtils.extractUrlParams(iframes[i].src); + + if (params.url) { + urls.push(params.url); + } + } + + return urls; + } + + /** + * Get a file size. + * + * @param file The file data. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the size. + */ + getFileSize(file: CoreWSExternalFile, siteId?: string): Promise { + return this.h5pProvider.getTrustedH5PFile(file.fileurl, {}, false, siteId).then((file) => { + return file.filesize; + }).catch((error): any => { + if (this.utils.isWebServiceError(error)) { + // WS returned an error, it means it cannot be downloaded. + return 0; + } + + return Promise.reject(error); + }); + } + + /** + * Whether or not the handler is enabled on a site level. + * + * @return Whether or not the handler is enabled on a site level. + */ + isEnabled(): boolean | Promise { + return this.h5pProvider.canGetTrustedH5PFileInSite(); + } + + /** + * Check whether the file should be treated by this handler. It is used in functions where the component isn't used. + * + * @param file The file data. + * @return Whether the file should be treated by this handler. + */ + shouldHandleFile(file: CoreWSExternalFile): boolean { + return this.mimeUtils.guessExtensionFromUrl(file.fileurl) == 'h5p'; + } + + /** + * Treat a downloaded file. + * + * @param fileUrl The file URL used to download the file. + * @param file The file entry of the downloaded file. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when done. + */ + treatDownloadedFile(fileUrl: string, file: FileEntry, siteId?: string): Promise { + return this.h5pProvider.extractH5PFile(fileUrl, file, siteId); + } +} diff --git a/src/core/h5p/providers/utils.ts b/src/core/h5p/providers/utils.ts new file mode 100644 index 000000000..026b2b9b7 --- /dev/null +++ b/src/core/h5p/providers/utils.ts @@ -0,0 +1,429 @@ +// (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 { Injectable } from '@angular/core'; +import { TranslateService } from '@ngx-translate/core'; +import { CoreTextUtilsProvider } from '@providers/utils/text'; +import { CoreH5PContentDependencyData, CoreH5PDependencyAsset } from './h5p'; +import { Md5 } from 'ts-md5/dist/md5'; + +/** + * Utils service with helper functions for H5P. + */ +@Injectable() +export class CoreH5PUtilsProvider { + + // Map to slugify characters. + protected SLUGIFY_MAP = { + æ: 'ae', + ø: 'oe', + ö: 'o', + ó: 'o', + ô: 'o', + Ò: 'oe', + Õ: 'o', + Ý: 'o', + ý: 'y', + ÿ: 'y', + ā: 'y', + ă: 'a', + ą: 'a', + œ: 'a', + å: 'a', + ä: 'a', + á: 'a', + à: 'a', + â: 'a', + ã: 'a', + ç: 'c', + ć: 'c', + ĉ: 'c', + ċ: 'c', + č: 'c', + é: 'e', + è: 'e', + ê: 'e', + ë: 'e', + í: 'i', + ì: 'i', + î: 'i', + ï: 'i', + ú: 'u', + ñ: 'n', + ü: 'u', + ù: 'u', + û: 'u', + ß: 'es', + ď: 'd', + đ: 'd', + ē: 'e', + ĕ: 'e', + ė: 'e', + ę: 'e', + ě: 'e', + ĝ: 'g', + ğ: 'g', + ġ: 'g', + ģ: 'g', + ĥ: 'h', + ħ: 'h', + ĩ: 'i', + ī: 'i', + ĭ: 'i', + į: 'i', + ı: 'i', + ij: 'ij', + ĵ: 'j', + ķ: 'k', + ĺ: 'l', + ļ: 'l', + ľ: 'l', + ŀ: 'l', + ł: 'l', + ń: 'n', + ņ: 'n', + ň: 'n', + ʼn: 'n', + ō: 'o', + ŏ: 'o', + ő: 'o', + ŕ: 'r', + ŗ: 'r', + ř: 'r', + ś: 's', + ŝ: 's', + ş: 's', + š: 's', + ţ: 't', + ť: 't', + ŧ: 't', + ũ: 'u', + ū: 'u', + ŭ: 'u', + ů: 'u', + ű: 'u', + ų: 'u', + ŵ: 'w', + ŷ: 'y', + ź: 'z', + ż: 'z', + ž: 'z', + ſ: 's', + ƒ: 'f', + ơ: 'o', + ư: 'u', + ǎ: 'a', + ǐ: 'i', + ǒ: 'o', + ǔ: 'u', + ǖ: 'u', + ǘ: 'u', + ǚ: 'u', + ǜ: 'u', + ǻ: 'a', + ǽ: 'ae', + ǿ: 'oe' + }; + + constructor(private translate: TranslateService, + private textUtils: CoreTextUtilsProvider) { } + + /** + * The metadataSettings field in libraryJson uses 1 for true and 0 for false. + * Here we are converting these to booleans, and also doing JSON encoding. + * + * @param metadataSettings Settings. + * @return Stringified settings. + */ + boolifyAndEncodeMetadataSettings(metadataSettings: any): string { + // Convert metadataSettings values to boolean. + if (typeof metadataSettings.disable != 'undefined') { + metadataSettings.disable = metadataSettings.disable === 1; + } + if (typeof metadataSettings.disableExtraTitleField != 'undefined') { + metadataSettings.disableExtraTitleField = metadataSettings.disableExtraTitleField === 1; + } + + return JSON.stringify(metadataSettings); + } + + /** + * Determine the correct embed type to use. + * + * @param Embed type of the content. + * @param Embed type of the main library. + * @return Either 'div' or 'iframe'. + */ + determineEmbedType(contentEmbedType: string, libraryEmbedTypes: string): string { + // Detect content embed type. + let embedType = contentEmbedType.toLowerCase().indexOf('div') != -1 ? 'div' : 'iframe'; + + if (libraryEmbedTypes) { + // Check that embed type is available for library + const embedTypes = libraryEmbedTypes.toLowerCase(); + + if (embedTypes.indexOf(embedType) == -1) { + // Not available, pick default. + embedType = embedTypes.indexOf('div') != -1 ? 'div' : 'iframe'; + } + } + + return embedType; + } + + /** + * Combines path with version. + * + * @param assets List of assets to get their URLs. + * @param assetsFolderPath The path of the folder where the assets are. + * @return List of urls. + */ + getAssetsUrls(assets: CoreH5PDependencyAsset[], assetsFolderPath: string = ''): string[] { + const urls = []; + + assets.forEach((asset) => { + let url = asset.path; + + // Add URL prefix if not external. + if (asset.path.indexOf('://') == -1 && assetsFolderPath) { + url = this.textUtils.concatenatePaths(assetsFolderPath, url); + } + + // Add version if set. + if (asset.version) { + url += asset.version; + } + + urls.push(url); + }); + + return urls; + } + + /** + * Get the hash of a list of dependencies. + * + * @param dependencies Dependencies. + * @return Hash. + */ + getDependenciesHash(dependencies: {[machineName: string]: CoreH5PContentDependencyData}): string { + // Build hash of dependencies. + const toHash = []; + + // Use unique identifier for each library version. + for (const name in dependencies) { + const dep = dependencies[name]; + toHash.push(dep.machineName + '-' + dep.majorVersion + '.' + dep.minorVersion + '.' + dep.patchVersion); + } + + // Sort in case the same dependencies comes in a different order. + toHash.sort((a, b) => { + return a.localeCompare(b); + }); + + // Calculate hash. + return Md5.hashAsciiStr(toHash.join('')); + } + + /** + * Provide localization for the Core JS. + * + * @return Object with the translations. + */ + getLocalization(): any { + return { + fullscreen: this.translate.instant('core.h5p.fullscreen'), + disableFullscreen: this.translate.instant('core.h5p.disablefullscreen'), + download: this.translate.instant('core.h5p.download'), + copyrights: this.translate.instant('core.h5p.copyright'), + embed: this.translate.instant('core.h5p.embed'), + size: this.translate.instant('core.h5p.size'), + showAdvanced: this.translate.instant('core.h5p.showadvanced'), + hideAdvanced: this.translate.instant('core.h5p.hideadvanced'), + advancedHelp: this.translate.instant('core.h5p.resizescript'), + copyrightInformation: this.translate.instant('core.h5p.copyright'), + close: this.translate.instant('core.h5p.close'), + title: this.translate.instant('core.h5p.title'), + author: this.translate.instant('core.h5p.author'), + year: this.translate.instant('core.h5p.year'), + source: this.translate.instant('core.h5p.source'), + license: this.translate.instant('core.h5p.license'), + thumbnail: this.translate.instant('core.h5p.thumbnail'), + noCopyrights: this.translate.instant('core.h5p.nocopyright'), + reuse: this.translate.instant('core.h5p.reuse'), + reuseContent: this.translate.instant('core.h5p.reuseContent'), + reuseDescription: this.translate.instant('core.h5p.reuseDescription'), + downloadDescription: this.translate.instant('core.h5p.downloadtitle'), + copyrightsDescription: this.translate.instant('core.h5p.copyrighttitle'), + embedDescription: this.translate.instant('core.h5p.embedtitle'), + h5pDescription: this.translate.instant('core.h5p.h5ptitle'), + contentChanged: this.translate.instant('core.h5p.contentchanged'), + startingOver: this.translate.instant('core.h5p.startingover'), + by: this.translate.instant('core.h5p.by'), + showMore: this.translate.instant('core.h5p.showmore'), + showLess: this.translate.instant('core.h5p.showless'), + subLevel: this.translate.instant('core.h5p.sublevel'), + confirmDialogHeader: this.translate.instant('core.h5p.confirmdialogheader'), + confirmDialogBody: this.translate.instant('core.h5p.confirmdialogbody'), + cancelLabel: this.translate.instant('core.h5p.cancellabel'), + confirmLabel: this.translate.instant('core.h5p.confirmlabel'), + licenseU: this.translate.instant('core.h5p.undisclosed'), + licenseCCBY: this.translate.instant('core.h5p.ccattribution'), + licenseCCBYSA: this.translate.instant('core.h5p.ccattributionsa'), + licenseCCBYND: this.translate.instant('core.h5p.ccattributionnd'), + licenseCCBYNC: this.translate.instant('core.h5p.ccattributionnc'), + licenseCCBYNCSA: this.translate.instant('core.h5p.ccattributionncsa'), + licenseCCBYNCND: this.translate.instant('core.h5p.ccattributionncnd'), + licenseCC40: this.translate.instant('core.h5p.licenseCC40'), + licenseCC30: this.translate.instant('core.h5p.licenseCC30'), + licenseCC25: this.translate.instant('core.h5p.licenseCC25'), + licenseCC20: this.translate.instant('core.h5p.licenseCC20'), + licenseCC10: this.translate.instant('core.h5p.licenseCC10'), + licenseGPL: this.translate.instant('core.h5p.licenseGPL'), + licenseV3: this.translate.instant('core.h5p.licenseV3'), + licenseV2: this.translate.instant('core.h5p.licenseV2'), + licenseV1: this.translate.instant('core.h5p.licenseV1'), + licensePD: this.translate.instant('core.h5p.pd'), + licenseCC010: this.translate.instant('core.h5p.licenseCC010'), + licensePDM: this.translate.instant('core.h5p.pdm'), + licenseC: this.translate.instant('core.h5p.copyrightstring'), + contentType: this.translate.instant('core.h5p.contenttype'), + licenseExtras: this.translate.instant('core.h5p.licenseextras'), + changes: this.translate.instant('core.h5p.changelog'), + contentCopied: this.translate.instant('core.h5p.contentCopied'), + connectionLost: this.translate.instant('core.h5p.connectionLost'), + connectionReestablished: this.translate.instant('core.h5p.connectionReestablished'), + resubmitScores: this.translate.instant('core.h5p.resubmitScores'), + offlineDialogHeader: this.translate.instant('core.h5p.offlineDialogHeader'), + offlineDialogBody: this.translate.instant('core.h5p.offlineDialogBody'), + offlineDialogRetryMessage: this.translate.instant('core.h5p.offlineDialogRetryMessage'), + offlineDialogRetryButtonLabel: this.translate.instant('core.h5p.offlineDialogRetryButtonLabel'), + offlineSuccessfulSubmit: this.translate.instant('core.h5p.offlineSuccessfulSubmit'), + }; + } + + /** + * Parses library data from a string on the form {machineName} {majorVersion}.{minorVersion}. + * + * @param libraryString On the form {machineName} {majorVersion}.{minorVersion} + * @return Object with keys machineName, majorVersion and minorVersion. Null if string is not parsable. + */ + libraryFromString(libraryString: string): {machineName: string, majorVersion: number, minorVersion: number} { + + const matches = libraryString.match(/^([\w0-9\-\.]{1,255})[\-\ ]([0-9]{1,5})\.([0-9]{1,5})$/i); + + if (matches && matches.length >= 4) { + return { + machineName: matches[1], + majorVersion: Number(matches[2]), + minorVersion: Number(matches[3]) + }; + } + + return null; + } + + /** + * Convert list of library parameter values to csv. + * + * @param libraryData Library data as found in library.json files. + * @param key Key that should be found in libraryData. + * @param searchParam The library parameter (Default: 'path'). + * @return Library parameter values separated by ', ' + */ + libraryParameterValuesToCsv(libraryData: any, key: string, searchParam: string = 'path'): string { + if (typeof libraryData[key] != 'undefined') { + const parameterValues = []; + + libraryData[key].forEach((file) => { + for (const index in file) { + if (index === searchParam) { + parameterValues.push(file[index]); + } + } + }); + + return parameterValues.join(','); + } + + return ''; + } + + /** + * Convert strings of text into simple kebab case slugs. Based on H5PCore::slugify. + * + * @param input The string to slugify. + * @return Slugified text. + */ + slugify(input: string): string { + input = input || ''; + + input = input.toLowerCase(); + + // Replace common chars. + let newInput = ''; + for (let i = 0; i < input.length; i++) { + const char = input[i]; + + newInput += this.SLUGIFY_MAP[char] || char; + } + + // Replace everything else. + newInput = newInput.replace(/[^a-z0-9]/g, '-'); + + // Prevent double hyphen + newInput = newInput.replace(/-{2,}/g, '-'); + + // Prevent hyphen in beginning or end. + newInput = newInput.replace(/(^-+|-+$)/g, ''); + + // Prevent too long slug. + if (newInput.length > 91) { + newInput = newInput.substr(0, 92); + } + + // Prevent empty slug + if (newInput === '') { + newInput = 'interactive'; + } + + return newInput; + } + + /** + * Determine if params contain any match. + * + * @param params Parameters. + * @param pattern Regular expression to identify pattern. + * @return True if params matches pattern. + */ + textAddonMatches(params: any, pattern: string): boolean { + + if (typeof params == 'string') { + if (params.match(pattern)) { + return true; + } + } else if (typeof params == 'object') { + for (const key in params) { + const value = params[key]; + + if (this.textAddonMatches(value, pattern)) { + return true; + } + } + } + + return false; + } +} diff --git a/src/core/login/lang/en.json b/src/core/login/lang/en.json index 84825ee48..fe816b435 100644 --- a/src/core/login/lang/en.json +++ b/src/core/login/lang/en.json @@ -2,7 +2,12 @@ "auth_email": "Email-based self-registration", "authenticating": "Authenticating", "cancel": "Cancel", - "checksiteversion": "Check that your site uses Moodle 3.1 or later.", + "changepassword": "Change password", + "changepasswordbutton": "Open the change password page", + "changepasswordhelp": "If you have problems changing your password, please contact your site administrator. \"Site Administrators\" are the people who manages the Moodle at your school/university/company or learning organisation. If you don't know how to contact them, please contact your teachers/trainers.", + "changepasswordinstructions": "You cannot change your password in the app. Please click the following button to open the site in a web browser to change your password. Take into account you need to close the browser after changing the password as you will not be redirected to the app.", + "changepasswordlogoutinstructions": "If you prefer to change site or log out, please click the following button:", + "changepasswordreconnectinstructions": "Click the following button to reconnect to the site. (Take into account that if you didn't change your password successfully, you would return to the previous screen).", "confirmdeletesite": "Are you sure you want to delete the site {{sitename}}?", "connect": "Connect!", "connecttomoodle": "Connect to Moodle", @@ -23,6 +28,7 @@ "errorupdatesite": "An error occurred while updating the site's token.", "findyoursite": "Find your site", "firsttime": "Is this your first time here?", + "forcepasswordchangenotice": "You must change your password to proceed.", "forgotten": "Forgotten your username or password?", "getanothercaptcha": "Get another CAPTCHA", "help": "Help", @@ -31,7 +37,7 @@ "invalidaccount": "Please check your login details or ask your site administrator to check the site configuration.", "invaliddate": "Invalid date", "invalidemail": "Invalid email address", - "invalidmoodleversion": "Invalid Moodle version. The minimum version required is 3.1.", + "invalidmoodleversion": "

Invalid Moodle site version. The Moodle app only supports Moodle systems {{$a}} onwards.

\n

You can contact your site administrators and ask them to update their Moodle system.

\n

\"Site Administrators\" are the people who manages the Moodle at your school/university/company or learning organisation. If you don't know how to contact them, please contact your teachers/trainers.

", "invalidsite": "The site URL is invalid.", "invalidtime": "Invalid time", "invalidurl": "Invalid URL specified", diff --git a/src/core/login/login.module.ts b/src/core/login/login.module.ts index cb1a902ec..05be940c0 100644 --- a/src/core/login/login.module.ts +++ b/src/core/login/login.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -13,12 +13,15 @@ // limitations under the License. import { NgModule } from '@angular/core'; +import { CoreCronDelegate } from '@providers/cron'; import { CoreLoginHelperProvider } from './providers/helper'; +import { CoreLoginCronHandler } from './providers/cron-handler'; import { CoreLoginSitesPageModule } from './pages/sites/sites.module'; // List of providers. export const CORE_LOGIN_PROVIDERS = [ - CoreLoginHelperProvider + CoreLoginHelperProvider, + CoreLoginCronHandler ]; @NgModule({ @@ -29,4 +32,9 @@ export const CORE_LOGIN_PROVIDERS = [ ], providers: CORE_LOGIN_PROVIDERS }) -export class CoreLoginModule {} +export class CoreLoginModule { + constructor(cronDelegate: CoreCronDelegate, cronHandler: CoreLoginCronHandler) { + // Register handlers. + cronDelegate.register(cronHandler); + } +} diff --git a/src/core/login/login.scss b/src/core/login/login.scss new file mode 100644 index 000000000..d0bc7225b --- /dev/null +++ b/src/core/login/login.scss @@ -0,0 +1,74 @@ +ion-app.app-root page-core-login-credentials, +ion-app.app-root page-core-login-reconnect, +ion-app.app-root page-core-login-site { + .scroll-content { + background: $core-login-page-background-color; + + @include darkmode() { + background: $core-dark-login-page-background-color; + } + } + + img { + max-width: 100%; + } + + img.login-logo { + width: 90%; + max-width: 300px; + } + + .box { + padding: 16px; + margin: 8px; + background: $core-login-box-background-color; + border: 1px solid $core-login-box-background-border; + color: $core-login-box-text-color; + + @include darkmode() { + background: $core-dark-login-box-background-color; + border-color: $core-dark-login-box-background-border; + color: $core-dark-login-box-text-color; + } + } + + .core-sitename, .core-siteurl { + @if $core-fixed-url { display: none; } + } + + @if $core-login-button-outline { + .button-md.button-default-md, .button-ios.button-default-ios { + @extend .button-md-light; + } + } + + @if $core-login-loading-color { + .core-loading-container { + color: $core-login-loading-color; + + .spinner circle, .spinner line { + stroke: $core-login-loading-color; + } + } + } + + @if $core-dark-login-button-outline { + .button-md.button-default-md, .button-ios.button-default-ios { + @extend .button-md-light; + } + } + + @include darkmode() { + .core-loading-container { + color: $core-dark-login-loading-color; + + .spinner circle, .spinner line { + stroke: $core-dark-login-loading-color; + } + } + } + + .item-input { + margin-bottom: 20px; + } +} diff --git a/src/core/login/pages/change-password/change-password.html b/src/core/login/pages/change-password/change-password.html new file mode 100644 index 000000000..7c965a3a0 --- /dev/null +++ b/src/core/login/pages/change-password/change-password.html @@ -0,0 +1,27 @@ + + + {{ 'core.login.changepassword' | translate }} + + + + + + + + +

{{ 'core.login.forcepasswordchangenotice' | translate }}

+

{{ 'core.login.changepasswordinstructions' | translate }}

+ +
+ +

{{ 'core.login.changepasswordreconnectinstructions' | translate }}

+ +
+ +

{{ 'core.login.changepasswordlogoutinstructions' | translate }}

+ +
+
+
diff --git a/src/core/login/pages/change-password/change-password.module.ts b/src/core/login/pages/change-password/change-password.module.ts new file mode 100644 index 000000000..b505ebe48 --- /dev/null +++ b/src/core/login/pages/change-password/change-password.module.ts @@ -0,0 +1,33 @@ +// (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 { NgModule } from '@angular/core'; +import { IonicPageModule } from 'ionic-angular'; +import { TranslateModule } from '@ngx-translate/core'; +import { CoreLoginChangePasswordPage } from './change-password'; +import { CoreComponentsModule } from '@components/components.module'; +import { CoreDirectivesModule } from '@directives/directives.module'; + +@NgModule({ + declarations: [ + CoreLoginChangePasswordPage + ], + imports: [ + CoreComponentsModule, + CoreDirectivesModule, + IonicPageModule.forChild(CoreLoginChangePasswordPage), + TranslateModule.forChild() + ] +}) +export class CoreLoginChangePasswordPageModule {} diff --git a/src/core/login/pages/change-password/change-password.ts b/src/core/login/pages/change-password/change-password.ts new file mode 100644 index 000000000..ae894cb50 --- /dev/null +++ b/src/core/login/pages/change-password/change-password.ts @@ -0,0 +1,69 @@ +// (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 { Component } from '@angular/core'; +import { IonicPage } from 'ionic-angular'; +import { TranslateService } from '@ngx-translate/core'; +import { CoreSitesProvider } from '@providers/sites'; +import { CoreLoginHelperProvider } from '../../providers/helper'; +import { CoreDomUtilsProvider } from '@providers/utils/dom'; + +/** + * Page that shows instructions to change the password. + */ +@IonicPage({ segment: 'core-login-change-password' }) +@Component({ + selector: 'page-core-change-password', + templateUrl: 'change-password.html', +}) +export class CoreLoginChangePasswordPage { + changingPassword = false; + logoutLabel: string; + + constructor(private translate: TranslateService, private sitesProvider: CoreSitesProvider, + private loginHelper: CoreLoginHelperProvider, private domUtls: CoreDomUtilsProvider) { + this.logoutLabel = this.loginHelper.getLogoutLabel(); + } + + /** + * Show a help modal. + */ + showHelp(): void { + this.domUtls.showAlert(this.translate.instant('core.help'), this.translate.instant('core.login.changepasswordhelp')); + } + + /** + * Open the change password page in a browser. + */ + openChangePasswordPage(): void { + this.loginHelper.openInAppForEdit(this.sitesProvider.getCurrentSiteId(), '/login/change_password.php', undefined, true); + this.changingPassword = true; + } + + /** + * Login the user. + */ + login(): void { + this.loginHelper.goToSiteInitialPage(); + this.changingPassword = false; + } + + /** + * Logout the user. + */ + logout(): void { + this.sitesProvider.logout(); + this.changingPassword = false; + } +} diff --git a/src/core/login/pages/credentials/credentials.html b/src/core/login/pages/credentials/credentials.html index dcadd2e23..467bb49dd 100644 --- a/src/core/login/pages/credentials/credentials.html +++ b/src/core/login/pages/credentials/credentials.html @@ -20,7 +20,7 @@

{{siteUrl}}

-

+

{{siteUrl}}

@@ -36,11 +36,11 @@ -
+ - + - + diff --git a/src/core/login/pages/credentials/credentials.module.ts b/src/core/login/pages/credentials/credentials.module.ts index 98918503d..85086a9ab 100644 --- a/src/core/login/pages/credentials/credentials.module.ts +++ b/src/core/login/pages/credentials/credentials.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/core/login/pages/credentials/credentials.scss b/src/core/login/pages/credentials/credentials.scss index 6a65689ff..e93abd9d1 100644 --- a/src/core/login/pages/credentials/credentials.scss +++ b/src/core/login/pages/credentials/credentials.scss @@ -1,45 +1,4 @@ ion-app.app-root page-core-login-credentials { - .scroll-content { - background: $core-login-page-background-color; - } - - img { - max-width: 100%; - } - - img.login-logo { - width: 90%; - max-width: 300px; - } - - .box { - padding: 16px; - margin: 8px; - background: $core-login-box-background-color; - border: 1px solid $core-login-box-background-border; - color: $core-login-box-text-color; - } - - .core-sitename, .core-siteurl { - @if $core-fixed-url { display: none; } - } - - @if $core-login-button-outline { - .button-md.button-default-md, .button-ios.button-default-ios { - @extend .button-md-light; - } - } - - @if $core-login-loading-color { - .core-loading-container { - color: $core-login-loading-color; - - .spinner circle, .spinner line { - stroke: $core-login-loading-color; - } - } - } - .item-input { margin-bottom: 20px; } diff --git a/src/core/login/pages/credentials/credentials.ts b/src/core/login/pages/credentials/credentials.ts index fbf6eb574..ed0b43d0e 100644 --- a/src/core/login/pages/credentials/credentials.ts +++ b/src/core/login/pages/credentials/credentials.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -43,6 +43,7 @@ export class CoreLoginCredentialsPage { pageLoaded = false; isBrowserSSO = false; isFixedUrlSet = false; + showForgottenPassword = true; protected siteConfig; protected eventThrown = false; @@ -50,9 +51,14 @@ export class CoreLoginCredentialsPage { protected siteId: string; protected urlToOpen: string; - constructor(private navCtrl: NavController, navParams: NavParams, fb: FormBuilder, private appProvider: CoreAppProvider, - private sitesProvider: CoreSitesProvider, private loginHelper: CoreLoginHelperProvider, - private domUtils: CoreDomUtilsProvider, private translate: TranslateService, + constructor(private navCtrl: NavController, + navParams: NavParams, + fb: FormBuilder, + private appProvider: CoreAppProvider, + private sitesProvider: CoreSitesProvider, + private loginHelper: CoreLoginHelperProvider, + private domUtils: CoreDomUtilsProvider, + private translate: TranslateService, private eventsProvider: CoreEventsProvider) { this.siteUrl = navParams.get('siteUrl'); @@ -100,8 +106,8 @@ export class CoreLoginCredentialsPage { * Check if a site uses local_mobile, requires SSO login, etc. * This should be used only if a fixed URL is set, otherwise this check is already performed in CoreLoginSitePage. * - * @param {string} siteUrl Site URL to check. - * @return {Promise} Promise resolved when done. + * @param siteUrl Site URL to check. + * @return Promise resolved when done. */ protected checkSite(siteUrl: string): Promise { this.pageLoaded = false; @@ -149,9 +155,13 @@ export class CoreLoginCredentialsPage { this.siteName = CoreConfigConstants.sitename ? CoreConfigConstants.sitename : this.siteConfig.sitename; this.logoUrl = this.siteConfig.logourl || this.siteConfig.compactlogourl; this.authInstructions = this.siteConfig.authinstructions || this.translate.instant('core.login.loginsteps'); - this.canSignup = this.siteConfig.registerauth == 'email' && !this.loginHelper.isEmailSignupDisabled(this.siteConfig); this.identityProviders = this.loginHelper.getValidIdentityProviders(this.siteConfig); + const disabledFeatures = this.loginHelper.getDisabledFeatures(this.siteConfig); + this.canSignup = this.siteConfig.registerauth == 'email' && + !this.loginHelper.isEmailSignupDisabled(this.siteConfig, disabledFeatures); + this.showForgottenPassword = !this.loginHelper.isForgottenPasswordDisabled(this.siteConfig, disabledFeatures); + if (!this.eventThrown && !this.viewLeft) { this.eventThrown = true; this.eventsProvider.trigger(CoreEventsProvider.LOGIN_SITE_CHECKED, { config: this.siteConfig }); @@ -168,7 +178,7 @@ export class CoreLoginCredentialsPage { /** * Tries to authenticate the user. * - * @param {Event} [e] Event. + * @param e Event. */ login(e?: Event): void { if (e) { @@ -245,7 +255,7 @@ export class CoreLoginCredentialsPage { /** * An OAuth button was clicked. * - * @param {any} provider The provider that was clicked. + * @param provider The provider that was clicked. */ oauthClicked(provider: any): void { if (!this.loginHelper.openBrowserForOAuthLogin(this.siteUrl, provider, this.siteConfig.launchurl)) { diff --git a/src/core/login/pages/email-signup/email-signup.html b/src/core/login/pages/email-signup/email-signup.html index e00adbfca..2e49ea822 100644 --- a/src/core/login/pages/email-signup/email-signup.html +++ b/src/core/login/pages/email-signup/email-signup.html @@ -31,7 +31,7 @@ {{ 'core.wheredoyoulive' | translate }} {{ 'core.login.selectacountry' | translate }} - {{countries[key]}} + {{country.name}} @@ -52,7 +52,7 @@

{{siteUrl}}

-

+

{{siteUrl}}

@@ -103,7 +103,7 @@ {{ 'core.user.country' | translate }} {{ 'core.login.selectacountry' | translate }} - {{countries[key]}} + {{country.name}} @@ -145,7 +145,7 @@ -

+

{{ 'core.considereddigitalminor' | translate }}

diff --git a/src/core/login/pages/email-signup/email-signup.module.ts b/src/core/login/pages/email-signup/email-signup.module.ts index c0db4ac71..27a91c364 100644 --- a/src/core/login/pages/email-signup/email-signup.module.ts +++ b/src/core/login/pages/email-signup/email-signup.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/core/login/pages/email-signup/email-signup.ts b/src/core/login/pages/email-signup/email-signup.ts index 128a37f67..ffb8a6f42 100644 --- a/src/core/login/pages/email-signup/email-signup.ts +++ b/src/core/login/pages/email-signup/email-signup.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -43,7 +43,6 @@ export class CoreLoginEmailSignupPage { authInstructions: string; settings: any; countries: any; - countriesKeys: any[]; categories: any[]; settingsLoaded = false; captcha = { @@ -53,6 +52,7 @@ export class CoreLoginEmailSignupPage { // Data for age verification. ageVerificationForm: FormGroup; countryControl: FormControl; + signUpCountryControl: FormControl; isMinor = false; // Whether the user is minor age. ageDigitalConsentVerification: boolean; // Whether the age verification is enabled. supportName: string; @@ -111,7 +111,8 @@ export class CoreLoginEmailSignupPage { */ protected completeFormGroup(): void { this.signupForm.addControl('city', this.fb.control(this.settings.defaultcity || '')); - this.signupForm.addControl('country', this.fb.control(this.settings.country || '')); + this.signUpCountryControl = this.fb.control(this.settings.country || ''); + this.signupForm.addControl('country', this.signUpCountryControl); // Add the name fields. for (const i in this.settings.namefields) { @@ -177,9 +178,8 @@ export class CoreLoginEmailSignupPage { }); } - return this.utils.getCountryList().then((countries) => { + return this.utils.getCountryListSorted().then((countries) => { this.countries = countries; - this.countriesKeys = Object.keys(countries); }); }); } @@ -187,8 +187,8 @@ export class CoreLoginEmailSignupPage { /** * Treat the site config, checking if it's valid and extracting the data we're interested in. * - * @param {any} siteConfig Site config to treat. - * @return {boolean} True if success. + * @param siteConfig Site config to treat. + * @return True if success. */ protected treatSiteConfig(siteConfig: any): boolean { if (siteConfig && siteConfig.registerauth == 'email' && !this.loginHelper.isEmailSignupDisabled(siteConfig)) { @@ -212,7 +212,7 @@ export class CoreLoginEmailSignupPage { /** * Pull to refresh. * - * @param {any} refresher Refresher. + * @param refresher Refresher. */ refreshSettings(refresher: any): void { this.fetchData().finally(() => { @@ -223,7 +223,7 @@ export class CoreLoginEmailSignupPage { /** * Create account. * - * @param {Event} e Event. + * @param e Event. */ create(e: Event): void { e.preventDefault(); @@ -292,8 +292,8 @@ export class CoreLoginEmailSignupPage { /** * Escape mail to avoid special characters to be treated as a RegExp. * - * @param {string} text Initial mail. - * @return {string} Escaped mail. + * @param text Initial mail. + * @return Escaped mail. */ escapeMail(text: string): string { return this.textUtils.escapeForRegex(text); @@ -316,7 +316,7 @@ export class CoreLoginEmailSignupPage { /** * Verify Age. * - * @param {Event} e Event. + * @param e Event. */ verifyAge(e: Event): void { e.preventDefault(); @@ -335,6 +335,10 @@ export class CoreLoginEmailSignupPage { this.wsProvider.callAjax('core_auth_is_minor', params, {siteUrl: this.siteUrl}).then((result) => { if (!result.status) { + if (this.countryControl.value) { + this.signUpCountryControl.setValue(this.countryControl.value); + } + // Not a minor, go ahead! this.ageDigitalConsentVerification = false; } else { diff --git a/src/core/login/pages/forgotten-password/forgotten-password.module.ts b/src/core/login/pages/forgotten-password/forgotten-password.module.ts index 050a769f8..a61027049 100644 --- a/src/core/login/pages/forgotten-password/forgotten-password.module.ts +++ b/src/core/login/pages/forgotten-password/forgotten-password.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/core/login/pages/forgotten-password/forgotten-password.ts b/src/core/login/pages/forgotten-password/forgotten-password.ts index e4b3be972..b1d5d6785 100644 --- a/src/core/login/pages/forgotten-password/forgotten-password.ts +++ b/src/core/login/pages/forgotten-password/forgotten-password.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -44,7 +44,7 @@ export class CoreLoginForgottenPasswordPage { /** * Request to reset the password. * - * @param {Event} e Event. + * @param e Event. */ resetPassword(e: Event): void { e.preventDefault(); diff --git a/src/core/login/pages/init/init.module.ts b/src/core/login/pages/init/init.module.ts index 9a0744bc9..c12db00be 100644 --- a/src/core/login/pages/init/init.module.ts +++ b/src/core/login/pages/init/init.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/core/login/pages/init/init.scss b/src/core/login/pages/init/init.scss index f50f047fe..b5f282ac3 100644 --- a/src/core/login/pages/init/init.scss +++ b/src/core/login/pages/init/init.scss @@ -22,6 +22,8 @@ ion-app.app-root page-core-login-init { background-size: 100%; background-size: $core-splash-bgsize; background-position: center; + width: 100%; + height: 100%; .spinner circle, .spinner line { stroke: $core-splash-spinner-color; diff --git a/src/core/login/pages/init/init.ts b/src/core/login/pages/init/init.ts index 3cadcccfb..b7ea64352 100644 --- a/src/core/login/pages/init/init.ts +++ b/src/core/login/pages/init/init.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -81,19 +81,26 @@ export class CoreLoginInitPage { /** * Load the right page. * - * @return {Promise} Promise resolved when done. + * @return Promise resolved when done. */ protected loadPage(): Promise { if (this.sitesProvider.isLoggedIn()) { - if (!this.loginHelper.isSiteLoggedOut()) { - // User is logged in, go to site initial page. - return this.loginHelper.goToSiteInitialPage(); - } else { - // The site is marked as logged out. Logout and try again. + if (this.loginHelper.isSiteLoggedOut()) { return this.sitesProvider.logout().then(() => { return this.loadPage(); }); } + + return this.sitesProvider.getCurrentSite().getPublicConfig().catch(() => { + return {}; + }).then((config) => { + return this.sitesProvider.checkRequiredMinimumVersion(config).then(() => { + // User is logged in, go to site initial page. + return this.loginHelper.goToSiteInitialPage(); + }).catch(() => { + return this.loadPage(); + }); + }); } return this.navCtrl.setRoot('CoreLoginSitesPage'); diff --git a/src/core/login/pages/reconnect/reconnect.html b/src/core/login/pages/reconnect/reconnect.html index 44955e85a..3876f7e76 100644 --- a/src/core/login/pages/reconnect/reconnect.html +++ b/src/core/login/pages/reconnect/reconnect.html @@ -3,59 +3,60 @@ {{ 'core.login.reconnect' | translate }} - +
-
- - - {{ 'core.pictureof' | translate:{$a: site.fullname} }} - +
+ + + + {{ 'core.pictureof' | translate:{$a: site.fullname} }} + + - - - + + + + +

{{siteUrl}}

-

+

{{siteUrl}}

{{ 'core.login.reconnectdescription' | translate }}

- + + + + + + + + + {{ 'core.login.cancel' | translate }} + + + + + + + -
+ - +
diff --git a/src/core/user/pages/about/about.module.ts b/src/core/user/pages/about/about.module.ts index 736e06435..939075a06 100644 --- a/src/core/user/pages/about/about.module.ts +++ b/src/core/user/pages/about/about.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/core/user/pages/about/about.ts b/src/core/user/pages/about/about.ts index baed48158..3c87a2716 100644 --- a/src/core/user/pages/about/about.ts +++ b/src/core/user/pages/about/about.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -83,7 +83,7 @@ export class CoreUserAboutPage { /** * Refresh the user. * - * @param {any} refresher Refresher. + * @param refresher Refresher. */ refreshUser(refresher?: any): void { this.userProvider.invalidateUserCache(this.userId).finally(() => { diff --git a/src/core/user/pages/participants/participants.module.ts b/src/core/user/pages/participants/participants.module.ts index 57d0d5e5d..40c211bbb 100644 --- a/src/core/user/pages/participants/participants.module.ts +++ b/src/core/user/pages/participants/participants.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/core/user/pages/participants/participants.ts b/src/core/user/pages/participants/participants.ts index ae2a470c8..9c4b01a1f 100644 --- a/src/core/user/pages/participants/participants.ts +++ b/src/core/user/pages/participants/participants.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/core/user/pages/profile/profile.html b/src/core/user/pages/profile/profile.html index fae5a9edd..fcda7fd09 100644 --- a/src/core/user/pages/profile/profile.html +++ b/src/core/user/pages/profile/profile.html @@ -1,6 +1,6 @@ - + {{ title }} @@ -13,11 +13,11 @@ -

-

+

{{ user.fullname }}

+

{{ user.address }}

{{ 'core.user.roles' | translate}}{{'core.labelsep' | translate}} - + {{ user.roles }}

diff --git a/src/core/user/pages/profile/profile.module.ts b/src/core/user/pages/profile/profile.module.ts index 2511d5573..6185cc7d8 100644 --- a/src/core/user/pages/profile/profile.module.ts +++ b/src/core/user/pages/profile/profile.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/core/user/pages/profile/profile.scss b/src/core/user/pages/profile/profile.scss index edee0fc71..71e3312c2 100644 --- a/src/core/user/pages/profile/profile.scss +++ b/src/core/user/pages/profile/profile.scss @@ -9,7 +9,10 @@ ion-app.app-root page-core-user-profile { width: 30px; height: 30px; border-radius: 50%; - background-color: white; + background-color: $white; + @include darkmode() { + background: $core-dark-item-bg-color; + } } [core-user-avatar].item-avatar-center { display: inline-block; @@ -26,11 +29,19 @@ ion-app.app-root page-core-user-profile { background: $list-background-color; border-bottom: 1px solid $list-border-color; + @include darkmode() { + background: $core-dark-item-bg-color; + } + .core-user-profile-handler { background: $list-background-color; border: 0; color: $core-user-profile-communication-icons-color; + @include darkmode() { + background: $core-dark-item-bg-color; + } + p { margin: 0; } diff --git a/src/core/user/pages/profile/profile.ts b/src/core/user/pages/profile/profile.ts index fd3875f0d..05a36e6e0 100644 --- a/src/core/user/pages/profile/profile.ts +++ b/src/core/user/pages/profile/profile.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -192,7 +192,7 @@ export class CoreUserProfilePage { /** * Refresh the user. * - * @param {any} refresher Refresher. + * @param refresher Refresher. */ refreshUser(refresher?: any): void { const promises = []; @@ -225,8 +225,8 @@ export class CoreUserProfilePage { /** * A handler was clicked. * - * @param {Event} event Click event. - * @param {CoreUserProfileHandlerData} handler Handler that was clicked. + * @param event Click event. + * @param handler Handler that was clicked. */ handlerClicked(event: Event, handler: CoreUserProfileHandlerData): void { // Decide which navCtrl to use. If this page is inside a split view, use the split view's master nav. diff --git a/src/core/user/providers/course-option-handler.ts b/src/core/user/providers/course-option-handler.ts index 9636dcfeb..b0407191d 100644 --- a/src/core/user/providers/course-option-handler.ts +++ b/src/core/user/providers/course-option-handler.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -31,10 +31,10 @@ export class CoreUserParticipantsCourseOptionHandler implements CoreCourseOption /** * Should invalidate the data to determine if the handler is enabled for a certain course. * - * @param {number} courseId The course ID. - * @param {any} [navOptions] Course navigation options for current user. See CoreCoursesProvider.getUserNavigationOptions. - * @param {any} [admOptions] Course admin options for current user. See CoreCoursesProvider.getUserAdministrationOptions. - * @return {Promise} Promise resolved when done. + * @param courseId The course ID. + * @param navOptions Course navigation options for current user. See CoreCoursesProvider.getUserNavigationOptions. + * @param admOptions Course admin options for current user. See CoreCoursesProvider.getUserAdministrationOptions. + * @return Promise resolved when done. */ invalidateEnabledForCourse(courseId: number, navOptions?: any, admOptions?: any): Promise { if (navOptions && typeof navOptions.participants != 'undefined') { @@ -48,7 +48,7 @@ export class CoreUserParticipantsCourseOptionHandler implements CoreCourseOption /** * Check if the handler is enabled on a site level. * - * @return {boolean} Whether or not the handler is enabled on a site level. + * @return Whether or not the handler is enabled on a site level. */ isEnabled(): boolean | Promise { return true; @@ -57,11 +57,11 @@ export class CoreUserParticipantsCourseOptionHandler implements CoreCourseOption /** * Whether or not the handler is enabled for a certain course. * - * @param {number} courseId The course ID. - * @param {any} accessData Access type and data. Default, guest, ... - * @param {any} [navOptions] Course navigation options for current user. See CoreCoursesProvider.getUserNavigationOptions. - * @param {any} [admOptions] Course admin options for current user. See CoreCoursesProvider.getUserAdministrationOptions. - * @return {boolean|Promise} True or promise resolved with true if enabled. + * @param courseId The course ID. + * @param accessData Access type and data. Default, guest, ... + * @param navOptions Course navigation options for current user. See CoreCoursesProvider.getUserNavigationOptions. + * @param admOptions Course admin options for current user. See CoreCoursesProvider.getUserAdministrationOptions. + * @return True or promise resolved with true if enabled. */ isEnabledForCourse(courseId: number, accessData: any, navOptions?: any, admOptions?: any): boolean | Promise { if (accessData && accessData.type == CoreCourseProvider.ACCESS_GUEST) { @@ -78,9 +78,9 @@ export class CoreUserParticipantsCourseOptionHandler implements CoreCourseOption /** * Returns the data needed to render the handler. * - * @param {Injector} injector Injector. - * @param {number} course The course. - * @return {CoreCourseOptionsHandlerData|Promise} Data or promise resolved with the data. + * @param injector Injector. + * @param course The course. + * @return Data or promise resolved with the data. */ getDisplayData(injector: Injector, course: any): CoreCourseOptionsHandlerData | Promise { return { @@ -93,8 +93,8 @@ export class CoreUserParticipantsCourseOptionHandler implements CoreCourseOption /** * Called when a course is downloaded. It should prefetch all the data to be able to see the addon in offline. * - * @param {any} course The course. - * @return {Promise} Promise resolved when done. + * @param course The course. + * @return Promise resolved when done. */ prefetch(course: any): Promise { return this.getParticipantsPage(course.id, 0); @@ -103,9 +103,9 @@ export class CoreUserParticipantsCourseOptionHandler implements CoreCourseOption /** * Get a participant page and, if there are more participants, call the function again to get it too. * - * @param {number} courseId Course ID. - * @param {number} limitFrom The number of participants already loaded. - * @return {Promise} Promise resolved when done. + * @param courseId Course ID. + * @param limitFrom The number of participants already loaded. + * @return Promise resolved when done. */ protected getParticipantsPage(courseId: number, limitFrom: number): Promise { return this.userProvider.getParticipants(courseId, limitFrom, undefined, undefined, true).then((result) => { diff --git a/src/core/user/providers/helper.ts b/src/core/user/providers/helper.ts index fd9367869..97daa62d5 100644 --- a/src/core/user/providers/helper.ts +++ b/src/core/user/providers/helper.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -30,10 +30,10 @@ export class CoreUserHelperProvider { /** * Formats a user address, concatenating address, city and country. * - * @param {string} address Address. - * @param {string} city City. - * @param {string} country Country. - * @return {string} Formatted address. + * @param address Address. + * @param city City. + * @param country Country. + * @return Formatted address. */ formatAddress(address: string, city: string, country: string): string { const separator = this.translate.instant('core.listsep'); @@ -49,8 +49,8 @@ export class CoreUserHelperProvider { /** * Formats a user role list, translating and concatenating them. * - * @param {any[]} [roles] List of user roles. - * @return {string} The formatted roles. + * @param roles List of user roles. + * @return The formatted roles. */ formatRoleList(roles?: any[]): string { if (!roles || roles.length <= 0) { diff --git a/src/core/user/providers/offline.ts b/src/core/user/providers/offline.ts index 36ccc4c05..cfd4be00b 100644 --- a/src/core/user/providers/offline.ts +++ b/src/core/user/providers/offline.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -65,7 +65,7 @@ export class CoreUserOfflineProvider { /** * Get preferences that were changed offline. * - * @return {Promise} Promise resolved with list of preferences. + * @return Promise resolved with list of preferences. */ getChangedPreferences(siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -76,8 +76,8 @@ export class CoreUserOfflineProvider { /** * Get an offline preference. * - * @param {string} name Name of the preference. - * @return {Promise} Promise resolved with the preference, rejected if not found. + * @param name Name of the preference. + * @return Promise resolved with the preference, rejected if not found. */ getPreference(name: string, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -90,10 +90,10 @@ export class CoreUserOfflineProvider { /** * Set an offline preference. * - * @param {string} name Name of the preference. - * @param {string} value Value of the preference. - * @param {string} onlineValue Online value of the preference. If unedfined, preserve previously stored value. - * @return {Promise} Promise resolved when done. + * @param name Name of the preference. + * @param value Value of the preference. + * @param onlineValue Online value of the preference. If unedfined, preserve previously stored value. + * @return Promise resolved when done. */ setPreference(name: string, value: string, onlineValue?: string, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { diff --git a/src/core/user/providers/participants-link-handler.ts b/src/core/user/providers/participants-link-handler.ts index fb9971f02..c5a1a1635 100644 --- a/src/core/user/providers/participants-link-handler.ts +++ b/src/core/user/providers/participants-link-handler.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -39,11 +39,11 @@ export class CoreUserParticipantsLinkHandler extends CoreContentLinksHandlerBase /** * Get the list of actions for a link (url). * - * @param {string[]} siteIds List of sites the URL belongs to. - * @param {string} url The URL to treat. - * @param {any} params The params of the URL. E.g. 'mysite.com?id=1' -> {id: 1} - * @param {number} [courseId] Course ID related to the URL. Optional but recommended. - * @return {CoreContentLinksAction[]|Promise} List of (or promise resolved with list of) actions. + * @param siteIds List of sites the URL belongs to. + * @param url The URL to treat. + * @param params The params of the URL. E.g. 'mysite.com?id=1' -> {id: 1} + * @param courseId Course ID related to the URL. Optional but recommended. + * @return List of (or promise resolved with list of) actions. */ getActions(siteIds: string[], url: string, params: any, courseId?: number): CoreContentLinksAction[] | Promise { @@ -84,11 +84,11 @@ export class CoreUserParticipantsLinkHandler extends CoreContentLinksHandlerBase * Check if the handler is enabled for a certain site (site + user) and a URL. * If not defined, defaults to true. * - * @param {string} siteId The site ID. - * @param {string} url The URL to treat. - * @param {any} params The params of the URL. E.g. 'mysite.com?id=1' -> {id: 1} - * @param {number} [courseId] Course ID related to the URL. Optional but recommended. - * @return {boolean|Promise} Whether the handler is enabled for the URL and site. + * @param siteId The site ID. + * @param url The URL to treat. + * @param params The params of the URL. E.g. 'mysite.com?id=1' -> {id: 1} + * @param courseId Course ID related to the URL. Optional but recommended. + * @return Whether the handler is enabled for the URL and site. */ isEnabled(siteId: string, url: string, params: any, courseId?: number): boolean | Promise { courseId = parseInt(params.id, 10) || courseId; diff --git a/src/core/user/providers/sync-cron-handler.ts b/src/core/user/providers/sync-cron-handler.ts index a51b2da8b..48eb4ab74 100644 --- a/src/core/user/providers/sync-cron-handler.ts +++ b/src/core/user/providers/sync-cron-handler.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -29,9 +29,9 @@ export class CoreUserSyncCronHandler implements CoreCronHandler { * Execute the process. * Receives the ID of the site affected, undefined for all sites. * - * @param {string} [siteId] ID of the site affected, undefined for all sites. - * @param {boolean} [force] Wether the execution is forced (manual sync). - * @return {Promise} Promise resolved when done, rejected if failure. + * @param siteId ID of the site affected, undefined for all sites. + * @param force Wether the execution is forced (manual sync). + * @return Promise resolved when done, rejected if failure. */ execute(siteId?: string, force?: boolean): Promise { return this.userSync.syncPreferences(siteId); @@ -40,7 +40,7 @@ export class CoreUserSyncCronHandler implements CoreCronHandler { /** * Get the time between consecutive executions. * - * @return {number} Time between consecutive executions (in ms). + * @return Time between consecutive executions (in ms). */ getInterval(): number { return 300000; // 5 minutes. diff --git a/src/core/user/providers/sync.ts b/src/core/user/providers/sync.ts index 62de2f679..e0a7fb2bb 100644 --- a/src/core/user/providers/sync.ts +++ b/src/core/user/providers/sync.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -43,8 +43,8 @@ export class CoreUserSyncProvider extends CoreSyncBaseProvider { /** * Try to synchronize user preferences in a certain site or in all sites. * - * @param {string} [siteId] Site ID to sync. If not defined, sync all sites. - * @return {Promise} Promise resolved if sync is successful, rejected if sync fails. + * @param siteId Site ID to sync. If not defined, sync all sites. + * @return Promise resolved if sync is successful, rejected if sync fails. */ syncPreferences(siteId?: string): Promise { const syncFunctionLog = 'all user preferences'; @@ -55,8 +55,8 @@ export class CoreUserSyncProvider extends CoreSyncBaseProvider { /** * Sync user preferences of a site. * - * @param {string} [siteId] Site ID to sync. If not defined, sync all sites. - * @param {Promise} Promise resolved if sync is successful, rejected if sync fails. + * @param siteId Site ID to sync. If not defined, sync all sites. + * @param Promise resolved if sync is successful, rejected if sync fails. */ protected syncPreferencesFunc(siteId?: string): Promise { siteId = siteId || this.sitesProvider.getCurrentSiteId(); diff --git a/src/core/user/providers/tag-area-handler.ts b/src/core/user/providers/tag-area-handler.ts index ab2d167cf..32b30851d 100644 --- a/src/core/user/providers/tag-area-handler.ts +++ b/src/core/user/providers/tag-area-handler.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -29,7 +29,7 @@ export class CoreUserTagAreaHandler implements CoreTagAreaHandler { /** * Whether or not the handler is enabled on a site level. - * @return {boolean|Promise} Whether or not the handler is enabled on a site level. + * @return Whether or not the handler is enabled on a site level. */ isEnabled(): boolean | Promise { return true; @@ -38,8 +38,8 @@ export class CoreUserTagAreaHandler implements CoreTagAreaHandler { /** * Parses the rendered content of a tag index and returns the items. * - * @param {string} content Rendered content. - * @return {any[]|Promise} Area items (or promise resolved with the items). + * @param content Rendered content. + * @return Area items (or promise resolved with the items). */ parseContent(content: string): any[] | Promise { const items = []; @@ -73,8 +73,8 @@ export class CoreUserTagAreaHandler implements CoreTagAreaHandler { /** * Get the component to use to display items. * - * @param {Injector} injector Injector. - * @return {any|Promise} The component (or promise resolved with component) to use, undefined if not found. + * @param injector Injector. + * @return The component (or promise resolved with component) to use, undefined if not found. */ getComponent(injector: Injector): any | Promise { return CoreUserTagAreaComponent; diff --git a/src/core/user/providers/user-delegate.ts b/src/core/user/providers/user-delegate.ts index 92796f273..588bb817d 100644 --- a/src/core/user/providers/user-delegate.ts +++ b/src/core/user/providers/user-delegate.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -28,7 +28,6 @@ import { Subject, BehaviorSubject } from 'rxjs'; export interface CoreUserProfileHandler extends CoreDelegateHandler { /** * The highest priority is displayed first. - * @type {number} */ priority: number; @@ -38,25 +37,24 @@ export interface CoreUserProfileHandler extends CoreDelegateHandler { * - TYPE_NEW_PAGE: will be displayed as a list of items. Should have icon. Spinner not used. * Default value if none is specified. * - TYPE_ACTION: will be displayed as a button and should not redirect to any state. Spinner use is recommended. - * @type {string} */ type: string; /** * Whether or not the handler is enabled for a user. - * @param {any} user User object. - * @param {number} courseId Course ID where to show. - * @param {any} [navOptions] Navigation options for the course. - * @param {any} [admOptions] Admin options for the course. - * @return {boolean|Promise} Whether or not the handler is enabled for a user. + * @param user User object. + * @param courseId Course ID where to show. + * @param navOptions Navigation options for the course. + * @param admOptions Admin options for the course. + * @return Whether or not the handler is enabled for a user. */ isEnabledForUser(user: any, courseId: number, navOptions?: any, admOptions?: any): boolean | Promise; /** * Returns the data needed to render the handler. - * @param {any} user User object. - * @param {number} courseId Course ID where to show. - * @return {CoreUserProfileHandlerData} Data to be shown. + * @param user User object. + * @param courseId Course ID where to show. + * @return Data to be shown. */ getDisplayData(user: any, courseId: number): CoreUserProfileHandlerData; } @@ -67,41 +65,36 @@ export interface CoreUserProfileHandler extends CoreDelegateHandler { export interface CoreUserProfileHandlerData { /** * Title to display. - * @type {string} */ title: string; /** * Name of the icon to display. Mandatory for TYPE_COMMUNICATION. - * @type {string} */ icon?: string; /** * Additional class to add to the HTML. - * @type {string} */ class?: string; /** * If enabled, element will be hidden. Only for TYPE_NEW_PAGE and TYPE_ACTION. - * @type {boolean} */ hidden?: boolean; /** * If enabled will show an spinner. Only for TYPE_ACTION. - * @type {boolean} */ spinner?: boolean; /** * Action to do when clicked. * - * @param {Event} event Click event. - * @param {NavController} Nav controller to use to navigate. - * @param {any} user User object. - * @param {number} [courseId] Course ID being viewed. If not defined, site context. + * @param event Click event. + * @param Nav controller to use to navigate. + * @param user User object. + * @param courseId Course ID being viewed. If not defined, site context. */ action?(event: Event, navCtrl: NavController, user: any, courseId?: number): void; } @@ -112,25 +105,21 @@ export interface CoreUserProfileHandlerData { export interface CoreUserProfileHandlerToDisplay { /** * Name of the handler. - * @type {string} */ name?: string; /** * Data to display. - * @type {CoreUserProfileHandlerData} */ data: CoreUserProfileHandlerData; /** * The highest priority is displayed first. - * @type {number} */ priority?: number; /** * The type of the handler. See CoreUserProfileHandler. - * @type {string} */ type: string; } @@ -143,23 +132,19 @@ export interface CoreUserProfileHandlerToDisplay { export class CoreUserDelegate extends CoreDelegate { /** * User profile handler type for communication. - * @type {string} */ static TYPE_COMMUNICATION = 'communication'; /** * User profile handler type for new page. - * @type {string} */ static TYPE_NEW_PAGE = 'newpage'; /** * User profile handler type for actions. - * @type {string} */ static TYPE_ACTION = 'action'; /** * Update handler information event. - * @type {string} */ static UPDATE_HANDLER_EVENT = 'CoreUserDelegate_update_handler_event'; @@ -198,7 +183,7 @@ export class CoreUserDelegate extends CoreDelegate { /** * Check if handlers are loaded. * - * @return {boolean} True if handlers are loaded, false otherwise. + * @return True if handlers are loaded, false otherwise. */ areHandlersLoaded(userId: number): boolean { return this.userHandlers[userId] && this.userHandlers[userId].loaded; @@ -207,7 +192,7 @@ export class CoreUserDelegate extends CoreDelegate { /** * Clear current user handlers. * - * @param {number} userId The user to clear. + * @param userId The user to clear. */ clearUserHandlers(userId: number): void { const userData = this.userHandlers[userId]; @@ -222,9 +207,9 @@ export class CoreUserDelegate extends CoreDelegate { /** * Get the profile handlers for a user. * - * @param {any} user The user object. - * @param {number} courseId The course ID. - * @return {Subject} Resolved with the handlers. + * @param user The user object. + * @param courseId The course ID. + * @return Resolved with the handlers. */ getProfileHandlersFor(user: any, courseId: number): Subject { let promise, diff --git a/src/core/user/providers/user-handler.ts b/src/core/user/providers/user-handler.ts index 699f1fac2..212b9e057 100644 --- a/src/core/user/providers/user-handler.ts +++ b/src/core/user/providers/user-handler.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -31,7 +31,7 @@ export class CoreUserProfileMailHandler implements CoreUserProfileHandler { /** * Check if handler is enabled. * - * @return {boolean} Always enabled. + * @return Always enabled. */ isEnabled(): boolean { return true; @@ -40,11 +40,11 @@ export class CoreUserProfileMailHandler implements CoreUserProfileHandler { /** * Check if handler is enabled for this user in this context. * - * @param {any} user User to check. - * @param {number} courseId Course ID. - * @param {any} [navOptions] Course navigation options for current user. See CoreCoursesProvider.getUserNavigationOptions. - * @param {any} [admOptions] Course admin options for current user. See CoreCoursesProvider.getUserAdministrationOptions. - * @return {boolean|Promise} Promise resolved with true if enabled, resolved with false otherwise. + * @param user User to check. + * @param courseId Course ID. + * @param navOptions Course navigation options for current user. See CoreCoursesProvider.getUserNavigationOptions. + * @param admOptions Course admin options for current user. See CoreCoursesProvider.getUserAdministrationOptions. + * @return Promise resolved with true if enabled, resolved with false otherwise. */ isEnabledForUser(user: any, courseId: number, navOptions?: any, admOptions?: any): boolean | Promise { // Not current user required. @@ -54,7 +54,7 @@ export class CoreUserProfileMailHandler implements CoreUserProfileHandler { /** * Returns the data needed to render the handler. * - * @return {CoreUserProfileHandlerData} Data needed to render the handler. + * @return Data needed to render the handler. */ getDisplayData(user: any, courseId: number): CoreUserProfileHandlerData { return { diff --git a/src/core/user/providers/user-link-handler.ts b/src/core/user/providers/user-link-handler.ts index eb9a0b0b6..f82c07bc3 100644 --- a/src/core/user/providers/user-link-handler.ts +++ b/src/core/user/providers/user-link-handler.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -33,11 +33,11 @@ export class CoreUserProfileLinkHandler extends CoreContentLinksHandlerBase { /** * Get the list of actions for a link (url). * - * @param {string[]} siteIds List of sites the URL belongs to. - * @param {string} url The URL to treat. - * @param {any} params The params of the URL. E.g. 'mysite.com?id=1' -> {id: 1} - * @param {number} [courseId] Course ID related to the URL. Optional but recommended. - * @return {CoreContentLinksAction[]|Promise} List of (or promise resolved with list of) actions. + * @param siteIds List of sites the URL belongs to. + * @param url The URL to treat. + * @param params The params of the URL. E.g. 'mysite.com?id=1' -> {id: 1} + * @param courseId Course ID related to the URL. Optional but recommended. + * @return List of (or promise resolved with list of) actions. */ getActions(siteIds: string[], url: string, params: any, courseId?: number): CoreContentLinksAction[] | Promise { @@ -56,11 +56,11 @@ export class CoreUserProfileLinkHandler extends CoreContentLinksHandlerBase { * Check if the handler is enabled for a certain site (site + user) and a URL. * If not defined, defaults to true. * - * @param {string} siteId The site ID. - * @param {string} url The URL to treat. - * @param {any} params The params of the URL. E.g. 'mysite.com?id=1' -> {id: 1} - * @param {number} [courseId] Course ID related to the URL. Optional but recommended. - * @return {boolean|Promise} Whether the handler is enabled for the URL and site. + * @param siteId The site ID. + * @param url The URL to treat. + * @param params The params of the URL. E.g. 'mysite.com?id=1' -> {id: 1} + * @param courseId Course ID related to the URL. Optional but recommended. + * @return Whether the handler is enabled for the URL and site. */ isEnabled(siteId: string, url: string, params: any, courseId?: number): boolean | Promise { return url.indexOf('/grade/report/') == -1; diff --git a/src/core/user/providers/user-profile-field-delegate.ts b/src/core/user/providers/user-profile-field-delegate.ts index e047100bd..4acef8d5e 100644 --- a/src/core/user/providers/user-profile-field-delegate.ts +++ b/src/core/user/providers/user-profile-field-delegate.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -21,7 +21,6 @@ import { CoreEventsProvider } from '@providers/events'; export interface CoreUserProfileFieldHandler extends CoreDelegateHandler { /** * Type of the field the handler supports. E.g. 'checkbox'. - * @type {string} */ type: string; @@ -29,18 +28,18 @@ export interface CoreUserProfileFieldHandler extends CoreDelegateHandler { * Return the Component to use to display the user profile field. * It's recommended to return the class of the component, but you can also return an instance of the component. * - * @param {Injector} injector Injector. - * @return {any|Promise} The component (or promise resolved with component) to use, undefined if not found. + * @param injector Injector. + * @return The component (or promise resolved with component) to use, undefined if not found. */ getComponent(injector: Injector): any | Promise; /** * Get the data to send for the field based on the input data. - * @param {any} field User field to get the data for. - * @param {boolean} signup True if user is in signup page. - * @param {string} [registerAuth] Register auth method. E.g. 'email'. - * @param {any} formValues Form Values. - * @return {Promise|CoreUserProfileFieldHandlerData} Data to send for the field. + * @param field User field to get the data for. + * @param signup True if user is in signup page. + * @param registerAuth Register auth method. E.g. 'email'. + * @param formValues Form Values. + * @return Data to send for the field. */ getData?(field: any, signup: boolean, registerAuth: string, formValues: any): Promise | CoreUserProfileFieldHandlerData; @@ -49,19 +48,16 @@ export interface CoreUserProfileFieldHandler extends CoreDelegateHandler { export interface CoreUserProfileFieldHandlerData { /** * Name to display. - * @type {string} */ name: string; /** * Field type. - * @type {string} */ type?: string; /** * Value of the field. - * @type {any} */ value: any; } @@ -81,10 +77,10 @@ export class CoreUserProfileFieldDelegate extends CoreDelegate { /** * Get the component to use to display an user field. * - * @param {Injector} injector Injector. - * @param {any} field User field to get the directive for. - * @param {boolean} signup True if user is in signup page. - * @return {Promise} Promise resolved with component to use, undefined if not found. + * @param injector Injector. + * @param field User field to get the directive for. + * @param signup True if user is in signup page. + * @return Promise resolved with component to use, undefined if not found. */ getComponent(injector: Injector, field: any, signup: boolean): Promise { const type = field.type || field.datatype; @@ -103,11 +99,11 @@ export class CoreUserProfileFieldDelegate extends CoreDelegate { /** * Get the data to send for a certain field based on the input data. * - * @param {any} field User field to get the data for. - * @param {boolean} signup True if user is in signup page. - * @param {string} registerAuth Register auth method. E.g. 'email'. - * @param {any} formValues Form values. - * @return {Promise} Data to send for the field. + * @param field User field to get the data for. + * @param signup True if user is in signup page. + * @param registerAuth Register auth method. E.g. 'email'. + * @param formValues Form values. + * @return Data to send for the field. */ getDataForField(field: any, signup: boolean, registerAuth: string, formValues: any): Promise { const type = field.type || field.datatype, @@ -133,11 +129,11 @@ export class CoreUserProfileFieldDelegate extends CoreDelegate { /** * Get the data to send for a list of fields based on the input data. * - * @param {any[]} fields User fields to get the data for. - * @param {boolean} [signup] True if user is in signup page. - * @param {string} [registerAuth] Register auth method. E.g. 'email'. - * @param {any} formValues Form values. - * @return {Promise} Data to send. + * @param fields User fields to get the data for. + * @param signup True if user is in signup page. + * @param registerAuth Register auth method. E.g. 'email'. + * @param formValues Form values. + * @return Data to send. */ getDataForFields(fields: any[], signup: boolean = false, registerAuth: string = '', formValues: any): Promise { const result = [], diff --git a/src/core/user/providers/user.ts b/src/core/user/providers/user.ts index 5b6141006..fc685dadb 100644 --- a/src/core/user/providers/user.ts +++ b/src/core/user/providers/user.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -69,12 +69,38 @@ export class CoreUserProvider { this.sitesProvider.registerSiteSchema(this.siteSchema); } + /** + * Check if WS to search participants is available in site. + * + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with boolean: whether it's available. + * @since 3.8 + */ + canSearchParticipants(siteId?: string): Promise { + return this.sitesProvider.getSite(siteId).then((site) => { + return this.canSearchParticipantsInSite(site); + }); + } + + /** + * Check if WS to search participants is available in site. + * + * @param site Site. If not defined, current site. + * @return Whether it's available. + * @since 3.8 + */ + canSearchParticipantsInSite(site?: CoreSite): boolean { + site = site || this.sitesProvider.getCurrentSite(); + + return site.wsAvailable('core_enrol_search_users'); + } + /** * Change the given user profile picture. * - * @param {number} draftItemId New picture draft item id. - * @param {number} userId User ID. - * @return {Promise} Promise resolve with the new profileimageurl + * @param draftItemId New picture draft item id. + * @param userId User ID. + * @return Promise resolve with the new profileimageurl */ changeProfilePicture(draftItemId: number, userId: number): Promise { const data = { @@ -95,9 +121,9 @@ export class CoreUserProvider { /** * Store user basic information in local DB to be retrieved if the WS call fails. * - * @param {number} userId User ID. - * @param {string} [siteId] ID of the site. If not defined, use current site. - * @return {Promise} Promise resolve when the user is deleted. + * @param userId User ID. + * @param siteId ID of the site. If not defined, use current site. + * @return Promise resolve when the user is deleted. */ deleteStoredUser(userId: number, siteId?: string): Promise { if (isNaN(userId)) { @@ -121,12 +147,12 @@ export class CoreUserProvider { /** * Get participants for a certain course. * - * @param {number} courseId ID of the course. - * @param {number} limitFrom Position of the first participant to get. - * @param {number} limitNumber Number of participants to get. - * @param {string} [siteId] Site Id. If not defined, use current site. - * @param {boolean} [ignoreCache] True if it should ignore cached data (it will always fail in offline or server down). - * @return {Promise<{participants: any[], canLoadMore: boolean}>} Promise resolved when the participants are retrieved. + * @param courseId ID of the course. + * @param limitFrom Position of the first participant to get. + * @param limitNumber Number of participants to get. + * @param siteId Site Id. If not defined, use current site. + * @param ignoreCache True if it should ignore cached data (it will always fail in offline or server down). + * @return Promise resolved when the participants are retrieved. */ getParticipants(courseId: number, limitFrom: number = 0, limitNumber: number = CoreUserProvider.PARTICIPANTS_LIST_LIMIT, siteId?: string, ignoreCache?: boolean): Promise<{participants: any[], canLoadMore: boolean}> { @@ -172,8 +198,8 @@ export class CoreUserProvider { /** * Get cache key for participant list WS calls. * - * @param {number} courseId Course ID. - * @return {string} Cache key. + * @param courseId Course ID. + * @return Cache key. */ protected getParticipantsListCacheKey(courseId: number): string { return this.ROOT_CACHE_KEY + 'list:' + courseId; @@ -182,11 +208,11 @@ export class CoreUserProvider { /** * Get user profile. The type of profile retrieved depends on the params. * - * @param {number} userId User's ID. - * @param {number} [courseId] Course ID to get course profile, undefined or 0 to get site profile. - * @param {boolean} [forceLocal] True to retrieve the user data from local DB, false to retrieve it from WS. - * @param {string} [siteId] ID of the site. If not defined, use current site. - * @return {Promise} Promise resolved with the user data. + * @param userId User's ID. + * @param courseId Course ID to get course profile, undefined or 0 to get site profile. + * @param forceLocal True to retrieve the user data from local DB, false to retrieve it from WS. + * @param siteId ID of the site. If not defined, use current site. + * @return Promise resolved with the user data. */ getProfile(userId: number, courseId?: number, forceLocal: boolean = false, siteId?: string): Promise { siteId = siteId || this.sitesProvider.getCurrentSiteId(); @@ -205,8 +231,8 @@ export class CoreUserProvider { /** * Get cache key for a user WS call. * - * @param {number} userId User ID. - * @return {string} Cache key. + * @param userId User ID. + * @return Cache key. */ protected getUserCacheKey(userId: number): string { return this.ROOT_CACHE_KEY + 'data:' + userId; @@ -215,9 +241,9 @@ export class CoreUserProvider { /** * Get user basic information from local DB. * - * @param {number} userId User ID. - * @param {string} [siteId] ID of the site. If not defined, use current site. - * @return {Promise} Promise resolve when the user is retrieved. + * @param userId User ID. + * @param siteId ID of the site. If not defined, use current site. + * @return Promise resolve when the user is retrieved. */ protected getUserFromLocalDb(userId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -228,10 +254,10 @@ export class CoreUserProvider { /** * Get user profile from WS. * - * @param {number} userId User ID. - * @param {number} [courseId] Course ID to get course profile, undefined or 0 to get site profile. - * @param {string} [siteId] ID of the site. If not defined, use current site. - * @return {Promise} Promise resolve when the user is retrieved. + * @param userId User ID. + * @param courseId Course ID to get course profile, undefined or 0 to get site profile. + * @param siteId ID of the site. If not defined, use current site. + * @return Promise resolve when the user is retrieved. */ protected getUserFromWS(userId: number, courseId?: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -282,9 +308,9 @@ export class CoreUserProvider { /** * Get a user preference (online or offline). * - * @param {string} name Name of the preference. - * @param {string} [siteId] Site Id. If not defined, use current site. - * @return {string} Preference value or null if preference not set. + * @param name Name of the preference. + * @param siteId Site Id. If not defined, use current site. + * @return Preference value or null if preference not set. */ getUserPreference(name: string, siteId?: string): Promise { siteId = siteId || this.sitesProvider.getCurrentSiteId(); @@ -313,8 +339,8 @@ export class CoreUserProvider { /** * Get cache key for a user preference WS call. * - * @param {string} name Preference name. - * @return {string} Cache key. + * @param name Preference name. + * @return Cache key. */ protected getUserPreferenceCacheKey(name: string): string { return this.ROOT_CACHE_KEY + 'preference:' + name; @@ -323,9 +349,9 @@ export class CoreUserProvider { /** * Get a user preference online. * - * @param {string} name Name of the preference. - * @param {string} [siteId] Site Id. If not defined, use current site. - * @return {string} Preference value or null if preference not set. + * @param name Name of the preference. + * @param siteId Site Id. If not defined, use current site. + * @return Preference value or null if preference not set. */ getUserPreferenceOnline(name: string, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -344,9 +370,9 @@ export class CoreUserProvider { /** * Invalidates user WS calls. * - * @param {number} userId User ID. - * @param {string} [siteId] Site Id. If not defined, use current site. - * @return {Promise} Promise resolved when the data is invalidated. + * @param userId User ID. + * @param siteId Site Id. If not defined, use current site. + * @return Promise resolved when the data is invalidated. */ invalidateUserCache(userId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -357,9 +383,9 @@ export class CoreUserProvider { /** * Invalidates participant list for a certain course. * - * @param {number} courseId Course ID. - * @param {string} [siteId] Site Id. If not defined, use current site. - * @return {Promise} Promise resolved when the list is invalidated. + * @param courseId Course ID. + * @param siteId Site Id. If not defined, use current site. + * @return Promise resolved when the list is invalidated. */ invalidateParticipantsList(courseId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -370,9 +396,9 @@ export class CoreUserProvider { /** * Invalidate user preference. * - * @param {string} name Name of the preference. - * @param {string} [siteId] Site Id. If not defined, use current site. - * @return {Promise} Promise resolved when the data is invalidated. + * @param name Name of the preference. + * @param siteId Site Id. If not defined, use current site. + * @return Promise resolved when the data is invalidated. */ invalidateUserPreference(name: string, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -383,8 +409,8 @@ export class CoreUserProvider { /** * Check if course participants is disabled in a certain site. * - * @param {string} [siteId] Site Id. If not defined, use current site. - * @return {Promise} Promise resolved with true if disabled, rejected or resolved with false otherwise. + * @param siteId Site Id. If not defined, use current site. + * @return Promise resolved with true if disabled, rejected or resolved with false otherwise. */ isParticipantsDisabled(siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -395,8 +421,8 @@ export class CoreUserProvider { /** * Check if course participants is disabled in a certain site. * - * @param {CoreSite} [site] Site. If not defined, use current site. - * @return {boolean} Whether it's disabled. + * @param site Site. If not defined, use current site. + * @return Whether it's disabled. */ isParticipantsDisabledInSite(site?: any): boolean { site = site || this.sitesProvider.getCurrentSite(); @@ -407,9 +433,9 @@ export class CoreUserProvider { /** * Returns whether or not participants is enabled for a certain course. * - * @param {number} courseId Course ID. - * @param {string} [siteId] Site Id. If not defined, use current site. - * @return {Promise} Promise resolved with true if plugin is enabled, rejected or resolved with false otherwise. + * @param courseId Course ID. + * @param siteId Site Id. If not defined, use current site. + * @return Promise resolved with true if plugin is enabled, rejected or resolved with false otherwise. */ isPluginEnabledForCourse(courseId: number, siteId?: string): Promise { if (!courseId) { @@ -423,8 +449,8 @@ export class CoreUserProvider { /** * Check if update profile picture is disabled in a certain site. * - * @param {CoreSite} [site] Site. If not defined, use current site. - * @return {boolean} True if disabled, false otherwise. + * @param site Site. If not defined, use current site. + * @return True if disabled, false otherwise. */ isUpdatePictureDisabledInSite(site?: CoreSite): boolean { site = site || this.sitesProvider.getCurrentSite(); @@ -434,10 +460,10 @@ export class CoreUserProvider { /** * Log User Profile View in Moodle. - * @param {number} userId User ID. - * @param {number} [courseId] Course ID. - * @param {string} [name] Name of the user. - * @return {Promise} Promise resolved when done. + * @param userId User ID. + * @param courseId Course ID. + * @param name Name of the user. + * @return Promise resolved when done. */ logView(userId: number, courseId?: number, name?: string): Promise { const params = { @@ -456,8 +482,8 @@ export class CoreUserProvider { /** * Log Participants list view in Moodle. - * @param {number} courseId Course ID. - * @return {Promise} Promise resolved when done. + * @param courseId Course ID. + * @return Promise resolved when done. */ logParticipantsView(courseId?: number): Promise { const params = { @@ -472,10 +498,10 @@ export class CoreUserProvider { /** * Prefetch user profiles and their images from a certain course. It prevents duplicates. * - * @param {number[]} userIds List of user IDs. - * @param {number} [courseId] Course the users belong to. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when prefetched. + * @param userIds List of user IDs. + * @param courseId Course the users belong to. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when prefetched. */ prefetchProfiles(userIds: number[], courseId?: number, siteId?: string): Promise { siteId = siteId || this.sitesProvider.getCurrentSiteId(); @@ -505,14 +531,51 @@ export class CoreUserProvider { return Promise.all(promises); } + /** + * Search participants in a certain course. + * + * @param courseId ID of the course. + * @param search The string to search. + * @param searchAnywhere Whether to find a match anywhere or only at the beginning. + * @param page Page to get. + * @param limitNumber Number of participants to get. + * @param siteId Site Id. If not defined, use current site. + * @return Promise resolved when the participants are retrieved. + * @since 3.8 + */ + searchParticipants(courseId: number, search: string, searchAnywhere: boolean = true, page: number = 0, + perPage: number = CoreUserProvider.PARTICIPANTS_LIST_LIMIT, siteId?: string) + : Promise<{participants: any[], canLoadMore: boolean}> { + + return this.sitesProvider.getSite(siteId).then((site) => { + + const data = { + courseid: courseId, + search: search, + searchanywhere: searchAnywhere ? 1 : 0, + page: page, + perpage: perPage, + }, preSets: any = { + getFromCache: false // Always try to get updated data. If it fails, it will get it from cache. + }; + + return site.read('core_enrol_search_users', data, preSets).then((users) => { + const canLoadMore = users.length >= perPage; + this.storeUsers(users, siteId); + + return { participants: users, canLoadMore: canLoadMore }; + }); + }); + } + /** * Store user basic information in local DB to be retrieved if the WS call fails. * - * @param {number} userId User ID. - * @param {string} fullname User full name. - * @param {string} avatar User avatar URL. - * @param {string} [siteId] ID of the site. If not defined, use current site. - * @return {Promise} Promise resolve when the user is stored. + * @param userId User ID. + * @param fullname User full name. + * @param avatar User avatar URL. + * @param siteId ID of the site. If not defined, use current site. + * @return Promise resolve when the user is stored. */ protected storeUser(userId: number, fullname: string, avatar: string, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -529,9 +592,9 @@ export class CoreUserProvider { /** * Store users basic information in local DB. * - * @param {any[]} users Users to store. Fields stored: id, fullname, profileimageurl. - * @param {string} [siteId] ID of the site. If not defined, use current site. - * @return {Promise} Promise resolve when the user is stored. + * @param users Users to store. Fields stored: id, fullname, profileimageurl. + * @param siteId ID of the site. If not defined, use current site. + * @return Promise resolve when the user is stored. */ storeUsers(users: any[], siteId?: string): Promise { const promises = []; @@ -548,10 +611,10 @@ export class CoreUserProvider { /** * Set a user preference (online or offline). * - * @param {string} name Name of the preference. - * @param {string} value Value of the preference. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved on success. + * @param name Name of the preference. + * @param value Value of the preference. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved on success. */ setUserPreference(name: string, value: string, siteId?: string): Promise { siteId = siteId || this.sitesProvider.getCurrentSiteId(); @@ -587,11 +650,11 @@ export class CoreUserProvider { /** * Update a preference for a user. * - * @param {string} name Preference name. - * @param {any} value Preference new value. - * @param {number} [userId] User ID. If not defined, site's current user. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved if success. + * @param name Preference name. + * @param value Preference new value. + * @param userId User ID. If not defined, site's current user. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved if success. */ updateUserPreference(name: string, value: any, userId?: number, siteId?: string): Promise { const preferences = [ @@ -607,11 +670,11 @@ export class CoreUserProvider { /** * Update some preferences for a user. * - * @param {{name: string, value: string}[]} preferences List of preferences. - * @param {boolean} [disableNotifications] Whether to disable all notifications. Undefined to not update this value. - * @param {number} [userId] User ID. If not defined, site's current user. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved if success. + * @param preferences List of preferences. + * @param disableNotifications Whether to disable all notifications. Undefined to not update this value. + * @param userId User ID. If not defined, site's current user. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved if success. */ updateUserPreferences(preferences: {type: string, value: string}[], disableNotifications?: boolean, userId?: number, siteId?: string): Promise { @@ -634,3 +697,21 @@ export class CoreUserProvider { }); } } + +/** + * Data returned by user_summary_exporter. + */ +export type CoreUserSummary = { + id: number; // Id. + email: string; // Email. + idnumber: string; // Idnumber. + phone1: string; // Phone1. + phone2: string; // Phone2. + department: string; // Department. + institution: string; // Institution. + fullname: string; // Fullname. + identity: string; // Identity. + profileurl: string; // Profileurl. + profileimageurl: string; // Profileimageurl. + profileimageurlsmall: string; // Profileimageurlsmall. +}; diff --git a/src/core/user/user.module.ts b/src/core/user/user.module.ts index 0370243a6..59ffb67c7 100644 --- a/src/core/user/user.module.ts +++ b/src/core/user/user.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/core/viewer/pages/iframe/iframe.module.ts b/src/core/viewer/pages/iframe/iframe.module.ts index 0430f4a0c..958c3e40c 100644 --- a/src/core/viewer/pages/iframe/iframe.module.ts +++ b/src/core/viewer/pages/iframe/iframe.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/core/viewer/pages/iframe/iframe.ts b/src/core/viewer/pages/iframe/iframe.ts index 8bcec245b..fb130528f 100644 --- a/src/core/viewer/pages/iframe/iframe.ts +++ b/src/core/viewer/pages/iframe/iframe.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/core/viewer/pages/image/image.module.ts b/src/core/viewer/pages/image/image.module.ts index f3f864171..3f079b3a7 100644 --- a/src/core/viewer/pages/image/image.module.ts +++ b/src/core/viewer/pages/image/image.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/core/viewer/pages/image/image.ts b/src/core/viewer/pages/image/image.ts index b1e7ced87..c19119715 100644 --- a/src/core/viewer/pages/image/image.ts +++ b/src/core/viewer/pages/image/image.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/core/viewer/pages/text/text.html b/src/core/viewer/pages/text/text.html index 56fc902a9..410063705 100644 --- a/src/core/viewer/pages/text/text.html +++ b/src/core/viewer/pages/text/text.html @@ -10,7 +10,7 @@ - + diff --git a/src/core/viewer/pages/text/text.module.ts b/src/core/viewer/pages/text/text.module.ts index 08a594bc3..acf320e54 100644 --- a/src/core/viewer/pages/text/text.module.ts +++ b/src/core/viewer/pages/text/text.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/core/viewer/pages/text/text.ts b/src/core/viewer/pages/text/text.ts index 9139d5473..6c8c1b6f8 100644 --- a/src/core/viewer/pages/text/text.ts +++ b/src/core/viewer/pages/text/text.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -30,6 +30,10 @@ export class CoreViewerTextPage { component: string; // Component to use in format-text. componentId: string | number; // Component ID to use in format-text. files: any[]; // List of files. + filter: boolean; // Whether to filter the text. + contextLevel: string; // The context level. + instanceId: number; // The instance ID related to the context. + courseId: number; // Course ID the text belongs to. It can be used to improve performance with filters. constructor(private viewCtrl: ViewController, params: NavParams, textUtils: CoreTextUtilsProvider) { this.title = params.get('title'); @@ -37,6 +41,10 @@ export class CoreViewerTextPage { this.component = params.get('component'); this.componentId = params.get('componentId'); this.files = params.get('files'); + this.filter = params.get('filter'); + this.contextLevel = params.get('contextLevel'); + this.instanceId = params.get('instanceId'); + this.courseId = params.get('courseId'); } /** diff --git a/src/directives/auto-focus.ts b/src/directives/auto-focus.ts index 5d6e2446d..162f3bbbc 100644 --- a/src/directives/auto-focus.ts +++ b/src/directives/auto-focus.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/directives/auto-rows.ts b/src/directives/auto-rows.ts index 3160c00f1..868fc44b7 100644 --- a/src/directives/auto-rows.ts +++ b/src/directives/auto-rows.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -56,7 +56,7 @@ export class CoreAutoRowsDirective { /** * Resize the textarea. - * @param {any} $event Event fired. + * @param $event Event fired. */ protected resize($event?: any): void { let nativeElement = this.element.nativeElement; diff --git a/src/directives/back-button.ts b/src/directives/back-button.ts index 100c2adfd..0a14f881c 100644 --- a/src/directives/back-button.ts +++ b/src/directives/back-button.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/directives/directives.module.ts b/src/directives/directives.module.ts index 6b58c1054..1217c26ce 100644 --- a/src/directives/directives.module.ts +++ b/src/directives/directives.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/directives/download-file.ts b/src/directives/download-file.ts index c8ecebc23..99c8a59e4 100644 --- a/src/directives/download-file.ts +++ b/src/directives/download-file.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/directives/external-content.ts b/src/directives/external-content.ts index 4026e7e0a..1879772c7 100644 --- a/src/directives/external-content.ts +++ b/src/directives/external-content.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -84,7 +84,7 @@ export class CoreExternalContentDirective implements AfterViewInit, OnChanges { /** * Add a new source with a certain URL as a sibling of the current element. * - * @param {string} url URL to use in the source. + * @param url URL to use in the source. */ protected addSource(url: string): void { if (this.element.tagName !== 'SOURCE') { @@ -174,10 +174,10 @@ export class CoreExternalContentDirective implements AfterViewInit, OnChanges { /** * Handle external content, setting the right URL. * - * @param {string} targetAttr Attribute to modify. - * @param {string} url Original URL to treat. - * @param {string} [siteId] Site ID. - * @return {Promise} Promise resolved if the element is successfully treated. + * @param targetAttr Attribute to modify. + * @param url Original URL to treat. + * @param siteId Site ID. + * @return Promise resolved if the element is successfully treated. */ protected handleExternalContent(targetAttr: string, url: string, siteId?: string): Promise { @@ -285,8 +285,8 @@ export class CoreExternalContentDirective implements AfterViewInit, OnChanges { /** * Handle inline styles, trying to download referenced files. * - * @param {string} siteId Site ID. - * @return {Promise} Promise resolved if the element is successfully treated. + * @param siteId Site ID. + * @return Promise resolved if the element is successfully treated. */ protected handleInlineStyles(siteId: string): Promise { let inlineStyles = this.element.getAttribute('style'); diff --git a/src/directives/fab.ts b/src/directives/fab.ts index 828c1cec5..f32da9b27 100644 --- a/src/directives/fab.ts +++ b/src/directives/fab.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/directives/format-text.ts b/src/directives/format-text.ts index ede668a6b..9c4a8c314 100644 --- a/src/directives/format-text.ts +++ b/src/directives/format-text.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -12,7 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { Directive, ElementRef, Input, Output, EventEmitter, OnChanges, SimpleChange, Optional } from '@angular/core'; +import { + Directive, ElementRef, Input, Output, EventEmitter, OnChanges, SimpleChange, Optional, ViewContainerRef +} from '@angular/core'; import { Platform, NavController, Content } from 'ionic-angular'; import { TranslateService } from '@ngx-translate/core'; import { CoreAppProvider } from '@providers/app'; @@ -30,14 +32,18 @@ import { CoreLinkDirective } from '../directives/link'; import { CoreExternalContentDirective } from '../directives/external-content'; import { CoreContentLinksHelperProvider } from '@core/contentlinks/providers/helper'; import { CoreSplitViewComponent } from '@components/split-view/split-view'; +import { CoreFilterProvider, CoreFilterFilter, CoreFilterFormatTextOptions } from '@core/filter/providers/filter'; +import { CoreFilterHelperProvider } from '@core/filter/providers/helper'; +import { CoreFilterDelegate } from '@core/filter/providers/delegate'; /** * Directive to format text rendered. It renders the HTML and treats all links and media, using CoreLinkDirective - * and CoreExternalContentDirective. + * and CoreExternalContentDirective. It also applies filters if needed. + * + * Please use this directive if your text needs to be filtered or it can contain links or media (images, audio, video). * * Example usage: * - * */ @Directive({ selector: 'core-format-text' @@ -56,19 +62,39 @@ export class CoreFormatTextDirective implements OnChanges { @Input() fullOnClick?: boolean | string; // Whether it should open a new page with the full contents on click. @Input() fullTitle?: string; // Title to use in full view. Defaults to "Description". @Input() highlight?: string; // Text to highlight. + @Input() filter?: boolean | string; // Whether to filter the text. If not defined, true if contextLevel and instanceId are set. + @Input() contextLevel?: string; // The context level of the text. + @Input() contextInstanceId?: number; // The instance ID related to the context. + @Input() courseId?: number; // Course ID the text belongs to. It can be used to improve performance with filters. + @Input() wsNotFiltered?: boolean | string; // If true it means the WS didn't filter the text for some reason. @Output() afterRender?: EventEmitter; // Called when the data is rendered. protected element: HTMLElement; protected showMoreDisplayed: boolean; protected loadingChangedListener; - constructor(element: ElementRef, private sitesProvider: CoreSitesProvider, private domUtils: CoreDomUtilsProvider, - private textUtils: CoreTextUtilsProvider, private translate: TranslateService, private platform: Platform, - private utils: CoreUtilsProvider, private urlUtils: CoreUrlUtilsProvider, private loggerProvider: CoreLoggerProvider, - private filepoolProvider: CoreFilepoolProvider, private appProvider: CoreAppProvider, - private contentLinksHelper: CoreContentLinksHelperProvider, @Optional() private navCtrl: NavController, - @Optional() private content: Content, @Optional() private svComponent: CoreSplitViewComponent, - private iframeUtils: CoreIframeUtilsProvider, private eventsProvider: CoreEventsProvider) { + constructor(element: ElementRef, + private sitesProvider: CoreSitesProvider, + private domUtils: CoreDomUtilsProvider, + private textUtils: CoreTextUtilsProvider, + private translate: TranslateService, + private platform: Platform, + private utils: CoreUtilsProvider, + private urlUtils: CoreUrlUtilsProvider, + private loggerProvider: CoreLoggerProvider, + private filepoolProvider: CoreFilepoolProvider, + private appProvider: CoreAppProvider, + private contentLinksHelper: CoreContentLinksHelperProvider, + @Optional() private navCtrl: NavController, + @Optional() private content: Content, @Optional() + private svComponent: CoreSplitViewComponent, + private iframeUtils: CoreIframeUtilsProvider, + private eventsProvider: CoreEventsProvider, + private filterProvider: CoreFilterProvider, + private filterHelper: CoreFilterHelperProvider, + private filterDelegate: CoreFilterDelegate, + private viewContainerRef: ViewContainerRef) { + this.element = element.nativeElement; this.element.classList.add('opacity-hide'); // Hide contents until they're treated. this.afterRender = new EventEmitter(); @@ -89,8 +115,8 @@ export class CoreFormatTextDirective implements OnChanges { /** * Apply CoreExternalContentDirective to a certain element. * - * @param {HTMLElement} element Element to add the attributes to. - * @return {CoreExternalContentDirective} External content instance. + * @param element Element to add the attributes to. + * @return External content instance. */ protected addExternalContent(element: HTMLElement): CoreExternalContentDirective { // Angular 2 doesn't let adding directives dynamically. Create the CoreExternalContentDirective manually. @@ -113,7 +139,7 @@ export class CoreFormatTextDirective implements OnChanges { /** * Add class to adapt media to a certain element. * - * @param {HTMLElement} element Element to add the class to. + * @param element Element to add the class to. */ protected addMediaAdaptClass(element: HTMLElement): void { element.classList.add('core-media-adapt-width'); @@ -122,7 +148,7 @@ export class CoreFormatTextDirective implements OnChanges { /** * Wrap an image with a container to adapt its width. * - * @param {HTMLElement} img Image to adapt. + * @param img Image to adapt. */ protected adaptImage(img: HTMLElement): void { // Element to wrap the image. @@ -251,7 +277,7 @@ export class CoreFormatTextDirective implements OnChanges { /** * Listener to call when the element is clicked. * - * @param {MouseEvent} e Click event. + * @param e Click event. */ protected elementClicked(e: MouseEvent): void { if (e.defaultPrevented) { @@ -276,8 +302,10 @@ export class CoreFormatTextDirective implements OnChanges { return; } else { // Open a new state with the contents. + const filter = this.utils.isTrueOrOne(this.filter); + this.textUtils.expandText(this.fullTitle || this.translate.instant('core.description'), this.text, - this.component, this.componentId); + this.component, this.componentId, undefined, filter, this.contextLevel, this.contextInstanceId, this.courseId); } } @@ -312,15 +340,15 @@ export class CoreFormatTextDirective implements OnChanges { this.text = this.text ? this.text.trim() : ''; - this.formatContents().then((div: HTMLElement) => { + this.formatContents().then((result) => { // Disable media adapt to correctly calculate the height. this.element.classList.add('core-disable-media-adapt'); this.element.innerHTML = ''; // Remove current contents. - if (this.maxHeight && div.innerHTML != '') { + if (this.maxHeight && result.div.innerHTML != '') { // Move the children to the current element to be able to calculate the height. - this.domUtils.moveChildren(div, this.element); + this.domUtils.moveChildren(result.div, this.element); // Calculate the height now. this.calculateHeight(); @@ -338,12 +366,18 @@ export class CoreFormatTextDirective implements OnChanges { }); } } else { - this.domUtils.moveChildren(div, this.element); + this.domUtils.moveChildren(result.div, this.element); // Add magnifying glasses to images. this.addMagnifyingGlasses(); } + if (result.options.filter) { + // Let filters hnadle HTML. We do it here because we don't want them to block the render of the text. + this.filterDelegate.handleHtml(this.element, result.filters, this.viewContainerRef, result.options, [], + this.component, this.componentId, result.siteId); + } + this.element.classList.remove('core-disable-media-adapt'); this.finishRender(); }); @@ -352,10 +386,17 @@ export class CoreFormatTextDirective implements OnChanges { /** * Apply formatText and set sub-directives. * - * @return {Promise} Promise resolved with a div element containing the code. + * @return Promise resolved with a div element containing the code. */ - protected formatContents(): Promise { + protected formatContents(): Promise<{div: HTMLElement, filters: CoreFilterFilter[], options: CoreFilterFormatTextOptions, + siteId: string}> { + const result = { + div: null, + filters: [], + options: {}, + siteId: this.siteId + }; let site: CoreSite; // Retrieve the site since it might be needed later. @@ -364,9 +405,36 @@ export class CoreFormatTextDirective implements OnChanges { }).then((siteInstance: CoreSite) => { site = siteInstance; - // Apply format text function. - return this.textUtils.formatText(this.text, this.utils.isTrueOrOne(this.clean), - this.utils.isTrueOrOne(this.singleLine), undefined, this.highlight); + if (site) { + result.siteId = site.getId(); + } + + if (this.contextLevel == 'course' && this.contextInstanceId <= 0) { + this.contextInstanceId = site.getSiteHomeId(); + } + + this.filter = typeof this.filter == 'undefined' ? !!(this.contextLevel && this.contextInstanceId) : !!this.filter; + + result.options = { + clean: this.utils.isTrueOrOne(this.clean), + singleLine: this.utils.isTrueOrOne(this.singleLine), + highlight: this.highlight, + courseId: this.courseId, + wsNotFiltered: this.utils.isTrueOrOne(this.wsNotFiltered) + }; + + if (this.filter) { + return this.filterHelper.getFiltersAndFormatText(this.text, this.contextLevel, this.contextInstanceId, + result.options, result.siteId).then((res) => { + + result.filters = res.filters; + + return res.text; + }); + } else { + return this.filterProvider.formatText(this.text, result.options, [], result.siteId); + } + }).then((formatted) => { const div = document.createElement('div'), canTreatVimeo = site && site.isVersionGreaterEqualThan(['3.3.4', '3.4']), @@ -426,7 +494,6 @@ export class CoreFormatTextDirective implements OnChanges { }); videos.forEach((video) => { - this.treatVideoFilters(video, navCtrl); this.treatMedia(video); }); @@ -489,7 +556,9 @@ export class CoreFormatTextDirective implements OnChanges { return promise.catch(() => { // Ignore errors. So content gets always shown. }).then(() => { - return div; + result.div = div; + + return result; }); }); } @@ -497,8 +566,8 @@ export class CoreFormatTextDirective implements OnChanges { /** * Returns the element width in pixels. * - * @param {HTMLElement} element Element to get width from. - * @return {number} The width of the element in pixels. When 0 is returned it means the element is not visible. + * @param element Element to get width from. + * @return The width of the element in pixels. When 0 is returned it means the element is not visible. */ protected getElementWidth(element: HTMLElement): number { let width = this.domUtils.getElementWidth(element); @@ -526,8 +595,8 @@ export class CoreFormatTextDirective implements OnChanges { /** * Returns the element height in pixels. * - * @param {HTMLElement} elementAng Element to get height from. - * @return {number} The height of the element in pixels. When 0 is returned it means the element is not visible. + * @param elementAng Element to get height from. + * @return The height of the element in pixels. When 0 is returned it means the element is not visible. */ protected getElementHeight(element: HTMLElement): number { return this.domUtils.getElementHeight(element) || 0; @@ -550,44 +619,10 @@ export class CoreFormatTextDirective implements OnChanges { this.showMoreDisplayed = false; } - /** - * Treat video filters. Currently only treating youtube video using video JS. - * - * @param {HTMLElement} el Video element. - * @param {NavController} navCtrl NavController to use. - */ - protected treatVideoFilters(video: HTMLElement, navCtrl: NavController): void { - // Treat Video JS Youtube video links and translate them to iframes. - if (!video.classList.contains('video-js')) { - return; - } - - const data = this.textUtils.parseJSON(video.getAttribute('data-setup') || video.getAttribute('data-setup-lazy') || '{}'), - youtubeData = data.techOrder && data.techOrder[0] && data.techOrder[0] == 'youtube' && - this.parseYoutubeUrl(data.sources && data.sources[0] && data.sources[0].src); - - if (!youtubeData || !youtubeData.videoId) { - return; - } - - const iframe = document.createElement('iframe'); - iframe.id = video.id; - iframe.src = 'https://www.youtube.com/embed/' + youtubeData.videoId; // Don't apply other params to align with Moodle web. - iframe.setAttribute('frameborder', '0'); - iframe.setAttribute('allowfullscreen', '1'); - iframe.width = '100%'; - iframe.height = '300'; - - // Replace video tag by the iframe. - video.parentNode.replaceChild(iframe, video); - - this.iframeUtils.treatFrame(iframe, false, navCtrl); - } - /** * Add media adapt class and apply CoreExternalContentDirective to the media element and its sources and tracks. * - * @param {HTMLElement} element Video or audio to treat. + * @param element Video or audio to treat. */ protected treatMedia(element: HTMLElement): void { this.addMediaAdaptClass(element); @@ -615,10 +650,10 @@ export class CoreFormatTextDirective implements OnChanges { /** * Add media adapt class and treat the iframe source. * - * @param {HTMLIFrameElement} iframe Iframe to treat. - * @param {CoreSite} site Site instance. - * @param {boolean} canTreatVimeo Whether Vimeo videos can be treated in the site. - * @param {NavController} navCtrl NavController to use. + * @param iframe Iframe to treat. + * @param site Site instance. + * @param canTreatVimeo Whether Vimeo videos can be treated in the site. + * @param navCtrl NavController to use. */ protected treatIframe(iframe: HTMLIFrameElement, site: CoreSite, canTreatVimeo: boolean, navCtrl: NavController): void { const src = iframe.src, @@ -694,54 +729,4 @@ export class CoreFormatTextDirective implements OnChanges { this.iframeUtils.treatFrame(iframe, false, navCtrl); } - - /** - * Parse a YouTube URL. - * Based on Youtube.parseUrl from Moodle media/player/videojs/amd/src/Youtube-lazy.js - * - * @param {string} url URL of the video. - */ - protected parseYoutubeUrl(url: string): {videoId: string, listId?: string, start?: number} { - const result = { - videoId: null, - listId: null, - start: null - }; - - if (!url) { - return result; - } - - url = this.textUtils.decodeHTML(url); - - // Get the video ID. - let match = url.match(/^.*(youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=|\&v=)([^#\&\?]*).*/); - - if (match && match[2].length === 11) { - result.videoId = match[2]; - } - - // Now get the playlist (if any). - match = url.match(/[?&]list=([^#\&\?]+)/); - - if (match && match[1]) { - result.listId = match[1]; - } - - // Now get the start time (if any). - match = url.match(/[?&]start=(\d+)/); - - if (match && match[1]) { - result.start = parseInt(match[1], 10); - } else { - // No start param, but it could have a time param. - match = url.match(/[?&]t=(\d+h)?(\d+m)?(\d+s)?/); - if (match) { - result.start = (match[1] ? parseInt(match[1], 10) * 3600 : 0) + (match[2] ? parseInt(match[2], 10) * 60 : 0) + - (match[3] ? parseInt(match[3], 10) : 0); - } - } - - return result; - } } diff --git a/src/directives/keep-keyboard.ts b/src/directives/keep-keyboard.ts index 1c99b27ea..7cb57b5af 100644 --- a/src/directives/keep-keyboard.ts +++ b/src/directives/keep-keyboard.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/directives/link.ts b/src/directives/link.ts index a10f69fde..5ccfe3c64 100644 --- a/src/directives/link.ts +++ b/src/directives/link.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -87,7 +87,7 @@ export class CoreLinkDirective implements OnInit { /** * Convenience function to correctly navigate, open file or url in the browser. * - * @param {string} href HREF to be opened. + * @param href HREF to be opened. */ protected navigate(href: string): void { const contentLinksScheme = CoreConfigConstants.customurlscheme + '://link='; diff --git a/src/directives/long-press.ts b/src/directives/long-press.ts index 2b6e84569..e7f385d14 100644 --- a/src/directives/long-press.ts +++ b/src/directives/long-press.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/directives/supress-events.ts b/src/directives/supress-events.ts index 88bfc891b..5ee971c18 100644 --- a/src/directives/supress-events.ts +++ b/src/directives/supress-events.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -86,7 +86,7 @@ export class CoreSupressEventsDirective implements OnInit { /** * Stop event default and propagation. * - * @param {Event} event Event. + * @param event Event. */ protected stopBubble(event: Event): void { event.preventDefault(); diff --git a/src/directives/user-link.ts b/src/directives/user-link.ts index 981e489b1..7359ee3f8 100644 --- a/src/directives/user-link.ts +++ b/src/directives/user-link.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/index.html b/src/index.html index 066800c23..bd4bf7fb1 100644 --- a/src/index.html +++ b/src/index.html @@ -58,6 +58,8 @@ + + diff --git a/src/lang/en.json b/src/lang/en.json index 7ada9676e..34a7890d1 100644 --- a/src/lang/en.json +++ b/src/lang/en.json @@ -12,7 +12,7 @@ "areyousure": "Are you sure?", "back": "Back", "cancel": "Cancel", - "cannotconnect": "Cannot connect: Verify that you have correctly typed the URL and that your site uses Moodle 3.1 or later.", + "cannotconnect": "Cannot connect: Verify that you have correctly typed the URL and that your site uses Moodle {{$a}} or later.", "cannotdownloadfiles": "File downloading is disabled. Please contact your site administrator.", "captureaudio": "Record audio", "capturedimage": "Taken picture.", @@ -120,6 +120,7 @@ "invalidformdata": "Incorrect form data", "ios": "iOS", "labelsep": ":", + "filter": "Filter", "lastaccess": "Last access", "lastdownloaded": "Last downloaded", "lastmodified": "Last modified", @@ -181,6 +182,7 @@ "notapplicable": "n/a", "notenrolledprofile": "This profile is not available because this user is not enrolled in this course.", "notice": "Notice", + "nooptionavailable": "No option available", "notingroup": "Sorry, but you need to be part of a group to see this page.", "notsent": "Not sent", "now": "now", @@ -205,6 +207,7 @@ "redirectingtosite": "You will be redirected to the site.", "refresh": "Refresh", "remove": "Remove", + "removefiles": "Remove files {{$a}}", "required": "Required", "requireduserdatamissing": "This user lacks some required profile data. Please enter the data in your site and try again.
{{$a}}", "resourcedisplayopen": "Open", @@ -213,6 +216,7 @@ "restricted": "Restricted", "retry": "Retry", "save": "Save", + "savechanges": "Save changes", "search": "Search", "searching": "Searching", "searchresults": "Search results", @@ -273,6 +277,8 @@ "unlimited": "Unlimited", "unzipping": "Unzipping", "upgraderunning": "Site is being upgraded, please retry later.", + "updaterequired": "App update required", + "updaterequireddesc": "Please update your app to version {{$a}}", "user": "User", "userdeleted": "This user account has been deleted", "userdetails": "User details", @@ -293,5 +299,7 @@ "wsfunctionnotavailable": "The web service function is not available.", "year": "year", "years": "years", - "yes": "Yes" -} \ No newline at end of file + "yes": "Yes", + "youreoffline": "You are offline", + "youreonline": "You are back online" +} diff --git a/src/pipes/bytes-to-size.ts b/src/pipes/bytes-to-size.ts index 6e2d07127..f2b37ddfc 100644 --- a/src/pipes/bytes-to-size.ts +++ b/src/pipes/bytes-to-size.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -32,8 +32,8 @@ export class CoreBytesToSizePipe implements PipeTransform { /** * Takes a number and turns it to a human readable size. * - * @param {number|string} value The bytes to convert. - * @return {string} Readable bytes. + * @param value The bytes to convert. + * @return Readable bytes. */ transform(value: number | string): string { if (typeof value == 'string') { diff --git a/src/pipes/create-links.ts b/src/pipes/create-links.ts index 35bb659d5..c0a2a66af 100644 --- a/src/pipes/create-links.ts +++ b/src/pipes/create-links.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -26,8 +26,8 @@ export class CoreCreateLinksPipe implements PipeTransform { /** * Takes some text and adds anchor tags to all links that aren't inside anchors. * - * @param {string} text Text to treat. - * @return {string} Treated text. + * @param text Text to treat. + * @return Treated text. */ transform(text: string): string { return text.replace(this.replacePattern, '$1'); diff --git a/src/pipes/date-day-or-time.ts b/src/pipes/date-day-or-time.ts index ae1da2238..8a99c076a 100644 --- a/src/pipes/date-day-or-time.ts +++ b/src/pipes/date-day-or-time.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -44,8 +44,8 @@ export class CoreDateDayOrTimePipe implements PipeTransform { /** * Format a timestamp. * - * @param {number|string} timestamp The UNIX timestamp (without milliseconds). - * @return {string} Formatted time. + * @param timestamp The UNIX timestamp (without milliseconds). + * @return Formatted time. */ transform(timestamp: string | number): string { if (typeof timestamp == 'string') { diff --git a/src/pipes/duration.ts b/src/pipes/duration.ts index 7e2f03b5b..8af08da6e 100644 --- a/src/pipes/duration.ts +++ b/src/pipes/duration.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -32,8 +32,8 @@ export class CoreDurationPipe implements PipeTransform { /** * Turn a number of seconds to a duration. E.g. 60 -> 1 minute. * - * @param {number|string} seconds The number of seconds. - * @return {string} Formatted duration. + * @param seconds The number of seconds. + * @return Formatted duration. */ transform(seconds: string | number): string { if (typeof seconds == 'string') { diff --git a/src/pipes/format-date.ts b/src/pipes/format-date.ts index f255d4716..6cff3f5bc 100644 --- a/src/pipes/format-date.ts +++ b/src/pipes/format-date.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -32,11 +32,11 @@ export class CoreFormatDatePipe implements PipeTransform { /** * Format a date. * - * @param {string|number} timestamp Timestamp to format (in milliseconds). If not defined, use current time. - * @param {string} [format] Format to use. It should be a string code to handle i18n (e.g. core.strftimetime). - * Defaults to strftimedaydatetime. - * @param {boolean} [convert] If true, convert the format from PHP to Moment. Set it to false for Moment formats. - * @return {string} Formatted date. + * @param timestamp Timestamp to format (in milliseconds). If not defined, use current time. + * @param format Format to use. It should be a string code to handle i18n (e.g. core.strftimetime). + * Defaults to strftimedaydatetime. + * @param convert If true, convert the format from PHP to Moment. Set it to false for Moment formats. + * @return Formatted date. */ transform(timestamp: string | number, format?: string, convert?: boolean): string { timestamp = timestamp || Date.now(); diff --git a/src/pipes/no-tags.ts b/src/pipes/no-tags.ts index 921241ac4..c1da56431 100644 --- a/src/pipes/no-tags.ts +++ b/src/pipes/no-tags.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -25,8 +25,8 @@ export class CoreNoTagsPipe implements PipeTransform { /** * Takes a text and removes HTML tags. * - * @param {string} text The text to treat. - * @return {string} Treated text. + * @param text The text to treat. + * @return Treated text. */ transform(text: string): string { return text.replace(/(<([^>]+)>)/ig, ''); diff --git a/src/pipes/pipes.module.ts b/src/pipes/pipes.module.ts index 7667b224b..a4bd5024f 100644 --- a/src/pipes/pipes.module.ts +++ b/src/pipes/pipes.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. diff --git a/src/pipes/seconds-to-hms.ts b/src/pipes/seconds-to-hms.ts index dd71624d6..be95a30a7 100644 --- a/src/pipes/seconds-to-hms.ts +++ b/src/pipes/seconds-to-hms.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -35,8 +35,8 @@ export class CoreSecondsToHMSPipe implements PipeTransform { /** * Convert a number of seconds to Hours:Minutes:Seconds. * - * @param {number|string} seconds Number of seconds. - * @return {string} Formatted seconds. + * @param seconds Number of seconds. + * @return Formatted seconds. */ transform(seconds: string | number): string { let hours, diff --git a/src/pipes/time-ago.ts b/src/pipes/time-ago.ts index bef10d8f0..e2462c9ac 100644 --- a/src/pipes/time-ago.ts +++ b/src/pipes/time-ago.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -33,8 +33,8 @@ export class CoreTimeAgoPipe implements PipeTransform { /** * Turn a UNIX timestamp to "time ago". * - * @param {number|string} timestamp The UNIX timestamp (without milliseconds). - * @return {string} Formatted time. + * @param timestamp The UNIX timestamp (without milliseconds). + * @return Formatted time. */ transform(timestamp: string | number): string { if (typeof timestamp == 'string') { diff --git a/src/pipes/to-locale-string.ts b/src/pipes/to-locale-string.ts index 2bc2bae49..0503c9770 100644 --- a/src/pipes/to-locale-string.ts +++ b/src/pipes/to-locale-string.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -33,8 +33,8 @@ export class CoreToLocaleStringPipe implements PipeTransform { /** * Format a timestamp to a locale string. * - * @param {number|string} timestamp The timestamp (can be in seconds or milliseconds). - * @return {string} Formatted time. + * @param timestamp The timestamp (can be in seconds or milliseconds). + * @return Formatted time. */ transform(timestamp: number | string): string { if (typeof timestamp == 'string') { diff --git a/src/providers/app.ts b/src/providers/app.ts index abbf4dd9f..f2071b076 100644 --- a/src/providers/app.ts +++ b/src/providers/app.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -30,25 +30,21 @@ import { CoreConfigConstants } from '../configconstants'; export interface CoreRedirectData { /** * ID of the site to load. - * @type {string} */ siteId?: string; /** * Name of the page to redirect to. - * @type {string} */ page?: string; /** * Params to pass to the page. - * @type {any} */ params?: any; /** * Timestamp when this redirect was last modified. - * @type {number} */ timemodified?: number; } @@ -105,13 +101,15 @@ export class CoreAppProvider { }, 100); // Export the app provider so Behat tests can change the forceOffline flag. - ( window).appProvider = this; + if (CoreAppProvider.isAutomated()) { + ( window).appProvider = this; + } } /** * Check if the browser supports mediaDevices.getUserMedia. * - * @return {boolean} Whether the function is supported. + * @return Whether the function is supported. */ canGetUserMedia(): boolean { return !!(navigator && navigator.mediaDevices && navigator.mediaDevices.getUserMedia); @@ -120,7 +118,7 @@ export class CoreAppProvider { /** * Check if the browser supports MediaRecorder. * - * @return {boolean} Whether the function is supported. + * @return Whether the function is supported. */ canRecordMedia(): boolean { return !!( window).MediaRecorder; @@ -138,7 +136,7 @@ export class CoreAppProvider { /** * Get the application global database. * - * @return {SQLiteDB} App's DB. + * @return App's DB. */ getDB(): SQLiteDB { return this.db; @@ -147,7 +145,7 @@ export class CoreAppProvider { /** * Get an ID for a main menu. * - * @return {number} Main menu ID. + * @return Main menu ID. */ getMainMenuId(): number { return this.mainMenuId++; @@ -156,17 +154,26 @@ export class CoreAppProvider { /** * Get the app's root NavController. * - * @return {NavController} Root NavController. + * @return Root NavController. */ getRootNavController(): NavController { // Function getRootNav is deprecated. Get the first root nav, there should always be one. return this.appCtrl.getRootNavs()[0]; } + /** + * Returns whether the user agent is controlled by automation. I.e. Behat testing. + * + * @return True if the user agent is controlled by automation, false otherwise. + */ + static isAutomated(): boolean { + return !!navigator.webdriver; + } + /** * Checks if the app is running in a 64 bits desktop environment (not browser). * - * @return {boolean} Whether the app is running in a 64 bits desktop environment (not browser). + * @return Whether the app is running in a 64 bits desktop environment (not browser). */ is64Bits(): boolean { const process = ( window).process; @@ -174,10 +181,19 @@ export class CoreAppProvider { return this.isDesktop() && process.arch == 'x64'; } + /** + * Checks if the app is running in an Android mobile or tablet device. + * + * @return Whether the app is running in an Android mobile or tablet device. + */ + isAndroid(): boolean { + return this.platform.is('android'); + } + /** * Checks if the app is running in a desktop environment (not browser). * - * @return {boolean} Whether the app is running in a desktop environment (not browser). + * @return Whether the app is running in a desktop environment (not browser). */ isDesktop(): boolean { const process = ( window).process; @@ -185,10 +201,19 @@ export class CoreAppProvider { return !!(process && process.versions && typeof process.versions.electron != 'undefined'); } + /** + * Checks if the app is running in an iOS mobile or tablet device. + * + * @return Whether the app is running in an iOS mobile or tablet device. + */ + isIOS(): boolean { + return this.platform.is('ios'); + } + /** * Check if the keyboard is visible. * - * @return {boolean} Whether keyboard is visible. + * @return Whether keyboard is visible. */ isKeyboardVisible(): boolean { return this.isKeyboardShown; @@ -197,7 +222,7 @@ export class CoreAppProvider { /** * Check if the app is running in a Linux environment. * - * @return {boolean} Whether it's running in a Linux environment. + * @return Whether it's running in a Linux environment. */ isLinux(): boolean { if (!this.isDesktop()) { @@ -214,7 +239,7 @@ export class CoreAppProvider { /** * Check if the app is running in a Mac OS environment. * - * @return {boolean} Whether it's running in a Mac OS environment. + * @return Whether it's running in a Mac OS environment. */ isMac(): boolean { if (!this.isDesktop()) { @@ -231,7 +256,7 @@ export class CoreAppProvider { /** * Check if the main menu is open. * - * @return {boolean} Whether the main menu is open. + * @return Whether the main menu is open. */ isMainMenuOpen(): boolean { return typeof this.mainMenuOpen != 'undefined'; @@ -240,7 +265,7 @@ export class CoreAppProvider { /** * Checks if the app is running in a mobile or tablet device (Cordova). * - * @return {boolean} Whether the app is running in a mobile or tablet device. + * @return Whether the app is running in a mobile or tablet device. */ isMobile(): boolean { return this.platform.is('cordova'); @@ -249,7 +274,7 @@ export class CoreAppProvider { /** * Checks if the current window is wider than a mobile. * - * @return {boolean} Whether the app the current window is wider than a mobile. + * @return Whether the app the current window is wider than a mobile. */ isWide(): boolean { return this.platform.width() > 768; @@ -258,7 +283,7 @@ export class CoreAppProvider { /** * Returns whether we are online. * - * @return {boolean} Whether the app is online. + * @return Whether the app is online. */ isOnline(): boolean { if (this.forceOffline) { @@ -277,7 +302,7 @@ export class CoreAppProvider { /** * Check if device uses a limited connection. * - * @return {boolean} Whether the device uses a limited connection. + * @return Whether the device uses a limited connection. */ isNetworkAccessLimited(): boolean { const type = this.network.type; @@ -294,7 +319,7 @@ export class CoreAppProvider { /** * Check if device uses a wifi connection. * - * @return {boolean} Whether the device uses a wifi connection. + * @return Whether the device uses a wifi connection. */ isWifi(): boolean { return this.isOnline() && !this.isNetworkAccessLimited(); @@ -303,7 +328,7 @@ export class CoreAppProvider { /** * Check if the app is running in a Windows environment. * - * @return {boolean} Whether it's running in a Windows environment. + * @return Whether it's running in a Windows environment. */ isWindows(): boolean { if (!this.isDesktop()) { @@ -330,8 +355,8 @@ export class CoreAppProvider { /** * Set a main menu as open or not. * - * @param {number} id Main menu ID. - * @param {boolean} open Whether it's open or not. + * @param id Main menu ID. + * @param open Whether it's open or not. */ setMainMenuOpen(id: number, open: boolean): void { if (open) { @@ -382,7 +407,7 @@ export class CoreAppProvider { /** * Check if there's an ongoing SSO authentication process. * - * @return {boolean} Whether there's a SSO authentication ongoing. + * @return Whether there's a SSO authentication ongoing. */ isSSOAuthenticationOngoing(): boolean { return !!this.ssoAuthenticationPromise; @@ -391,7 +416,7 @@ export class CoreAppProvider { /** * Returns a promise that will be resolved once SSO authentication finishes. * - * @return {Promise} Promise resolved once SSO authentication finishes. + * @return Promise resolved once SSO authentication finishes. */ waitForSSOAuthentication(): Promise { return this.ssoAuthenticationPromise || Promise.resolve(); @@ -400,7 +425,7 @@ export class CoreAppProvider { /** * Retrieve redirect data. * - * @return {CoreRedirectData} Object with siteid, state, params and timemodified. + * @return Object with siteid, state, params and timemodified. */ getRedirect(): CoreRedirectData { if (localStorage && localStorage.getItem) { @@ -428,9 +453,9 @@ export class CoreAppProvider { /** * Store redirect params. * - * @param {string} siteId Site ID. - * @param {string} page Page to go. - * @param {any} params Page params. + * @param siteId Site ID. + * @param page Page to go. + * @param params Page params. */ storeRedirect(siteId: string, page: string, params: any): void { if (localStorage && localStorage.setItem) { @@ -511,14 +536,14 @@ export class CoreAppProvider { * button is pressed. This method decides which of the registered back button * actions has the highest priority and should be called. * - * @param {Function} fn Called when the back button is pressed, - * if this registered action has the highest priority. - * @param {number} priority Set the priority for this action. All actions sorted by priority will be executed since one of - * them returns true. - * * Priorities higher or equal than 1000 will go before closing modals - * * Priorities lower than 500 will only be executed if you are in the first state of the app (before exit). - * @returns {Function} A function that, when called, will unregister - * the back button action. + * @param fn Called when the back button is pressed, + * if this registered action has the highest priority. + * @param priority Set the priority for this action. All actions sorted by priority will be executed since one of + * them returns true. + * * Priorities higher or equal than 1000 will go before closing modals + * * Priorities lower than 500 will only be executed if you are in the first state of the app (before exit). + * @return A function that, when called, will unregister + * the back button action. */ registerBackButtonAction(fn: Function, priority: number = 0): Function { const action = { fn: fn, priority: priority }; @@ -579,7 +604,7 @@ export class CoreAppProvider { /** * Set value of forceOffline flag. If true, the app will think the device is offline. * - * @param {boolean} value Value to set. + * @param value Value to set. */ setForceOffline(value: boolean): void { this.forceOffline = !!value; diff --git a/src/providers/config.ts b/src/providers/config.ts index e368c073b..38039bff6 100644 --- a/src/providers/config.ts +++ b/src/providers/config.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -47,8 +47,8 @@ export class CoreConfigProvider { /** * Deletes an app setting. * - * @param {string} name The config name. - * @return {Promise} Promise resolved when done. + * @param name The config name. + * @return Promise resolved when done. */ delete(name: string): Promise { return this.appDB.deleteRecords(this.TABLE_NAME, { name: name }); @@ -57,9 +57,9 @@ export class CoreConfigProvider { /** * Get an app setting. * - * @param {string} name The config name. - * @param {any} [defaultValue] Default value to use if the entry is not found. - * @return {Promise} Resolves upon success along with the config data. Reject on failure. + * @param name The config name. + * @param defaultValue Default value to use if the entry is not found. + * @return Resolves upon success along with the config data. Reject on failure. */ get(name: string, defaultValue?: any): Promise { return this.appDB.getRecord(this.TABLE_NAME, { name: name }).then((entry) => { @@ -76,9 +76,9 @@ export class CoreConfigProvider { /** * Set an app setting. * - * @param {string} name The config name. - * @param {number|string} value The config value. Can only store number or strings. - * @return {Promise} Promise resolved when done. + * @param name The config name. + * @param value The config value. Can only store number or strings. + * @return Promise resolved when done. */ set(name: string, value: number | string): Promise { return this.appDB.insertRecord(this.TABLE_NAME, { name: name, value: value }); diff --git a/src/providers/cron.ts b/src/providers/cron.ts index b29bc654f..dbf9ce3e1 100644 --- a/src/providers/cron.ts +++ b/src/providers/cron.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -27,57 +27,54 @@ import { SQLiteDB, SQLiteDBTableSchema } from '@classes/sqlitedb'; export interface CoreCronHandler { /** * A name to identify the handler. - * @type {string} */ name: string; /** * Returns handler's interval in milliseconds. Defaults to CoreCronDelegate.DEFAULT_INTERVAL. * - * @return {number} Interval time (in milliseconds). + * @return Interval time (in milliseconds). */ getInterval?(): number; /** * Check whether the process uses network or not. True if not defined. * - * @return {boolean} Whether the process uses network or not + * @return Whether the process uses network or not */ usesNetwork?(): boolean; /** * Check whether it's a synchronization process or not. True if not defined. * - * @return {boolean} Whether it's a synchronization process or not. + * @return Whether it's a synchronization process or not. */ isSync?(): boolean; /** * Check whether the sync can be executed manually. Call isSync if not defined. * - * @return {boolean} Whether the sync can be executed manually. + * @return Whether the sync can be executed manually. */ canManualSync?(): boolean; /** * Execute the process. * - * @param {string} [siteId] ID of the site affected. If not defined, all sites. - * @param {boolean} [force] Determines if it's a forced execution. - * @return {Promise} Promise resolved when done. If the promise is rejected, this function will be called again often, - * it shouldn't be abused. + * @param siteId ID of the site affected. If not defined, all sites. + * @param force Determines if it's a forced execution. + * @return Promise resolved when done. If the promise is rejected, this function will be called again often, + * it shouldn't be abused. */ execute?(siteId?: string, force?: boolean): Promise; /** * Whether the handler is running. Used internally by the provider, there's no need to set it. - * @type {boolean} */ running?: boolean; /** * Timeout ID for the handler scheduling. Used internally by the provider, there's no need to set it. - * @type {number} */ timeout?: number; } @@ -135,10 +132,10 @@ export class CoreCronDelegate { * Try to execute a handler. It will schedule the next execution once done. * If the handler cannot be executed or it fails, it will be re-executed after mmCoreCronMinInterval. * - * @param {string} name Name of the handler. - * @param {boolean} [force] Wether the execution is forced (manual sync). - * @param {string} [siteId] Site ID. If not defined, all sites. - * @return {Promise} Promise resolved if handler is executed successfully, rejected otherwise. + * @param name Name of the handler. + * @param force Wether the execution is forced (manual sync). + * @param siteId Site ID. If not defined, all sites. + * @return Promise resolved if handler is executed successfully, rejected otherwise. */ protected checkAndExecuteHandler(name: string, force?: boolean, siteId?: string): Promise { if (!this.handlers[name] || !this.handlers[name].execute) { @@ -204,10 +201,10 @@ export class CoreCronDelegate { /** * Run a handler, cancelling the execution if it takes more than MAX_TIME_PROCESS. * - * @param {string} name Name of the handler. - * @param {boolean} [force] Wether the execution is forced (manual sync). - * @param {string} [siteId] Site ID. If not defined, all sites. - * @return {Promise} Promise resolved when the handler finishes or reaches max time, rejected if it fails. + * @param name Name of the handler. + * @param force Wether the execution is forced (manual sync). + * @param siteId Site ID. If not defined, all sites. + * @return Promise resolved when the handler finishes or reaches max time, rejected if it fails. */ protected executeHandler(name: string, force?: boolean, siteId?: string): Promise { return new Promise((resolve, reject): void => { @@ -232,8 +229,8 @@ export class CoreCronDelegate { * Force execution of synchronization cron tasks without waiting for the scheduled time. * Please notice that some tasks may not be executed depending on the network connection and sync settings. * - * @param {string} [siteId] Site ID. If not defined, all sites. - * @return {Promise} Promise resolved if all handlers are executed successfully, rejected otherwise. + * @param siteId Site ID. If not defined, all sites. + * @return Promise resolved if all handlers are executed successfully, rejected otherwise. */ forceSyncExecution(siteId?: string): Promise { const promises = []; @@ -252,9 +249,9 @@ export class CoreCronDelegate { * Force execution of a cron tasks without waiting for the scheduled time. * Please notice that some tasks may not be executed depending on the network connection and sync settings. * - * @param {string} [name] If provided, the name of the handler. - * @param {string} [siteId] Site ID. If not defined, all sites. - * @return {Promise} Promise resolved if handler has been executed successfully, rejected otherwise. + * @param name If provided, the name of the handler. + * @param siteId Site ID. If not defined, all sites. + * @return Promise resolved if handler has been executed successfully, rejected otherwise. */ forceCronHandlerExecution(name?: string, siteId?: string): Promise { const handler = this.handlers[name]; @@ -273,8 +270,8 @@ export class CoreCronDelegate { /** * Get a handler's interval. * - * @param {string} name Handler's name. - * @return {number} Handler's interval. + * @param name Handler's name. + * @return Handler's interval. */ protected getHandlerInterval(name: string): number { if (!this.handlers[name] || !this.handlers[name].getInterval) { @@ -296,8 +293,8 @@ export class CoreCronDelegate { /** * Get a handler's last execution ID. * - * @param {string} name Handler's name. - * @return {string} Handler's last execution ID. + * @param name Handler's name. + * @return Handler's last execution ID. */ protected getHandlerLastExecutionId(name: string): string { return 'last_execution_' + name; @@ -306,8 +303,8 @@ export class CoreCronDelegate { /** * Get a handler's last execution time. If not defined, return 0. * - * @param {string} name Handler's name. - * @return {Promise} Promise resolved with the handler's last execution time. + * @param name Handler's name. + * @return Promise resolved with the handler's last execution time. */ protected getHandlerLastExecutionTime(name: string): Promise { const id = this.getHandlerLastExecutionId(name); @@ -324,8 +321,8 @@ export class CoreCronDelegate { /** * Check if a handler uses network. Defaults to true. * - * @param {string} name Handler's name. - * @return {boolean} True if handler uses network or not defined, false otherwise. + * @param name Handler's name. + * @return True if handler uses network or not defined, false otherwise. */ protected handlerUsesNetwork(name: string): boolean { if (!this.handlers[name] || !this.handlers[name].usesNetwork) { @@ -339,7 +336,7 @@ export class CoreCronDelegate { /** * Check if there is any manual sync handler registered. * - * @return {boolean} Whether it has at least 1 manual sync handler. + * @return Whether it has at least 1 manual sync handler. */ hasManualSyncHandlers(): boolean { for (const name in this.handlers) { @@ -354,7 +351,7 @@ export class CoreCronDelegate { /** * Check if there is any sync handler registered. * - * @return {boolean} Whether it has at least 1 sync handler. + * @return Whether it has at least 1 sync handler. */ hasSyncHandlers(): boolean { for (const name in this.handlers) { @@ -369,8 +366,8 @@ export class CoreCronDelegate { /** * Check if a handler can be manually synced. Defaults will use isSync instead. * - * @param {string} name Handler's name. - * @return {boolean} True if handler is a sync process and can be manually executed or not defined, false otherwise. + * @param name Handler's name. + * @return True if handler is a sync process and can be manually executed or not defined, false otherwise. */ protected isHandlerManualSync(name: string): boolean { if (!this.handlers[name] || !this.handlers[name].canManualSync) { @@ -384,8 +381,8 @@ export class CoreCronDelegate { /** * Check if a handler is a sync process. Defaults to true. * - * @param {string} name Handler's name. - * @return {boolean} True if handler is a sync process or not defined, false otherwise. + * @param name Handler's name. + * @return True if handler is a sync process or not defined, false otherwise. */ protected isHandlerSync(name: string): boolean { if (!this.handlers[name] || !this.handlers[name].isSync) { @@ -399,7 +396,7 @@ export class CoreCronDelegate { /** * Register a handler to be executed every certain time. * - * @param {CoreCronHandler} handler The handler to register. + * @param handler The handler to register. */ register(handler: CoreCronHandler): void { if (!handler || !handler.name) { @@ -424,9 +421,9 @@ export class CoreCronDelegate { /** * Schedule a next execution for a handler. * - * @param {string} name Name of the handler. - * @param {number} [time] Time to the next execution. If not supplied it will be calculated using the last execution and - * the handler's interval. This param should be used only if it's really necessary. + * @param name Name of the handler. + * @param time Time to the next execution. If not supplied it will be calculated using the last execution and + * the handler's interval. This param should be used only if it's really necessary. */ protected scheduleNextExecution(name: string, time?: number): void { if (!this.handlers[name]) { @@ -470,9 +467,9 @@ export class CoreCronDelegate { /** * Set a handler's last execution time. * - * @param {string} name Handler's name. - * @param {number} time Time to set. - * @return {Promise} Promise resolved when the execution time is saved. + * @param name Handler's name. + * @param time Time to set. + * @return Promise resolved when the execution time is saved. */ protected setHandlerLastExecutionTime(name: string, time: number): Promise { const id = this.getHandlerLastExecutionId(name), @@ -487,7 +484,7 @@ export class CoreCronDelegate { /** * Start running a handler periodically. * - * @param {string} name Name of the handler. + * @param name Name of the handler. */ protected startHandler(name: string): void { if (!this.handlers[name]) { @@ -522,7 +519,7 @@ export class CoreCronDelegate { /** * Stop running a handler periodically. * - * @param {string} name Name of the handler. + * @param name Name of the handler. */ protected stopHandler(name: string): void { if (!this.handlers[name]) { diff --git a/src/providers/db.ts b/src/providers/db.ts index e1007fa27..6805c82e1 100644 --- a/src/providers/db.ts +++ b/src/providers/db.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -33,9 +33,9 @@ export class CoreDbProvider { * * The database objects are cached statically. * - * @param {string} name DB name. - * @param {boolean} forceNew True if it should always create a new instance. - * @return {SQLiteDB} DB. + * @param name DB name. + * @param forceNew True if it should always create a new instance. + * @return DB. */ getDB(name: string, forceNew?: boolean): SQLiteDB { if (typeof this.dbInstances[name] === 'undefined' || forceNew) { @@ -52,8 +52,8 @@ export class CoreDbProvider { /** * Delete a DB. * - * @param {string} name DB name. - * @return {Promise} Promise resolved when the DB is deleted. + * @param name DB name. + * @return Promise resolved when the DB is deleted. */ deleteDB(name: string): Promise { let promise; diff --git a/src/providers/events.ts b/src/providers/events.ts index 48f3dcf32..5d1557ed8 100644 --- a/src/providers/events.ts +++ b/src/providers/events.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -62,6 +62,8 @@ export class CoreEventsProvider { static SEND_ON_ENTER_CHANGED = 'send_on_enter_changed'; static MAIN_MENU_OPEN = 'main_menu_open'; static SELECT_COURSE_TAB = 'select_course_tab'; + static WS_CACHE_INVALIDATED = 'ws_cache_invalidated'; + static SITE_STORAGE_DELETED = 'site_storage_deleted'; protected logger; protected observables: { [s: string]: Subject } = {}; @@ -77,10 +79,10 @@ export class CoreEventsProvider { * ... * observer.off(); * - * @param {string} eventName Name of the event to listen to. - * @param {Function} callBack Function to call when the event is triggered. - * @param {string} [siteId] Site where to trigger the event. Undefined won't check the site. - * @return {CoreEventObserver} Observer to stop listening. + * @param eventName Name of the event to listen to. + * @param callBack Function to call when the event is triggered. + * @param siteId Site where to trigger the event. Undefined won't check the site. + * @return Observer to stop listening. */ on(eventName: string, callBack: (value: any) => void, siteId?: string): CoreEventObserver { // If it's a unique event and has been triggered already, call the callBack. @@ -121,9 +123,9 @@ export class CoreEventsProvider { /** * Triggers an event, notifying all the observers. * - * @param {string} event Name of the event to trigger. - * @param {any} [data] Data to pass to the observers. - * @param {string} [siteId] Site where to trigger the event. Undefined means no Site. + * @param event Name of the event to trigger. + * @param data Data to pass to the observers. + * @param siteId Site where to trigger the event. Undefined means no Site. */ trigger(eventName: string, data?: any, siteId?: string): void { this.logger.debug(`Event '${eventName}' triggered.`); @@ -141,9 +143,9 @@ export class CoreEventsProvider { /** * Triggers a unique event, notifying all the observers. If the event has already been triggered, don't do anything. * - * @param {string} event Name of the event to trigger. - * @param {any} data Data to pass to the observers. - * @param {string} [siteId] Site where to trigger the event. Undefined means no Site. + * @param event Name of the event to trigger. + * @param data Data to pass to the observers. + * @param siteId Site where to trigger the event. Undefined means no Site. */ triggerUnique(eventName: string, data: any, siteId?: string): void { if (this.uniqueEvents[eventName]) { diff --git a/src/providers/file-helper.ts b/src/providers/file-helper.ts index 0260eb610..73daa59b3 100644 --- a/src/providers/file-helper.ts +++ b/src/providers/file-helper.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -18,6 +18,7 @@ import { CoreAppProvider } from './app'; import { CoreFileProvider } from './file'; import { CoreFilepoolProvider } from './filepool'; import { CoreSitesProvider } from './sites'; +import { CoreWSProvider } from './ws'; import { CoreUtilsProvider } from './utils/utils'; import { CoreConstants } from '@core/constants'; @@ -29,18 +30,18 @@ export class CoreFileHelperProvider { constructor(private fileProvider: CoreFileProvider, private filepoolProvider: CoreFilepoolProvider, private sitesProvider: CoreSitesProvider, private appProvider: CoreAppProvider, private translate: TranslateService, - private utils: CoreUtilsProvider) { } + private utils: CoreUtilsProvider, private wsProvider: CoreWSProvider) { } /** * Convenience function to open a file, downloading it if needed. * - * @param {any} file The file to download. - * @param {string} [component] The component to link the file to. - * @param {string|number} [componentId] An ID to use in conjunction with the component. - * @param {string} [state] The file's state. If not provided, it will be calculated. - * @param {Function} [onProgress] Function to call on progress. - * @param {string} [siteId] The site ID. If not defined, current site. - * @return {Promise} Resolved on success. + * @param file The file to download. + * @param component The component to link the file to. + * @param componentId An ID to use in conjunction with the component. + * @param state The file's state. If not provided, it will be calculated. + * @param onProgress Function to call on progress. + * @param siteId The site ID. If not defined, current site. + * @return Resolved on success. */ downloadAndOpenFile(file: any, component: string, componentId: string | number, state?: string, onProgress?: (event: any) => any, siteId?: string): Promise { @@ -104,22 +105,23 @@ export class CoreFileHelperProvider { /** * Download a file if it needs to be downloaded. * - * @param {any} file The file to download. - * @param {string} fileUrl The file URL. - * @param {string} [component] The component to link the file to. - * @param {string|number} [componentId] An ID to use in conjunction with the component. - * @param {number} [timemodified] The time this file was modified. - * @param {string} [state] The file's state. If not provided, it will be calculated. - * @param {Function} [onProgress] Function to call on progress. - * @param {string} [siteId] The site ID. If not defined, current site. - * @return {Promise} Resolved with the URL to use on success. + * @param file The file to download. + * @param fileUrl The file URL. + * @param component The component to link the file to. + * @param componentId An ID to use in conjunction with the component. + * @param timemodified The time this file was modified. + * @param state The file's state. If not provided, it will be calculated. + * @param onProgress Function to call on progress. + * @param siteId The site ID. If not defined, current site. + * @return Resolved with the URL to use on success. */ protected downloadFileIfNeeded(file: any, fileUrl: string, component?: string, componentId?: string | number, timemodified?: number, state?: string, onProgress?: (event: any) => any, siteId?: string): Promise { siteId = siteId || this.sitesProvider.getCurrentSiteId(); return this.sitesProvider.getSite(siteId).then((site) => { - const fixedUrl = site.fixPluginfileURL(fileUrl); + return site.checkAndFixPluginfileURL(fileUrl); + }).then((fixedUrl) => { if (this.fileProvider.isAvailable()) { let promise; @@ -185,14 +187,14 @@ export class CoreFileHelperProvider { /** * Download the file. * - * @param {string} fileUrl The file URL. - * @param {string} [component] The component to link the file to. - * @param {string|number} [componentId] An ID to use in conjunction with the component. - * @param {number} [timemodified] The time this file was modified. - * @param {Function} [onProgress] Function to call on progress. - * @param {any} [file] The file to download. - * @param {string} [siteId] The site ID. If not defined, current site. - * @return {Promise} Resolved with internal URL on success, rejected otherwise. + * @param fileUrl The file URL. + * @param component The component to link the file to. + * @param componentId An ID to use in conjunction with the component. + * @param timemodified The time this file was modified. + * @param onProgress Function to call on progress. + * @param file The file to download. + * @param siteId The site ID. If not defined, current site. + * @return Resolved with internal URL on success, rejected otherwise. */ downloadFile(fileUrl: string, component?: string, componentId?: string | number, timemodified?: number, onProgress?: (event: any) => any, file?: any, siteId?: string): Promise { @@ -222,7 +224,7 @@ export class CoreFileHelperProvider { /** * Get the file's URL. * - * @param {any} file The file. + * @param file The file. */ getFileUrl(file: any): string { return file.fileurl || file.url; @@ -231,7 +233,7 @@ export class CoreFileHelperProvider { /** * Get the file's timemodified. * - * @param {any} file The file. + * @param file The file. */ getFileTimemodified(file: any): number { return file.timemodified || 0; @@ -240,7 +242,7 @@ export class CoreFileHelperProvider { /** * Check if a state is downloaded or outdated. * - * @param {string} state The state to check. + * @param state The state to check. */ isStateDownloaded(state: string): boolean { return state === CoreConstants.DOWNLOADED || state === CoreConstants.OUTDATED; @@ -250,8 +252,8 @@ export class CoreFileHelperProvider { * Whether the file has to be opened in browser (external repository). * The file must have a mimetype attribute. * - * @param {any} file The file to check. - * @return {boolean} Whether the file should be opened in browser. + * @param file The file to check. + * @return Whether the file should be opened in browser. */ shouldOpenInBrowser(file: any): boolean { if (!file || !file.isexternalfile || !file.mimetype) { @@ -272,4 +274,64 @@ export class CoreFileHelperProvider { return false; } + + /** + * Calculate the total size of the given files. + * + * @param files The files to check. + * @return Total files size. + */ + async getTotalFilesSize(files: any[]): Promise { + let totalSize = 0; + + for (const file of files) { + totalSize += await this.getFileSize(file); + } + + return totalSize; + } + + /** + * Calculate the file size. + * + * @param file The file to check. + * @return File size. + */ + async getFileSize(file: any): Promise { + if (file.filesize) { + return file.filesize; + } + + // If it's a remote file. First check if we have the file downloaded since it's more reliable. + if (file.filename && !file.name) { + try { + const siteId = this.sitesProvider.getCurrentSiteId(); + + const path = await this.filepoolProvider.getFilePathByUrl(siteId, file.fileurl); + const fileEntry = await this.fileProvider.getFile(path); + const fileObject = await this.fileProvider.getFileObjectFromFileEntry(fileEntry); + + return fileObject.size; + } catch (error) { + // Error getting the file, maybe it's not downloaded. Get remote size. + const size = await this.wsProvider.getRemoteFileSize(file.fileurl); + + if (size === -1) { + throw new Error('Couldn\'t determine file size: ' + file.fileurl); + } + + return size; + } + } + + // If it's a local file, get its size. + if (file.name) { + const fileObject = await this.fileProvider.getFileObjectFromFileEntry(file); + + return fileObject.size; + } + + throw new Error('Couldn\'t determine file size: ' + file.fileurl); + } + } diff --git a/src/providers/file-session.ts b/src/providers/file-session.ts index 8d35df55e..79798ae0f 100644 --- a/src/providers/file-session.ts +++ b/src/providers/file-session.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -31,10 +31,10 @@ export class CoreFileSessionProvider { /** * Add a file to the session. * - * @param {string} component Component Name. - * @param {string|number} id File area identifier. - * @param {any} file File to add. - * @param {string} [siteId] Site ID. If not defined, current site. + * @param component Component Name. + * @param id File area identifier. + * @param file File to add. + * @param siteId Site ID. If not defined, current site. */ addFile(component: string, id: string | number, file: any, siteId?: string): void { siteId = siteId || this.sitesProvider.getCurrentSiteId(); @@ -47,9 +47,9 @@ export class CoreFileSessionProvider { /** * Clear files stored in session. * - * @param {string} component Component Name. - * @param {string|number} id File area identifier. - * @param {string} [siteId] Site ID. If not defined, current site. + * @param component Component Name. + * @param id File area identifier. + * @param siteId Site ID. If not defined, current site. */ clearFiles(component: string, id: string | number, siteId?: string): void { siteId = siteId || this.sitesProvider.getCurrentSiteId(); @@ -61,10 +61,10 @@ export class CoreFileSessionProvider { /** * Get files stored in session. * - * @param {string} component Component Name. - * @param {string|number} id File area identifier. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {any[]} Array of files in session. + * @param component Component Name. + * @param id File area identifier. + * @param siteId Site ID. If not defined, current site. + * @return Array of files in session. */ getFiles(component: string, id: string | number, siteId?: string): any[] { siteId = siteId || this.sitesProvider.getCurrentSiteId(); @@ -78,9 +78,9 @@ export class CoreFileSessionProvider { /** * Initializes the filearea to store the file. * - * @param {string} component Component Name. - * @param {string|number} id File area identifier. - * @param {string} [siteId] Site ID. If not defined, current site. + * @param component Component Name. + * @param id File area identifier. + * @param siteId Site ID. If not defined, current site. */ protected initFileArea(component: string, id: string | number, siteId?: string): void { if (!this.files[siteId]) { @@ -99,10 +99,10 @@ export class CoreFileSessionProvider { /** * Remove a file stored in session. * - * @param {string} component Component Name. - * @param {string|number} id File area identifier. - * @param {any} file File to remove. The instance should be exactly the same as the one stored in session. - * @param {string} [siteId] Site ID. If not defined, current site. + * @param component Component Name. + * @param id File area identifier. + * @param file File to remove. The instance should be exactly the same as the one stored in session. + * @param siteId Site ID. If not defined, current site. */ removeFile(component: string, id: string | number, file: any, siteId?: string): void { siteId = siteId || this.sitesProvider.getCurrentSiteId(); @@ -117,10 +117,10 @@ export class CoreFileSessionProvider { /** * Remove a file stored in session. * - * @param {string} component Component Name. - * @param {string|number} id File area identifier. - * @param {number} index Position of the file to remove. - * @param {string} [siteId] Site ID. If not defined, current site. + * @param component Component Name. + * @param id File area identifier. + * @param index Position of the file to remove. + * @param siteId Site ID. If not defined, current site. */ removeFileByIndex(component: string, id: string | number, index: number, siteId?: string): void { siteId = siteId || this.sitesProvider.getCurrentSiteId(); @@ -133,10 +133,10 @@ export class CoreFileSessionProvider { /** * Set a group of files in the session. * - * @param {string} component Component Name. - * @param {string|number} id File area identifier. - * @param {any[]} newFiles Files to set. - * @param {string} [siteId] Site ID. If not defined, current site. + * @param component Component Name. + * @param id File area identifier. + * @param newFiles Files to set. + * @param siteId Site ID. If not defined, current site. */ setFiles(component: string, id: string | number, newFiles: any[], siteId?: string): void { siteId = siteId || this.sitesProvider.getCurrentSiteId(); diff --git a/src/providers/file.ts b/src/providers/file.ts index a2c53ceb1..07d1628f5 100644 --- a/src/providers/file.ts +++ b/src/providers/file.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -28,19 +28,16 @@ import { Zip } from '@ionic-native/zip'; export interface CoreFileProgressEvent { /** * Whether the values are reliabñe. - * @type {boolean} */ lengthComputable?: boolean; /** * Number of treated bytes. - * @type {number} */ loaded?: number; /** * Total of bytes. - * @type {number} */ total?: number; } @@ -55,6 +52,7 @@ export class CoreFileProvider { static FORMATDATAURL = 1; static FORMATBINARYSTRING = 2; static FORMATARRAYBUFFER = 3; + static FORMATJSON = 4; // Folders. static SITESFOLDER = 'sites'; @@ -97,7 +95,7 @@ export class CoreFileProvider { /** * Define an event for FileReader. * - * @param {string} eventName Name of the event. + * @param eventName Name of the event. */ protected defineEvent(eventName: string): void { this.defineGetterSetter(FileReader.prototype, eventName, function(): any { @@ -110,10 +108,10 @@ export class CoreFileProvider { /** * Define a getter and, optionally, a setter for a certain property in an object. * - * @param {any} obj Object to set the getter/setter for. - * @param {string} key Name of the property where to set them. - * @param {Function} getFunc The getter function. - * @param {Function} [setFunc] The setter function. + * @param obj Object to set the getter/setter for. + * @param key Name of the property where to set them. + * @param getFunc The getter function. + * @param setFunc The setter function. */ protected defineGetterSetter(obj: any, key: string, getFunc: Function, setFunc?: Function): void { if (Object.defineProperty) { @@ -138,7 +136,7 @@ export class CoreFileProvider { /** * Sets basePath to use with HTML API. Reserved for core use. * - * @param {string} path Base path to use. + * @param path Base path to use. */ setHTMLBasePath(path: string): void { this.isHTMLAPI = true; @@ -148,7 +146,7 @@ export class CoreFileProvider { /** * Checks if we're using HTML API. * - * @return {boolean} True if uses HTML API, false otherwise. + * @return True if uses HTML API, false otherwise. */ usesHTMLAPI(): boolean { return this.isHTMLAPI; @@ -157,7 +155,7 @@ export class CoreFileProvider { /** * Initialize basePath based on the OS if it's not initialized already. * - * @return {Promise} Promise to be resolved when the initialization is finished. + * @return Promise to be resolved when the initialization is finished. */ init(): Promise { if (this.initialized) { @@ -184,7 +182,7 @@ export class CoreFileProvider { /** * Check if the plugin is available. * - * @return {boolean} Whether the plugin is available. + * @return Whether the plugin is available. */ isAvailable(): boolean { return typeof window.resolveLocalFileSystemURL !== 'undefined'; @@ -193,8 +191,8 @@ export class CoreFileProvider { /** * Get a file. * - * @param {string} path Relative path to the file. - * @return {Promise} Promise resolved when the file is retrieved. + * @param path Relative path to the file. + * @return Promise resolved when the file is retrieved. */ getFile(path: string): Promise { return this.init().then(() => { @@ -209,8 +207,8 @@ export class CoreFileProvider { /** * Get a directory. * - * @param {string} path Relative path to the directory. - * @return {Promise} Promise resolved when the directory is retrieved. + * @param path Relative path to the directory. + * @return Promise resolved when the directory is retrieved. */ getDir(path: string): Promise { return this.init().then(() => { @@ -223,8 +221,8 @@ export class CoreFileProvider { /** * Get site folder path. * - * @param {string} siteId Site ID. - * @return {string} Site folder path. + * @param siteId Site ID. + * @return Site folder path. */ getSiteFolder(siteId: string): string { return CoreFileProvider.SITESFOLDER + '/' + siteId; @@ -233,11 +231,11 @@ export class CoreFileProvider { /** * Create a directory or a file. * - * @param {boolean} isDirectory True if a directory should be created, false if it should create a file. - * @param {string} path Relative path to the dir/file. - * @param {boolean} [failIfExists] True if it should fail if the dir/file exists, false otherwise. - * @param {string} [base] Base path to create the dir/file in. If not set, use basePath. - * @return {Promise} Promise to be resolved when the dir/file is created. + * @param isDirectory True if a directory should be created, false if it should create a file. + * @param path Relative path to the dir/file. + * @param failIfExists True if it should fail if the dir/file exists, false otherwise. + * @param base Base path to create the dir/file in. If not set, use basePath. + * @return Promise to be resolved when the dir/file is created. */ protected create(isDirectory: boolean, path: string, failIfExists?: boolean, base?: string): Promise { return this.init().then(() => { @@ -277,9 +275,9 @@ export class CoreFileProvider { /** * Create a directory. * - * @param {string} path Relative path to the directory. - * @param {boolean} [failIfExists] True if it should fail if the directory exists, false otherwise. - * @return {Promise} Promise to be resolved when the directory is created. + * @param path Relative path to the directory. + * @param failIfExists True if it should fail if the directory exists, false otherwise. + * @return Promise to be resolved when the directory is created. */ createDir(path: string, failIfExists?: boolean): Promise { return this.create(true, path, failIfExists); @@ -288,9 +286,9 @@ export class CoreFileProvider { /** * Create a file. * - * @param {string} path Relative path to the file. - * @param {boolean} [failIfExists] True if it should fail if the file exists, false otherwise.. - * @return {Promise} Promise to be resolved when the file is created. + * @param path Relative path to the file. + * @param failIfExists True if it should fail if the file exists, false otherwise.. + * @return Promise to be resolved when the file is created. */ createFile(path: string, failIfExists?: boolean): Promise { return this.create(false, path, failIfExists); @@ -299,8 +297,8 @@ export class CoreFileProvider { /** * Removes a directory and all its contents. * - * @param {string} path Relative path to the directory. - * @return {Promise} Promise to be resolved when the directory is deleted. + * @param path Relative path to the directory. + * @return Promise to be resolved when the directory is deleted. */ removeDir(path: string): Promise { return this.init().then(() => { @@ -315,8 +313,8 @@ export class CoreFileProvider { /** * Removes a file and all its contents. * - * @param {string} path Relative path to the file. - * @return {Promise} Promise to be resolved when the file is deleted. + * @param path Relative path to the file. + * @return Promise to be resolved when the file is deleted. */ removeFile(path: string): Promise { return this.init().then(() => { @@ -340,8 +338,8 @@ export class CoreFileProvider { /** * Removes a file given its FileEntry. * - * @param {FileEntry} fileEntry File Entry. - * @return {Promise} Promise resolved when the file is deleted. + * @param fileEntry File Entry. + * @return Promise resolved when the file is deleted. */ removeFileByFileEntry(fileEntry: any): Promise { return new Promise((resolve, reject): void => { @@ -352,8 +350,8 @@ export class CoreFileProvider { /** * Retrieve the contents of a directory (not subdirectories). * - * @param {string} path Relative path to the directory. - * @return {Promise} Promise to be resolved when the contents are retrieved. + * @param path Relative path to the directory. + * @return Promise to be resolved when the contents are retrieved. */ getDirectoryContents(path: string): Promise { return this.init().then(() => { @@ -368,8 +366,8 @@ export class CoreFileProvider { /** * Calculate the size of a directory or a file. * - * @param {any} entry Directory or file. - * @return {Promise} Promise to be resolved when the size is calculated. + * @param entry Directory or file. + * @return Promise to be resolved when the size is calculated. */ protected getSize(entry: any): Promise { return new Promise((resolve, reject): void => { @@ -411,8 +409,8 @@ export class CoreFileProvider { /** * Calculate the size of a directory. * - * @param {string} path Relative path to the directory. - * @return {Promise} Promise to be resolved when the size is calculated. + * @param path Relative path to the directory. + * @return Promise to be resolved when the size is calculated. */ getDirectorySize(path: string): Promise { // Remove basePath if it's in the path. @@ -428,8 +426,8 @@ export class CoreFileProvider { /** * Calculate the size of a file. * - * @param {string} path Relative path to the file. - * @return {Promise} Promise to be resolved when the size is calculated. + * @param path Relative path to the file. + * @return Promise to be resolved when the size is calculated. */ getFileSize(path: string): Promise { // Remove basePath if it's in the path. @@ -445,8 +443,8 @@ export class CoreFileProvider { /** * Get file object from a FileEntry. * - * @param {FileEntry} path Relative path to the file. - * @return {Promise} Promise to be resolved when the file is retrieved. + * @param path Relative path to the file. + * @return Promise to be resolved when the file is retrieved. */ getFileObjectFromFileEntry(entry: FileEntry): Promise { return new Promise((resolve, reject): void => { @@ -459,7 +457,7 @@ export class CoreFileProvider { * Calculate the free space in the disk. * Please notice that this function isn't reliable and it's not documented in the Cordova File plugin. * - * @return {Promise} Promise resolved with the estimated free space in bytes. + * @return Promise resolved with the estimated free space in bytes. */ calculateFreeSpace(): Promise { return this.file.getFreeDiskSpace().then((size) => { @@ -476,8 +474,8 @@ export class CoreFileProvider { /** * Normalize a filename that usually comes URL encoded. * - * @param {string} filename The file name. - * @return {string} The file name normalized. + * @param filename The file name. + * @return The file name normalized. */ normalizeFileName(filename: string): string { filename = this.textUtils.decodeURIComponent(filename); @@ -488,13 +486,14 @@ export class CoreFileProvider { /** * Read a file from local file system. * - * @param {string} path Relative path to the file. - * @param {number} [format=FORMATTEXT] Format to read the file. Must be one of: - * FORMATTEXT - * FORMATDATAURL - * FORMATBINARYSTRING - * FORMATARRAYBUFFER - * @return {Promise} Promise to be resolved when the file is read. + * @param path Relative path to the file. + * @param format Format to read the file. Must be one of: + * FORMATTEXT + * FORMATDATAURL + * FORMATBINARYSTRING + * FORMATARRAYBUFFER + * FORMATJSON + * @return Promise to be resolved when the file is read. */ readFile(path: string, format: number = CoreFileProvider.FORMATTEXT): Promise { // Remove basePath if it's in the path. @@ -508,6 +507,16 @@ export class CoreFileProvider { return this.file.readAsBinaryString(this.basePath, path); case CoreFileProvider.FORMATARRAYBUFFER: return this.file.readAsArrayBuffer(this.basePath, path); + case CoreFileProvider.FORMATJSON: + return this.file.readAsText(this.basePath, path).then((text) => { + const parsed = this.textUtils.parseJSON(text, null); + + if (parsed == null && text != null) { + return Promise.reject('Error parsing JSON file: ' + path); + } + + return parsed; + }); default: return this.file.readAsText(this.basePath, path); } @@ -516,13 +525,14 @@ export class CoreFileProvider { /** * Read file contents from a file data object. * - * @param {any} fileData File's data. - * @param {number} [format=FORMATTEXT] Format to read the file. Must be one of: - * FORMATTEXT - * FORMATDATAURL - * FORMATBINARYSTRING - * FORMATARRAYBUFFER - * @return {Promise} Promise to be resolved when the file is read. + * @param fileData File's data. + * @param format Format to read the file. Must be one of: + * FORMATTEXT + * FORMATDATAURL + * FORMATBINARYSTRING + * FORMATARRAYBUFFER + * FORMATJSON + * @return Promise to be resolved when the file is read. */ readFileData(fileData: any, format: number = CoreFileProvider.FORMATTEXT): Promise { format = format || CoreFileProvider.FORMATTEXT; @@ -534,7 +544,18 @@ export class CoreFileProvider { reader.onloadend = (evt): void => { const target = evt.target; // Convert to to be able to use non-standard properties. if (target.result !== undefined || target.result !== null) { - resolve(target.result); + if (format == CoreFileProvider.FORMATJSON) { + // Convert to object. + const parsed = this.textUtils.parseJSON(target.result, null); + + if (parsed == null) { + reject('Error parsing JSON file.'); + } + + resolve(parsed); + } else { + resolve(target.result); + } } else if (target.error !== undefined || target.error !== null) { reject(target.error); } else { @@ -574,10 +595,10 @@ export class CoreFileProvider { /** * Writes some data in a file. * - * @param {string} path Relative path to the file. - * @param {any} data Data to write. - * @param {boolean} [append] Whether to append the data to the end of the file. - * @return {Promise} Promise to be resolved when the file is written. + * @param path Relative path to the file. + * @param data Data to write. + * @param append Whether to append the data to the end of the file. + * @return Promise to be resolved when the file is written. */ writeFile(path: string, data: any, append?: boolean): Promise { return this.init().then(() => { @@ -605,12 +626,12 @@ export class CoreFileProvider { * Write some file data into a filesystem file. * It's done in chunks to prevent crashing the app for big files. * - * @param {any} file The data to write. - * @param {string} path Path where to store the data. - * @param {Function} [onProgress] Function to call on progress. - * @param {number} [offset=0] Offset where to start reading from. - * @param {boolean} [append] Whether to append the data to the end of the file. - * @return {Promise} Promise resolved when done. + * @param file The data to write. + * @param path Path where to store the data. + * @param onProgress Function to call on progress. + * @param offset Offset where to start reading from. + * @param append Whether to append the data to the end of the file. + * @return Promise resolved when done. */ writeFileDataInFile(file: any, path: string, onProgress?: (event: CoreFileProgressEvent) => any, offset: number = 0, append?: boolean): Promise { @@ -642,10 +663,10 @@ export class CoreFileProvider { /** * Write a chunk of data into a file. * - * @param {any} chunkData The chunk of data. - * @param {string} path Path where to store the data. - * @param {boolean} [append] Whether to append the data to the end of the file. - * @return {Promise} Promise resolved when done. + * @param chunkData The chunk of data. + * @param path Path where to store the data. + * @param append Whether to append the data to the end of the file. + * @return Promise resolved when done. */ protected writeFileDataInFileChunk(chunkData: any, path: string, append?: boolean): Promise { // Read the chunk data. @@ -658,8 +679,8 @@ export class CoreFileProvider { /** * Gets a file that might be outside the app's folder. * - * @param {string} fullPath Absolute path to the file. - * @return {Promise} Promise to be resolved when the file is retrieved. + * @param fullPath Absolute path to the file. + * @return Promise to be resolved when the file is retrieved. */ getExternalFile(fullPath: string): Promise { return this.file.resolveLocalFilesystemUrl(fullPath).then((entry) => { @@ -670,8 +691,8 @@ export class CoreFileProvider { /** * Removes a file that might be outside the app's folder. * - * @param {string} fullPath Absolute path to the file. - * @return {Promise} Promise to be resolved when the file is removed. + * @param fullPath Absolute path to the file. + * @return Promise to be resolved when the file is removed. */ removeExternalFile(fullPath: string): Promise { const directory = fullPath.substring(0, fullPath.lastIndexOf('/')), @@ -683,7 +704,7 @@ export class CoreFileProvider { /** * Get the base path where the application files are stored. * - * @return {Promise} Promise to be resolved when the base path is retrieved. + * @return Promise to be resolved when the base path is retrieved. */ getBasePath(): Promise { return this.init().then(() => { @@ -700,7 +721,7 @@ export class CoreFileProvider { * iOS: Internal URL (cdvfile://). * Others: basePath (file://) * - * @return {Promise} Promise to be resolved when the base path is retrieved. + * @return Promise to be resolved when the base path is retrieved. */ getBasePathToDownload(): Promise { return this.init().then(() => { @@ -719,7 +740,7 @@ export class CoreFileProvider { /** * Get the base path where the application files are stored. Returns the value instantly, without waiting for it to be ready. * - * @return {string} Base path. If the service hasn't been initialized it will return an invalid value. + * @return Base path. If the service hasn't been initialized it will return an invalid value. */ getBasePathInstant(): string { if (!this.basePath) { @@ -731,19 +752,58 @@ export class CoreFileProvider { } } + /** + * Move a dir. + * + * @param originalPath Path to the dir to move. + * @param newPath New path of the dir. + * @param destDirExists Set it to true if you know the directory where to put the dir exists. If false, the function will + * try to create it (slower). + * @return Promise resolved when the entry is moved. + */ + moveDir(originalPath: string, newPath: string, destDirExists?: boolean): Promise { + return this.moveFileOrDir(originalPath, newPath, true, destDirExists); + } + /** * Move a file. * - * @param {string} [originalPath] Path to the file to move. - * @param {string} [newPath] New path of the file. - * @return {Promise} Promise resolved when the entry is moved. + * @param originalPath Path to the file to move. + * @param newPath New path of the file. + * @param destDirExists Set it to true if you know the directory where to put the file exists. If false, the function will + * try to create it (slower). + * @return Promise resolved when the entry is moved. */ - moveFile(originalPath: string, newPath: string): Promise { + moveFile(originalPath: string, newPath: string, destDirExists?: boolean): Promise { + return this.moveFileOrDir(originalPath, newPath, false, destDirExists); + } + + /** + * Move a file/dir. + * + * @param originalPath Path to the file/dir to move. + * @param newPath New path of the file/dir. + * @param isDir Whether it's a dir or a file. + * @param destDirExists Set it to true if you know the directory where to put the file/dir exists. If false, the function will + * try to create it (slower). + * @return Promise resolved when the entry is moved. + */ + protected moveFileOrDir(originalPath: string, newPath: string, isDir?: boolean, destDirExists?: boolean): Promise { + const moveFn = isDir ? this.file.moveDir.bind(this.file) : this.file.moveFile.bind(this.file); + return this.init().then(() => { // Remove basePath if it's in the paths. originalPath = this.removeStartingSlash(originalPath.replace(this.basePath, '')); newPath = this.removeStartingSlash(newPath.replace(this.basePath, '')); + const newPathFileAndDir = this.getFileAndDirectoryFromPath(newPath); + + if (newPathFileAndDir.directory && !destDirExists) { + // Create the target directory if it doesn't exist. + return this.createDir(newPathFileAndDir.directory); + } + }).then(() => { + if (this.isHTMLAPI) { // In Cordova API we need to calculate the longest matching path to make it work. // The function this.file.moveFile('a/', 'b/c.ext', 'a/', 'b/d.ext') doesn't work. @@ -766,15 +826,15 @@ export class CoreFileProvider { } } - return this.file.moveFile(commonPath, originalPath, commonPath, newPath); + return moveFn(commonPath, originalPath, commonPath, newPath); } else { - return this.file.moveFile(this.basePath, originalPath, this.basePath, newPath).catch((error) => { + return moveFn(this.basePath, originalPath, this.basePath, newPath).catch((error) => { // The move can fail if the path has encoded characters. Try again if that's the case. const decodedOriginal = decodeURI(originalPath), decodedNew = decodeURI(newPath); if (decodedOriginal != originalPath || decodedNew != newPath) { - return this.file.moveFile(this.basePath, decodedOriginal, this.basePath, decodedNew); + return moveFn(this.basePath, decodedOriginal, this.basePath, decodedNew); } else { return Promise.reject(error); } @@ -783,16 +843,46 @@ export class CoreFileProvider { }); } + /** + * Copy a directory. + * + * @param from Path to the directory to move. + * @param to New path of the directory. + * @param destDirExists Set it to true if you know the directory where to put the dir exists. If false, the function will + * try to create it (slower). + * @return Promise resolved when the entry is copied. + */ + copyDir(from: string, to: string, destDirExists?: boolean): Promise { + return this.copyFileOrDir(from, to, true, destDirExists); + } + /** * Copy a file. * - * @param {string} from Path to the file to move. - * @param {string} to New path of the file. - * @return {Promise} Promise resolved when the entry is copied. + * @param from Path to the file to move. + * @param to New path of the file. + * @param destDirExists Set it to true if you know the directory where to put the file exists. If false, the function will + * try to create it (slower). + * @return Promise resolved when the entry is copied. */ - copyFile(from: string, to: string): Promise { + copyFile(from: string, to: string, destDirExists?: boolean): Promise { + return this.copyFileOrDir(from, to, false, destDirExists); + } + + /** + * Copy a file or a directory. + * + * @param from Path to the file/dir to move. + * @param to New path of the file/dir. + * @param isDir Whether it's a dir or a file. + * @param destDirExists Set it to true if you know the directory where to put the file/dir exists. If false, the function will + * try to create it (slower). + * @return Promise resolved when the entry is copied. + */ + protected copyFileOrDir(from: string, to: string, isDir?: boolean, destDirExists?: boolean): Promise { let fromFileAndDir, toFileAndDir; + const copyFn = isDir ? this.file.copyDir.bind(this.file) : this.file.copyFile.bind(this.file); return this.init().then(() => { // Paths cannot start with "/". Remove basePath if present. @@ -802,7 +892,7 @@ export class CoreFileProvider { fromFileAndDir = this.getFileAndDirectoryFromPath(from); toFileAndDir = this.getFileAndDirectoryFromPath(to); - if (toFileAndDir.directory) { + if (toFileAndDir.directory && !destDirExists) { // Create the target directory if it doesn't exist. return this.createDir(toFileAndDir.directory); } @@ -812,15 +902,15 @@ export class CoreFileProvider { const fromDir = this.textUtils.concatenatePaths(this.basePath, fromFileAndDir.directory), toDir = this.textUtils.concatenatePaths(this.basePath, toFileAndDir.directory); - return this.file.copyFile(fromDir, fromFileAndDir.name, toDir, toFileAndDir.name); + return copyFn(fromDir, fromFileAndDir.name, toDir, toFileAndDir.name); } else { - return this.file.copyFile(this.basePath, from, this.basePath, to).catch((error) => { + return copyFn(this.basePath, from, this.basePath, to).catch((error) => { // The copy can fail if the path has encoded characters. Try again if that's the case. const decodedFrom = decodeURI(from), decodedTo = decodeURI(to); if (from != decodedFrom || to != decodedTo) { - return this.file.copyFile(this.basePath, decodedFrom, this.basePath, decodedTo); + return copyFn(this.basePath, decodedFrom, this.basePath, decodedTo); } else { return Promise.reject(error); } @@ -832,8 +922,8 @@ export class CoreFileProvider { /** * Extract the file name and directory from a given path. * - * @param {string} path Path to be extracted. - * @return {any} Plain object containing the file name and directory. + * @param path Path to be extracted. + * @return Plain object containing the file name and directory. * @description * file.pdf -> directory: '', name: 'file.pdf' * /file.pdf -> directory: '', name: 'file.pdf' @@ -856,8 +946,8 @@ export class CoreFileProvider { /** * Get the internal URL of a file. * - * @param {FileEntry} fileEntry File Entry. - * @return {string} Internal URL. + * @param fileEntry File Entry. + * @return Internal URL. */ getInternalURL(fileEntry: FileEntry): string { if (!fileEntry.toInternalURL) { @@ -871,8 +961,8 @@ export class CoreFileProvider { /** * Adds the basePath to a path if it doesn't have it already. * - * @param {string} path Path to treat. - * @return {string} Path with basePath added. + * @param path Path to treat. + * @return Path with basePath added. */ addBasePathIfNeeded(path: string): string { if (path.indexOf(this.basePath) > -1) { @@ -885,8 +975,8 @@ export class CoreFileProvider { /** * Remove the base path from a path. If basePath isn't found, return false. * - * @param {string} path Path to treat. - * @return {string} Path without basePath if basePath was found, undefined otherwise. + * @param path Path to treat. + * @return Path without basePath if basePath was found, undefined otherwise. */ removeBasePath(path: string): string { if (path.indexOf(this.basePath) > -1) { @@ -897,22 +987,37 @@ export class CoreFileProvider { /** * Unzips a file. * - * @param {string} path Path to the ZIP file. - * @param {string} [destFolder] Path to the destination folder. If not defined, a new folder will be created with the - * same location and name as the ZIP file (without extension). - * @param {Function} [onProgress] Function to call on progress. - * @return {Promise} Promise resolved when the file is unzipped. + * @param path Path to the ZIP file. + * @param destFolder Path to the destination folder. If not defined, a new folder will be created with the + * same location and name as the ZIP file (without extension). + * @param onProgress Function to call on progress. + * @param recreateDir Delete the dest directory before unzipping. Defaults to true. + * @return Promise resolved when the file is unzipped. */ - unzipFile(path: string, destFolder?: string, onProgress?: Function): Promise { + unzipFile(path: string, destFolder?: string, onProgress?: Function, recreateDir: boolean = true): Promise { // Get the source file. - return this.getFile(path).then((fileEntry) => { + let fileEntry: FileEntry; + + return this.getFile(path).then((fe) => { + fileEntry = fe; + + if (destFolder && recreateDir) { + // Make sure the dest dir doesn't exist already. + return this.removeDir(destFolder).catch(() => { + // Ignore errors. + }).then(() => { + // Now create the dir, otherwise if any of the ancestor dirs doesn't exist the unzip would fail. + return this.createDir(destFolder); + }); + } + }).then(() => { // If destFolder is not set, use same location as ZIP file. We need to use absolute paths (including basePath). destFolder = this.addBasePathIfNeeded(destFolder || this.mimeUtils.removeExtension(path)); return this.zip.unzip(fileEntry.toURL(), destFolder, onProgress); }).then((result) => { if (result == -1) { - return Promise.reject(null); + return Promise.reject('Unzip failed.'); } }); } @@ -920,10 +1025,10 @@ export class CoreFileProvider { /** * Search a string or regexp in a file contents and replace it. The result is saved in the same file. * - * @param {string} path Path to the file. - * @param {string|RegExp} search Value to search. - * @param {string} newValue New value. - * @return {Promise} Promise resolved in success. + * @param path Path to the file. + * @param search Value to search. + * @param newValue New value. + * @return Promise resolved in success. */ replaceInFile(path: string, search: string | RegExp, newValue: string): Promise { return this.readFile(path).then((content) => { @@ -942,8 +1047,8 @@ export class CoreFileProvider { /** * Get a file/dir metadata given the file's entry. * - * @param {Entry} fileEntry FileEntry retrieved from getFile or similar. - * @return {Promise} Promise resolved with metadata. + * @param fileEntry FileEntry retrieved from getFile or similar. + * @return Promise resolved with metadata. */ getMetadata(fileEntry: Entry): Promise { if (!fileEntry || !fileEntry.getMetadata) { @@ -958,9 +1063,9 @@ export class CoreFileProvider { /** * Get a file/dir metadata given the path. * - * @param {string} path Path to the file/dir. - * @param {boolean} [isDir] True if directory, false if file. - * @return {Promise} Promise resolved with metadata. + * @param path Path to the file/dir. + * @param isDir True if directory, false if file. + * @return Promise resolved with metadata. */ getMetadataFromPath(path: string, isDir?: boolean): Promise { let promise; @@ -978,8 +1083,8 @@ export class CoreFileProvider { /** * Remove the starting slash of a path if it's there. E.g. '/sites/filepool' -> 'sites/filepool'. * - * @param {string} path Path. - * @return {string} Path without a slash in the first position. + * @param path Path. + * @return Path without a slash in the first position. */ removeStartingSlash(path: string): string { if (path[0] == '/') { @@ -992,10 +1097,10 @@ export class CoreFileProvider { /** * Convenience function to copy or move an external file. * - * @param {string} from Absolute path to the file to copy/move. - * @param {string} to Relative new path of the file (inside the app folder). - * @param {boolean} copy True to copy, false to move. - * @return {Promise} Promise resolved when the entry is copied/moved. + * @param from Absolute path to the file to copy/move. + * @param to Relative new path of the file (inside the app folder). + * @param copy True to copy, false to move. + * @return Promise resolved when the entry is copied/moved. */ protected copyOrMoveExternalFile(from: string, to: string, copy?: boolean): Promise { // Get the file to copy/move. @@ -1019,9 +1124,9 @@ export class CoreFileProvider { /** * Copy a file from outside of the app folder to somewhere inside the app folder. * - * @param {string} from Absolute path to the file to copy. - * @param {string} to Relative new path of the file (inside the app folder). - * @return {Promise} Promise resolved when the entry is copied. + * @param from Absolute path to the file to copy. + * @param to Relative new path of the file (inside the app folder). + * @return Promise resolved when the entry is copied. */ copyExternalFile(from: string, to: string): Promise { return this.copyOrMoveExternalFile(from, to, true); @@ -1030,9 +1135,9 @@ export class CoreFileProvider { /** * Move a file from outside of the app folder to somewhere inside the app folder. * - * @param {string} from Absolute path to the file to move. - * @param {string} to Relative new path of the file (inside the app folder). - * @return {Promise} Promise resolved when the entry is moved. + * @param from Absolute path to the file to move. + * @param to Relative new path of the file (inside the app folder). + * @return Promise resolved when the entry is moved. */ moveExternalFile(from: string, to: string): Promise { return this.copyOrMoveExternalFile(from, to, false); @@ -1041,10 +1146,10 @@ export class CoreFileProvider { /** * Get a unique file name inside a folder, adding numbers to the file name if needed. * - * @param {string} dirPath Path to the destination folder. - * @param {string} fileName File name that wants to be used. - * @param {string} [defaultExt] Default extension to use if no extension found in the file. - * @return {Promise} Promise resolved with the unique file name. + * @param dirPath Path to the destination folder. + * @param fileName File name that wants to be used. + * @param defaultExt Default extension to use if no extension found in the file. + * @return Promise resolved with the unique file name. */ getUniqueNameInFolder(dirPath: string, fileName: string, defaultExt?: string): Promise { // Get existing files in the folder. @@ -1094,7 +1199,7 @@ export class CoreFileProvider { /** * Remove app temporary folder. * - * @return {Promise} Promise resolved when done. + * @return Promise resolved when done. */ clearTmpFolder(): Promise { return this.removeDir(CoreFileProvider.TMPFOLDER).catch(() => { @@ -1105,9 +1210,9 @@ export class CoreFileProvider { /** * Given a folder path and a list of used files, remove all the files of the folder that aren't on the list of used files. * - * @param {string} dirPath Folder path. - * @param {any[]} files List of used files. - * @return {Promise} Promise resolved when done, rejected if failure. + * @param dirPath Folder path. + * @param files List of used files. + * @return Promise resolved when done, rejected if failure. */ removeUnusedFiles(dirPath: string, files: any[]): Promise { // Get the directory contents. @@ -1143,10 +1248,25 @@ export class CoreFileProvider { /** * Check if a file is inside the app's folder. * - * @param {string} path The absolute path of the file to check. - * @return {boolean} Whether the file is in the app's folder. + * @param path The absolute path of the file to check. + * @return Whether the file is in the app's folder. */ isFileInAppFolder(path: string): boolean { return path.indexOf(this.basePath) != -1; } + + /** + * Get the full path to the www folder at runtime. + * + * @return Path. + */ + getWWWPath(): string { + const position = window.location.href.indexOf('index.html'); + + if (position != -1) { + return window.location.href.substr(0, position); + } + + return window.location.href; + } } diff --git a/src/providers/filepool.ts b/src/providers/filepool.ts index aded3de7e..9e8fa5790 100644 --- a/src/providers/filepool.ts +++ b/src/providers/filepool.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -21,7 +21,7 @@ import { CoreInitDelegate } from './init'; import { CoreLoggerProvider } from './logger'; import { CorePluginFileDelegate } from './plugin-file-delegate'; import { CoreSitesProvider, CoreSiteSchema } from './sites'; -import { CoreWSProvider } from './ws'; +import { CoreWSProvider, CoreWSExternalFile } from './ws'; import { CoreDomUtilsProvider } from './utils/dom'; import { CoreMimetypeUtilsProvider } from './utils/mimetype'; import { CoreTextUtilsProvider } from './utils/text'; @@ -38,61 +38,51 @@ import { Md5 } from 'ts-md5/dist/md5'; export interface CoreFilepoolFileEntry { /** * The fileId to identify the file. - * @type {string} */ fileId?: string; /** * File's URL. - * @type {string} */ url?: string; /** * File's revision. - * @type {number} */ revision?: number; /** * File's timemodified. - * @type {number} */ timemodified?: number; /** * 1 if file is stale (needs to be updated), 0 otherwise. - * @type {number} */ stale?: number; /** * Timestamp when this file was downloaded. - * @type {number} */ downloadTime?: number; /** * 1 if it's a external file (from an external repository), 0 otherwise. - * @type {number} */ isexternalfile?: number; /** * Type of the repository this file belongs to. - * @type {string} */ repositorytype?: string; /** * File's path. - * @type {string} */ path?: string; /** * File's extension. - * @type {string} */ extension?: string; } @@ -103,67 +93,56 @@ export interface CoreFilepoolFileEntry { export interface CoreFilepoolQueueEntry { /** * The site the file belongs to. - * @type {string} */ siteId?: string; /** * The fileId to identify the file. - * @type {string} */ fileId?: string; /** * Timestamp when the file was added to the queue. - * @type {number} */ added?: number; /** * The priority of the file. - * @type {number} */ priority?: number; /** * File's URL. - * @type {string} */ url?: string; /** * File's revision. - * @type {number} */ revision?: number; /** * File's timemodified. - * @type {number} */ timemodified?: number; /** * 1 if it's a external file (from an external repository), 0 otherwise. - * @type {number} */ isexternalfile?: number; /** * Type of the repository this file belongs to. - * @type {string} */ repositorytype?: string; /** * File's path. - * @type {string} */ path?: string; /** * File links (to link the file to components and componentIds). - * @type {any[]} */ links?: any[]; } @@ -174,55 +153,46 @@ export interface CoreFilepoolQueueEntry { export interface CoreFilepoolPackageEntry { /** * Package id. - * @type {string} */ id?: string; /** * The component to link the files to. - * @type {string} */ component?: string; /** * An ID to use in conjunction with the component. - * @type {string|number} */ componentId?: string | number; /** * Package status. - * @type {string} */ status?: string; /** * Package previous status. - * @type {string} */ previous?: string; /** * Timestamp when this package was updated. - * @type {number} */ updated?: number; /** * Timestamp when this package was downloaded. - * @type {number} */ downloadTime?: number; /** * Previous download time. - * @type {number} */ previousDownloadTime?: number; /** * Extra data stored by the package. - * @type {string} */ extra?: string; } @@ -466,11 +436,11 @@ export class CoreFilepoolProvider { /** * Link a file with a component. * - * @param {string} siteId The site ID. - * @param {string} fileId The file ID. - * @param {string} component The component to link the file to. - * @param {string|number} [componentId] An ID to use in conjunction with the component. - * @return {Promise} Promise resolved on success. + * @param siteId The site ID. + * @param fileId The file ID. + * @param component The component to link the file to. + * @param componentId An ID to use in conjunction with the component. + * @return Promise resolved on success. */ protected addFileLink(siteId: string, fileId: string, component: string, componentId?: string | number): Promise { if (!component) { @@ -493,18 +463,18 @@ export class CoreFilepoolProvider { /** * Link a file with a component by URL. * - * @param {string} siteId The site ID. - * @param {string} fileUrl The file Url. - * @param {string} component The component to link the file to. - * @param {string|number} [componentId] An ID to use in conjunction with the component. - * @return {Promise} Promise resolved on success. + * @param siteId The site ID. + * @param fileUrl The file Url. + * @param component The component to link the file to. + * @param componentId An ID to use in conjunction with the component. + * @return Promise resolved on success. * @description * Use this method to create a link between a URL and a component. You usually do not need to call this manually since * downloading a file automatically does this. Note that this method does not check if the file exists in the pool. */ addFileLinkByUrl(siteId: string, fileUrl: string, component: string, componentId?: string | number): Promise { - return this.fixPluginfileURL(siteId, fileUrl).then((fileUrl) => { - const fileId = this.getFileIdByUrl(fileUrl); + return this.fixPluginfileURL(siteId, fileUrl).then((file) => { + const fileId = this.getFileIdByUrl(file.fileurl); return this.addFileLink(siteId, fileId, component, componentId); }); @@ -513,10 +483,10 @@ export class CoreFilepoolProvider { /** * Link a file with several components. * - * @param {string} siteId The site ID. - * @param {string} fileId The file ID. - * @param {any[]} links Array of objects containing the component and optionally componentId. - * @return {Promise} Promise resolved on success. + * @param siteId The site ID. + * @param fileId The file ID. + * @param links Array of objects containing the component and optionally componentId. + * @return Promise resolved on success. */ protected addFileLinks(siteId: string, fileId: string, links: any[]): Promise { const promises = []; @@ -530,11 +500,11 @@ export class CoreFilepoolProvider { /** * Add files to queue using a URL. * - * @param {string} siteId The site ID. - * @param {any[]} files Array of files to add. - * @param {string} [component] The component to link the file to. - * @param {string|number} [componentId] An ID to use in conjunction with the component (optional). - * @return {Promise} Resolved on success. + * @param siteId The site ID. + * @param files Array of files to add. + * @param component The component to link the file to. + * @param componentId An ID to use in conjunction with the component (optional). + * @return Resolved on success. */ addFilesToQueue(siteId: string, files: any[], component?: string, componentId?: string | number): Promise { return this.downloadOrPrefetchFiles(siteId, files, true, false, component, componentId); @@ -543,10 +513,10 @@ export class CoreFilepoolProvider { /** * Add a file to the pool. * - * @param {string} siteId The site ID. - * @param {string} fileId The file ID. - * @param {any} data Additional information to store about the file (timemodified, url, ...). See FILES_TABLE schema. - * @return {Promise} Promise resolved on success. + * @param siteId The site ID. + * @param fileId The file ID. + * @param data Additional information to store about the file (timemodified, url, ...). See FILES_TABLE schema. + * @return Promise resolved on success. */ protected addFileToPool(siteId: string, fileId: string, data: any): Promise { const values = Object.assign({}, data); @@ -560,9 +530,9 @@ export class CoreFilepoolProvider { /** * Adds a hash to a filename if needed. * - * @param {string} url The URL of the file, already treated (decoded, without revision, etc.). - * @param {string} filename The filename. - * @return {string} The filename with the hash. + * @param url The URL of the file, already treated (decoded, without revision, etc.). + * @param filename The filename. + * @return The filename with the hash. */ protected addHashToFilename(url: string, filename: string): string { // Check if the file already has a hash. If a file is downloaded and re-uploaded with the app it will have a hash already. @@ -586,16 +556,16 @@ export class CoreFilepoolProvider { /** * Add a file to the queue. * - * @param {string} siteId The site ID. - * @param {string} fileId The file ID. - * @param {string} url The absolute URL to the file. - * @param {number} priority The priority this file should get in the queue (range 0-999). - * @param {number} revision The revision of the file. - * @param {number} timemodified The time this file was modified. Can be used to check file state. - * @param {string} [filePath] Filepath to download the file to. If not defined, download to the filepool folder. - * @param {any} options Extra options (isexternalfile, repositorytype). - * @param {any} [link] The link to add for the file. - * @return {Promise} Promise resolved when the file is downloaded. + * @param siteId The site ID. + * @param fileId The file ID. + * @param url The absolute URL to the file. + * @param priority The priority this file should get in the queue (range 0-999). + * @param revision The revision of the file. + * @param timemodified The time this file was modified. Can be used to check file state. + * @param filePath Filepath to download the file to. If not defined, download to the filepool folder. + * @param options Extra options (isexternalfile, repositorytype). + * @param link The link to add for the file. + * @return Promise resolved when the file is downloaded. */ protected addToQueue(siteId: string, fileId: string, url: string, priority: number, revision: number, timemodified: number, filePath: string, onProgress?: (event: any) => any, options: any = {}, link?: any): Promise { @@ -625,21 +595,22 @@ export class CoreFilepoolProvider { /** * Add an entry to queue using a URL. * - * @param {string} siteId The site ID. - * @param {string} fileUrl The absolute URL to the file. - * @param {string} [component] The component to link the file to. - * @param {string|number} [componentId] An ID to use in conjunction with the component (optional). - * @param {number} [timemodified=0] The time this file was modified. Can be used to check file state. - * @param {string} [filePath] Filepath to download the file to. If not defined, download to the filepool folder. - * @param {Function} [onProgress] Function to call on progress. - * @param {number} [priority=0] The priority this file should get in the queue (range 0-999). - * @param {any} [options] Extra options (isexternalfile, repositorytype). - * @param {number} [revision] File revision. If not defined, it will be calculated using the URL. - * @return {Promise} Resolved on success. + * @param siteId The site ID. + * @param fileUrl The absolute URL to the file. + * @param component The component to link the file to. + * @param componentId An ID to use in conjunction with the component (optional). + * @param timemodified The time this file was modified. Can be used to check file state. + * @param filePath Filepath to download the file to. If not defined, download to the filepool folder. + * @param onProgress Function to call on progress. + * @param priority The priority this file should get in the queue (range 0-999). + * @param options Extra options (isexternalfile, repositorytype). + * @param revision File revision. If not defined, it will be calculated using the URL. + * @param alreadyFixed Whether the URL has already been fixed. + * @return Resolved on success. */ addToQueueByUrl(siteId: string, fileUrl: string, component?: string, componentId?: string | number, timemodified: number = 0, - filePath?: string, onProgress?: (event: any) => any, priority: number = 0, options: any = {}, revision?: number) - : Promise { + filePath?: string, onProgress?: (event: any) => any, priority: number = 0, options: any = {}, revision?: number, + alreadyFixed?: boolean): Promise { let fileId, link, queueDeferred; @@ -653,94 +624,102 @@ export class CoreFilepoolProvider { return Promise.reject(null); } - return this.fixPluginfileURL(siteId, fileUrl).then((fileUrl) => { - const primaryKey = { siteId: siteId, fileId: fileId }; + if (alreadyFixed) { + // Already fixed, if we reached here it means it can be downloaded. + return {fileurl: fileUrl}; + } else { + return this.fixPluginfileURL(siteId, fileUrl); + } + }).then((file) => { - revision = revision || this.getRevisionFromUrl(fileUrl); - fileId = this.getFileIdByUrl(fileUrl); + fileUrl = file.fileurl; + timemodified = file.timemodified || timemodified; + revision = revision || this.getRevisionFromUrl(fileUrl); + fileId = this.getFileIdByUrl(fileUrl); - // Set up the component. - if (typeof component != 'undefined') { - link = { - component: component, - componentId: this.fixComponentId(componentId) - }; - } + const primaryKey = { siteId: siteId, fileId: fileId }; - // Retrieve the queue deferred now if it exists. - // This is to prevent errors if file is removed from queue while we're checking if the file is in queue. - queueDeferred = this.getQueueDeferred(siteId, fileId, false, onProgress); + // Set up the component. + if (typeof component != 'undefined') { + link = { + component: component, + componentId: this.fixComponentId(componentId) + }; + } - return this.hasFileInQueue(siteId, fileId).then((entry: CoreFilepoolQueueEntry) => { - const newData: any = {}; - let foundLink = false; + // Retrieve the queue deferred now if it exists. + // This is to prevent errors if file is removed from queue while we're checking if the file is in queue. + queueDeferred = this.getQueueDeferred(siteId, fileId, false, onProgress); - if (entry) { - // We already have the file in queue, we update the priority and links. - if (entry.priority < priority) { - newData.priority = priority; - } - if (revision && entry.revision !== revision) { - newData.revision = revision; - } - if (timemodified && entry.timemodified !== timemodified) { - newData.timemodified = timemodified; - } - if (filePath && entry.path !== filePath) { - newData.path = filePath; - } - if (entry.isexternalfile !== options.isexternalfile && (entry.isexternalfile || options.isexternalfile)) { - newData.isexternalfile = options.isexternalfile; - } - if (entry.repositorytype !== options.repositorytype && (entry.repositorytype || options.repositorytype)) { - newData.repositorytype = options.repositorytype; - } + return this.hasFileInQueue(siteId, fileId).then((entry: CoreFilepoolQueueEntry) => { + const newData: any = {}; + let foundLink = false; - if (link) { - // We need to add the new link if it does not exist yet. - if (entry.links && entry.links.length) { - for (const i in entry.links) { - const fileLink = entry.links[i]; - if (fileLink.component == link.component && fileLink.componentId == link.componentId) { - foundLink = true; - break; - } + if (entry) { + // We already have the file in queue, we update the priority and links. + if (entry.priority < priority) { + newData.priority = priority; + } + if (revision && entry.revision !== revision) { + newData.revision = revision; + } + if (timemodified && entry.timemodified !== timemodified) { + newData.timemodified = timemodified; + } + if (filePath && entry.path !== filePath) { + newData.path = filePath; + } + if (entry.isexternalfile !== options.isexternalfile && (entry.isexternalfile || options.isexternalfile)) { + newData.isexternalfile = options.isexternalfile; + } + if (entry.repositorytype !== options.repositorytype && (entry.repositorytype || options.repositorytype)) { + newData.repositorytype = options.repositorytype; + } + + if (link) { + // We need to add the new link if it does not exist yet. + if (entry.links && entry.links.length) { + for (const i in entry.links) { + const fileLink = entry.links[i]; + if (fileLink.component == link.component && fileLink.componentId == link.componentId) { + foundLink = true; + break; } } - - if (!foundLink) { - newData.links = entry.links || []; - newData.links.push(link); - newData.links = JSON.stringify(entry.links); - } } - if (Object.keys(newData).length) { - // Update only when required. - this.logger.debug(`Updating file ${fileId} which is already in queue`); - - return this.appDB.updateRecords(this.QUEUE_TABLE, newData, primaryKey).then(() => { - return this.getQueuePromise(siteId, fileId, true, onProgress); - }); + if (!foundLink) { + newData.links = entry.links || []; + newData.links.push(link); + newData.links = JSON.stringify(entry.links); } - - this.logger.debug(`File ${fileId} already in queue and does not require update`); - if (queueDeferred) { - // If we were able to retrieve the queue deferred before, we use that one. - return queueDeferred.promise; - } else { - // Create a new deferred and return its promise. - return this.getQueuePromise(siteId, fileId, true, onProgress); - } - } else { - return this.addToQueue( - siteId, fileId, fileUrl, priority, revision, timemodified, filePath, onProgress, options, link); } - }, () => { - // Unsure why we could not get the record, let's add to the queue anyway. + + if (Object.keys(newData).length) { + // Update only when required. + this.logger.debug(`Updating file ${fileId} which is already in queue`); + + return this.appDB.updateRecords(this.QUEUE_TABLE, newData, primaryKey).then(() => { + return this.getQueuePromise(siteId, fileId, true, onProgress); + }); + } + + this.logger.debug(`File ${fileId} already in queue and does not require update`); + if (queueDeferred) { + // If we were able to retrieve the queue deferred before, we use that one. + return queueDeferred.promise; + } else { + // Create a new deferred and return its promise. + return this.getQueuePromise(siteId, fileId, true, onProgress); + } + } else { return this.addToQueue( siteId, fileId, fileUrl, priority, revision, timemodified, filePath, onProgress, options, link); - }); + } + }, () => { + // Unsure why we could not get the record, let's add to the queue anyway. + return this.addToQueue( + siteId, fileId, fileUrl, priority, revision, timemodified, filePath, onProgress, options, link); }); }); } @@ -748,17 +727,17 @@ export class CoreFilepoolProvider { /** * Adds a file to the queue if the size is allowed to be downloaded. * - * @param {string} siteId The site ID. - * @param {string} fileUrl The absolute URL to the file. - * @param {string} component The component to link the file to. - * @param {string|number} [componentId] An ID to use in conjunction with the component. - * @param {number} [timemodified=0] The time this file was modified. - * @param {boolean} [checkSize=true] True if we shouldn't download files if their size is big, false otherwise. - * @param {boolean} [downloadUnknown] True to download file in WiFi if their size is unknown, false otherwise. - * Ignored if checkSize=false. - * @param {any} [options] Extra options (isexternalfile, repositorytype). - * @param {number} [revision] File revision. If not defined, it will be calculated using the URL. - * @return {Promise} Promise resolved when the file is downloaded. + * @param siteId The site ID. + * @param fileUrl The absolute URL to the file, already fixed. + * @param component The component to link the file to. + * @param componentId An ID to use in conjunction with the component. + * @param timemodified The time this file was modified. + * @param checkSize True if we shouldn't download files if their size is big, false otherwise. + * @param downloadUnknown True to download file in WiFi if their size is unknown, false otherwise. + * Ignored if checkSize=false. + * @param options Extra options (isexternalfile, repositorytype). + * @param revision File revision. If not defined, it will be calculated using the URL. + * @return Promise resolved when the file is downloaded. */ protected addToQueueIfNeeded(siteId: string, fileUrl: string, component: string, componentId?: string | number, timemodified: number = 0, checkSize: boolean = true, downloadUnknown?: boolean, options: any = {}, revision?: number) @@ -790,18 +769,18 @@ export class CoreFilepoolProvider { // Check if the file should be downloaded. if (sizeUnknown) { if (downloadUnknown && isWifi) { - return this.addToQueueByUrl( - siteId, fileUrl, component, componentId, timemodified, undefined, undefined, 0, options, revision); + return this.addToQueueByUrl(siteId, fileUrl, component, componentId, timemodified, undefined, undefined, + 0, options, revision, true); } - } else if (size <= this.DOWNLOAD_THRESHOLD || (isWifi && size <= this.WIFI_DOWNLOAD_THRESHOLD)) { - return this.addToQueueByUrl( - siteId, fileUrl, component, componentId, timemodified, undefined, undefined, 0, options, revision); + } else if (this.shouldDownload(size)) { + return this.addToQueueByUrl(siteId, fileUrl, component, componentId, timemodified, undefined, undefined, 0, + options, revision, true); } }); } else { // No need to check size, just add it to the queue. return this.addToQueueByUrl(siteId, fileUrl, component, componentId, timemodified, undefined, undefined, 0, options, - revision); + revision, true); } } @@ -829,8 +808,8 @@ export class CoreFilepoolProvider { /** * Clear all packages status in a site. * - * @param {string} siteId Site ID. - * @return {Promise} Promise resolved when all status are cleared. + * @param siteId Site ID. + * @return Promise resolved when all status are cleared. */ clearAllPackagesStatus(siteId: string): Promise { this.logger.debug('Clear all packages status for site ' + siteId); @@ -852,8 +831,8 @@ export class CoreFilepoolProvider { /** * Clears the filepool. Use it only when all the files from a site are deleted. * - * @param {string} siteId ID of the site to clear. - * @return {Promise} Promise resolved when the filepool is cleared. + * @param siteId ID of the site to clear. + * @return Promise resolved when the filepool is cleared. */ clearFilepool(siteId: string): Promise { return this.sitesProvider.getSiteDb(siteId).then((db) => { @@ -867,10 +846,10 @@ export class CoreFilepoolProvider { /** * Returns whether a component has files in the pool. * - * @param {string} siteId The site ID. - * @param {string} component The component to link the file to. - * @param {string|number} [componentId] An ID to use in conjunction with the component. - * @return {Promise} Resolved means yes, rejected means no. + * @param siteId The site ID. + * @param component The component to link the file to. + * @param componentId An ID to use in conjunction with the component. + * @return Resolved means yes, rejected means no. */ componentHasFiles(siteId: string, component: string, componentId?: string | number): Promise { return this.sitesProvider.getSiteDb(siteId).then((db) => { @@ -898,9 +877,9 @@ export class CoreFilepoolProvider { * - CoreConstants.OUTDATED if ALL the downloadable packages have status CoreConstants.OUTDATED or CoreConstants.DOWNLOADED * or CoreConstants.DOWNLOADING, with at least 1 package with CoreConstants.OUTDATED. * - * @param {string} current Current status of the list of packages. - * @param {string} packagestatus Status of one of the packages. - * @return {string} New status for the list of packages; + * @param current Current status of the list of packages. + * @param packagestatus Status of one of the packages. + * @return New status for the list of packages; */ determinePackagesStatus(current: string, packageStatus: string): string { if (!current) { @@ -931,13 +910,13 @@ export class CoreFilepoolProvider { * * This uses the file system, you should always make sure that it is accessible before calling this method. * - * @param {string} siteId The site ID. - * @param {string} fileUrl The file URL. - * @param {any} [options] Extra options (revision, timemodified, isexternalfile, repositorytype). - * @param {string} [filePath] Filepath to download the file to. If defined, no extension will be added. - * @param {Function} [onProgress] Function to call on progress. - * @param {CoreFilepoolFileEntry} [poolFileObject] When set, the object will be updated, a new entry will not be created. - * @return {Promise} Resolved with internal URL on success, rejected otherwise. + * @param siteId The site ID. + * @param fileUrl The file URL. + * @param options Extra options (revision, timemodified, isexternalfile, repositorytype). + * @param filePath Filepath to download the file to. If defined, no extension will be added. + * @param onProgress Function to call on progress. + * @param poolFileObject When set, the object will be updated, a new entry will not be created. + * @return Resolved with internal URL on success, rejected otherwise. */ protected downloadForPoolByUrl(siteId: string, fileUrl: string, options: any = {}, filePath?: string, onProgress?: (event: any) => any, poolFileObject?: CoreFilepoolFileEntry): Promise { @@ -968,7 +947,13 @@ export class CoreFilepoolProvider { return Promise.reject(null); } - return this.wsProvider.downloadFile(fileUrl, filePath, addExtension, onProgress).then((fileEntry) => { + let fileEntry; + + return this.wsProvider.downloadFile(fileUrl, filePath, addExtension, onProgress).then((entry) => { + fileEntry = entry; + + return this.pluginFileDelegate.treatDownloadedFile(fileUrl, fileEntry, siteId); + }).then(() => { const data: CoreFilepoolFileEntry = poolFileObject || {}; data.downloadTime = Date.now(); @@ -997,15 +982,15 @@ export class CoreFilepoolProvider { /** * Download or prefetch several files into the filepool folder. * - * @param {string} siteId The site ID. - * @param {any[]} files Array of files to download. - * @param {boolean} prefetch True if should prefetch the contents (queue), false if they should be downloaded right now. - * @param {boolean} [ignoreStale] True if 'stale' should be ignored. Only if prefetch=false. - * @param {string} [component] The component to link the file to. - * @param {string|number} [componentId] An ID to use in conjunction with the component. - * @param {string} [dirPath] Name of the directory where to store the files (inside filepool dir). If not defined, store - * the files directly inside the filepool folder. - * @return {Promise} Resolved on success. + * @param siteId The site ID. + * @param files Array of files to download. + * @param prefetch True if should prefetch the contents (queue), false if they should be downloaded right now. + * @param ignoreStale True if 'stale' should be ignored. Only if prefetch=false. + * @param component The component to link the file to. + * @param componentId An ID to use in conjunction with the component. + * @param dirPath Name of the directory where to store the files (inside filepool dir). If not defined, store + * the files directly inside the filepool folder. + * @return Resolved on success. */ downloadOrPrefetchFiles(siteId: string, files: any[], prefetch: boolean, ignoreStale?: boolean, component?: string, componentId?: string | number, dirPath?: string): Promise { @@ -1045,16 +1030,16 @@ export class CoreFilepoolProvider { /** * Downloads or prefetches a list of files as a "package". * - * @param {string} siteId The site ID. - * @param {any[]} fileList List of files to download. - * @param {boolean} [prefetch] True if should prefetch the contents (queue), false if they should be downloaded right now. - * @param {string} [component] The component to link the file to. - * @param {string|number} [componentId] An ID to use in conjunction with the component. - * @param {string} [extra] Extra data to store for the package. - * @param {string} [dirPath] Name of the directory where to store the files (inside filepool dir). If not defined, store - * the files directly inside the filepool folder. - * @param {Function} [onProgress] Function to call on progress. - * @return {Promise} Promise resolved when the package is downloaded. + * @param siteId The site ID. + * @param fileList List of files to download. + * @param prefetch True if should prefetch the contents (queue), false if they should be downloaded right now. + * @param component The component to link the file to. + * @param componentId An ID to use in conjunction with the component. + * @param extra Extra data to store for the package. + * @param dirPath Name of the directory where to store the files (inside filepool dir). If not defined, store + * the files directly inside the filepool folder. + * @param onProgress Function to call on progress. + * @return Promise resolved when the package is downloaded. */ protected downloadOrPrefetchPackage(siteId: string, fileList: any[], prefetch?: boolean, component?: string, componentId?: string | number, extra?: string, dirPath?: string, onProgress?: (event: any) => any): Promise { @@ -1145,15 +1130,15 @@ export class CoreFilepoolProvider { /** * Downloads a list of files. * - * @param {string} siteId The site ID. - * @param {any[]} fileList List of files to download. - * @param {string} component The component to link the file to. - * @param {string|number} [componentId] An ID to identify the download. - * @param {string} [extra] Extra data to store for the package. - * @param {string} [dirPath] Name of the directory where to store the files (inside filepool dir). If not defined, store - * the files directly inside the filepool folder. - * @param {Function} [onProgress] Function to call on progress. - * @return {Promise} Promise resolved when all files are downloaded. + * @param siteId The site ID. + * @param fileList List of files to download. + * @param component The component to link the file to. + * @param componentId An ID to identify the download. + * @param extra Extra data to store for the package. + * @param dirPath Name of the directory where to store the files (inside filepool dir). If not defined, store + * the files directly inside the filepool folder. + * @param onProgress Function to call on progress. + * @return Promise resolved when all files are downloaded. */ downloadPackage(siteId: string, fileList: any[], component: string, componentId?: string | number, extra?: string, dirPath?: string, onProgress?: (event: any) => any): Promise { @@ -1163,16 +1148,16 @@ export class CoreFilepoolProvider { /** * Downloads a file on the spot. * - * @param {string} siteId The site ID. - * @param {string} fileUrl The file URL. - * @param {boolean} [ignoreStale] Whether 'stale' should be ignored. - * @param {string} [component] The component to link the file to. - * @param {string|number} [componentId] An ID to use in conjunction with the component. - * @param {number} [timemodified=0] The time this file was modified. Can be used to check file state. - * @param {string} [filePath] Filepath to download the file to. If not defined, download to the filepool folder. - * @param {any} [options] Extra options (isexternalfile, repositorytype). - * @param {number} [revision] File revision. If not defined, it will be calculated using the URL. - * @return {Promise} Resolved with internal URL on success, rejected otherwise. + * @param siteId The site ID. + * @param fileUrl The file URL. + * @param ignoreStale Whether 'stale' should be ignored. + * @param component The component to link the file to. + * @param componentId An ID to use in conjunction with the component. + * @param timemodified The time this file was modified. Can be used to check file state. + * @param filePath Filepath to download the file to. If not defined, download to the filepool folder. + * @param options Extra options (isexternalfile, repositorytype). + * @param revision File revision. If not defined, it will be calculated using the URL. + * @return Resolved with internal URL on success, rejected otherwise. * @description * Downloads a file on the spot. * @@ -1187,8 +1172,10 @@ export class CoreFilepoolProvider { promise; if (this.fileProvider.isAvailable()) { - return this.fixPluginfileURL(siteId, fileUrl).then((fixedUrl) => { - fileUrl = fixedUrl; + return this.fixPluginfileURL(siteId, fileUrl).then((file) => { + + fileUrl = file.fileurl; + timemodified = file.timemodified || timemodified; options = Object.assign({}, options); // Create a copy to prevent modifying the original object. options.timemodified = timemodified || 0; @@ -1252,13 +1239,66 @@ export class CoreFilepoolProvider { } } + /** + * Extract the downloadable URLs from an HTML code. + * + * @param html HTML code. + * @return List of file urls. + */ + extractDownloadableFilesFromHtml(html: string): string[] { + let urls = [], + elements; + + const element = this.domUtils.convertToElement(html); + elements = element.querySelectorAll('a, img, audio, video, source, track'); + + for (let i = 0; i < elements.length; i++) { + const element = elements[i]; + let url = element.tagName === 'A' ? element.href : element.src; + + if (url && this.urlUtils.isDownloadableUrl(url) && urls.indexOf(url) == -1) { + urls.push(url); + } + + // Treat video poster. + if (element.tagName == 'VIDEO' && element.getAttribute('poster')) { + url = element.getAttribute('poster'); + if (url && this.urlUtils.isDownloadableUrl(url) && urls.indexOf(url) == -1) { + urls.push(url); + } + } + } + + // Now get other files from plugin file handlers. + urls = urls.concat(this.pluginFileDelegate.getDownloadableFilesFromHTML(element)); + + return urls; + } + + /** + * Extract the downloadable URLs from an HTML code and returns them in fake file objects. + * + * @param html HTML code. + * @return List of fake file objects with file URLs. + */ + extractDownloadableFilesFromHtmlAsFakeFileObjects(html: string): any[] { + const urls = this.extractDownloadableFilesFromHtml(html); + + // Convert them to fake file objects. + return urls.map((url) => { + return { + fileurl: url + }; + }); + } + /** * Fill Missing Extension In the File Object if needed. * This is to migrate from old versions. * - * @param {CoreFilepoolFileEntry} fileObject File object to be migrated. - * @param {string} siteId SiteID to get migrated. - * @return {Promise} Promise resolved when done. + * @param fileObject File object to be migrated. + * @param siteId SiteID to get migrated. + * @return Promise resolved when done. */ protected fillExtensionInFile(entry: CoreFilepoolFileEntry, siteId: string): Promise { if (typeof entry.extension != 'undefined') { @@ -1299,8 +1339,8 @@ export class CoreFilepoolProvider { * Fill Missing Extension In Files, used to migrate from previous file handling. * Reserved for core use, please do not call. * - * @param {string} siteId SiteID to get migrated - * @return {Promise} Promise resolved when done. + * @param siteId SiteID to get migrated + * @return Promise resolved when done. */ fillMissingExtensionInFiles(siteId: string): Promise { this.logger.debug('Fill missing extensions in files of ' + siteId); @@ -1320,8 +1360,8 @@ export class CoreFilepoolProvider { /** * Fix a component ID to always be a Number if possible. * - * @param {string|number} componentId The component ID. - * @return {string|number} The normalised component ID. -1 when undefined was passed. + * @param componentId The component ID. + * @return The normalised component ID. -1 when undefined was passed. */ protected fixComponentId(componentId: string | number): string | number { if (typeof componentId == 'number') { @@ -1343,25 +1383,34 @@ export class CoreFilepoolProvider { } /** - * Add the wstoken url and points to the correct script. + * Check whether the file can be downloaded, add the wstoken url and points to the correct script. * - * @param {string} siteId The site ID. - * @param {string} fileUrl The file URL. - * @return {Promise} Resolved with fixed URL on success, rejected otherwise. + * @param siteId The site ID. + * @param fileUrl The file URL. + * @param timemodified The timemodified of the file. + * @return Promise resolved with the file data to use. */ - protected fixPluginfileURL(siteId: string, fileUrl: string): Promise { - return this.sitesProvider.getSite(siteId).then((site) => { - return site.fixPluginfileURL(fileUrl); + protected fixPluginfileURL(siteId: string, fileUrl: string, timemodified: number = 0): Promise { + + return this.pluginFileDelegate.getDownloadableFile({fileurl: fileUrl, timemodified: timemodified}).then((file) => { + + return this.sitesProvider.getSite(siteId).then((site) => { + return site.checkAndFixPluginfileURL(file.fileurl); + }).then((fixedUrl) => { + file.fileurl = fixedUrl; + + return file; + }); }); } /** * Convenience function to get component files. * - * @param {SQLiteDB} db Site's DB. - * @param {string} component The component to get. - * @param {string|number} [componentId] An ID to use in conjunction with the component. - * @return {Promise} Promise resolved with the files. + * @param db Site's DB. + * @param component The component to get. + * @param componentId An ID to use in conjunction with the component. + * @return Promise resolved with the files. */ protected getComponentFiles(db: SQLiteDB, component: string, componentId?: string | number): Promise { const conditions = { @@ -1375,14 +1424,14 @@ export class CoreFilepoolProvider { /** * Returns the local URL of a directory. * - * @param {string} siteId The site ID. - * @param {string} fileUrl The file URL. - * @return {Promise} Resolved with the URL. Rejected otherwise. + * @param siteId The site ID. + * @param fileUrl The file URL. + * @return Resolved with the URL. Rejected otherwise. */ getDirectoryUrlByUrl(siteId: string, fileUrl: string): Promise { if (this.fileProvider.isAvailable()) { - return this.fixPluginfileURL(siteId, fileUrl).then((fileUrl) => { - const fileId = this.getFileIdByUrl(fileUrl), + return this.fixPluginfileURL(siteId, fileUrl).then((file) => { + const fileId = this.getFileIdByUrl(file.fileurl), filePath = this.getFilePath(siteId, fileId, ''); // No extension, the function will return a string. return this.fileProvider.getDir(filePath).then((dirEntry) => { @@ -1397,9 +1446,9 @@ export class CoreFilepoolProvider { /** * Get the ID of a file download. Used to keep track of filePromises. * - * @param {string} fileUrl The file URL. - * @param {string} filePath The file destination path. - * @return {string} File download ID. + * @param fileUrl The file URL. + * @param filePath The file destination path. + * @return File download ID. */ protected getFileDownloadId(fileUrl: string, filePath: string): string { return Md5.hashAsciiStr(fileUrl + '###' + filePath); @@ -1408,9 +1457,9 @@ export class CoreFilepoolProvider { /** * Get the name of the event used to notify download events (CoreEventsProvider). * - * @param {string} siteId The site ID. - * @param {string} fileId The file ID. - * @return {string} Event name. + * @param siteId The site ID. + * @param fileId The file ID. + * @return Event name. */ protected getFileEventName(siteId: string, fileId: string): string { return 'CoreFilepoolFile:' + siteId + ':' + fileId; @@ -1419,13 +1468,13 @@ export class CoreFilepoolProvider { /** * Get the name of the event used to notify download events (CoreEventsProvider). * - * @param {string} siteId The site ID. - * @param {string} fileUrl The absolute URL to the file. - * @return {Promise} Promise resolved with event name. + * @param siteId The site ID. + * @param fileUrl The absolute URL to the file. + * @return Promise resolved with event name. */ getFileEventNameByUrl(siteId: string, fileUrl: string): Promise { - return this.fixPluginfileURL(siteId, fileUrl).then((fileUrl) => { - const fileId = this.getFileIdByUrl(fileUrl); + return this.fixPluginfileURL(siteId, fileUrl).then((file) => { + const fileId = this.getFileIdByUrl(file.fileurl); return this.getFileEventName(siteId, fileId); }); @@ -1437,13 +1486,20 @@ export class CoreFilepoolProvider { * This has a minimal handling of pluginfiles in order to generate a clean file ID which will not change if * pointing to the same pluginfile URL even if the token or extra attributes have changed. * - * @param {string} fileUrl The absolute URL to the file. - * @return {string} The file ID. + * @param fileUrl The absolute URL to the file. + * @return The file ID. */ protected getFileIdByUrl(fileUrl: string): string { - let url = this.removeRevisionFromUrl(fileUrl), + let url = fileUrl, filename; + // If site supports it, since 3.8 we use tokenpluginfile instead of pluginfile. + // For compatibility with files already downloaded, we need to use pluginfile to calculate the file ID. + url = url.replace(/\/tokenpluginfile\.php\/[^\/]+\//, '/webservice/pluginfile.php/'); + + // Remove the revision number from the URL so updates on the file aren't detected as a different file. + url = this.removeRevisionFromUrl(url); + // Decode URL. url = this.textUtils.decodeHTML(this.textUtils.decodeURIComponent(url)); @@ -1464,9 +1520,9 @@ export class CoreFilepoolProvider { /** * Get the links of a file. * - * @param {string} siteId The site ID. - * @param {string} fileId The file ID. - * @return {Promise} Promise resolved with the links. + * @param siteId The site ID. + * @param fileId The file ID. + * @return Promise resolved with the links. */ protected getFileLinks(siteId: string, fileId: string): Promise { return this.sitesProvider.getSiteDb(siteId).then((db) => { @@ -1477,10 +1533,10 @@ export class CoreFilepoolProvider { /** * Get the path to a file. This does not check if the file exists or not. * - * @param {string} siteId The site ID. - * @param {string} fileId The file ID. - * @param {string} [extension] Previously calculated extension. Empty to not add any. Undefined to calculate it. - * @return {string|Promise} The path to the file relative to storage root. + * @param siteId The site ID. + * @param fileId The file ID. + * @param extension Previously calculated extension. Empty to not add any. Undefined to calculate it. + * @return The path to the file relative to storage root. */ protected getFilePath(siteId: string, fileId: string, extension?: string): string | Promise { let path = this.getFilepoolFolderPath(siteId) + '/' + fileId; @@ -1508,13 +1564,13 @@ export class CoreFilepoolProvider { /** * Get the path to a file from its URL. This does not check if the file exists or not. * - * @param {string} siteId The site ID. - * @param {string} fileUrl The file URL. - * @return {Promise} Promise resolved with the path to the file relative to storage root. + * @param siteId The site ID. + * @param fileUrl The file URL. + * @return Promise resolved with the path to the file relative to storage root. */ getFilePathByUrl(siteId: string, fileUrl: string): Promise { - return this.fixPluginfileURL(siteId, fileUrl).then((fileUrl) => { - const fileId = this.getFileIdByUrl(fileUrl); + return this.fixPluginfileURL(siteId, fileUrl).then((file) => { + const fileId = this.getFileIdByUrl(file.fileurl); return this.getFilePath(siteId, fileId); }); @@ -1523,8 +1579,8 @@ export class CoreFilepoolProvider { /** * Get site Filepool Folder Path * - * @param {string} siteId The site ID. - * @return {string} The root path to the filepool of the site. + * @param siteId The site ID. + * @return The root path to the filepool of the site. */ getFilepoolFolderPath(siteId: string): string { return this.fileProvider.getSiteFolder(siteId) + '/' + this.FOLDER; @@ -1533,10 +1589,10 @@ export class CoreFilepoolProvider { /** * Get all the matching files from a component. Returns objects containing properties like path, extension and url. * - * @param {string} siteId The site ID. - * @param {string} component The component to get. - * @param {string|number} [componentId] An ID to use in conjunction with the component. - * @return {Promise} Promise resolved with the files on success. + * @param siteId The site ID. + * @param component The component to get. + * @param componentId An ID to use in conjunction with the component. + * @return Promise resolved with the files on success. */ getFilesByComponent(siteId: string, component: string, componentId?: string | number): Promise { return this.sitesProvider.getSiteDb(siteId).then((db) => { @@ -1572,10 +1628,10 @@ export class CoreFilepoolProvider { /** * Get the size of all the files from a component. * - * @param {string} siteId The site ID. - * @param {string} component The component to get. - * @param {string|number} [componentId] An ID to use in conjunction with the component. - * @return {Promise} Promise resolved with the size on success. + * @param siteId The site ID. + * @param component The component to get. + * @param componentId An ID to use in conjunction with the component. + * @return Promise resolved with the size on success. */ getFilesSizeByComponent(siteId: string, component: string, componentId?: string | number): Promise { return this.getFilesByComponent(siteId, component, componentId).then((files) => { @@ -1599,19 +1655,21 @@ export class CoreFilepoolProvider { /** * Returns the file state: mmCoreDownloaded, mmCoreDownloading, mmCoreNotDownloaded or mmCoreOutdated. * - * @param {string} siteId The site ID. - * @param {string} fileUrl File URL. - * @param {number} [timemodified=0] The time this file was modified. - * @param {string} [filePath] Filepath to download the file to. If defined, no extension will be added. - * @param {number} [revision] File revision. If not defined, it will be calculated using the URL. - * @return {Promise} Promise resolved with the file state. + * @param siteId The site ID. + * @param fileUrl File URL. + * @param timemodified The time this file was modified. + * @param filePath Filepath to download the file to. If defined, no extension will be added. + * @param revision File revision. If not defined, it will be calculated using the URL. + * @return Promise resolved with the file state. */ getFileStateByUrl(siteId: string, fileUrl: string, timemodified: number = 0, filePath?: string, revision?: number) : Promise { let fileId; - return this.fixPluginfileURL(siteId, fileUrl).then((fixedUrl) => { - fileUrl = fixedUrl; + return this.fixPluginfileURL(siteId, fileUrl, timemodified).then((file) => { + + fileUrl = file.fileurl; + timemodified = file.timemodified || timemodified; revision = revision || this.getRevisionFromUrl(fileUrl); fileId = this.getFileIdByUrl(fileUrl); @@ -1641,24 +1699,26 @@ export class CoreFilepoolProvider { }); }); }); + }, () => { + return CoreConstants.NOT_DOWNLOADABLE; }); } /** * Returns an absolute URL to access the file URL. * - * @param {string} siteId The site ID. - * @param {string} fileUrl The absolute URL to the file. - * @param {string} [mode=url] The type of URL to return. Accepts 'url' or 'src'. - * @param {string} component The component to link the file to. - * @param {string|number} [componentId] An ID to use in conjunction with the component. - * @param {number} [timemodified=0] The time this file was modified. - * @param {boolean} [checkSize=true] True if we shouldn't download files if their size is big, false otherwise. - * @param {boolean} [downloadUnknown] True to download file in WiFi if their size is unknown, false otherwise. - * Ignored if checkSize=false. - * @param {any} [options] Extra options (isexternalfile, repositorytype). - * @param {number} [revision] File revision. If not defined, it will be calculated using the URL. - * @return {Promise} Resolved with the URL to use. + * @param siteId The site ID. + * @param fileUrl The absolute URL to the file. + * @param mode The type of URL to return. Accepts 'url' or 'src'. + * @param component The component to link the file to. + * @param componentId An ID to use in conjunction with the component. + * @param timemodified The time this file was modified. + * @param checkSize True if we shouldn't download files if their size is big, false otherwise. + * @param downloadUnknown True to download file in WiFi if their size is unknown, false otherwise. + * Ignored if checkSize=false. + * @param options Extra options (isexternalfile, repositorytype). + * @param revision File revision. If not defined, it will be calculated using the URL. + * @return Resolved with the URL to use. * @description * This will return a URL pointing to the content of the requested URL. * @@ -1678,8 +1738,10 @@ export class CoreFilepoolProvider { }); }; - return this.fixPluginfileURL(siteId, fileUrl).then((fixedUrl) => { - fileUrl = fixedUrl; + return this.fixPluginfileURL(siteId, fileUrl, timemodified).then((file) => { + + fileUrl = file.fileurl; + timemodified = file.timemodified || timemodified; revision = revision || this.getRevisionFromUrl(fileUrl); fileId = this.getFileIdByUrl(fileUrl); @@ -1736,9 +1798,9 @@ export class CoreFilepoolProvider { * * The returned URL from this method is typically used with IMG tags. * - * @param {string} siteId The site ID. - * @param {string} fileId The file ID. - * @return {Promise} Resolved with the internal URL. Rejected otherwise. + * @param siteId The site ID. + * @param fileId The file ID. + * @return Resolved with the internal URL. Rejected otherwise. */ protected getInternalSrcById(siteId: string, fileId: string): Promise { if (this.fileProvider.isAvailable()) { @@ -1756,9 +1818,9 @@ export class CoreFilepoolProvider { /** * Returns the local URL of a file. * - * @param {string} siteId The site ID. - * @param {string} fileId The file ID. - * @return {Promise} Resolved with the URL. Rejected otherwise. + * @param siteId The site ID. + * @param fileId The file ID. + * @return Resolved with the URL. Rejected otherwise. */ protected getInternalUrlById(siteId: string, fileId: string): Promise { if (this.fileProvider.isAvailable()) { @@ -1780,8 +1842,8 @@ export class CoreFilepoolProvider { /** * Returns the local URL of a file. * - * @param {string} filePath The file path. - * @return {Promise} Resolved with the URL. + * @param filePath The file path. + * @return Resolved with the URL. */ protected getInternalUrlByPath(filePath: string): Promise { if (this.fileProvider.isAvailable()) { @@ -1796,14 +1858,14 @@ export class CoreFilepoolProvider { /** * Returns the local URL of a file. * - * @param {string} siteId The site ID. - * @param {string} fileUrl The file URL. - * @return {Promise} Resolved with the URL. Rejected otherwise. + * @param siteId The site ID. + * @param fileUrl The file URL. + * @return Resolved with the URL. Rejected otherwise. */ getInternalUrlByUrl(siteId: string, fileUrl: string): Promise { if (this.fileProvider.isAvailable()) { - return this.fixPluginfileURL(siteId, fileUrl).then((fileUrl) => { - const fileId = this.getFileIdByUrl(fileUrl); + return this.fixPluginfileURL(siteId, fileUrl).then((file) => { + const fileId = this.getFileIdByUrl(file.fileurl); return this.getInternalUrlById(siteId, fileId); }); @@ -1815,10 +1877,10 @@ export class CoreFilepoolProvider { /** * Get the data stored for a package. * - * @param {string} siteId Site ID. - * @param {string} component Package's component. - * @param {string|number} [componentId] An ID to use in conjunction with the component. - * @return {Promise} Promise resolved with the data. + * @param siteId Site ID. + * @param component Package's component. + * @param componentId An ID to use in conjunction with the component. + * @return Promise resolved with the data. */ getPackageData(siteId: string, component: string, componentId?: string | number): Promise { componentId = this.fixComponentId(componentId); @@ -1833,8 +1895,8 @@ export class CoreFilepoolProvider { /** * Creates the name for a package directory (hash). * - * @param {string} url An URL to identify the package. - * @return {string} The directory name. + * @param url An URL to identify the package. + * @return The directory name. */ protected getPackageDirNameByUrl(url: string): string { let candidate, @@ -1861,13 +1923,13 @@ export class CoreFilepoolProvider { /** * Get the path to a directory to store a package files. This does not check if the file exists or not. * - * @param {string} siteId The site ID. - * @param {string} url An URL to identify the package. - * @return {Promise} Promise resolved with the path of the package. + * @param siteId The site ID. + * @param url An URL to identify the package. + * @return Promise resolved with the path of the package. */ getPackageDirPathByUrl(siteId: string, url: string): Promise { - return this.fixPluginfileURL(siteId, url).then((fixedUrl) => { - const dirName = this.getPackageDirNameByUrl(fixedUrl); + return this.fixPluginfileURL(siteId, url).then((file) => { + const dirName = this.getPackageDirNameByUrl(file.fileurl); return this.getFilePath(siteId, dirName, ''); }); @@ -1876,14 +1938,14 @@ export class CoreFilepoolProvider { /** * Returns the local URL of a package directory. * - * @param {string} siteId The site ID. - * @param {string} url An URL to identify the package. - * @return {Promise} Resolved with the URL. + * @param siteId The site ID. + * @param url An URL to identify the package. + * @return Resolved with the URL. */ getPackageDirUrlByUrl(siteId: string, url: string): Promise { if (this.fileProvider.isAvailable()) { - return this.fixPluginfileURL(siteId, url).then((fixedUrl) => { - const dirName = this.getPackageDirNameByUrl(fixedUrl), + return this.fixPluginfileURL(siteId, url).then((file) => { + const dirName = this.getPackageDirNameByUrl(file.fileurl), dirPath = this.getFilePath(siteId, dirName, ''); // No extension, the function will return a string. return this.fileProvider.getDir(dirPath).then((dirEntry) => { @@ -1898,10 +1960,10 @@ export class CoreFilepoolProvider { /** * Get a download promise. If the promise is not set, return undefined. * - * @param {string} siteId Site ID. - * @param {string} component The component of the package. - * @param {string|number} [componentId] An ID to use in conjunction with the component. - * @return {Promise} Download promise or undefined. + * @param siteId Site ID. + * @param component The component of the package. + * @param componentId An ID to use in conjunction with the component. + * @return Download promise or undefined. */ getPackageDownloadPromise(siteId: string, component: string, componentId?: string | number): Promise { const packageId = this.getPackageId(component, componentId); @@ -1912,10 +1974,10 @@ export class CoreFilepoolProvider { /** * Get a package extra data. * - * @param {string} siteId Site ID. - * @param {string} component Package's component. - * @param {string|number} [componentId] An ID to use in conjunction with the component. - * @return {Promise} Promise resolved with the extra data. + * @param siteId Site ID. + * @param component Package's component. + * @param componentId An ID to use in conjunction with the component. + * @return Promise resolved with the extra data. */ getPackageExtra(siteId: string, component: string, componentId?: string | number): Promise { return this.getPackageData(siteId, component, componentId).then((entry) => { @@ -1926,9 +1988,9 @@ export class CoreFilepoolProvider { /** * Get the ID of a package. * - * @param {string} component Package's component. - * @param {string|number} [componentId] An ID to use in conjunction with the component. - * @return {string} Package ID. + * @param component Package's component. + * @param componentId An ID to use in conjunction with the component. + * @return Package ID. */ getPackageId(component: string, componentId?: string | number): string { return Md5.hashAsciiStr(component + '#' + this.fixComponentId(componentId)); @@ -1937,10 +1999,10 @@ export class CoreFilepoolProvider { /** * Get a package previous status. * - * @param {string} siteId Site ID. - * @param {string} component Package's component. - * @param {string|number} [componentId] An ID to use in conjunction with the component. - * @return {Promise} Promise resolved with the status. + * @param siteId Site ID. + * @param component Package's component. + * @param componentId An ID to use in conjunction with the component. + * @return Promise resolved with the status. */ getPackagePreviousStatus(siteId: string, component: string, componentId?: string | number): Promise { return this.getPackageData(siteId, component, componentId).then((entry) => { @@ -1953,10 +2015,10 @@ export class CoreFilepoolProvider { /** * Get a package status. * - * @param {string} siteId Site ID. - * @param {string} component Package's component. - * @param {string|number} [componentId] An ID to use in conjunction with the component. - * @return {Promise} Promise resolved with the status. + * @param siteId Site ID. + * @param component Package's component. + * @param componentId An ID to use in conjunction with the component. + * @return Promise resolved with the status. */ getPackageStatus(siteId: string, component: string, componentId?: string | number): Promise { return this.getPackageData(siteId, component, componentId).then((entry) => { @@ -1969,8 +2031,8 @@ export class CoreFilepoolProvider { /** * Return the array of arguments of the pluginfile url. * - * @param {string} url URL to get the args. - * @return {string[]} The args found, undefined if not a pluginfile. + * @param url URL to get the args. + * @return The args found, undefined if not a pluginfile. */ protected getPluginFileArgs(url: string): string[] { if (!this.urlUtils.isPluginFileUrl(url)) { @@ -1992,11 +2054,11 @@ export class CoreFilepoolProvider { /** * Get the deferred object for a file in the queue. * - * @param {string} siteId The site ID. - * @param {string} fileId The file ID. - * @param {boolean} [create=true] True if it should create a new deferred if it doesn't exist. - * @param {Function} [onProgress] Function to call on progress. - * @return {any} Deferred. + * @param siteId The site ID. + * @param fileId The file ID. + * @param create True if it should create a new deferred if it doesn't exist. + * @param onProgress Function to call on progress. + * @return Deferred. */ protected getQueueDeferred(siteId: string, fileId: string, create: boolean = true, onProgress?: (event: any) => any): any { if (!this.queueDeferreds[siteId]) { @@ -2022,9 +2084,9 @@ export class CoreFilepoolProvider { /** * Get the on progress for a file in the queue. * - * @param {string} siteId The site ID. - * @param {string} fileId The file ID. - * @return {Function} On progress function, undefined if not found. + * @param siteId The site ID. + * @param fileId The file ID. + * @return On progress function, undefined if not found. */ protected getQueueOnProgress(siteId: string, fileId: string): (event: any) => any { const deferred = this.getQueueDeferred(siteId, fileId, false); @@ -2036,11 +2098,11 @@ export class CoreFilepoolProvider { /** * Get the promise for a file in the queue. * - * @param {string} siteId The site ID. - * @param {string} fileId The file ID. - * @param {boolean} [create=true] True if it should create a new promise if it doesn't exist. - * @param {Function} [onProgress] Function to call on progress. - * @return {Promise} Promise. + * @param siteId The site ID. + * @param fileId The file ID. + * @param create True if it should create a new promise if it doesn't exist. + * @param onProgress Function to call on progress. + * @return Promise. */ protected getQueuePromise(siteId: string, fileId: string, create: boolean = true, onProgress?: (event: any) => any) : Promise { @@ -2050,8 +2112,8 @@ export class CoreFilepoolProvider { /** * Get a revision number from a list of files (highest revision). * - * @param {any[]} files Package files. - * @return {number} Highest revision. + * @param files Package files. + * @return Highest revision. */ getRevisionFromFileList(files: any[]): number { let revision = 0; @@ -2071,8 +2133,8 @@ export class CoreFilepoolProvider { /** * Get the revision number from a file URL. * - * @param {string} url URL to get the revision number. - * @return {number} Revision number. + * @param url URL to get the revision number. + * @return Revision number. */ protected getRevisionFromUrl(url: string): number { const args = this.getPluginFileArgs(url); @@ -2097,18 +2159,18 @@ export class CoreFilepoolProvider { /** * Returns an absolute URL to use in IMG tags. * - * @param {string} siteId The site ID. - * @param {string} fileUrl The absolute URL to the file. - * @param {string} [mode=url] The type of URL to return. Accepts 'url' or 'src'. - * @param {string} component The component to link the file to. - * @param {string|number} [componentId] An ID to use in conjunction with the component. - * @param {number} [timemodified=0] The time this file was modified. - * @param {boolean} [checkSize=true] True if we shouldn't download files if their size is big, false otherwise. - * @param {boolean} [downloadUnknown] True to download file in WiFi if their size is unknown, false otherwise. - * Ignored if checkSize=false. - * @param {any} [options] Extra options (isexternalfile, repositorytype). - * @param {number} [revision] File revision. If not defined, it will be calculated using the URL. - * @return {Promise} Resolved with the URL to use. + * @param siteId The site ID. + * @param fileUrl The absolute URL to the file. + * @param mode The type of URL to return. Accepts 'url' or 'src'. + * @param component The component to link the file to. + * @param componentId An ID to use in conjunction with the component. + * @param timemodified The time this file was modified. + * @param checkSize True if we shouldn't download files if their size is big, false otherwise. + * @param downloadUnknown True to download file in WiFi if their size is unknown, false otherwise. + * Ignored if checkSize=false. + * @param options Extra options (isexternalfile, repositorytype). + * @param revision File revision. If not defined, it will be calculated using the URL. + * @return Resolved with the URL to use. * @description * This will return a URL pointing to the content of the requested URL. * The URL returned is compatible to use with IMG tags. @@ -2122,8 +2184,8 @@ export class CoreFilepoolProvider { /** * Get time modified from a list of files. * - * @param {any[]} files List of files. - * @return {number} Time modified. + * @param files List of files. + * @return Time modified. */ getTimemodifiedFromFileList(files: any[]): number { let timemodified = 0; @@ -2140,18 +2202,18 @@ export class CoreFilepoolProvider { /** * Returns an absolute URL to access the file. * - * @param {string} siteId The site ID. - * @param {string} fileUrl The absolute URL to the file. - * @param {string} [mode=url] The type of URL to return. Accepts 'url' or 'src'. - * @param {string} component The component to link the file to. - * @param {string|number} [componentId] An ID to use in conjunction with the component. - * @param {number} [timemodified=0] The time this file was modified. - * @param {boolean} [checkSize=true] True if we shouldn't download files if their size is big, false otherwise. - * @param {boolean} [downloadUnknown] True to download file in WiFi if their size is unknown, false otherwise. - * Ignored if checkSize=false. - * @param {any} [options] Extra options (isexternalfile, repositorytype). - * @param {number} [revision] File revision. If not defined, it will be calculated using the URL. - * @return {Promise} Resolved with the URL to use. + * @param siteId The site ID. + * @param fileUrl The absolute URL to the file. + * @param mode The type of URL to return. Accepts 'url' or 'src'. + * @param component The component to link the file to. + * @param componentId An ID to use in conjunction with the component. + * @param timemodified The time this file was modified. + * @param checkSize True if we shouldn't download files if their size is big, false otherwise. + * @param downloadUnknown True to download file in WiFi if their size is unknown, false otherwise. + * Ignored if checkSize=false. + * @param options Extra options (isexternalfile, repositorytype). + * @param revision File revision. If not defined, it will be calculated using the URL. + * @return Resolved with the URL to use. * @description * This will return a URL pointing to the content of the requested URL. * The URL returned is compatible to use with a local browser. @@ -2165,8 +2227,8 @@ export class CoreFilepoolProvider { /** * Guess the filename of a file from its URL. This is very weak and unreliable. * - * @param {string} fileUrl The file URL. - * @return {string} The filename treated so it doesn't have any special character. + * @param fileUrl The file URL. + * @return The filename treated so it doesn't have any special character. */ protected guessFilenameFromUrl(fileUrl: string): string { let filename = ''; @@ -2224,9 +2286,9 @@ export class CoreFilepoolProvider { /** * Check if the file is already in the pool. This does not check if the file is on the disk. * - * @param {string} siteId The site ID. - * @param {string} fileUrl The file URL. - * @return {Promise} Resolved with file object from DB on success, rejected otherwise. + * @param siteId The site ID. + * @param fileUrl The file URL. + * @return Resolved with file object from DB on success, rejected otherwise. */ protected hasFileInPool(siteId: string, fileId: string): Promise { return this.sitesProvider.getSiteDb(siteId).then((db) => { @@ -2243,9 +2305,9 @@ export class CoreFilepoolProvider { /** * Check if the file is in the queue. * - * @param {string} siteId The site ID. - * @param {string} fileUrl The file URL. - * @return {Promise} Resolved with file object from DB on success, rejected otherwise. + * @param siteId The site ID. + * @param fileUrl The file URL. + * @return Resolved with file object from DB on success, rejected otherwise. */ protected hasFileInQueue(siteId: string, fileId: string): Promise { return this.appDB.getRecord(this.QUEUE_TABLE, { siteId: siteId, fileId: fileId }).then((entry) => { @@ -2262,10 +2324,10 @@ export class CoreFilepoolProvider { /** * Invalidate all the files in a site. * - * @param {string} siteId The site ID. - * @param {boolean} [onlyUnknown=true] True to only invalidate files from external repos or without revision/timemodified. - * It is advised to set it to true to reduce the performance and data usage of the app. - * @return {Promise} Resolved on success. + * @param siteId The site ID. + * @param onlyUnknown True to only invalidate files from external repos or without revision/timemodified. + * It is advised to set it to true to reduce the performance and data usage of the app. + * @return Resolved on success. */ invalidateAllFiles(siteId: string, onlyUnknown: boolean = true): Promise { return this.sitesProvider.getSiteDb(siteId).then((db) => { @@ -2283,9 +2345,9 @@ export class CoreFilepoolProvider { /** * Invalidate a file by URL. * - * @param {string} siteId The site ID. - * @param {string} fileUrl The file URL. - * @return {Promise} Resolved on success. + * @param siteId The site ID. + * @param fileUrl The file URL. + * @return Resolved on success. * @description * Invalidates a file by marking it stale. It will not be added to the queue automatically, but the next time this file * is requested it will be added to the queue. @@ -2293,8 +2355,8 @@ export class CoreFilepoolProvider { * Please note that, if a file is stale, the user will be presented the stale file if there is no network access. */ invalidateFileByUrl(siteId: string, fileUrl: string): Promise { - return this.fixPluginfileURL(siteId, fileUrl).then((fileUrl) => { - const fileId = this.getFileIdByUrl(fileUrl); + return this.fixPluginfileURL(siteId, fileUrl).then((file) => { + const fileId = this.getFileIdByUrl(file.fileurl); return this.sitesProvider.getSiteDb(siteId).then((db) => { return db.updateRecords(this.FILES_TABLE, { stale: 1 }, { fileId: fileId }); @@ -2305,12 +2367,12 @@ export class CoreFilepoolProvider { /** * Invalidate all the matching files from a component. * - * @param {string} siteId The site ID. - * @param {string} component The component to invalidate. - * @param {string|number} [componentId] An ID to use in conjunction with the component. - * @param {boolean} [onlyUnknown=true] True to only invalidate files from external repos or without revision/timemodified. - * It is advised to set it to true to reduce the performance and data usage of the app. - * @return {Promise} Resolved when done. + * @param siteId The site ID. + * @param component The component to invalidate. + * @param componentId An ID to use in conjunction with the component. + * @param onlyUnknown True to only invalidate files from external repos or without revision/timemodified. + * It is advised to set it to true to reduce the performance and data usage of the app. + * @return Resolved when done. */ invalidateFilesByComponent(siteId: string, component: string, componentId?: string | number, onlyUnknown: boolean = true) : Promise { @@ -2336,13 +2398,13 @@ export class CoreFilepoolProvider { /** * Check if a file is downloading. * - * @param {string} siteId The site ID. - * @param {string} fileUrl File URL. - * @param {Promise} Promise resolved if file is downloading, rejected otherwise. + * @param siteId The site ID. + * @param fileUrl File URL. + * @param Promise resolved if file is downloading, rejected otherwise. */ isFileDownloadingByUrl(siteId: string, fileUrl: string): Promise { - return this.fixPluginfileURL(siteId, fileUrl).then((fileUrl) => { - const fileId = this.getFileIdByUrl(fileUrl); + return this.fixPluginfileURL(siteId, fileUrl).then((file) => { + const fileId = this.getFileIdByUrl(file.fileurl); return this.hasFileInQueue(siteId, fileId); }); @@ -2351,10 +2413,10 @@ export class CoreFilepoolProvider { /** * Check if a file is outdated. * - * @param {CoreFilepoolFileEntry} entry Filepool entry. - * @param {number} [revision] File revision number. - * @param {number} [timemodified] The time this file was modified. - * @param {boolean} Whether the file is outdated. + * @param entry Filepool entry. + * @param revision File revision number. + * @param timemodified The time this file was modified. + * @param Whether the file is outdated. */ protected isFileOutdated(entry: CoreFilepoolFileEntry, revision?: number, timemodified?: number): boolean { return !!entry.stale || revision > entry.revision || timemodified > entry.timemodified; @@ -2363,8 +2425,8 @@ export class CoreFilepoolProvider { /** * Check if cannot determine if a file has been updated. * - * @param {CoreFilepoolFileEntry} entry Filepool entry. - * @return {boolean} Whether it cannot determine updates. + * @param entry Filepool entry. + * @return Whether it cannot determine updates. */ protected isFileUpdateUnknown(entry: CoreFilepoolFileEntry): boolean { return !!entry.isexternalfile || (entry.revision < 1 && !entry.timemodified); @@ -2373,8 +2435,8 @@ export class CoreFilepoolProvider { /** * Notify a file has been deleted. * - * @param {string} siteId The site ID. - * @param {string} fileId The file ID. + * @param siteId The site ID. + * @param fileId The file ID. */ protected notifyFileDeleted(siteId: string, fileId: string): void { this.eventsProvider.trigger(this.getFileEventName(siteId, fileId), { action: 'deleted' }); @@ -2383,8 +2445,8 @@ export class CoreFilepoolProvider { /** * Notify a file has been downloaded. * - * @param {string} siteId The site ID. - * @param {string} fileId The file ID. + * @param siteId The site ID. + * @param fileId The file ID. */ protected notifyFileDownloaded(siteId: string, fileId: string): void { this.eventsProvider.trigger(this.getFileEventName(siteId, fileId), { action: 'download', success: true }); @@ -2393,8 +2455,8 @@ export class CoreFilepoolProvider { /** * Notify error occurred while downloading a file. * - * @param {string} siteId The site ID. - * @param {string} fileId The file ID. + * @param siteId The site ID. + * @param fileId The file ID. */ protected notifyFileDownloadError(siteId: string, fileId: string): void { this.eventsProvider.trigger(this.getFileEventName(siteId, fileId), { action: 'download', success: false }); @@ -2403,8 +2465,8 @@ export class CoreFilepoolProvider { /** * Notify a file starts being downloaded or added to queue. * - * @param {string} siteId The site ID. - * @param {string} fileId The file ID. + * @param siteId The site ID. + * @param fileId The file ID. */ protected notifyFileDownloading(siteId: string, fileId: string): void { this.eventsProvider.trigger(this.getFileEventName(siteId, fileId), { action: 'downloading' }); @@ -2413,8 +2475,8 @@ export class CoreFilepoolProvider { /** * Notify a file has been outdated. * - * @param {string} siteId The site ID. - * @param {string} fileId The file ID. + * @param siteId The site ID. + * @param fileId The file ID. */ protected notifyFileOutdated(siteId: string, fileId: string): void { this.eventsProvider.trigger(this.getFileEventName(siteId, fileId), { action: 'outdated' }); @@ -2423,15 +2485,15 @@ export class CoreFilepoolProvider { /** * Prefetches a list of files. * - * @param {string} siteId The site ID. - * @param {any[]} fileList List of files to download. - * @param {string} component The component to link the file to. - * @param {string|number} [componentId] An ID to identify the download. - * @param {string} [extra] Extra data to store for the package. - * @param {string} [dirPath] Name of the directory where to store the files (inside filepool dir). If not defined, store - * the files directly inside the filepool folder. - * @param {Function} [onProgress] Function to call on progress. - * @return {Promise} Promise resolved when all files are downloaded. + * @param siteId The site ID. + * @param fileList List of files to download. + * @param component The component to link the file to. + * @param componentId An ID to identify the download. + * @param extra Extra data to store for the package. + * @param dirPath Name of the directory where to store the files (inside filepool dir). If not defined, store + * the files directly inside the filepool folder. + * @param onProgress Function to call on progress. + * @return Promise resolved when all files are downloaded. */ prefetchPackage(siteId: string, fileList: any[], component: string, componentId?: string | number, extra?: string, dirPath?: string, onProgress?: (event: any) => any): Promise { @@ -2482,7 +2544,7 @@ export class CoreFilepoolProvider { /** * Process the most important queue item. * - * @return {Promise} Resolved on success. Rejected on failure. + * @return Resolved on success. Rejected on failure. */ protected processImportantQueueItem(): Promise { return this.appDB.getRecords(this.QUEUE_TABLE, undefined, 'priority DESC, added ASC', undefined, 0, 1).then((items) => { @@ -2502,8 +2564,8 @@ export class CoreFilepoolProvider { /** * Process a queue item. * - * @param {CoreFilepoolQueueEntry} item The object from the queue store. - * @return {Promise} Resolved on success. Rejected on failure. + * @param item The object from the queue store. + * @return Resolved on success. Rejected on failure. */ protected processQueueItem(item: CoreFilepoolQueueEntry): Promise { // Cast optional fields to undefined instead of null. @@ -2619,9 +2681,9 @@ export class CoreFilepoolProvider { /** * Remove a file from the queue. * - * @param {string} siteId The site ID. - * @param {string} fileId The file ID. - * @return {Promise} Resolved on success. Rejected on failure. It is advised to silently ignore failures. + * @param siteId The site ID. + * @param fileId The file ID. + * @return Resolved on success. Rejected on failure. It is advised to silently ignore failures. */ protected removeFromQueue(siteId: string, fileId: string): Promise { return this.appDB.deleteRecords(this.QUEUE_TABLE, { siteId: siteId, fileId: fileId }); @@ -2630,14 +2692,29 @@ export class CoreFilepoolProvider { /** * Remove a file from the pool. * - * @param {string} siteId The site ID. - * @param {string} fileId The file ID. - * @return {Promise} Resolved on success. + * @param siteId The site ID. + * @param fileId The file ID. + * @return Resolved on success. */ protected removeFileById(siteId: string, fileId: string): Promise { return this.sitesProvider.getSiteDb(siteId).then((db) => { // Get the path to the file first since it relies on the file object stored in the pool. - return Promise.resolve(this.getFilePath(siteId, fileId)).then((path) => { + // Don't use getFilePath to prevent performing 2 DB requests. + let path = this.getFilepoolFolderPath(siteId) + '/' + fileId, + fileUrl; + + return this.hasFileInPool(siteId, fileId).then((entry) => { + fileUrl = entry.url; + + if (entry.extension) { + path += '.' + entry.extension; + } + + return path; + }).catch(() => { + // If file not found, use the path without extension. + return path; + }).then((path) => { const promises = []; // Remove entry from filepool store. @@ -2659,6 +2736,10 @@ export class CoreFilepoolProvider { return Promise.all(promises).then(() => { this.notifyFileDeleted(siteId, fileId); + + return this.pluginFileDelegate.fileDeleted(fileUrl, path, siteId).catch((error) => { + // Ignore errors. + }); }); }); }); @@ -2667,10 +2748,10 @@ export class CoreFilepoolProvider { /** * Delete all the matching files from a component. * - * @param {string} siteId The site ID. - * @param {string} component The component to link the file to. - * @param {string|number} [componentId] An ID to use in conjunction with the component. - * @return {Promise} Resolved on success. + * @param siteId The site ID. + * @param component The component to link the file to. + * @param componentId An ID to use in conjunction with the component. + * @return Resolved on success. */ removeFilesByComponent(siteId: string, component: string, componentId?: string | number): Promise { return this.sitesProvider.getSiteDb(siteId).then((db) => { @@ -2685,13 +2766,13 @@ export class CoreFilepoolProvider { /** * Remove a file from the pool. * - * @param {string} siteId The site ID. - * @param {string} fileUrl The file URL. - * @return {Promise} Resolved on success, rejected on failure. + * @param siteId The site ID. + * @param fileUrl The file URL. + * @return Resolved on success, rejected on failure. */ removeFileByUrl(siteId: string, fileUrl: string): Promise { - return this.fixPluginfileURL(siteId, fileUrl).then((fileUrl) => { - const fileId = this.getFileIdByUrl(fileUrl); + return this.fixPluginfileURL(siteId, fileUrl).then((file) => { + const fileId = this.getFileIdByUrl(file.fileurl); return this.removeFileById(siteId, fileId); }); @@ -2700,8 +2781,8 @@ export class CoreFilepoolProvider { /** * Removes the revision number from a file URL. * - * @param {string} url URL to remove the revision number. - * @return {string} URL without revision number. + * @param url URL to remove the revision number. + * @return URL without revision number. * @description * The revision is used to know if a file has changed. We remove it from the URL to prevent storing a file per revision. */ @@ -2718,10 +2799,10 @@ export class CoreFilepoolProvider { /** * Change the package status, setting it to the previous status. * - * @param {string} siteId Site ID. - * @param {string} component Package's component. - * @param {string|number} [componentId] An ID to use in conjunction with the component. - * @return {Promise} Promise resolved when the status is changed. Resolve param: new status. + * @param siteId Site ID. + * @param component Package's component. + * @param componentId An ID to use in conjunction with the component. + * @return Promise resolved when the status is changed. Resolve param: new status. */ setPackagePreviousStatus(siteId: string, component: string, componentId?: string | number): Promise { componentId = this.fixComponentId(componentId); @@ -2751,12 +2832,22 @@ export class CoreFilepoolProvider { }); } + /** + * Check if a file should be downloaded based on its size. + * + * @param size File size. + * @return Whether file should be downloaded. + */ + shouldDownload(size: number): boolean { + return size <= this.DOWNLOAD_THRESHOLD || (this.appProvider.isWifi() && size <= this.WIFI_DOWNLOAD_THRESHOLD); + } + /** * Convenience function to check if a file should be downloaded before opening it. * - * @param {string} url File online URL. - * @param {number} size File size. - * @return {Promise} Promise resolved if should download before open, rejected otherwise. + * @param url File online URL. + * @param size File size. + * @return Promise resolved if should download before open, rejected otherwise. * @description * Convenience function to check if a file should be downloaded before opening it. * @@ -2787,12 +2878,12 @@ export class CoreFilepoolProvider { /** * Store package status. * - * @param {string} siteId Site ID. - * @param {string} status New package status. - * @param {string} component Package's component. - * @param {string|number} [componentId] An ID to use in conjunction with the component. - * @param {string} [extra] Extra data to store for the package. If you want to store more than 1 value, use JSON.stringify. - * @return {Promise} Promise resolved when status is stored. + * @param siteId Site ID. + * @param status New package status. + * @param component Package's component. + * @param componentId An ID to use in conjunction with the component. + * @param extra Extra data to store for the package. If you want to store more than 1 value, use JSON.stringify. + * @return Promise resolved when status is stored. */ storePackageStatus(siteId: string, status: string, component: string, componentId?: string | number, extra?: string) : Promise { @@ -2859,13 +2950,13 @@ export class CoreFilepoolProvider { * Search for files in a CSS code and try to download them. Once downloaded, replace their URLs * and store the result in the CSS file. * - * @param {string} siteId Site ID. - * @param {string} fileUrl CSS file URL. - * @param {string} cssCode CSS code. - * @param {string} [component] The component to link the file to. - * @param {string|number} [componentId] An ID to use in conjunction with the component. - * @param {number} [revision] Revision to use in all files. If not defined, it will be calculated using the URL of each file. - * @return {Promise} Promise resolved with the CSS code. + * @param siteId Site ID. + * @param fileUrl CSS file URL. + * @param cssCode CSS code. + * @param component The component to link the file to. + * @param componentId An ID to use in conjunction with the component. + * @param revision Revision to use in all files. If not defined, it will be calculated using the URL of each file. + * @return Promise resolved with the CSS code. */ treatCSSCode(siteId: string, fileUrl: string, cssCode: string, component?: string, componentId?: string | number, revision?: number): Promise { @@ -2910,7 +3001,7 @@ export class CoreFilepoolProvider { /** * Remove extension from fileId in queue, used to migrate from previous file handling. * - * @return {Promise} Promise resolved when done. + * @return Promise resolved when done. */ treatExtensionInQueue(): Promise { this.logger.debug('Treat extensions in queue'); @@ -2938,10 +3029,10 @@ export class CoreFilepoolProvider { /** * Resolves or rejects a queue deferred and removes it from the list. * - * @param {string} siteId The site ID. - * @param {string} fileId The file ID. - * @param {boolean} resolve True if promise should be resolved, false if it should be rejected. - * @param {string} error String identifier for error message, if rejected. + * @param siteId The site ID. + * @param fileId The file ID. + * @param resolve True if promise should be resolved, false if it should be rejected. + * @param error String identifier for error message, if rejected. */ protected treatQueueDeferred(siteId: string, fileId: string, resolve: boolean, error?: string): void { if (this.queueDeferreds[siteId] && this.queueDeferreds[siteId][fileId]) { @@ -2957,10 +3048,10 @@ export class CoreFilepoolProvider { /** * Trigger mmCoreEventPackageStatusChanged with the right data. * - * @param {string} siteId Site ID. - * @param {string} status New package status. - * @param {string} component Package's component. - * @param {string|number} [componentId] An ID to use in conjunction with the component. + * @param siteId Site ID. + * @param status New package status. + * @param component Package's component. + * @param componentId An ID to use in conjunction with the component. */ protected triggerPackageStatusChanged(siteId: string, status: string, component: string, componentId?: string | number): void { const data = { @@ -2976,10 +3067,10 @@ export class CoreFilepoolProvider { * This function should be used if a package generates some new data during a download. Calling this function * right after generating the data in the download will prevent detecting this data as an update. * - * @param {string} siteId Site ID. - * @param {string} component Package's component. - * @param {string|number} [componentId] An ID to use in conjunction with the component. - * @return {Promise} Promise resolved when status is stored. + * @param siteId Site ID. + * @param component Package's component. + * @param componentId An ID to use in conjunction with the component. + * @return Promise resolved when status is stored. */ updatePackageDownloadTime(siteId: string, component: string, componentId?: string | number): Promise { componentId = this.fixComponentId(componentId); diff --git a/src/providers/groups.ts b/src/providers/groups.ts index 35d0d2e11..d0c96e8d2 100644 --- a/src/providers/groups.ts +++ b/src/providers/groups.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -24,25 +24,21 @@ import { CoreSite, CoreSiteWSPreSets } from '@classes/site'; export interface CoreGroupInfo { /** * List of groups. - * @type {any[]} */ groups?: any[]; /** * Whether it's separate groups. - * @type {boolean} */ separateGroups?: boolean; /** * Whether it's visible groups. - * @type {boolean} */ visibleGroups?: boolean; /** * The group ID to use by default. If all participants is visible, 0 will be used. First group ID otherwise. - * @type {number} */ defaultGroupId?: number; } @@ -64,10 +60,10 @@ export class CoreGroupsProvider { /** * Check if group mode of an activity is enabled. * - * @param {number} cmId Course module ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @param {boolean} [ignoreCache] True if it should ignore cached data (it will always fail in offline or server down). - * @return {Promise} Promise resolved with true if the activity has groups, resolved with false otherwise. + * @param cmId Course module ID. + * @param siteId Site ID. If not defined, current site. + * @param ignoreCache True if it should ignore cached data (it will always fail in offline or server down). + * @return Promise resolved with true if the activity has groups, resolved with false otherwise. */ activityHasGroups(cmId: number, siteId?: string, ignoreCache?: boolean): Promise { return this.getActivityGroupMode(cmId, siteId, ignoreCache).then((groupmode) => { @@ -80,11 +76,11 @@ export class CoreGroupsProvider { /** * Get the groups allowed in an activity. * - * @param {number} cmId Course module ID. - * @param {number} [userId] User ID. If not defined, use current user. - * @param {string} [siteId] Site ID. If not defined, current site. - * @param {boolean} [ignoreCache] True if it should ignore cached data (it will always fail in offline or server down). - * @return {Promise} Promise resolved when the groups are retrieved. + * @param cmId Course module ID. + * @param userId User ID. If not defined, use current user. + * @param siteId Site ID. If not defined, current site. + * @param ignoreCache True if it should ignore cached data (it will always fail in offline or server down). + * @return Promise resolved when the groups are retrieved. */ getActivityAllowedGroups(cmId: number, userId?: number, siteId?: string, ignoreCache?: boolean): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -117,9 +113,9 @@ export class CoreGroupsProvider { /** * Get cache key for group mode WS calls. * - * @param {number} cmId Course module ID. - * @param {number} userId User ID. - * @return {string} Cache key. + * @param cmId Course module ID. + * @param userId User ID. + * @return Cache key. */ protected getActivityAllowedGroupsCacheKey(cmId: number, userId: number): string { return this.ROOT_CACHE_KEY + 'allowedgroups:' + cmId + ':' + userId; @@ -128,11 +124,11 @@ export class CoreGroupsProvider { /** * Get the groups allowed in an activity if they are allowed. * - * @param {number} cmId Course module ID. - * @param {number} [userId] User ID. If not defined, use current user. - * @param {string} [siteId] Site ID. If not defined, current site. - * @param {boolean} [ignoreCache] True if it should ignore cached data (it will always fail in offline or server down). - * @return {Promise} Promise resolved when the groups are retrieved. If not allowed, empty array will be returned. + * @param cmId Course module ID. + * @param userId User ID. If not defined, use current user. + * @param siteId Site ID. If not defined, current site. + * @param ignoreCache True if it should ignore cached data (it will always fail in offline or server down). + * @return Promise resolved when the groups are retrieved. If not allowed, empty array will be returned. */ getActivityAllowedGroupsIfEnabled(cmId: number, userId?: number, siteId?: string, ignoreCache?: boolean): Promise { siteId = siteId || this.sitesProvider.getCurrentSiteId(); @@ -153,12 +149,12 @@ export class CoreGroupsProvider { /** * Helper function to get activity group info (group mode and list of groups). * - * @param {number} cmId Course module ID. - * @param {boolean} [addAllParts] Deprecated. - * @param {number} [userId] User ID. If not defined, use current user. - * @param {string} [siteId] Site ID. If not defined, current site. - * @param {boolean} [ignoreCache] True if it should ignore cached data (it will always fail in offline or server down). - * @return {Promise} Promise resolved with the group info. + * @param cmId Course module ID. + * @param addAllParts Deprecated. + * @param userId User ID. If not defined, use current user. + * @param siteId Site ID. If not defined, current site. + * @param ignoreCache True if it should ignore cached data (it will always fail in offline or server down). + * @return Promise resolved with the group info. */ getActivityGroupInfo(cmId: number, addAllParts?: boolean, userId?: number, siteId?: string, ignoreCache?: boolean) : Promise { @@ -203,10 +199,10 @@ export class CoreGroupsProvider { /** * Get the group mode of an activity. * - * @param {number} cmId Course module ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @param {boolean} [ignoreCache] True if it should ignore cached data (it will always fail in offline or server down). - * @return {Promise} Promise resolved when the group mode is retrieved. + * @param cmId Course module ID. + * @param siteId Site ID. If not defined, current site. + * @param ignoreCache True if it should ignore cached data (it will always fail in offline or server down). + * @return Promise resolved when the group mode is retrieved. */ getActivityGroupMode(cmId: number, siteId?: string, ignoreCache?: boolean): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -236,8 +232,8 @@ export class CoreGroupsProvider { /** * Get cache key for group mode WS calls. * - * @param {number} cmId Course module ID. - * @return {string} Cache key. + * @param cmId Course module ID. + * @return Cache key. */ protected getActivityGroupModeCacheKey(cmId: number): string { return this.ROOT_CACHE_KEY + 'groupmode:' + cmId; @@ -246,8 +242,8 @@ export class CoreGroupsProvider { /** * Get user groups in all the user enrolled courses. * - * @param {string} [siteId] Site to get the groups from. If not defined, use current site. - * @return {Promise} Promise resolved when the groups are retrieved. + * @param siteId Site to get the groups from. If not defined, use current site. + * @return Promise resolved when the groups are retrieved. */ getAllUserGroups(siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -268,10 +264,10 @@ export class CoreGroupsProvider { /** * Get user groups in all the supplied courses. * - * @param {any[]} courses List of courses or course ids to get the groups from. - * @param {string} [siteId] Site to get the groups from. If not defined, use current site. - * @param {number} [userId] ID of the user. If not defined, use the userId related to siteId. - * @return {Promise} Promise resolved when the groups are retrieved. + * @param courses List of courses or course ids to get the groups from. + * @param siteId Site to get the groups from. If not defined, use current site. + * @param userId ID of the user. If not defined, use the userId related to siteId. + * @return Promise resolved when the groups are retrieved. */ getUserGroups(courses: any[], siteId?: string, userId?: number): Promise { // Get all courses one by one. @@ -289,10 +285,10 @@ export class CoreGroupsProvider { /** * Get user groups in a course. * - * @param {number} courseId ID of the course. 0 to get all enrolled courses groups (Moodle version > 3.6). - * @param {string} [siteId] Site to get the groups from. If not defined, use current site. - * @param {number} [userId] ID of the user. If not defined, use ID related to siteid. - * @return {Promise} Promise resolved when the groups are retrieved. + * @param courseId ID of the course. 0 to get all enrolled courses groups (Moodle version > 3.6). + * @param siteId Site to get the groups from. If not defined, use current site. + * @param userId ID of the user. If not defined, use ID related to siteid. + * @return Promise resolved when the groups are retrieved. */ getUserGroupsInCourse(courseId: number, siteId?: string, userId?: number): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -319,7 +315,7 @@ export class CoreGroupsProvider { /** * Get prefix cache key for user groups in course WS calls. * - * @return {string} Prefix Cache key. + * @return Prefix Cache key. */ protected getUserGroupsInCoursePrefixCacheKey(): string { return this.ROOT_CACHE_KEY + 'courseGroups:'; @@ -328,9 +324,9 @@ export class CoreGroupsProvider { /** * Get cache key for user groups in course WS calls. * - * @param {number} courseId Course ID. - * @param {number} userId User ID. - * @return {string} Cache key. + * @param courseId Course ID. + * @param userId User ID. + * @return Cache key. */ protected getUserGroupsInCourseCacheKey(courseId: number, userId: number): string { return this.getUserGroupsInCoursePrefixCacheKey() + courseId + ':' + userId; @@ -339,10 +335,10 @@ export class CoreGroupsProvider { /** * Invalidates activity allowed groups. * - * @param {number} cmId Course module ID. - * @param {number} [userId] User ID. If not defined, use current user. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the data is invalidated. + * @param cmId Course module ID. + * @param userId User ID. If not defined, use current user. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the data is invalidated. */ invalidateActivityAllowedGroups(cmId: number, userId?: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -355,9 +351,9 @@ export class CoreGroupsProvider { /** * Invalidates activity group mode. * - * @param {number} cmId Course module ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the data is invalidated. + * @param cmId Course module ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the data is invalidated. */ invalidateActivityGroupMode(cmId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -368,10 +364,10 @@ export class CoreGroupsProvider { /** * Invalidates all activity group info: mode and allowed groups. * - * @param {number} cmId Course module ID. - * @param {number} [userId] User ID. If not defined, use current user. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the data is invalidated. + * @param cmId Course module ID. + * @param userId User ID. If not defined, use current user. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the data is invalidated. */ invalidateActivityGroupInfo(cmId: number, userId?: number, siteId?: string): Promise { const promises = []; @@ -384,8 +380,8 @@ export class CoreGroupsProvider { /** * Invalidates user groups in all user enrolled courses. * - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the data is invalidated. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the data is invalidated. */ invalidateAllUserGroups(siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -400,10 +396,10 @@ export class CoreGroupsProvider { /** * Invalidates user groups in courses. * - * @param {any[]} courses List of courses or course ids. - * @param {string} [siteId] Site ID. If not defined, current site. - * @param {number} [userId] User ID. If not defined, use current user. - * @return {Promise} Promise resolved when the data is invalidated. + * @param courses List of courses or course ids. + * @param siteId Site ID. If not defined, current site. + * @param userId User ID. If not defined, use current user. + * @return Promise resolved when the data is invalidated. */ invalidateUserGroups(courses: any[], siteId?: string, userId?: number): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -422,10 +418,10 @@ export class CoreGroupsProvider { /** * Invalidates user groups in course. * - * @param {number} courseId ID of the course. 0 to get all enrolled courses groups (Moodle version > 3.6). - * @param {string} [siteId] Site ID. If not defined, current site. - * @param {number} [userId] User ID. If not defined, use current user. - * @return {Promise} Promise resolved when the data is invalidated. + * @param courseId ID of the course. 0 to get all enrolled courses groups (Moodle version > 3.6). + * @param siteId Site ID. If not defined, current site. + * @param userId User ID. If not defined, use current user. + * @return Promise resolved when the data is invalidated. */ invalidateUserGroupsInCourse(courseId: number, siteId?: string, userId?: number): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -438,9 +434,9 @@ export class CoreGroupsProvider { /** * Validate a group ID. If the group is not visible by the user, it will return the first group ID. * - * @param {number} groupId Group ID to validate. - * @param {CoreGroupInfo} groupInfo Group info. - * @return {number} Group ID to use. + * @param groupId Group ID to validate. + * @param groupInfo Group info. + * @return Group ID to use. */ validateGroupId(groupId: number, groupInfo: CoreGroupInfo): number { if (groupId > 0 && groupInfo && groupInfo.groups && groupInfo.groups.length > 0) { diff --git a/src/providers/init.ts b/src/providers/init.ts index e06ce450d..f44e6f79e 100644 --- a/src/providers/init.ts +++ b/src/providers/init.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -23,26 +23,23 @@ import { CoreUtilsProvider } from './utils/utils'; export interface CoreInitHandler { /** * A name to identify the handler. - * @type {string} */ name: string; /** * Function to execute during the init process. * - * @return {Promise} Promise resolved when done. + * @return Promise resolved when done. */ load(): Promise; /** * The highest priority is executed first. You should use values lower than MAX_RECOMMENDED_PRIORITY. - * @type {number} */ priority?: number; /** * Set this to true when this process should be resolved before any following one. - * @type {boolean} */ blocking?: boolean; } @@ -107,7 +104,7 @@ export class CoreInitDelegate { /** * Instantly returns if the app is ready. * - * @return {boolean} Whether it's ready. + * @return Whether it's ready. */ isReady(): boolean { return this.readiness.resolved; @@ -116,8 +113,8 @@ export class CoreInitDelegate { /** * Convenience function to return a function that executes the process. * - * @param {CoreInitHandler} data The data of the process. - * @return {Promise} Promise of the process. + * @param data The data of the process. + * @return Promise of the process. */ protected prepareProcess(data: CoreInitHandler): Promise { let promise; @@ -138,7 +135,7 @@ export class CoreInitDelegate { /** * Notifies when the app is ready. This returns a promise that is resolved when the app is initialised. * - * @return {Promise} Resolved when the app is initialised. Never rejected. + * @return Resolved when the app is initialised. Never rejected. */ ready(): Promise { if (typeof this.readiness === 'undefined') { @@ -161,7 +158,7 @@ export class CoreInitDelegate { * * This delegate cannot be used by site plugins. * - * @param {CoreInitHandler} instance The instance of the handler. + * @param instance The instance of the handler. */ registerProcess(handler: CoreInitHandler): void { if (typeof handler.priority == 'undefined') { diff --git a/src/providers/lang.ts b/src/providers/lang.ts index 68463d4e3..df16004e3 100644 --- a/src/providers/lang.ts +++ b/src/providers/lang.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -17,6 +17,7 @@ import { TranslateService } from '@ngx-translate/core'; import * as moment from 'moment'; import { Globalization } from '@ionic-native/globalization'; import { Platform, Config } from 'ionic-angular'; +import { CoreAppProvider } from '@providers/app'; import { CoreConfigProvider } from './config'; import { CoreConfigConstants } from '../configconstants'; @@ -39,6 +40,13 @@ export class CoreLangProvider { translate.use(this.defaultLanguage); platform.ready().then(() => { + if (CoreAppProvider.isAutomated()) { + // Force current language to English when Behat is running. + this.changeCurrentLanguage('en'); + + return; + } + this.getCurrentLanguage().then((language) => { this.changeCurrentLanguage(language); }); @@ -53,9 +61,9 @@ export class CoreLangProvider { /** * Add a set of site plugins strings for a certain language. * - * @param {string} lang The language where to add the strings. - * @param {any} strings Object with the strings to add. - * @param {string} [prefix] A prefix to add to all keys. + * @param lang The language where to add the strings. + * @param strings Object with the strings to add. + * @param prefix A prefix to add to all keys. */ addSitePluginsStrings(lang: string, strings: any, prefix?: string): void { lang = lang.replace(/_/g, '-'); // Use the app format instead of Moodle format. @@ -90,8 +98,8 @@ export class CoreLangProvider { * Capitalize a string (make the first letter uppercase). * We cannot use a function from text utils because it would cause a circular dependency. * - * @param {string} value String to capitalize. - * @return {string} Capitalized string. + * @param value String to capitalize. + * @return Capitalized string. */ protected capitalize(value: string): string { return value.charAt(0).toUpperCase() + value.slice(1); @@ -100,8 +108,8 @@ export class CoreLangProvider { /** * Change current language. * - * @param {string} language New language to use. - * @return {Promise} Promise resolved when the change is finished. + * @param language New language to use. + * @return Promise resolved when the change is finished. */ changeCurrentLanguage(language: string): Promise { const promises = []; @@ -191,7 +199,7 @@ export class CoreLangProvider { /** * Get all current custom strings. * - * @return {any} Custom strings. + * @return Custom strings. */ getAllCustomStrings(): any { return this.customStrings; @@ -200,7 +208,7 @@ export class CoreLangProvider { /** * Get all current site plugins strings. * - * @return {any} Site plugins strings. + * @return Site plugins strings. */ getAllSitePluginsStrings(): any { return this.sitePluginsStrings; @@ -209,7 +217,7 @@ export class CoreLangProvider { /** * Get current language. * - * @return {Promise} Promise resolved with the current language. + * @return Promise resolved with the current language. */ getCurrentLanguage(): Promise { @@ -263,7 +271,7 @@ export class CoreLangProvider { /** * Get the default language. * - * @return {string} Default language. + * @return Default language. */ getDefaultLanguage(): string { return this.defaultLanguage; @@ -272,7 +280,7 @@ export class CoreLangProvider { /** * Get the fallback language. * - * @return {string} Fallback language. + * @return Fallback language. */ getFallbackLanguage(): string { return this.fallbackLanguage; @@ -281,8 +289,8 @@ export class CoreLangProvider { /** * Get the full list of translations for a certain language. * - * @param {string} lang The language to check. - * @return {Promise} Promise resolved when done. + * @param lang The language to check. + * @return Promise resolved when done. */ getTranslationTable(lang: string): Promise { // Create a promise to convert the observable into a promise. @@ -300,7 +308,7 @@ export class CoreLangProvider { /** * Load certain custom strings. * - * @param {string} strings Custom strings to load (tool_mobile_customlangstrings). + * @param strings Custom strings to load (tool_mobile_customlangstrings). */ loadCustomStrings(strings: string): void { if (strings == this.customStringsRaw) { @@ -315,6 +323,8 @@ export class CoreLangProvider { return; } + let currentLangChanged = false; + const list: string[] = strings.split(/(?:\r\n|\r|\n)/); list.forEach((entry: string) => { const values: string[] = entry.split('|'); @@ -327,6 +337,10 @@ export class CoreLangProvider { lang = values[2].replace(/_/g, '-'); // Use the app format instead of Moodle format. + if (lang == this.currentLanguage) { + currentLangChanged = true; + } + if (!this.customStrings[lang]) { this.customStrings[lang] = {}; } @@ -340,14 +354,22 @@ export class CoreLangProvider { }); this.customStringsRaw = strings; + + if (currentLangChanged) { + // Some lang strings have changed, emit an event to update the pipes. + this.translate.onLangChange.emit({ + lang: this.currentLanguage, + translations: this.translate.translations[this.currentLanguage] + }); + } } /** * Load custom strings for a certain language that weren't loaded because the language wasn't active. * - * @param {any} langObject The object with the strings to load. - * @param {string} lang Language to load. - * @return {boolean} Whether the translation table was modified. + * @param langObject The object with the strings to load. + * @param lang Language to load. + * @return Whether the translation table was modified. */ loadLangStrings(langObject: any, lang: string): boolean { let langApplied = false; @@ -375,10 +397,10 @@ export class CoreLangProvider { /** * Load a string in a certain lang object and in the translate table if the lang is loaded. * - * @param {any} langObject The object where to store the lang. - * @param {string} lang Language code. - * @param {string} key String key. - * @param {string} value String value. + * @param langObject The object where to store the lang. + * @param lang Language code. + * @param key String key. + * @param value String value. */ loadString(langObject: any, lang: string, key: string, value: string): void { lang = lang.replace(/_/g, '-'); // Use the app format instead of Moodle format. @@ -407,7 +429,7 @@ export class CoreLangProvider { /** * Unload custom or site plugin strings, removing them from the translations table. * - * @param {any} strings Strings to unload. + * @param strings Strings to unload. */ protected unloadStrings(strings: any): void { // Iterate over all languages and strings. diff --git a/src/providers/local-notifications.ts b/src/providers/local-notifications.ts index ae9de1652..3967f8cfe 100644 --- a/src/providers/local-notifications.ts +++ b/src/providers/local-notifications.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -163,10 +163,10 @@ export class CoreLocalNotificationsProvider { /** * Cancel a local notification. * - * @param {number} id Notification id. - * @param {string} component Component of the notification. - * @param {string} siteId Site ID. - * @return {Promise} Promise resolved when the notification is cancelled. + * @param id Notification id. + * @param component Component of the notification. + * @param siteId Site ID. + * @return Promise resolved when the notification is cancelled. */ cancel(id: number, component: string, siteId: string): Promise { return this.getUniqueNotificationId(id, component, siteId).then((uniqueId) => { @@ -177,8 +177,8 @@ export class CoreLocalNotificationsProvider { /** * Cancel all the scheduled notifications belonging to a certain site. * - * @param {string} siteId Site ID. - * @return {Promise} Promise resolved when the notifications are cancelled. + * @param siteId Site ID. + * @return Promise resolved when the notifications are cancelled. */ cancelSiteNotifications(siteId: string): Promise { @@ -208,7 +208,7 @@ export class CoreLocalNotificationsProvider { /** * Check whether sound can be disabled for notifications. * - * @return {boolean} Whether sound can be disabled for notifications. + * @return Whether sound can be disabled for notifications. */ canDisableSound(): boolean { // Only allow disabling sound in Android 7 or lower. In iOS and Android 8+ it can easily be done with system settings. @@ -219,7 +219,7 @@ export class CoreLocalNotificationsProvider { /** * Create the default channel. It is used to change the name. * - * @return {Promise} Promise resolved when done. + * @return Promise resolved when done. */ protected createDefaultChannel(): Promise { if (!this.platform.is('android')) { @@ -238,9 +238,9 @@ export class CoreLocalNotificationsProvider { /** * Get a code to create unique notifications. If there's no code assigned, create a new one. * - * @param {string} table Table to search in local DB. - * @param {string} id ID of the element to get its code. - * @return {Promise} Promise resolved when the code is retrieved. + * @param table Table to search in local DB. + * @param id ID of the element to get its code. + * @return Promise resolved when the code is retrieved. */ protected getCode(table: string, id: string): Promise { const key = table + '#' + id; @@ -276,8 +276,8 @@ export class CoreLocalNotificationsProvider { * Get a notification component code to be used. * If it's the first time this component is used to send notifications, create a new code for it. * - * @param {string} component Component name. - * @return {Promise} Promise resolved when the component code is retrieved. + * @param component Component name. + * @return Promise resolved when the component code is retrieved. */ protected getComponentCode(component: string): Promise { return this.requestCode(this.COMPONENTS_TABLE, component); @@ -287,8 +287,8 @@ export class CoreLocalNotificationsProvider { * Get a site code to be used. * If it's the first time this site is used to send notifications, create a new code for it. * - * @param {string} siteId Site ID. - * @return {Promise} Promise resolved when the site code is retrieved. + * @param siteId Site ID. + * @return Promise resolved when the site code is retrieved. */ protected getSiteCode(siteId: string): Promise { return this.requestCode(this.SITES_TABLE, siteId); @@ -302,10 +302,10 @@ export class CoreLocalNotificationsProvider { * -There are less than 11 components. * -The notificationId passed as parameter is lower than 10000000. * - * @param {number} notificationId Notification ID. - * @param {string} component Component triggering the notification. - * @param {string} siteId Site ID. - * @return {Promise} Promise resolved when the notification ID is generated. + * @param notificationId Notification ID. + * @param component Component triggering the notification. + * @param siteId Site ID. + * @return Promise resolved when the notification ID is generated. */ protected getUniqueNotificationId(notificationId: number, component: string, siteId: string): Promise { if (!siteId || !component) { @@ -323,8 +323,8 @@ export class CoreLocalNotificationsProvider { /** * Handle an event triggered by the local notifications plugin. * - * @param {string} eventName Name of the event. - * @param {any} notification Notification. + * @param eventName Name of the event. + * @param notification Notification. */ protected handleEvent(eventName: string, notification: any): void { if (notification && notification.data) { @@ -337,7 +337,7 @@ export class CoreLocalNotificationsProvider { /** * Returns whether local notifications plugin is installed. * - * @return {boolean} Whether local notifications plugin is installed. + * @return Whether local notifications plugin is installed. */ isAvailable(): boolean { const win = window; @@ -349,8 +349,8 @@ export class CoreLocalNotificationsProvider { /** * Check if a notification has been triggered with the same trigger time. * - * @param {ILocalNotification} notification Notification to check. - * @return {Promise} Promise resolved with a boolean indicating if promise is triggered (true) or not. + * @param notification Notification to check. + * @return Promise resolved with a boolean indicating if promise is triggered (true) or not. */ isTriggered(notification: ILocalNotification): Promise { return this.appDB.getRecord(this.TRIGGERED_TABLE, { id: notification.id }).then((stored) => { @@ -369,7 +369,7 @@ export class CoreLocalNotificationsProvider { /** * Notify notification click to observers. Only the observers with the same component as the notification will be notified. * - * @param {any} data Data received by the notification. + * @param data Data received by the notification. */ notifyClick(data: any): void { this.notifyEvent('click', data); @@ -378,8 +378,8 @@ export class CoreLocalNotificationsProvider { /** * Notify a certain event to observers. Only the observers with the same component as the notification will be notified. * - * @param {string} eventName Name of the event to notify. - * @param {any} data Data received by the notification. + * @param eventName Name of the event to notify. + * @param data Data received by the notification. */ notifyEvent(eventName: string, data: any): void { // Execute the code in the Angular zone, so change detection doesn't stop working. @@ -434,9 +434,9 @@ export class CoreLocalNotificationsProvider { /** * Register an observer to be notified when a notification belonging to a certain component is clicked. * - * @param {string} component Component to listen notifications for. - * @param {Function} callback Function to call with the data received by the notification. - * @return {any} Object with an "off" property to stop listening for clicks. + * @param component Component to listen notifications for. + * @param callback Function to call with the data received by the notification. + * @return Object with an "off" property to stop listening for clicks. */ registerClick(component: string, callback: Function): any { return this.registerObserver('click', component, callback); @@ -445,10 +445,10 @@ export class CoreLocalNotificationsProvider { /** * Register an observer to be notified when a certain event is fired for a notification belonging to a certain component. * - * @param {string} eventName Name of the event to listen to. - * @param {string} component Component to listen notifications for. - * @param {Function} callback Function to call with the data received by the notification. - * @return {any} Object with an "off" property to stop listening for events. + * @param eventName Name of the event to listen to. + * @param component Component to listen notifications for. + * @param callback Function to call with the data received by the notification. + * @return Object with an "off" property to stop listening for events. */ registerObserver(eventName: string, component: string, callback: Function): any { this.logger.debug(`Register observer '${component}' for event '${eventName}'.`); @@ -474,8 +474,8 @@ export class CoreLocalNotificationsProvider { /** * Remove a notification from triggered store. * - * @param {number} id Notification ID. - * @return {Promise} Promise resolved when it is removed. + * @param id Notification ID. + * @return Promise resolved when it is removed. */ removeTriggered(id: number): Promise { return this.appDB.deleteRecords(this.TRIGGERED_TABLE, { id: id }); @@ -484,9 +484,9 @@ export class CoreLocalNotificationsProvider { /** * Request a unique code. The request will be added to the queue and the queue is going to be started if it's paused. * - * @param {string} table Table to search in local DB. - * @param {string} id ID of the element to get its code. - * @return {Promise} Promise resolved when the code is retrieved. + * @param table Table to search in local DB. + * @param id ID of the element to get its code. + * @return Promise resolved when the code is retrieved. */ protected requestCode(table: string, id: string): Promise { const deferred = this.utils.promiseDefer(), @@ -515,7 +515,7 @@ export class CoreLocalNotificationsProvider { /** * Reschedule all notifications that are already scheduled. * - * @return {Promise} Promise resolved when all notifications have been rescheduled. + * @return Promise resolved when all notifications have been rescheduled. */ rescheduleAll(): Promise { // Get all the scheduled notifications. @@ -536,12 +536,12 @@ export class CoreLocalNotificationsProvider { /** * Schedule a local notification. * - * @param {ILocalNotification} notification Notification to schedule. Its ID should be lower than 10000000 and it should - * be unique inside its component and site. - * @param {string} component Component triggering the notification. It is used to generate unique IDs. - * @param {string} siteId Site ID. - * @param {boolean} [alreadyUnique] Whether the ID is already unique. - * @return {Promise} Promise resolved when the notification is scheduled. + * @param notification Notification to schedule. Its ID should be lower than 10000000 and it should + * be unique inside its component and site. + * @param component Component triggering the notification. It is used to generate unique IDs. + * @param siteId Site ID. + * @param alreadyUnique Whether the ID is already unique. + * @return Promise resolved when the notification is scheduled. */ schedule(notification: ILocalNotification, component: string, siteId: string, alreadyUnique?: boolean): Promise { let promise; @@ -578,8 +578,8 @@ export class CoreLocalNotificationsProvider { /** * Helper function to schedule a notification object if it hasn't been triggered already. * - * @param {ILocalNotification} notification Notification to schedule. - * @return {Promise} Promise resolved when scheduled. + * @param notification Notification to schedule. + * @return Promise resolved when scheduled. */ protected scheduleNotification(notification: ILocalNotification): Promise { // Check if the notification has been triggered already. @@ -619,7 +619,7 @@ export class CoreLocalNotificationsProvider { * This function was used because local notifications weren't displayed when the app was in foreground in iOS10+, * but the issue was fixed in the plugin and this function is no longer used. * - * @param {ILocalNotification} notification Notification. + * @param notification Notification. */ showNotificationPopover(notification: ILocalNotification): void { @@ -711,8 +711,8 @@ export class CoreLocalNotificationsProvider { * Function to call when a notification is triggered. Stores the notification so it's not scheduled again unless the * time is changed. * - * @param {ILocalNotification} notification Triggered notification. - * @return {Promise} Promise resolved when stored, rejected otherwise. + * @param notification Triggered notification. + * @return Promise resolved when stored, rejected otherwise. */ trigger(notification: ILocalNotification): Promise { const entry = { @@ -726,9 +726,9 @@ export class CoreLocalNotificationsProvider { /** * Update a component name. * - * @param {string} oldName The old name. - * @param {string} newName The new name. - * @return {Promise} Promise resolved when done. + * @param oldName The old name. + * @param newName The new name. + * @return Promise resolved when done. */ updateComponentName(oldName: string, newName: string): Promise { const oldId = this.COMPONENTS_TABLE + '#' + oldName, diff --git a/src/providers/logger.ts b/src/providers/logger.ts index 85a74e4d3..1453f8cbf 100644 --- a/src/providers/logger.ts +++ b/src/providers/logger.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -38,8 +38,8 @@ export class CoreLoggerProvider { /** * Get a logger instance for a certain class, service or component. * - * @param {string} className Name to use in the messages. - * @return {ant} Instance. + * @param className Name to use in the messages. + * @return Instance. */ getInstance(className: string): any { className = className || ''; @@ -57,9 +57,9 @@ export class CoreLoggerProvider { /** * Prepare a logging function, concatenating the timestamp and class name to all messages. * - * @param {Function} logFn Log function to use. - * @param {string} className Name to use in the messages. - * @return {Function} Prepared function. + * @param logFn Log function to use. + * @param className Name to use in the messages. + * @return Prepared function. */ private prepareLogFn(logFn: Function, className: string): Function { // Return our own function that will call the logging function with the treated message. diff --git a/src/providers/plugin-file-delegate.ts b/src/providers/plugin-file-delegate.ts index 6f1e3e902..469518698 100644 --- a/src/providers/plugin-file-delegate.ts +++ b/src/providers/plugin-file-delegate.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -13,74 +13,169 @@ // limitations under the License. import { Injectable } from '@angular/core'; +import { CoreEventsProvider } from './events'; import { CoreLoggerProvider } from './logger'; +import { CoreSitesProvider } from './sites'; +import { CoreWSExternalFile } from '@providers/ws'; +import { FileEntry } from '@ionic-native/file'; +import { CoreDelegate, CoreDelegateHandler } from '@classes/delegate'; /** * Interface that all plugin file handlers must implement. */ -export interface CorePluginFileHandler { - /** - * A name to identify the handler. - * @type {string} - */ - name: string; +export interface CorePluginFileHandler extends CoreDelegateHandler { /** * The "component" of the handler. It should match the "component" of pluginfile URLs. - * @type {string} + * It is used to treat revision from URLs. */ - component: string; + component?: string; /** * Return the RegExp to match the revision on pluginfile URLs. * - * @param {string[]} args Arguments of the pluginfile URL defining component and filearea at least. - * @return {RegExp} RegExp to match the revision on pluginfile URLs. + * @param args Arguments of the pluginfile URL defining component and filearea at least. + * @return RegExp to match the revision on pluginfile URLs. */ getComponentRevisionRegExp?(args: string[]): RegExp; /** * Should return the string to remove the revision on pluginfile url. * - * @param {string[]} args Arguments of the pluginfile URL defining component and filearea at least. - * @return {string} String to remove the revision on pluginfile url. + * @param args Arguments of the pluginfile URL defining component and filearea at least. + * @return String to remove the revision on pluginfile url. */ getComponentRevisionReplace?(args: string[]): string; + + /** + * React to a file being deleted. + * + * @param fileUrl The file URL used to download the file. + * @param path The path of the deleted file. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when done. + */ + fileDeleted?(fileUrl: string, path: string, siteId?: string): Promise; + + /** + * Check whether a file can be downloaded. If so, return the file to download. + * + * @param file The file data. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the file to use. Rejected if cannot download. + */ + getDownloadableFile?(file: CoreWSExternalFile, siteId?: string): Promise; + + /** + * Given an HTML element, get the URLs of the files that should be downloaded and weren't treated by + * CoreFilepoolProvider.extractDownloadableFilesFromHtml. + * + * @param container Container where to get the URLs from. + * @return List of URLs. + */ + getDownloadableFilesFromHTML?(container: HTMLElement): string[]; + + /** + * Get a file size. + * + * @param file The file data. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the size. + */ + getFileSize?(file: CoreWSExternalFile, siteId?: string): Promise; + + /** + * Check whether the file should be treated by this handler. It is used in functions where the component isn't used. + * + * @param file The file data. + * @return Whether the file should be treated by this handler. + */ + shouldHandleFile?(file: CoreWSExternalFile): boolean; + + /** + * Treat a downloaded file. + * + * @param fileUrl The file URL used to download the file. + * @param file The file entry of the downloaded file. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when done. + */ + treatDownloadedFile?(fileUrl: string, file: FileEntry, siteId?: string): Promise; } /** * Delegate to register pluginfile information handlers. */ @Injectable() -export class CorePluginFileDelegate { - protected logger; - protected handlers: { [s: string]: CorePluginFileHandler } = {}; +export class CorePluginFileDelegate extends CoreDelegate { + protected handlerNameProperty = 'component'; - constructor(logger: CoreLoggerProvider) { - this.logger = logger.getInstance('CorePluginFileDelegate'); + constructor(loggerProvider: CoreLoggerProvider, + sitesProvider: CoreSitesProvider, + eventsProvider: CoreEventsProvider) { + super('CorePluginFileDelegate', loggerProvider, sitesProvider, eventsProvider); } /** - * Get the handler for a certain pluginfile url. + * React to a file being deleted. * - * @param {string} component Component of the plugin. - * @return {CorePluginFileHandler} Handler. Undefined if no handler found for the plugin. + * @param fileUrl The file URL used to download the file. + * @param path The path of the deleted file. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when done. */ - protected getPluginHandler(component: string): CorePluginFileHandler { - if (typeof this.handlers[component] != 'undefined') { - return this.handlers[component]; + fileDeleted(fileUrl: string, path: string, siteId?: string): Promise { + const handler = this.getHandlerForFile({fileurl: fileUrl}); + + if (handler && handler.fileDeleted) { + return handler.fileDeleted(fileUrl, path, siteId); } + + return Promise.resolve(); + } + + /** + * Check whether a file can be downloaded. If so, return the file to download. + * + * @param file The file data. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the file to use. Rejected if cannot download. + */ + getDownloadableFile(file: CoreWSExternalFile, siteId?: string): Promise { + const handler = this.getHandlerForFile(file); + + return this.getHandlerDownloadableFile(file, handler, siteId); + } + + /** + * Check whether a file can be downloaded. If so, return the file to download. + * + * @param file The file data. + * @param handler The handler to use. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the file to use. Rejected if cannot download. + */ + protected getHandlerDownloadableFile(file: CoreWSExternalFile, handler: CorePluginFileHandler, siteId?: string) + : Promise { + + if (handler && handler.getDownloadableFile) { + return handler.getDownloadableFile(file, siteId).then((newFile) => { + return newFile || file; + }); + } + + return Promise.resolve(file); } /** * Get the RegExp of the component and filearea described in the URL. * - * @param {string[]} args Arguments of the pluginfile URL defining component and filearea at least. - * @return {RegExp} RegExp to match the revision or undefined if not found. + * @param args Arguments of the pluginfile URL defining component and filearea at least. + * @return RegExp to match the revision or undefined if not found. */ getComponentRevisionRegExp(args: string[]): RegExp { // Get handler based on component (args[1]). - const handler = this.getPluginHandler(args[1]); + const handler = this.getHandler(args[1], true); if (handler && handler.getComponentRevisionRegExp) { return handler.getComponentRevisionRegExp(args); @@ -88,34 +183,108 @@ export class CorePluginFileDelegate { } /** - * Register a handler. + * Given an HTML element, get the URLs of the files that should be downloaded and weren't treated by + * CoreFilepoolProvider.extractDownloadableFilesFromHtml. * - * @param {CorePluginFileHandler} handler The handler to register. - * @return {boolean} True if registered successfully, false otherwise. + * @param container Container where to get the URLs from. + * @return List of URLs. */ - registerHandler(handler: CorePluginFileHandler): boolean { - if (typeof this.handlers[handler.component] !== 'undefined') { - this.logger.log(`Handler '${handler.component}' already registered`); + getDownloadableFilesFromHTML(container: HTMLElement): string[] { + let files = []; - return false; + for (const component in this.enabledHandlers) { + const handler = this.enabledHandlers[component]; + + if (handler && handler.getDownloadableFilesFromHTML) { + files = files.concat(handler.getDownloadableFilesFromHTML(container)); + } } - this.logger.log(`Registered handler '${handler.component}'`); - this.handlers[handler.component] = handler; + return files; + } - return true; + /** + * Sum the filesizes from a list of files checking if the size will be partial or totally calculated. + * + * @param files List of files to sum its filesize. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with file size and a boolean to indicate if it is the total size or only partial. + */ + getFilesSize(files: CoreWSExternalFile[], siteId?: string): Promise<{ size: number, total: boolean }> { + const promises = [], + result = { + size: 0, + total: true + }; + + files.forEach((file) => { + promises.push(this.getFileSize(file, siteId).then((size) => { + if (typeof size == 'undefined') { + // We don't have the file size, cannot calculate its total size. + result.total = false; + } else { + result.size += size; + } + })); + }); + + return Promise.all(promises).then(() => { + return result; + }); + } + + /** + * Get a file size. + * + * @param file The file data. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the size. + */ + getFileSize(file: CoreWSExternalFile, siteId?: string): Promise { + const handler = this.getHandlerForFile(file); + + // First of all check if file can be downloaded. + return this.getHandlerDownloadableFile(file, handler, siteId).then((file) => { + if (!file) { + return 0; + } + + if (handler && handler.getFileSize) { + return handler.getFileSize(file, siteId).catch(() => { + return file.filesize; + }); + } + + return Promise.resolve(file.filesize); + }); + } + + /** + * Get a handler to treat a certain file. + * + * @param file File data. + * @return Handler. + */ + protected getHandlerForFile(file: CoreWSExternalFile): CorePluginFileHandler { + for (const component in this.enabledHandlers) { + const handler = this.enabledHandlers[component]; + + if (handler && handler.shouldHandleFile && handler.shouldHandleFile(file)) { + return handler; + } + } } /** * Removes the revision number from a file URL. * - * @param {string} url URL to be replaced. - * @param {string[]} args Arguments of the pluginfile URL defining component and filearea at least. - * @return {string} Replaced URL without revision. + * @param url URL to be replaced. + * @param args Arguments of the pluginfile URL defining component and filearea at least. + * @return Replaced URL without revision. */ removeRevisionFromUrl(url: string, args: string[]): string { // Get handler based on component (args[1]). - const handler = this.getPluginHandler(args[1]); + const handler = this.getHandler(args[1], true); if (handler && handler.getComponentRevisionRegExp && handler.getComponentRevisionReplace) { const revisionRegex = handler.getComponentRevisionRegExp(args); @@ -126,4 +295,22 @@ export class CorePluginFileDelegate { return url; } + + /** + * Treat a downloaded file. + * + * @param fileUrl The file URL used to download the file. + * @param file The file entry of the downloaded file. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when done. + */ + treatDownloadedFile(fileUrl: string, file: FileEntry, siteId?: string): Promise { + const handler = this.getHandlerForFile({fileurl: fileUrl}); + + if (handler && handler.treatDownloadedFile) { + return handler.treatDownloadedFile(fileUrl, file, siteId); + } + + return Promise.resolve(); + } } diff --git a/src/providers/sites-factory.ts b/src/providers/sites-factory.ts index dda512f8c..f97d334cb 100644 --- a/src/providers/sites-factory.ts +++ b/src/providers/sites-factory.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -26,14 +26,14 @@ export class CoreSitesFactoryProvider { /** * Make a site object. * - * @param {string} id Site ID. - * @param {string} siteUrl Site URL. - * @param {string} [token] Site's WS token. - * @param {any} [info] Site info. - * @param {string} [privateToken] Private token. - * @param {any} [config] Site public config. - * @param {boolean} [loggedOut] Whether user is logged out. - * @return {CoreSite} Site instance. + * @param id Site ID. + * @param siteUrl Site URL. + * @param token Site's WS token. + * @param info Site info. + * @param privateToken Private token. + * @param config Site public config. + * @param loggedOut Whether user is logged out. + * @return Site instance. * @description * This returns a site object. */ @@ -45,7 +45,7 @@ export class CoreSitesFactoryProvider { /** * Gets the list of Site methods. * - * @return {string[]} List of methods. + * @return List of methods. */ getSiteMethods(): string[] { const methods = []; diff --git a/src/providers/sites.ts b/src/providers/sites.ts index 0c310c708..53230e7d2 100644 --- a/src/providers/sites.ts +++ b/src/providers/sites.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -19,13 +19,14 @@ import { CoreAppProvider } from './app'; import { CoreEventsProvider } from './events'; import { CoreLoggerProvider } from './logger'; import { CoreSitesFactoryProvider } from './sites-factory'; +import { CoreDomUtilsProvider } from './utils/dom'; import { CoreTextUtilsProvider } from './utils/text'; import { CoreUrlUtilsProvider } from './utils/url'; import { CoreUtilsProvider } from './utils/utils'; import { CoreWSProvider } from './ws'; import { CoreConstants } from '@core/constants'; import { CoreConfigConstants } from '../configconstants'; -import { CoreSite } from '@classes/site'; +import { CoreSite, CoreSiteWSPreSets } from '@classes/site'; import { SQLiteDB, SQLiteDBTableSchema } from '@classes/sqlitedb'; import { Md5 } from 'ts-md5/dist/md5'; import { WP_PROVIDER } from '@app/app.module'; @@ -36,31 +37,26 @@ import { WP_PROVIDER } from '@app/app.module'; export interface CoreSiteCheckResponse { /** * Code to identify the authentication method to use. - * @type {number} */ code: number; /** * Site url to use (might have changed during the process). - * @type {string} */ siteUrl: string; /** * Service used. - * @type {string} */ service: string; /** * Code of the warning message to show to the user. - * @type {string} */ warning?: string; /** * Site public config (if available). - * @type {any} */ config?: any; } @@ -71,19 +67,16 @@ export interface CoreSiteCheckResponse { export interface CoreSiteUserTokenResponse { /** * User token. - * @type {string} */ token: string; /** * Site URL to use. - * @type {string} */ siteUrl: string; /** * User private token. - * @type {string} */ privateToken?: string; } @@ -94,39 +87,38 @@ export interface CoreSiteUserTokenResponse { export interface CoreSiteBasicInfo { /** * Site ID. - * @type {string} */ id: string; /** * Site URL. - * @type {string} */ siteUrl: string; /** * User's full name. - * @type {string} */ fullName: string; /** * Site's name. - * @type {string} */ siteName: string; /** * User's avatar. - * @type {string} */ avatar: string; /** * Badge to display in the site. - * @type {number} */ badge?: number; + + /** + * Site home ID. + */ + siteHomeId?: number; } /** @@ -135,22 +127,16 @@ export interface CoreSiteBasicInfo { export interface CoreSiteSchema { /** * Name of the schema. - * - * @type {string} */ name: string; /** * Latest version of the schema (integer greater than 0). - * - * @type {number} */ version: number; /** * Names of the tables of the site schema that can be cleared. - * - * @type {string[]} */ canBeCleared?: string[]; @@ -164,14 +150,20 @@ export interface CoreSiteSchema { * * Called when installing and upgrading the schema, after creating the defined tables. * - * @param {SQLiteDB} db Site database. - * @param {number} oldVersion Old version of the schema or 0 if not installed. - * @param {string} siteId Site Id to migrate. - * @return {Promise | void} Promise resolved when done. + * @param db Site database. + * @param oldVersion Old version of the schema or 0 if not installed. + * @param siteId Site Id to migrate. + * @return Promise resolved when done. */ migrate?(db: SQLiteDB, oldVersion: number, siteId: string): Promise | void; } +export const enum CoreSitesReadingStrategy { + OnlyCache, + PreferCache, + PreferNetwork, +} + /* * Service to manage and interact with sites. * It allows creating tables in the databases of all sites. Each service or component should be responsible of creating @@ -326,7 +318,8 @@ export class CoreSitesProvider { constructor(logger: CoreLoggerProvider, private http: HttpClient, private sitesFactory: CoreSitesFactoryProvider, private appProvider: CoreAppProvider, private translate: TranslateService, private urlUtils: CoreUrlUtilsProvider, private eventsProvider: CoreEventsProvider, private textUtils: CoreTextUtilsProvider, - private utils: CoreUtilsProvider, private injector: Injector, private wsProvider: CoreWSProvider) { + private utils: CoreUtilsProvider, private injector: Injector, private wsProvider: CoreWSProvider, + protected domUtils: CoreDomUtilsProvider) { this.logger = logger.getInstance('CoreSitesProvider'); this.appDB = appProvider.getDB(); @@ -337,8 +330,8 @@ export class CoreSitesProvider { /** * Get the demo data for a certain "name" if it is a demo site. * - * @param {string} name Name of the site to check. - * @return {any} Site data if it's a demo site, undefined otherwise. + * @param name Name of the site to check. + * @return Site data if it's a demo site, undefined otherwise. */ getDemoSiteData(name: string): any { const demoSites = CoreConfigConstants.demo_sites; @@ -351,9 +344,9 @@ export class CoreSitesProvider { * Check if a site is valid and if it has specifics settings for authentication (like force to log in using the browser). * It will test both protocols if the first one fails: http and https. * - * @param {string} siteUrl URL of the site to check. - * @param {string} [protocol=https://] Protocol to use first. - * @return {Promise} A promise resolved when the site is checked. + * @param siteUrl URL of the site to check. + * @param protocol Protocol to use first. + * @return A promise resolved when the site is checked. */ checkSite(siteUrl: string, protocol: string = 'https://'): Promise { // The formatURL function adds the protocol if is missing. @@ -384,7 +377,7 @@ export class CoreSitesProvider { } else if (this.textUtils.getErrorMessageFromError(secondError)) { return Promise.reject(secondError); } else { - return this.translate.instant('core.cannotconnect'); + return this.translate.instant('core.cannotconnect', {$a: CoreSite.MINIMUM_MOODLE_VERSION}); } }); }); @@ -394,9 +387,9 @@ export class CoreSitesProvider { /** * Helper function to check if a site is valid and if it has specifics settings for authentication. * - * @param {string} siteUrl URL of the site to check. - * @param {string} protocol Protocol to use. - * @return {Promise} A promise resolved when the site is checked. + * @param siteUrl URL of the site to check. + * @param protocol Protocol to use. + * @return A promise resolved when the site is checked. */ checkSiteWithProtocol(siteUrl: string, protocol: string): Promise { let publicConfig; @@ -476,7 +469,8 @@ export class CoreSitesProvider { error.error = this.translate.instant('core.login.sitehasredirect'); } else { // We can't be sure if there is a redirect or not. Display cannot connect error. - error.error = this.translate.instant('core.cannotconnect'); + error.error = this.translate.instant('core.cannotconnect', + {$a: CoreSite.MINIMUM_MOODLE_VERSION}); } return Promise.reject(error); @@ -514,14 +508,14 @@ export class CoreSitesProvider { /** * Check if a site exists. * - * @param {string} siteUrl URL of the site to check. - * @return {Promise} A promise to be resolved if the site exists. + * @param siteUrl URL of the site to check. + * @return A promise to be resolved if the site exists. */ siteExists(siteUrl: string): Promise { return this.http.post(siteUrl + '/login/token.php', {}).timeout(this.wsProvider.getRequestTimeout()).toPromise() .catch(() => { // Default error messages are kinda bad, return our own message. - return Promise.reject({error: this.translate.instant('core.cannotconnect')}); + return Promise.reject({error: this.translate.instant('core.cannotconnect', {$a: CoreSite.MINIMUM_MOODLE_VERSION})}); }).then((data: any) => { if (data.errorcode && (data.errorcode == 'enablewsdescription' || data.errorcode == 'requirecorrectaccess')) { @@ -536,12 +530,12 @@ export class CoreSitesProvider { /** * Gets a user token from the server. * - * @param {string} siteUrl The site url. - * @param {string} username User name. - * @param {string} password Password. - * @param {string} [service] Service to use. If not defined, it will be searched in memory. - * @param {boolean} [retry] Whether we are retrying with a prefixed URL. - * @return {Promise} A promise resolved when the token is retrieved. + * @param siteUrl The site url. + * @param username User name. + * @param password Password. + * @param service Service to use. If not defined, it will be searched in memory. + * @param retry Whether we are retrying with a prefixed URL. + * @return A promise resolved when the token is retrieved. */ getUserToken(siteUrl: string, username: string, password: string, service?: string, retry?: boolean) : Promise { @@ -563,7 +557,7 @@ export class CoreSitesProvider { return promise.then((data: any): any => { if (typeof data == 'undefined') { - return Promise.reject(this.translate.instant('core.cannotconnect')); + return Promise.reject(this.translate.instant('core.cannotconnect', {$a: CoreSite.MINIMUM_MOODLE_VERSION})); } else { if (typeof data.token != 'undefined') { return { token: data.token, siteUrl: siteUrl, privateToken: data.privatetoken }; @@ -595,18 +589,18 @@ export class CoreSitesProvider { } } }, () => { - return Promise.reject(this.translate.instant('core.cannotconnect')); + return Promise.reject(this.translate.instant('core.cannotconnect', {$a: CoreSite.MINIMUM_MOODLE_VERSION})); }); } /** * Add a new site to the site list and authenticate the user in this site. * - * @param {string} siteUrl The site url. - * @param {string} token User's token. - * @param {string} [privateToken=''] User's private token. - * @param {boolean} [login=true] Whether to login the user in the site. Defaults to true. - * @return {Promise} A promise resolved with siteId when the site is added and the user is authenticated. + * @param siteUrl The site url. + * @param token User's token. + * @param privateToken User's private token. + * @param login Whether to login the user in the site. Defaults to true. + * @return A promise resolved with siteId when the site is added and the user is authenticated. */ newSite(siteUrl: string, token: string, privateToken: string = '', login: boolean = true): Promise { if (typeof login != 'boolean') { @@ -682,14 +676,15 @@ export class CoreSitesProvider { /** * Having the result of isValidMoodleVersion, it treats the error message to be shown. * - * @param {number} result Result returned by isValidMoodleVersion function. - * @param {string} siteUrl The site url. - * @param {string} siteId If site is already added, it will invalidate the token. - * @return {Promise} A promise rejected with the error info. + * @param result Result returned by isValidMoodleVersion function. + * @param siteUrl The site url. + * @param siteId If site is already added, it will invalidate the token. + * @return A promise rejected with the error info. */ protected treatInvalidAppVersion(result: number, siteUrl: string, siteId?: string): Promise { let errorCode, - errorKey; + errorKey, + translateParams; switch (result) { case this.MOODLE_APP: @@ -703,6 +698,7 @@ export class CoreSitesProvider { default: errorCode = 'invalidmoodleversion'; errorKey = 'core.login.invalidmoodleversion'; + translateParams = {$a: CoreSite.MINIMUM_MOODLE_VERSION}; } let promise; @@ -715,7 +711,7 @@ export class CoreSitesProvider { return promise.then(() => { return Promise.reject({ - error: this.translate.instant(errorKey), + error: this.translate.instant(errorKey, translateParams), errorcode: errorCode, loggedout: true }); @@ -725,9 +721,9 @@ export class CoreSitesProvider { /** * Create a site ID based on site URL and username. * - * @param {string} siteUrl The site url. - * @param {string} username Username. - * @return {string} Site ID. + * @param siteUrl The site url. + * @param username Username. + * @return Site ID. */ createSiteID(siteUrl: string, username: string): string { return Md5.hashAsciiStr(siteUrl + username); @@ -736,8 +732,8 @@ export class CoreSitesProvider { /** * Function for determine which service we should use (default or extended plugin). * - * @param {string} siteUrl The site URL. - * @return {string} The service shortname. + * @param siteUrl The site URL. + * @return The service shortname. */ determineService(siteUrl: string): string { // We need to try siteUrl in both https or http (due to loginhttps setting). @@ -761,8 +757,8 @@ export class CoreSitesProvider { /** * Check for the minimum required version. * - * @param {any} info Site info. - * @return {number} Either VALID_VERSION, WORKPLACE_APP, MOODLE_APP or INVALID_VERSION. + * @param info Site info. + * @return Either VALID_VERSION, WORKPLACE_APP, MOODLE_APP or INVALID_VERSION. */ protected isValidMoodleVersion(info: any): number { if (!info) { @@ -770,7 +766,7 @@ export class CoreSitesProvider { } const version31 = 2016052300, - release31 = '3.1'; + release31 = CoreSite.MINIMUM_MOODLE_VERSION; // Try to validate by version. if (info.version) { @@ -797,8 +793,8 @@ export class CoreSitesProvider { /** * Check if needs to be redirected to specific Workplace App or general Moodle App. * - * @param {any} info Site info. - * @return {number} Either VALID_VERSION, WORKPLACE_APP or MOODLE_APP. + * @param info Site info. + * @return Either VALID_VERSION, WORKPLACE_APP or MOODLE_APP. */ protected validateWorkplaceVersion(info: any): number { const isWorkplace = !!info.functions && info.functions.some((func) => { @@ -824,8 +820,8 @@ export class CoreSitesProvider { /** * Returns the release number from site release info. * - * @param {string} rawRelease Raw release info text. - * @return {string} Release number or empty. + * @param rawRelease Raw release info text. + * @return Release number or empty. */ getReleaseNumber(rawRelease: string): string { const matches = rawRelease.match(/^\d(\.\d(\.\d+)?)?/); @@ -839,8 +835,8 @@ export class CoreSitesProvider { /** * Check if site info is valid. If it's not, return error message. * - * @param {any} info Site info. - * @return {any} True if valid, object with error message to show and its params if not valid. + * @param info Site info. + * @return True if valid, object with error message to show and its params if not valid. */ protected validateSiteInfo(info: any): any { if (!info.firstname || !info.lastname) { @@ -855,13 +851,13 @@ export class CoreSitesProvider { /** * Saves a site in local DB. * - * @param {string} id Site ID. - * @param {string} siteUrl Site URL. - * @param {string} token User's token in the site. - * @param {any} info Site's info. - * @param {string} [privateToken=''] User's private token. - * @param {any} [config] Site config (from tool_mobile_get_config). - * @return {Promise} Promise resolved when done. + * @param id Site ID. + * @param siteUrl Site URL. + * @param token User's token in the site. + * @param info Site's info. + * @param privateToken User's private token. + * @param config Site config (from tool_mobile_get_config). + * @return Promise resolved when done. */ addSite(id: string, siteUrl: string, token: string, info: any, privateToken: string = '', config?: any): Promise { const entry = { @@ -877,13 +873,94 @@ export class CoreSitesProvider { return this.appDB.insertRecord(this.SITES_TABLE, entry); } + /** + * Check the required minimum version of the app for a site and shows a download dialog. + * + * @param config Config object of the site. + * @param siteId ID of the site to check. Current site id will be used otherwise. + * @return Resolved with if meets the requirements, rejected otherwise. + */ + checkRequiredMinimumVersion(config: any, siteId?: string): Promise { + if (config && config.tool_mobile_minimumversion) { + const requiredVersion = this.convertVersionName(config.tool_mobile_minimumversion), + appVersion = this.convertVersionName(CoreConfigConstants.versionname); + + if (requiredVersion > appVersion) { + let downloadUrl = ''; + + if (this.appProvider.isAndroid() && config.tool_mobile_androidappid) { + downloadUrl = 'market://details?id=' + config.tool_mobile_androidappid; + } else if (this.appProvider.isIOS() && config.tool_mobile_iosappid) { + downloadUrl = 'itms-apps://itunes.apple.com/app/id' + config.tool_mobile_iosappid; + } else if (config.tool_mobile_setuplink) { + downloadUrl = config.tool_mobile_setuplink; + } else if (this.appProvider.isMobile()) { + downloadUrl = 'https://download.moodle.org/mobile/'; + } else { + downloadUrl = 'https://download.moodle.org/desktop/'; + } + + siteId = siteId || this.getCurrentSiteId(); + + // Do not block interface. + this.domUtils.showConfirm( + this.translate.instant('core.updaterequireddesc', { $a: config.tool_mobile_minimumversion }), + this.translate.instant('core.updaterequired'), + this.translate.instant('core.download'), + this.translate.instant(siteId ? 'core.mainmenu.logout' : 'core.cancel')).then(() => { + + this.utils.openInBrowser(downloadUrl); + }).catch(() => { + // Do nothing. + }); + + if (siteId) { + // Logout if it's the currentSite. + const promise = siteId == this.getCurrentSiteId() ? this.logout() : Promise.resolve(); + + return promise.then(() => { + // Always expire the token. + return this.setSiteLoggedOut(siteId, true); + }).then(() => { + return Promise.reject(null); + }); + } + + return Promise.reject(null); + } + } + + return Promise.resolve(); + } + + /** + * Convert version name to numbers. + * + * @param name Version name (dot separated). + * @return Version translated to a comparable number. + */ + protected convertVersionName(name: string): number { + let version = 0; + + const parts = name.split('-')[0].split('.', 3); + parts.forEach((num) => { + version = (version * 100) + Number(num); + }); + + if (parts.length < 3) { + version = version * Math.pow(100, 3 - parts.length); + } + + return version; + } + /** * Login a user to a site from the list of sites. * - * @param {string} siteId ID of the site to load. - * @param {string} [pageName] Name of the page to go once authenticated if logged out. If not defined, site initial page. - * @param {any} [params] Params of the page to go once authenticated if logged out. - * @return {Promise} Promise resolved with true if site is loaded, resolved with false if cannot login. + * @param siteId ID of the site to load. + * @param pageName Name of the page to go once authenticated if logged out. If not defined, site initial page. + * @param params Params of the page to go once authenticated if logged out. + * @return Promise resolved with true if site is loaded, resolved with false if cannot login. */ loadSite(siteId: string, pageName?: string, params?: any): Promise { this.logger.debug(`Load site ${siteId}`); @@ -911,12 +988,20 @@ export class CoreSitesProvider { return false; }, () => { - this.login(siteId); + return site.getPublicConfig().catch(() => { + return {}; + }).then((config) => { + return this.checkRequiredMinimumVersion(config).then(() => { + this.login(siteId); - // Update site info. We don't block the UI. - this.updateSiteInfo(siteId); + // Update site info. We don't block the UI. + this.updateSiteInfo(siteId); - return true; + return true; + }).catch(() => { + return false; + }); + }); }); }); } @@ -924,7 +1009,7 @@ export class CoreSitesProvider { /** * Get current site. * - * @return {CoreSite} Current site. + * @return Current site. */ getCurrentSite(): CoreSite { return this.currentSite; @@ -933,7 +1018,7 @@ export class CoreSitesProvider { /** * Get the site home ID of the current site. * - * @return {number} Current site home ID. + * @return Current site home ID. */ getCurrentSiteHomeId(): number { if (this.currentSite) { @@ -946,7 +1031,7 @@ export class CoreSitesProvider { /** * Get current site ID. * - * @return {string} Current site ID. + * @return Current site ID. */ getCurrentSiteId(): string { if (this.currentSite) { @@ -959,7 +1044,7 @@ export class CoreSitesProvider { /** * Get current site User ID. * - * @return {number} Current site User ID. + * @return Current site User ID. */ getCurrentSiteUserId(): number { if (this.currentSite) { @@ -972,7 +1057,7 @@ export class CoreSitesProvider { /** * Check if the user is logged in a site. * - * @return {boolean} Whether the user is logged in a site. + * @return Whether the user is logged in a site. */ isLoggedIn(): boolean { return typeof this.currentSite != 'undefined' && typeof this.currentSite.token != 'undefined' && @@ -982,8 +1067,8 @@ export class CoreSitesProvider { /** * Delete a site from the sites list. * - * @param {string} siteId ID of the site to delete. - * @return {Promise} Promise to be resolved when the site is deleted. + * @param siteId ID of the site to delete. + * @return Promise to be resolved when the site is deleted. */ deleteSite(siteId: string): Promise { this.logger.debug(`Delete site ${siteId}`); @@ -1013,7 +1098,7 @@ export class CoreSitesProvider { /** * Check if there are sites stored. * - * @return {Promise} Promise resolved with true if there are sites and false if there aren't. + * @return Promise resolved with true if there are sites and false if there aren't. */ hasSites(): Promise { return this.appDB.countRecords(this.SITES_TABLE).then((count) => { @@ -1024,8 +1109,8 @@ export class CoreSitesProvider { /** * Returns a site object. * - * @param {string} [siteId] The site ID. If not defined, current site (if available). - * @return {Promise} Promise resolved with the site. + * @param siteId The site ID. If not defined, current site (if available). + * @return Promise resolved with the site. */ getSite(siteId?: string): Promise { if (!siteId) { @@ -1045,8 +1130,8 @@ export class CoreSitesProvider { /** * Create a site from an entry of the sites list DB. The new site is added to the list of "cached" sites: this.sites. * - * @param {any} entry Site list entry. - * @return {Promise} Promised resolved with the created site. + * @param entry Site list entry. + * @return Promised resolved with the created site. */ makeSiteFromSiteListEntry(entry: any): Promise { let site: CoreSite, @@ -1071,8 +1156,8 @@ export class CoreSitesProvider { /** * Returns if the site is the current one. * - * @param {string|CoreSite} [site] Site object or siteId to be compared. If not defined, use current site. - * @return {boolean} Whether site or siteId is the current one. + * @param site Site object or siteId to be compared. If not defined, use current site. + * @return Whether site or siteId is the current one. */ isCurrentSite(site: string | CoreSite): boolean { if (!site || !this.currentSite) { @@ -1087,8 +1172,8 @@ export class CoreSitesProvider { /** * Returns the database object of a site. * - * @param {string} [siteId] The site ID. If not defined, current site (if available). - * @return {Promise} Promise resolved with the database. + * @param siteId The site ID. If not defined, current site (if available). + * @return Promise resolved with the database. */ getSiteDb(siteId: string): Promise { return this.getSite(siteId).then((site) => { @@ -1099,8 +1184,8 @@ export class CoreSitesProvider { /** * Returns the site home ID of a site. * - * @param {number} [siteId] The site ID. If not defined, current site (if available). - * @return {Promise} Promise resolved with site home ID. + * @param siteId The site ID. If not defined, current site (if available). + * @return Promise resolved with site home ID. */ getSiteHomeId(siteId?: string): Promise { return this.getSite(siteId).then((site) => { @@ -1111,8 +1196,8 @@ export class CoreSitesProvider { /** * Get the list of sites stored. * - * @param {String[]} [ids] IDs of the sites to get. If not defined, return all sites. - * @return {Promise} Promise resolved when the sites are retrieved. + * @param ids IDs of the sites to get. If not defined, return all sites. + * @return Promise resolved when the sites are retrieved. */ getSites(ids?: string[]): Promise { return this.appDB.getAllRecords(this.SITES_TABLE).then((sites) => { @@ -1126,7 +1211,8 @@ export class CoreSitesProvider { siteUrl: site.siteUrl, fullName: siteInfo && siteInfo.fullname, siteName: CoreConfigConstants.sitename ? CoreConfigConstants.sitename : siteInfo && siteInfo.sitename, - avatar: siteInfo && siteInfo.userpictureurl + avatar: siteInfo && siteInfo.userpictureurl, + siteHomeId: siteInfo && siteInfo.siteid || 1 }; formattedSites.push(basicInfo); } @@ -1139,8 +1225,8 @@ export class CoreSitesProvider { /** * Get the list of sites stored, sorted by URL and full name. * - * @param {String[]} [ids] IDs of the sites to get. If not defined, return all sites. - * @return {Promise} Promise resolved when the sites are retrieved. + * @param ids IDs of the sites to get. If not defined, return all sites. + * @return Promise resolved when the sites are retrieved. */ getSortedSites(ids?: string[]): Promise { return this.getSites(ids).then((sites) => { @@ -1169,7 +1255,7 @@ export class CoreSitesProvider { /** * Get the list of IDs of sites stored and not logged out. * - * @return {Promise} Promise resolved when the sites IDs are retrieved. + * @return Promise resolved when the sites IDs are retrieved. */ getLoggedInSitesIds(): Promise { return this.appDB.getRecords(this.SITES_TABLE, {loggedOut : 0}).then((sites) => { @@ -1182,7 +1268,7 @@ export class CoreSitesProvider { /** * Get the list of IDs of sites stored. * - * @return {Promise} Promise resolved when the sites IDs are retrieved. + * @return Promise resolved when the sites IDs are retrieved. */ getSitesIds(): Promise { return this.appDB.getAllRecords(this.SITES_TABLE).then((sites) => { @@ -1195,8 +1281,8 @@ export class CoreSitesProvider { /** * Login the user in a site. * - * @param {string} siteid ID of the site the user is accessing. - * @return {Promise} Promise resolved when current site is stored. + * @param siteid ID of the site the user is accessing. + * @return Promise resolved when current site is stored. */ login(siteId: string): Promise { const entry = { @@ -1212,7 +1298,7 @@ export class CoreSitesProvider { /** * Logout the user. * - * @return {Promise} Promise resolved when the user is logged out. + * @return Promise resolved when the user is logged out. */ logout(): Promise { let siteId; @@ -1239,7 +1325,7 @@ export class CoreSitesProvider { /** * Restores the session to the previous one so the user doesn't has to login everytime the app is started. * - * @return {Promise} Promise resolved if a session is restored. + * @return Promise resolved if a session is restored. */ restoreSession(): Promise { if (this.sessionRestored) { @@ -1261,9 +1347,9 @@ export class CoreSitesProvider { /** * Mark or unmark a site as logged out so the user needs to authenticate again. * - * @param {string} siteId ID of the site. - * @param {boolean} loggedOut True to set the site as logged out, false otherwise. - * @return {Promise} Promise resolved when done. + * @param siteId ID of the site. + * @param loggedOut True to set the site as logged out, false otherwise. + * @return Promise resolved when done. */ setSiteLoggedOut(siteId: string, loggedOut: boolean): Promise { return this.getSite(siteId).then((site) => { @@ -1288,11 +1374,11 @@ export class CoreSitesProvider { /** * Updates a site's token. * - * @param {string} siteUrl Site's URL. - * @param {string} username Username. - * @param {string} token User's new token. - * @param {string} [privateToken=''] User's private token. - * @return {Promise} A promise resolved when the site is updated. + * @param siteUrl Site's URL. + * @param username Username. + * @param token User's new token. + * @param privateToken User's private token. + * @return A promise resolved when the site is updated. */ updateSiteToken(siteUrl: string, username: string, token: string, privateToken: string = ''): Promise { const siteId = this.createSiteID(siteUrl, username); @@ -1305,10 +1391,10 @@ export class CoreSitesProvider { /** * Updates a site's token using siteId. * - * @param {string} siteId Site Id. - * @param {string} token User's new token. - * @param {string} [privateToken=''] User's private token. - * @return {Promise} A promise resolved when the site is updated. + * @param siteId Site Id. + * @param token User's new token. + * @param privateToken User's private token. + * @return A promise resolved when the site is updated. */ updateSiteTokenBySiteId(siteId: string, token: string, privateToken: string = ''): Promise { return this.getSite(siteId).then((site) => { @@ -1329,8 +1415,8 @@ export class CoreSitesProvider { /** * Updates a site's info. * - * @param {string} siteid Site's ID. - * @return {Promise} A promise resolved when the site is updated. + * @param siteid Site's ID. + * @return A promise resolved when the site is updated. */ updateSiteInfo(siteId: string): Promise { return this.getSite(siteId).then((site) => { @@ -1370,9 +1456,9 @@ export class CoreSitesProvider { /** * Updates a site's info. * - * @param {string} siteUrl Site's URL. - * @param {string} username Username. - * @return {Promise} A promise to be resolved when the site is updated. + * @param siteUrl Site's URL. + * @param username Username. + * @return A promise to be resolved when the site is updated. */ updateSiteInfoByUrl(siteUrl: string, username: string): Promise { const siteId = this.createSiteID(siteUrl, username); @@ -1384,11 +1470,11 @@ export class CoreSitesProvider { * Get the site IDs a URL belongs to. * Someone can have more than one account in the same site, that's why this function returns an array of IDs. * - * @param {string} url URL to check. - * @param {boolean} [prioritize] True if it should prioritize current site. If the URL belongs to current site then it won't - * check any other site, it will only return current site. - * @param {string} [username] If set, it will return only the sites where the current user has this username. - * @return {Promise} Promise resolved with the site IDs (array). + * @param url URL to check. + * @param prioritize True if it should prioritize current site. If the URL belongs to current site then it won't + * check any other site, it will only return current site. + * @param username If set, it will return only the sites where the current user has this username. + * @return Promise resolved with the site IDs (array). */ getSiteIdsFromUrl(url: string, prioritize?: boolean, username?: string): Promise { // If prioritize is true, check current site first. @@ -1442,7 +1528,7 @@ export class CoreSitesProvider { /** * Get the site ID stored in DB as current site. * - * @return {Promise} Promise resolved with the site ID. + * @return Promise resolved with the site ID. */ getStoredCurrentSiteId(): Promise { return this.appDB.getRecord(this.CURRENT_SITE_TABLE, { id: 1 }).then((currentSite) => { @@ -1453,8 +1539,8 @@ export class CoreSitesProvider { /** * Get the public config of a certain site. * - * @param {string} siteUrl URL of the site. - * @return {Promise} Promise resolved with the public config. + * @param siteUrl URL of the site. + * @return Promise resolved with the public config. */ getSitePublicConfig(siteUrl: string): Promise { const temporarySite = this.sitesFactory.makeSite(undefined, siteUrl); @@ -1465,8 +1551,8 @@ export class CoreSitesProvider { /** * Get site config. * - * @param {any} site The site to get the config. - * @return {Promise} Promise resolved with config if available. + * @param site The site to get the config. + * @return Promise resolved with config if available. */ protected getSiteConfig(site: CoreSite): Promise { if (!site.wsAvailable('tool_mobile_get_config')) { @@ -1480,9 +1566,9 @@ export class CoreSitesProvider { /** * Check if a certain feature is disabled in a site. * - * @param {string} name Name of the feature to check. - * @param {string} [siteId] The site ID. If not defined, current site (if available). - * @return {Promise} Promise resolved with true if disabled. + * @param name Name of the feature to check. + * @param siteId The site ID. If not defined, current site (if available). + * @return Promise resolved with true if disabled. */ isFeatureDisabled(name: string, siteId?: string): Promise { return this.getSite(siteId).then((site) => { @@ -1493,7 +1579,7 @@ export class CoreSitesProvider { /** * Create a table in all the sites databases. * - * @param {SQLiteDBTamableSchema} table Table schema. + * @param table Table schema. */ createTableFromSchema(table: SQLiteDBTableSchema): void { this.createTablesFromSchema([table]); @@ -1502,7 +1588,7 @@ export class CoreSitesProvider { /** * Create several tables in all the sites databases. * - * @param {SQLiteDBTamableSchema[]} tables List of tables schema. + * @param tables List of tables schema. */ createTablesFromSchema(tables: SQLiteDBTableSchema[]): void { // Add the tables to the list of schemas. This list is to create all the tables in new sites. @@ -1517,9 +1603,9 @@ export class CoreSitesProvider { /** * Check if a WS is available in the current site, if any. * - * @param {string} method WS name. - * @param {boolean} [checkPrefix=true] When true also checks with the compatibility prefix. - * @return {boolean} Whether the WS is available. + * @param method WS name. + * @param checkPrefix When true also checks with the compatibility prefix. + * @return Whether the WS is available. */ wsAvailableInCurrentSite(method: string, checkPrefix: boolean = true): boolean { const site = this.getCurrentSite(); @@ -1530,8 +1616,8 @@ export class CoreSitesProvider { /** * Check if a site is a legacy site by its info. * - * @param {any} info The site info. - * @return {boolean} Whether it's a legacy Moodle. + * @param info The site info. + * @return Whether it's a legacy Moodle. * @deprecated since 3.7.1 */ isLegacyMoodleByInfo(info: any): boolean { @@ -1548,8 +1634,8 @@ export class CoreSitesProvider { /** * Install and upgrade all the registered schemas and tables. * - * @param {CoreSite} site Site. - * @return {Promise} Promise resolved when done. + * @param site Site. + * @return Promise resolved when done. */ migrateSiteSchemas(site: CoreSite): Promise { const db = site.getDb(); @@ -1607,10 +1693,10 @@ export class CoreSitesProvider { /** * Check if a URL is the root URL of any of the stored sites. * - * @param {string} url URL to check. - * @param {string} [username] Username to check. - * @return {Promise<{site: CoreSite, siteIds: string[]}>} Promise resolved with site to use and the list of sites that have - * the URL. Site will be undefined if it isn't the root URL of any stored site. + * @param url URL to check. + * @param username Username to check. + * @return Promise resolved with site to use and the list of sites that have + * the URL. Site will be undefined if it isn't the root URL of any stored site. */ isStoredRootURL(url: string, username?: string): Promise<{site: CoreSite, siteIds: string[]}> { // Check if the site is stored. @@ -1641,7 +1727,7 @@ export class CoreSitesProvider { /** * Returns the Site Schema names that can be cleared on space storage. * - * @return {string[]} Name of the site schemas. + * @return Name of the site schemas. */ getSiteTableSchemasToClear(): string[] { let reset = []; @@ -1653,4 +1739,28 @@ export class CoreSitesProvider { return reset; } + + /** + * Returns presets for a given reading strategy. + * + * @param strategy Reading strategy. + * @return PreSets options object. + */ + getReadingStrategyPreSets(strategy: CoreSitesReadingStrategy): CoreSiteWSPreSets { + switch (strategy) { + case CoreSitesReadingStrategy.PreferCache: + return { + omitExpires: true, + }; + case CoreSitesReadingStrategy.OnlyCache: + return { + omitExpires: true, + forceOffline: true, + }; + case CoreSitesReadingStrategy.PreferNetwork: + default: + return {}; + } + } + } diff --git a/src/providers/sync.ts b/src/providers/sync.ts index a248c2ad1..739d1b59f 100644 --- a/src/providers/sync.ts +++ b/src/providers/sync.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -70,10 +70,10 @@ export class CoreSyncProvider { /** * Block a component and ID so it cannot be synchronized. * - * @param {string} component Component name. - * @param {string | number} id Unique ID per component. - * @param {string} [operation] Operation name. If not defined, a default text is used. - * @param {string} [siteId] Site ID. If not defined, current site. + * @param component Component name. + * @param id Unique ID per component. + * @param operation Operation name. If not defined, a default text is used. + * @param siteId Site ID. If not defined, current site. */ blockOperation(component: string, id: string | number, operation?: string, siteId?: string): void { siteId = siteId || this.sitesProvider.getCurrentSiteId(); @@ -96,7 +96,7 @@ export class CoreSyncProvider { /** * Clear all blocks for a site or all sites. * - * @param {string} [siteId] If set, clear the blocked objects only for this site. Otherwise clear them for all sites. + * @param siteId If set, clear the blocked objects only for this site. Otherwise clear them for all sites. */ clearAllBlocks(siteId?: string): void { if (siteId) { @@ -109,9 +109,9 @@ export class CoreSyncProvider { /** * Clear all blocks for a certain component. * - * @param {string} component Component name. - * @param {string | number} id Unique ID per component. - * @param {string} [siteId] Site ID. If not defined, current site. + * @param component Component name. + * @param id Unique ID per component. + * @param siteId Site ID. If not defined, current site. */ clearBlocks(component: string, id: string | number, siteId?: string): void { siteId = siteId || this.sitesProvider.getCurrentSiteId(); @@ -124,10 +124,10 @@ export class CoreSyncProvider { /** * Returns a sync record. - * @param {string} component Component name. - * @param {string | number} id Unique ID per component. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Record if found or reject. + * @param component Component name. + * @param id Unique ID per component. + * @param siteId Site ID. If not defined, current site. + * @return Record if found or reject. */ getSyncRecord(component: string, id: string | number, siteId?: string): Promise { return this.sitesProvider.getSiteDb(siteId).then((db) => { @@ -137,11 +137,11 @@ export class CoreSyncProvider { /** * Inserts or Updates info of a sync record. - * @param {string} component Component name. - * @param {string | number} id Unique ID per component. - * @param {any} data Data that updates the record. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with done. + * @param component Component name. + * @param id Unique ID per component. + * @param data Data that updates the record. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with done. */ insertOrUpdateSyncRecord(component: string, id: string | number, data: any, siteId?: string): Promise { return this.sitesProvider.getSiteDb(siteId).then((db) => { @@ -155,9 +155,9 @@ export class CoreSyncProvider { /** * Convenience function to create unique identifiers for a component and id. * - * @param {string} component Component name. - * @param {string | number} id Unique ID per component. - * @return {string} Unique sync id. + * @param component Component name. + * @param id Unique ID per component. + * @return Unique sync id. */ protected getUniqueSyncBlockId(component: string, id: string | number): string { return component + '#' + id; @@ -167,10 +167,10 @@ export class CoreSyncProvider { * Check if a component is blocked. * One block can have different operations. Here we check how many operations are being blocking the object. * - * @param {string} component Component name. - * @param {string | number} id Unique ID per component. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {boolean} Whether it's blocked. + * @param component Component name. + * @param id Unique ID per component. + * @param siteId Site ID. If not defined, current site. + * @return Whether it's blocked. */ isBlocked(component: string, id: string | number, siteId?: string): boolean { siteId = siteId || this.sitesProvider.getCurrentSiteId(); @@ -190,10 +190,10 @@ export class CoreSyncProvider { /** * Unblock an operation on a component and ID. * - * @param {string} component Component name. - * @param {string | number} id Unique ID per component. - * @param {string} [operation] Operation name. If not defined, a default text is used. - * @param {string} [siteId] Site ID. If not defined, current site. + * @param component Component name. + * @param id Unique ID per component. + * @param operation Operation name. If not defined, a default text is used. + * @param siteId Site ID. If not defined, current site. */ unblockOperation(component: string, id: string | number, operation?: string, siteId?: string): void { operation = operation || '-'; diff --git a/src/providers/update-manager.ts b/src/providers/update-manager.ts index 37ed1c0e8..24ddb9f79 100644 --- a/src/providers/update-manager.ts +++ b/src/providers/update-manager.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -32,19 +32,16 @@ import { SQLiteDB } from '@classes/sqlitedb'; export interface CoreUpdateManagerMigrateTable { /** * Current name of the store/table. - * @type {string} */ name: string; /** * New name of the table. If not defined, "name" will be used. - * @type {string} */ newName?: string; /** * An object to rename and convert some fields of the table/store. - * @type {object} */ fields?: { name: string, // Field name in the old app. @@ -77,8 +74,6 @@ export class CoreUpdateManagerProvider implements CoreInitHandler { /** * Tables to migrate from app DB ('MoodleMobile'). Include all the core ones to decrease the dependencies. - * - * @type {CoreUpdateManagerMigrateTable[]} */ protected appDBTables: CoreUpdateManagerMigrateTable[] = [ { @@ -177,8 +172,6 @@ export class CoreUpdateManagerProvider implements CoreInitHandler { /** * Tables to migrate from each site DB. Include all the core ones to decrease the dependencies. - * - * @type {CoreUpdateManagerMigrateTable[]} */ protected siteDBTables: CoreUpdateManagerMigrateTable[] = [ { @@ -331,7 +324,7 @@ export class CoreUpdateManagerProvider implements CoreInitHandler { * Check if the app has been updated and performs the needed processes. * This function shouldn't be used outside of core. * - * @return {Promise} Promise resolved when the update process finishes. + * @return Promise resolved when the update process finishes. */ load(): Promise { const promises = [], @@ -379,7 +372,7 @@ export class CoreUpdateManagerProvider implements CoreInitHandler { /** * Register several app tables to be migrated to the new schema. * - * @param {CoreUpdateManagerMigrateTable[]} tables The tables to migrate. + * @param tables The tables to migrate. */ registerAppTablesMigration(tables: CoreUpdateManagerMigrateTable[]): void { tables.forEach((table) => { @@ -390,7 +383,7 @@ export class CoreUpdateManagerProvider implements CoreInitHandler { /** * Register an app table to be migrated to the new schema. * - * @param {CoreUpdateManagerMigrateTable} table The table to migrate. + * @param table The table to migrate. */ registerAppTableMigration(table: CoreUpdateManagerMigrateTable): void { this.appDBTables.push(table); @@ -399,7 +392,7 @@ export class CoreUpdateManagerProvider implements CoreInitHandler { /** * Register several site tables to be migrated to the new schema. * - * @param {CoreUpdateManagerMigrateTable[]} tables The tables to migrate. + * @param tables The tables to migrate. */ registerSiteTablesMigration(tables: CoreUpdateManagerMigrateTable[]): void { tables.forEach((table) => { @@ -410,7 +403,7 @@ export class CoreUpdateManagerProvider implements CoreInitHandler { /** * Register a site table to be migrated to the new schema. * - * @param {CoreUpdateManagerMigrateTable} table The table to migrate. + * @param table The table to migrate. */ registerSiteTableMigration(table: CoreUpdateManagerMigrateTable): void { this.siteDBTables.push(table); @@ -419,8 +412,8 @@ export class CoreUpdateManagerProvider implements CoreInitHandler { /** * Register a migration of component name for local notifications. * - * @param {string} oldName The old name. - * @param {string} newName The new name. + * @param oldName The old name. + * @param newName The new name. */ registerLocalNotifComponentMigration(oldName: string, newName: string): void { this.localNotificationsComponentsMigrate[oldName] = newName; @@ -429,7 +422,7 @@ export class CoreUpdateManagerProvider implements CoreInitHandler { /** * Migrate all DBs and tables from the old format to SQLite. * - * @return {Promise} Promise resolved when done. + * @return Promise resolved when done. */ protected migrateAllDBs(): Promise { if (!( window).ydn) { @@ -455,7 +448,7 @@ export class CoreUpdateManagerProvider implements CoreInitHandler { /** * Migrate the app DB. * - * @return {Promise} Promise resolved when done. + * @return Promise resolved when done. */ protected migrateAppDB(): Promise { const oldDb = new ( window).ydn.db.Storage('MoodleMobile'), @@ -467,8 +460,8 @@ export class CoreUpdateManagerProvider implements CoreInitHandler { /** * Migrate the DB of a certain site. * - * @param {string} siteId The site ID. - * @return {Promise} Promise resolved when done. + * @param siteId The site ID. + * @return Promise resolved when done. */ protected migrateSiteDB(siteId: string): Promise { // Get the site DB. @@ -482,10 +475,10 @@ export class CoreUpdateManagerProvider implements CoreInitHandler { /** * Migrate all the tables of a certain DB to the SQLite DB. * - * @param {any} oldDb The old DB (created using ydn-db). - * @param {SQLiteDB} newDb The new DB. - * @param {CoreUpdateManagerMigrateTable[]} tables The tables to migrate. - * @return {Promise} Promise resolved when done. + * @param oldDb The old DB (created using ydn-db). + * @param newDb The new DB. + * @param tables The tables to migrate. + * @return Promise resolved when done. */ protected migrateDB(oldDb: any, newDb: SQLiteDB, tables: CoreUpdateManagerMigrateTable[]): Promise { if (!oldDb || !newDb) { @@ -557,7 +550,7 @@ export class CoreUpdateManagerProvider implements CoreInitHandler { /** * Migrates files filling extensions. * - * @return {Promise} Promise resolved when the site migration is finished. + * @return Promise resolved when the site migration is finished. */ protected migrateFileExtensions(): Promise { return this.sitesProvider.getSitesIds().then((sites) => { @@ -574,7 +567,7 @@ export class CoreUpdateManagerProvider implements CoreInitHandler { /** * Migrate local notifications components from the old nomenclature to the new one. * - * @return {Promise} Promise resolved when done. + * @return Promise resolved when done. */ protected migrateLocalNotificationsComponents(): Promise { if (!this.notifProvider.isAvailable()) { @@ -599,7 +592,7 @@ export class CoreUpdateManagerProvider implements CoreInitHandler { * Calendar default notification time is configurable from version 3.2.1, and a new option "Default" is added. * All events that were configured to use the fixed default time should now be configured to use "Default" option. * - * @return {Promise} Promise resolved when the events are configured. + * @return Promise resolved when the events are configured. */ protected setCalendarDefaultNotifTime(): Promise { if (!this.notifProvider.isAvailable()) { @@ -643,7 +636,7 @@ export class CoreUpdateManagerProvider implements CoreInitHandler { * In version 3.2.1 we want the site config to be stored in each site if available. * Since it can be slow, we'll only block retrieving the config of current site, the rest will be in background. * - * @return {Promise} Promise resolved when the config is loaded for the current site (if any). + * @return Promise resolved when the config is loaded for the current site (if any). */ protected setSitesConfig(): Promise { return this.sitesProvider.getSitesIds().then((siteIds) => { @@ -675,8 +668,8 @@ export class CoreUpdateManagerProvider implements CoreInitHandler { /** * Store the config of a site. * - * @param {String} siteId Site ID. - * @return {Promise} Promise resolved when the config is loaded for the site. + * @param siteId Site ID. + * @return Promise resolved when the config is loaded for the site. */ protected setSiteConfig(siteId: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -698,7 +691,7 @@ export class CoreUpdateManagerProvider implements CoreInitHandler { /** * Logout from legacy sites. * - * @return {Promise} Promise resolved when done. + * @return Promise resolved when done. */ protected logoutLegacySites(): Promise { return this.sitesProvider.getSitesIds().then((siteIds) => { diff --git a/src/providers/urlschemes.ts b/src/providers/urlschemes.ts index 83b189c3a..9b8fe5e76 100644 --- a/src/providers/urlschemes.ts +++ b/src/providers/urlschemes.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -35,43 +35,36 @@ import { CoreConstants } from '@core/constants'; export interface CoreCustomURLSchemesParams { /** * The site's URL. - * @type {string} */ siteUrl: string; /** * User's token. If set, user will be authenticated. - * @type {string} */ token?: string; /** * User's private token. - * @type {string} */ privateToken?: string; /** * Username. - * @type {string} */ username?: string; /** * URL to open once authenticated. - * @type {string} */ redirect?: any; /** * Name of the page to go once authenticated. - * @type {string} */ pageName?: string; /** * Params to pass to the page. - * @type {string} */ pageParams?: any; } @@ -96,8 +89,8 @@ export class CoreCustomURLSchemesProvider { /** * Handle an URL received by custom URL scheme. * - * @param {string} url URL to treat. - * @return {Promise} Promise resolved when done. + * @param url URL to treat. + * @return Promise resolved when done. */ handleCustomURL(url: string): Promise { if (!this.isCustomURL(url)) { @@ -307,8 +300,8 @@ export class CoreCustomURLSchemesProvider { * Get the data from a custom URL scheme. The structure of the URL is: * moodlemobile://username@domain.com?token=TOKEN&privatetoken=PRIVATETOKEN&redirect=http://domain.com/course/view.php?id=2 * - * @param {string} url URL to treat. - * @return {Promise} Promise resolved with the data. + * @param url URL to treat. + * @return Promise resolved with the data. */ protected getCustomURLData(url: string): Promise { const urlScheme = CoreConfigConstants.customurlscheme + '://'; @@ -369,8 +362,8 @@ export class CoreCustomURLSchemesProvider { /** * Get the data from a "link" custom URL scheme. This kind of URL is deprecated. * - * @param {string} url URL to treat. - * @return {Promise} Promise resolved with the data. + * @param url URL to treat. + * @return Promise resolved with the data. */ protected getCustomURLLinkData(url: string): Promise { const contentLinksScheme = CoreConfigConstants.customurlscheme + '://link='; @@ -433,8 +426,8 @@ export class CoreCustomURLSchemesProvider { /** * Get the data from a "token" custom URL scheme. This kind of URL is deprecated. * - * @param {string} url URL to treat. - * @return {Promise} Promise resolved with the data. + * @param url URL to treat. + * @return Promise resolved with the data. */ protected getCustomURLTokenData(url: string): Promise { const ssoScheme = CoreConfigConstants.customurlscheme + '://token='; @@ -479,8 +472,8 @@ export class CoreCustomURLSchemesProvider { /** * Check whether a URL is a custom URL scheme. * - * @param {string} url URL to check. - * @return {boolean} Whether it's a custom URL scheme. + * @param url URL to check. + * @return Whether it's a custom URL scheme. */ isCustomURL(url: string): boolean { if (!url) { @@ -493,8 +486,8 @@ export class CoreCustomURLSchemesProvider { /** * Check whether a URL is a custom URL scheme with the "link" param (deprecated). * - * @param {string} url URL to check. - * @return {boolean} Whether it's a custom URL scheme. + * @param url URL to check. + * @return Whether it's a custom URL scheme. */ isCustomURLLink(url: string): boolean { if (!url) { @@ -507,8 +500,8 @@ export class CoreCustomURLSchemesProvider { /** * Check whether a URL is a custom URL scheme with a "token" param (deprecated). * - * @param {string} url URL to check. - * @return {boolean} Whether it's a custom URL scheme. + * @param url URL to check. + * @return Whether it's a custom URL scheme. */ isCustomURLToken(url: string): boolean { if (!url) { diff --git a/src/providers/utils/dom.ts b/src/providers/utils/dom.ts index 8776024e7..91e4b7ad1 100644 --- a/src/providers/utils/dom.ts +++ b/src/providers/utils/dom.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -22,7 +22,7 @@ import { TranslateService } from '@ngx-translate/core'; import { CoreTextUtilsProvider } from './text'; import { CoreAppProvider } from '../app'; import { CoreConfigProvider } from '../config'; -import { CoreConfigConstants } from '../../configconstants'; +import { CoreLoggerProvider } from '../logger'; import { CoreUrlUtilsProvider } from './url'; import { CoreFileProvider } from '@providers/file'; import { CoreConstants } from '@core/constants'; @@ -36,13 +36,11 @@ import { Subject } from 'rxjs'; export interface CoreAlert extends Alert { /** * Observable that will notify when the alert is dismissed. - * @type {Subject<{data: any, role: string}>} */ didDismiss: Subject<{data: any, role: string}>; /** * Observable that will notify when the alert will be dismissed. - * @type {Subject<{data: any, role: string}>} */ willDismiss: Subject<{data: any, role: string}>; } @@ -64,21 +62,29 @@ export class CoreDomUtilsProvider { protected lastInstanceId = 0; protected debugDisplay = false; // Whether to display debug messages. Store it in a variable to make it synchronous. protected displayedAlerts = {}; // To prevent duplicated alerts. + protected logger; - constructor(private translate: TranslateService, private loadingCtrl: LoadingController, private toastCtrl: ToastController, - private alertCtrl: AlertController, private textUtils: CoreTextUtilsProvider, private appProvider: CoreAppProvider, - private platform: Platform, private configProvider: CoreConfigProvider, private urlUtils: CoreUrlUtilsProvider, - private modalCtrl: ModalController, private sanitizer: DomSanitizer, private popoverCtrl: PopoverController, - private fileProvider: CoreFileProvider) { + constructor(private translate: TranslateService, + private loadingCtrl: LoadingController, + private toastCtrl: ToastController, + private alertCtrl: AlertController, + private textUtils: CoreTextUtilsProvider, + private appProvider: CoreAppProvider, + private platform: Platform, + private configProvider: CoreConfigProvider, + private urlUtils: CoreUrlUtilsProvider, + private modalCtrl: ModalController, + private sanitizer: DomSanitizer, + private popoverCtrl: PopoverController, + private fileProvider: CoreFileProvider, + loggerProvider: CoreLoggerProvider) { + + this.logger = loggerProvider.getInstance('CoreDomUtilsProvider'); // Check if debug messages should be displayed. configProvider.get(CoreConstants.SETTINGS_DEBUG_DISPLAY, false).then((debugDisplay) => { this.debugDisplay = !!debugDisplay; }); - // Set the font size based on user preference. - configProvider.get(CoreConstants.SETTINGS_FONT_SIZE, CoreConfigConstants.font_sizes[0]).then((fontSize) => { - document.documentElement.style.fontSize = fontSize + '%'; - }); } /** @@ -86,9 +92,9 @@ export class CoreDomUtilsProvider { * traverse the parents to achieve the same functionality. * Returns the closest ancestor of the current element (or the current element itself) which matches the selector. * - * @param {HTMLElement} element DOM Element. - * @param {string} selector Selector to search. - * @return {Element} Closest ancestor. + * @param element DOM Element. + * @param selector Selector to search. + * @return Closest ancestor. */ closest(element: HTMLElement, selector: string): Element { // Try to use closest if the browser supports it. @@ -125,13 +131,13 @@ export class CoreDomUtilsProvider { /** * If the download size is higher than a certain threshold shows a confirm dialog. * - * @param {any} size Object containing size to download and a boolean to indicate if its totally or partialy calculated. - * @param {string} [message] Code of the message to show. Default: 'core.course.confirmdownload'. - * @param {string} [unknownMessage] ID of the message to show if size is unknown. - * @param {number} [wifiThreshold] Threshold to show confirm in WiFi connection. Default: CoreWifiDownloadThreshold. - * @param {number} [limitedThreshold] Threshold to show confirm in limited connection. Default: CoreDownloadThreshold. - * @param {boolean} [alwaysConfirm] True to show a confirm even if the size isn't high, false otherwise. - * @return {Promise} Promise resolved when the user confirms or if no confirm needed. + * @param size Object containing size to download and a boolean to indicate if its totally or partialy calculated. + * @param message Code of the message to show. Default: 'core.course.confirmdownload'. + * @param unknownMessage ID of the message to show if size is unknown. + * @param wifiThreshold Threshold to show confirm in WiFi connection. Default: CoreWifiDownloadThreshold. + * @param limitedThreshold Threshold to show confirm in limited connection. Default: CoreDownloadThreshold. + * @param alwaysConfirm True to show a confirm even if the size isn't high, false otherwise. + * @return Promise resolved when the user confirms or if no confirm needed. */ confirmDownloadSize(size: any, message?: string, unknownMessage?: string, wifiThreshold?: number, limitedThreshold?: number, alwaysConfirm?: boolean): Promise { @@ -194,7 +200,7 @@ export class CoreDomUtilsProvider { { size: readableSize, availableSpace: availableSpace })); } else if (alwaysConfirm || size.size >= wifiThreshold || (this.appProvider.isNetworkAccessLimited() && size.size >= limitedThreshold)) { - message = message || 'core.course.confirmdownload'; + message = message || (size.size === 0 ? 'core.course.confirmdownloadzerosize' : 'core.course.confirmdownload'); return this.showConfirm(wifiPrefix + this.translate.instant(message, { size: readableSize, availableSpace: availableSpace })); @@ -207,8 +213,8 @@ export class CoreDomUtilsProvider { /** * Convert some HTML as text into an HTMLElement. This HTML is put inside a div or a body. * - * @param {string} html Text to convert. - * @return {HTMLElement} Element. + * @param html Text to convert. + * @return Element. */ convertToElement(html: string): HTMLElement { // Add a div to hold the content, that's the element that will be returned. @@ -220,7 +226,7 @@ export class CoreDomUtilsProvider { /** * Create a "cancelled" error. These errors won't display an error message in showErrorModal functions. * - * @return {any} The error object. + * @return The error object. */ createCanceledError(): any { return {coreCanceled: true}; @@ -230,8 +236,8 @@ export class CoreDomUtilsProvider { * Given a list of changes for a component input detected by a KeyValueDiffers, create an object similar to the one * passed to the ngOnChanges functions. * - * @param {any} changes Changes detected by KeyValueDiffer. - * @return {{[name: string]: SimpleChange}} Changes in a format like ngOnChanges. + * @param changes Changes detected by KeyValueDiffer. + * @return Changes in a format like ngOnChanges. */ createChangesFromKeyValueDiff(changes: any): { [name: string]: SimpleChange } { const newChanges: { [name: string]: SimpleChange } = {}; @@ -255,10 +261,14 @@ export class CoreDomUtilsProvider { /** * Extract the downloadable URLs from an HTML code. * - * @param {string} html HTML code. - * @return {string[]} List of file urls. + * @param html HTML code. + * @return List of file urls. + * @deprecated since 3.8. Use CoreFilepoolProvider.extractDownloadableFilesFromHtml instead. */ extractDownloadableFilesFromHtml(html: string): string[] { + this.logger.error('The function extractDownloadableFilesFromHtml has been moved to CoreFilepoolProvider.' + + ' Please use that function instead of this one.'); + const urls = []; let elements; @@ -288,8 +298,9 @@ export class CoreDomUtilsProvider { /** * Extract the downloadable URLs from an HTML code and returns them in fake file objects. * - * @param {string} html HTML code. - * @return {any[]} List of fake file objects with file URLs. + * @param html HTML code. + * @return List of fake file objects with file URLs. + * @deprecated since 3.8. Use CoreFilepoolProvider.extractDownloadableFilesFromHtmlAsFakeFileObjects instead. */ extractDownloadableFilesFromHtmlAsFakeFileObjects(html: string): any[] { const urls = this.extractDownloadableFilesFromHtml(html); @@ -305,8 +316,8 @@ export class CoreDomUtilsProvider { /** * Search all the URLs in a CSS file content. * - * @param {string} code CSS code. - * @return {string[]} List of URLs. + * @param code CSS code. + * @return List of URLs. */ extractUrlsFromCSS(code: string): string[] { // First of all, search all the url(...) occurrences that don't include "data:". @@ -331,8 +342,8 @@ export class CoreDomUtilsProvider { /** * Fix syntax errors in HTML. * - * @param {string} html HTML text. - * @return {string} Fixed HTML text. + * @param html HTML text. + * @return Fixed HTML text. */ fixHtml(html: string): string { this.template.innerHTML = html; @@ -358,7 +369,7 @@ export class CoreDomUtilsProvider { /** * Focus an element and open keyboard. * - * @param {HTMLElement} el HTML element to focus. + * @param el HTML element to focus. */ focusElement(el: HTMLElement): void { if (el && el.focus) { @@ -375,11 +386,11 @@ export class CoreDomUtilsProvider { * If the size is already valid (like '500px' or '50%') it won't be modified. * Returned size will have a format like '500px'. * - * @param {any} size Size to format. - * @return {string} Formatted size. If size is not valid, returns an empty string. + * @param size Size to format. + * @return Formatted size. If size is not valid, returns an empty string. */ formatPixelsSize(size: any): string { - if (typeof size == 'string' && (size.indexOf('px') > -1 || size.indexOf('%') > -1)) { + if (typeof size == 'string' && (size.indexOf('px') > -1 || size.indexOf('%') > -1 || size == 'auto' || size == 'initial')) { // It seems to be a valid size. return size; } @@ -395,9 +406,9 @@ export class CoreDomUtilsProvider { /** * Returns the contents of a certain selection in a DOM element. * - * @param {HTMLElement} element DOM element to search in. - * @param {string} selector Selector to search. - * @return {string} Selection contents. Undefined if not found. + * @param element DOM element to search in. + * @param selector Selector to search. + * @return Selection contents. Undefined if not found. */ getContentsOfElement(element: HTMLElement, selector: string): string { if (element) { @@ -411,8 +422,8 @@ export class CoreDomUtilsProvider { /** * Get the data from a form. It will only collect elements that have a name. * - * @param {HTMLFormElement} form The form to get the data from. - * @return {any} Object with the data. The keys are the names of the inputs. + * @param form The form to get the data from. + * @return Object with the data. The keys are the names of the inputs. */ getDataFromForm(form: HTMLFormElement): any { if (!form || !form.elements) { @@ -448,9 +459,9 @@ export class CoreDomUtilsProvider { /** * Returns the attribute value of a string element. Only the first element will be selected. * - * @param {string} html HTML element in string. - * @param {string} attribute Attribute to get. - * @return {string} Attribute value. + * @param html HTML element in string. + * @param attribute Attribute to get. + * @return Attribute value. */ getHTMLElementAttribute(html: string, attribute: string): string { return this.convertToElement(html).children[0].getAttribute('src'); @@ -459,12 +470,12 @@ export class CoreDomUtilsProvider { /** * Returns height of an element. * - * @param {any} element DOM element to measure. - * @param {boolean} [usePadding] Whether to use padding to calculate the measure. - * @param {boolean} [useMargin] Whether to use margin to calculate the measure. - * @param {boolean} [useBorder] Whether to use borders to calculate the measure. - * @param {boolean} [innerMeasure] If inner measure is needed: padding, margin or borders will be substracted. - * @return {number} Height in pixels. + * @param element DOM element to measure. + * @param usePadding Whether to use padding to calculate the measure. + * @param useMargin Whether to use margin to calculate the measure. + * @param useBorder Whether to use borders to calculate the measure. + * @param innerMeasure If inner measure is needed: padding, margin or borders will be substracted. + * @return Height in pixels. */ getElementHeight(element: any, usePadding?: boolean, useMargin?: boolean, useBorder?: boolean, innerMeasure?: boolean): number { @@ -474,13 +485,13 @@ export class CoreDomUtilsProvider { /** * Returns height or width of an element. * - * @param {any} element DOM element to measure. - * @param {boolean} [getWidth] Whether to get width or height. - * @param {boolean} [usePadding] Whether to use padding to calculate the measure. - * @param {boolean} [useMargin] Whether to use margin to calculate the measure. - * @param {boolean} [useBorder] Whether to use borders to calculate the measure. - * @param {boolean} [innerMeasure] If inner measure is needed: padding, margin or borders will be substracted. - * @return {number} Measure in pixels. + * @param element DOM element to measure. + * @param getWidth Whether to get width or height. + * @param usePadding Whether to use padding to calculate the measure. + * @param useMargin Whether to use margin to calculate the measure. + * @param useBorder Whether to use borders to calculate the measure. + * @param innerMeasure If inner measure is needed: padding, margin or borders will be substracted. + * @return Measure in pixels. */ getElementMeasure(element: any, getWidth?: boolean, usePadding?: boolean, useMargin?: boolean, useBorder?: boolean, innerMeasure?: boolean): number { @@ -531,9 +542,9 @@ export class CoreDomUtilsProvider { /** * Returns the computed style measure or 0 if not found or NaN. * - * @param {any} style Style from getComputedStyle. - * @param {string} measure Measure to get. - * @return {number} Result of the measure. + * @param style Style from getComputedStyle. + * @param measure Measure to get. + * @return Result of the measure. */ getComputedStyleMeasure(style: any, measure: string): number { return parseInt(style[measure], 10) || 0; @@ -542,7 +553,7 @@ export class CoreDomUtilsProvider { /** * Get the HTML code to render a connection warning icon. * - * @return {string} HTML Code. + * @return HTML Code. */ getConnectionWarningIconHtml(): string { return '
' + @@ -554,12 +565,12 @@ export class CoreDomUtilsProvider { /** * Returns width of an element. * - * @param {any} element DOM element to measure. - * @param {boolean} [usePadding] Whether to use padding to calculate the measure. - * @param {boolean} [useMargin] Whether to use margin to calculate the measure. - * @param {boolean} [useBorder] Whether to use borders to calculate the measure. - * @param {boolean} [innerMeasure] If inner measure is needed: padding, margin or borders will be substracted. - * @return {number} Width in pixels. + * @param element DOM element to measure. + * @param usePadding Whether to use padding to calculate the measure. + * @param useMargin Whether to use margin to calculate the measure. + * @param useBorder Whether to use borders to calculate the measure. + * @param innerMeasure If inner measure is needed: padding, margin or borders will be substracted. + * @return Width in pixels. */ getElementWidth(element: any, usePadding?: boolean, useMargin?: boolean, useBorder?: boolean, innerMeasure?: boolean): number { @@ -569,10 +580,10 @@ export class CoreDomUtilsProvider { /** * Retrieve the position of a element relative to another element. * - * @param {HTMLElement} container Element to search in. - * @param {string} [selector] Selector to find the element to gets the position. - * @param {string} [positionParentClass] Parent Class where to stop calculating the position. Default scroll-content. - * @return {number[]} positionLeft, positionTop of the element relative to. + * @param container Element to search in. + * @param selector Selector to find the element to gets the position. + * @param positionParentClass Parent Class where to stop calculating the position. Default scroll-content. + * @return positionLeft, positionTop of the element relative to. */ getElementXY(container: HTMLElement, selector?: string, positionParentClass?: string): number[] { let element: HTMLElement = (selector ? container.querySelector(selector) : container), @@ -617,8 +628,8 @@ export class CoreDomUtilsProvider { /** * Given an error message, return a suitable error title. * - * @param {string} message The error message. - * @return {any} Title. + * @param message The error message. + * @return Title. */ private getErrorTitle(message: string): any { if (message == this.translate.instant('core.networkerrormsg') || @@ -633,9 +644,9 @@ export class CoreDomUtilsProvider { /** * Get the error message from an error, including debug data if needed. * - * @param {any} error Message to show. - * @param {boolean} [needsTranslate] Whether the error needs to be translated. - * @return {string} Error message, null if no error should be displayed. + * @param error Message to show. + * @param needsTranslate Whether the error needs to be translated. + * @return Error message, null if no error should be displayed. */ getErrorMessage(error: any, needsTranslate?: boolean): string { let extraInfo = ''; @@ -693,8 +704,8 @@ export class CoreDomUtilsProvider { * Please use this function only if you cannot retrieve the instance using parent/child methods: ViewChild (or similar) * or Angular's injection. * - * @param {Element} element The root element of the component/directive. - * @return {any} The instance, undefined if not found. + * @param element The root element of the component/directive. + * @return The instance, undefined if not found. */ getInstanceByElement(element: Element): any { const id = element.getAttribute(this.INSTANCE_ID_ATTR_NAME); @@ -705,8 +716,8 @@ export class CoreDomUtilsProvider { /** * Wait an element to exists using the findFunction. * - * @param {Function} findFunction The function used to find the element. - * @return {Promise} Resolved if found, rejected if too many tries. + * @param findFunction The function used to find the element. + * @return Resolved if found, rejected if too many tries. */ waitElementToExist(findFunction: Function): Promise { const promiseInterval = { @@ -744,7 +755,7 @@ export class CoreDomUtilsProvider { /** * Handle bootstrap tooltips in a certain element. * - * @param {HTMLElement} element Element to check. + * @param element Element to check. */ handleBootstrapTooltips(element: HTMLElement): void { const els = Array.from(element.querySelectorAll('[data-toggle="tooltip"]')); @@ -783,9 +794,9 @@ export class CoreDomUtilsProvider { /** * Check if an element is outside of screen (viewport). * - * @param {HTMLElement} scrollEl The element that must be scrolled. - * @param {HTMLElement} element DOM element to check. - * @return {boolean} Whether the element is outside of the viewport. + * @param scrollEl The element that must be scrolled. + * @param element DOM element to check. + * @return Whether the element is outside of the viewport. */ isElementOutsideOfScreen(scrollEl: HTMLElement, element: HTMLElement): boolean { const elementRect = element.getBoundingClientRect(); @@ -808,7 +819,7 @@ export class CoreDomUtilsProvider { /** * Check if rich text editor is enabled. * - * @return {Promise} Promise resolved with boolean: true if enabled, false otherwise. + * @return Promise resolved with boolean: true if enabled, false otherwise. */ isRichTextEditorEnabled(): Promise { if (this.isRichTextEditorSupported()) { @@ -823,7 +834,7 @@ export class CoreDomUtilsProvider { /** * Check if rich text editor is supported in the platform. * - * @return {boolean} Whether it's supported. + * @return Whether it's supported. */ isRichTextEditorSupported(): boolean { return true; @@ -832,10 +843,10 @@ export class CoreDomUtilsProvider { /** * Move children from one HTMLElement to another. * - * @param {HTMLElement} oldParent The old parent. - * @param {HTMLElement} newParent The new parent. - * @param {boolean} [prepend] If true, adds the children to the beginning of the new parent. - * @return {Node[]} List of moved children. + * @param oldParent The old parent. + * @param newParent The new parent. + * @param prepend If true, adds the children to the beginning of the new parent. + * @return List of moved children. */ moveChildren(oldParent: HTMLElement, newParent: HTMLElement, prepend?: boolean): Node[] { const movedChildren: Node[] = []; @@ -854,8 +865,8 @@ export class CoreDomUtilsProvider { /** * Search and remove a certain element from inside another element. * - * @param {HTMLElement} element DOM element to search in. - * @param {string} selector Selector to search. + * @param element DOM element to search in. + * @param selector Selector to search. */ removeElement(element: HTMLElement, selector: string): void { if (element) { @@ -869,10 +880,10 @@ export class CoreDomUtilsProvider { /** * Search and remove a certain element from an HTML code. * - * @param {string} html HTML code to change. - * @param {string} selector Selector to search. - * @param {boolean} [removeAll] True if it should remove all matches found, false if it should only remove the first one. - * @return {string} HTML without the element. + * @param html HTML code to change. + * @param selector Selector to search. + * @param removeAll True if it should remove all matches found, false if it should only remove the first one. + * @return HTML without the element. */ removeElementFromHtml(html: string, selector: string, removeAll?: boolean): string { let selected; @@ -897,7 +908,7 @@ export class CoreDomUtilsProvider { /** * Remove a component/directive instance using the DOM Element. * - * @param {Element} element The root element of the component/directive. + * @param element The root element of the component/directive. */ removeInstanceByElement(element: Element): void { const id = element.getAttribute(this.INSTANCE_ID_ATTR_NAME); @@ -907,7 +918,7 @@ export class CoreDomUtilsProvider { /** * Remove a component/directive instance using the ID. * - * @param {string} id The ID to remove. + * @param id The ID to remove. */ removeInstanceById(id: string): void { delete this.instances[id]; @@ -916,8 +927,8 @@ export class CoreDomUtilsProvider { /** * Search for certain classes in an element contents and replace them with the specified new values. * - * @param {HTMLElement} element DOM element. - * @param {any} map Mapping of the classes to replace. Keys must be the value to replace, values must be + * @param element DOM element. + * @param map Mapping of the classes to replace. Keys must be the value to replace, values must be * the new class name. Example: {'correct': 'core-question-answer-correct'}. */ replaceClassesInElement(element: HTMLElement, map: any): void { @@ -934,10 +945,10 @@ export class CoreDomUtilsProvider { /** * Given an HTML, search all links and media and tries to restore original sources using the paths object. * - * @param {string} html HTML code. - * @param {object} paths Object linking URLs in the html code with the real URLs to use. - * @param {Function} [anchorFn] Function to call with each anchor. Optional. - * @return {string} Treated HTML code. + * @param html HTML code. + * @param paths Object linking URLs in the html code with the real URLs to use. + * @param anchorFn Function to call with each anchor. Optional. + * @return Treated HTML code. */ restoreSourcesInHtml(html: string, paths: object, anchorFn?: Function): string { let media, @@ -985,11 +996,11 @@ export class CoreDomUtilsProvider { * Scroll to somehere in the content. * Checks hidden property _scroll to avoid errors if view is not active. * - * @param {Content} content Content where to execute the function. - * @param {number} x The x-value to scroll to. - * @param {number} y The y-value to scroll to. - * @param {number} [duration] Duration of the scroll animation in milliseconds. Defaults to `300`. - * @returns {Promise} Returns a promise which is resolved when the scroll has completed. + * @param content Content where to execute the function. + * @param x The x-value to scroll to. + * @param y The y-value to scroll to. + * @param duration Duration of the scroll animation in milliseconds. Defaults to `300`. + * @return Returns a promise which is resolved when the scroll has completed. */ scrollTo(content: Content, x: number, y: number, duration?: number, done?: Function): Promise { return content && content._scroll && content.scrollTo(x, y, duration, done); @@ -999,9 +1010,9 @@ export class CoreDomUtilsProvider { * Scroll to Bottom of the content. * Checks hidden property _scroll to avoid errors if view is not active. * - * @param {Content} content Content where to execute the function. - * @param {number} [duration] Duration of the scroll animation in milliseconds. Defaults to `300`. - * @returns {Promise} Returns a promise which is resolved when the scroll has completed. + * @param content Content where to execute the function. + * @param duration Duration of the scroll animation in milliseconds. Defaults to `300`. + * @return Returns a promise which is resolved when the scroll has completed. */ scrollToBottom(content: Content, duration?: number): Promise { return content && content._scroll && content.scrollToBottom(duration); @@ -1011,9 +1022,9 @@ export class CoreDomUtilsProvider { * Scroll to Top of the content. * Checks hidden property _scroll to avoid errors if view is not active. * - * @param {Content} content Content where to execute the function. - * @param {number} [duration] Duration of the scroll animation in milliseconds. Defaults to `300`. - * @returns {Promise} Returns a promise which is resolved when the scroll has completed. + * @param content Content where to execute the function. + * @param duration Duration of the scroll animation in milliseconds. Defaults to `300`. + * @return Returns a promise which is resolved when the scroll has completed. */ scrollToTop(content: Content, duration?: number): Promise { return content && content._scroll && content.scrollToTop(duration); @@ -1023,8 +1034,8 @@ export class CoreDomUtilsProvider { * Returns contentHeight of the content. * Checks hidden property _scroll to avoid errors if view is not active. * - * @param {Content} content Content where to execute the function. - * @return {number} Content contentHeight or 0. + * @param content Content where to execute the function. + * @return Content contentHeight or 0. */ getContentHeight(content: Content): number { return (content && content._scroll && content.contentHeight) || 0; @@ -1034,8 +1045,8 @@ export class CoreDomUtilsProvider { * Returns scrollHeight of the content. * Checks hidden property _scroll to avoid errors if view is not active. * - * @param {Content} content Content where to execute the function. - * @return {number} Content scrollHeight or 0. + * @param content Content where to execute the function. + * @return Content scrollHeight or 0. */ getScrollHeight(content: Content): number { return (content && content._scroll && content.scrollHeight) || 0; @@ -1045,8 +1056,8 @@ export class CoreDomUtilsProvider { * Returns scrollTop of the content. * Checks hidden property _scroll to avoid errors if view is not active. * - * @param {Content} content Content where to execute the function. - * @return {number} Content scrollTop or 0. + * @param content Content where to execute the function. + * @return Content scrollTop or 0. */ getScrollTop(content: Content): number { return (content && content._scroll && content.scrollTop) || 0; @@ -1055,10 +1066,10 @@ export class CoreDomUtilsProvider { /** * Scroll to a certain element. * - * @param {Content} content The content that must be scrolled. - * @param {HTMLElement} element The element to scroll to. - * @param {string} [scrollParentClass] Parent class where to stop calculating the position. Default scroll-content. - * @return {boolean} True if the element is found, false otherwise. + * @param content The content that must be scrolled. + * @param element The element to scroll to. + * @param scrollParentClass Parent class where to stop calculating the position. Default scroll-content. + * @return True if the element is found, false otherwise. */ scrollToElement(content: Content, element: HTMLElement, scrollParentClass?: string): boolean { const position = this.getElementXY(element, undefined, scrollParentClass); @@ -1074,10 +1085,10 @@ export class CoreDomUtilsProvider { /** * Scroll to a certain element using a selector to find it. * - * @param {Content} content The content that must be scrolled. - * @param {string} selector Selector to find the element to scroll to. - * @param {string} [scrollParentClass] Parent class where to stop calculating the position. Default scroll-content. - * @return {boolean} True if the element is found, false otherwise. + * @param content The content that must be scrolled. + * @param selector Selector to find the element to scroll to. + * @param scrollParentClass Parent class where to stop calculating the position. Default scroll-content. + * @return True if the element is found, false otherwise. */ scrollToElementBySelector(content: Content, selector: string, scrollParentClass?: string): boolean { const position = this.getElementXY(content.getScrollElement(), selector, scrollParentClass); @@ -1093,9 +1104,9 @@ export class CoreDomUtilsProvider { /** * Search for an input with error (core-input-error directive) and scrolls to it if found. * - * @param {Content} content The content that must be scrolled. + * @param content The content that must be scrolled. * @param [scrollParentClass] Parent class where to stop calculating the position. Default scroll-content. - * @return {boolean} True if the element is found, false otherwise. + * @return True if the element is found, false otherwise. */ scrollToInputError(content: Content, scrollParentClass?: string): boolean { if (!content) { @@ -1108,7 +1119,7 @@ export class CoreDomUtilsProvider { /** * Set whether debug messages should be displayed. * - * @param {boolean} value Whether to display or not. + * @param value Whether to display or not. */ setDebugDisplay(value: boolean): void { this.debugDisplay = value; @@ -1117,11 +1128,11 @@ export class CoreDomUtilsProvider { /** * Show an alert modal with a button to close it. * - * @param {string} title Title to show. - * @param {string} message Message to show. - * @param {string} [buttonText] Text of the button. - * @param {number} [autocloseTime] Number of milliseconds to wait to close the modal. If not defined, modal won't be closed. - * @return {Promise} Promise resolved with the alert modal. + * @param title Title to show. + * @param message Message to show. + * @param buttonText Text of the button. + * @param autocloseTime Number of milliseconds to wait to close the modal. If not defined, modal won't be closed. + * @return Promise resolved with the alert modal. */ showAlert(title: string, message: string, buttonText?: string, autocloseTime?: number): Promise { const hasHTMLTags = this.textUtils.hasHTMLTags(message); @@ -1187,11 +1198,11 @@ export class CoreDomUtilsProvider { /** * Show an alert modal with a button to close it, translating the values supplied. * - * @param {string} title Title to show. - * @param {string} message Message to show. - * @param {string} [buttonText] Text of the button. - * @param {number} [autocloseTime] Number of milliseconds to wait to close the modal. If not defined, modal won't be closed. - * @return {Promise} Promise resolved with the alert modal. + * @param title Title to show. + * @param message Message to show. + * @param buttonText Text of the button. + * @param autocloseTime Number of milliseconds to wait to close the modal. If not defined, modal won't be closed. + * @return Promise resolved with the alert modal. */ showAlertTranslated(title: string, message: string, buttonText?: string, autocloseTime?: number): Promise { title = title ? this.translate.instant(title) : title; @@ -1201,15 +1212,28 @@ export class CoreDomUtilsProvider { return this.showAlert(title, message, buttonText, autocloseTime); } + /** + * Shortcut for a delete confirmation modal. + * + * @param translateMessage String key to show in the modal body translated. Default: 'core.areyousure'. + * @param translateArgs Arguments to pass to translate if necessary. + * @param options More options. See https://ionicframework.com/docs/v3/api/components/alert/AlertController/ + * @return Promise resolved if the user confirms and rejected with a canceled error if he cancels. + */ + showDeleteConfirm(translateMessage: string = 'core.areyousure', translateArgs: any = {}, options?: any): Promise { + return this.showConfirm(this.translate.instant(translateMessage, translateArgs), undefined, + this.translate.instant('core.delete'), undefined, options); + } + /** * Show a confirm modal. * - * @param {string} message Message to show in the modal body. - * @param {string} [title] Title of the modal. - * @param {string} [okText] Text of the OK button. - * @param {string} [cancelText] Text of the Cancel button. - * @param {any} [options] More options. See https://ionicframework.com/docs/v3/api/components/alert/AlertController/ - * @return {Promise} Promise resolved if the user confirms and rejected with a canceled error if he cancels. + * @param message Message to show in the modal body. + * @param title Title of the modal. + * @param okText Text of the OK button. + * @param cancelText Text of the Cancel button. + * @param options More options. See https://ionicframework.com/docs/v3/api/components/alert/AlertController/ + * @return Promise resolved if the user confirms and rejected with a canceled error if he cancels. */ showConfirm(message: string, title?: string, okText?: string, cancelText?: string, options?: any): Promise { return new Promise((resolve, reject): void => { @@ -1263,10 +1287,10 @@ export class CoreDomUtilsProvider { /** * Show an alert modal with an error message. * - * @param {any} error Message to show. - * @param {boolean} [needsTranslate] Whether the error needs to be translated. - * @param {number} [autocloseTime] Number of milliseconds to wait to close the modal. If not defined, modal won't be closed. - * @return {Promise} Promise resolved with the alert modal. + * @param error Message to show. + * @param needsTranslate Whether the error needs to be translated. + * @param autocloseTime Number of milliseconds to wait to close the modal. If not defined, modal won't be closed. + * @return Promise resolved with the alert modal. */ showErrorModal(error: any, needsTranslate?: boolean, autocloseTime?: number): Promise { const message = this.getErrorMessage(error, needsTranslate); @@ -1282,11 +1306,11 @@ export class CoreDomUtilsProvider { /** * Show an alert modal with an error message. It uses a default message if error is not a string. * - * @param {any} error Message to show. - * @param {any} [defaultError] Message to show if the error is not a string. - * @param {boolean} [needsTranslate] Whether the error needs to be translated. - * @param {number} [autocloseTime] Number of milliseconds to wait to close the modal. If not defined, modal won't be closed. - * @return {Promise} Promise resolved with the alert modal. + * @param error Message to show. + * @param defaultError Message to show if the error is not a string. + * @param needsTranslate Whether the error needs to be translated. + * @param autocloseTime Number of milliseconds to wait to close the modal. If not defined, modal won't be closed. + * @return Promise resolved with the alert modal. */ showErrorModalDefault(error: any, defaultError: any, needsTranslate?: boolean, autocloseTime?: number): Promise { if (error && error.coreCanceled) { @@ -1306,11 +1330,11 @@ export class CoreDomUtilsProvider { /** * Show an alert modal with the first warning error message. It uses a default message if error is not a string. * - * @param {any} warnings Warnings returned. - * @param {any} [defaultError] Message to show if the error is not a string. - * @param {boolean} [needsTranslate] Whether the error needs to be translated. - * @param {number} [autocloseTime] Number of milliseconds to wait to close the modal. If not defined, modal won't be closed. - * @return {Promise} Promise resolved with the alert modal. + * @param warnings Warnings returned. + * @param defaultError Message to show if the error is not a string. + * @param needsTranslate Whether the error needs to be translated. + * @param autocloseTime Number of milliseconds to wait to close the modal. If not defined, modal won't be closed. + * @return Promise resolved with the alert modal. */ showErrorModalFirstWarning(warnings: any, defaultError: any, needsTranslate?: boolean, autocloseTime?: number): Promise { const error = warnings && warnings.length && warnings[0].message; @@ -1321,9 +1345,9 @@ export class CoreDomUtilsProvider { /** * Displays a loading modal window. * - * @param {string} [text] The text of the modal window. Default: core.loading. - * @param {boolean} [needsTranslate] Whether the 'text' needs to be translated. - * @return {Loading} Loading modal instance. + * @param text The text of the modal window. Default: core.loading. + * @param needsTranslate Whether the 'text' needs to be translated. + * @return Loading modal instance. * @description * Usage: * let modal = domUtils.showModalLoading(myText); @@ -1371,11 +1395,11 @@ export class CoreDomUtilsProvider { /** * Show a prompt modal to input some data. * - * @param {string} message Modal message. - * @param {string} [title] Modal title. - * @param {string} [placeholder] Placeholder of the input element. By default, "Password". - * @param {string} [type] Type of the input element. By default, password. - * @return {Promise} Promise resolved with the input data if the user clicks OK, rejected if cancels. + * @param message Modal message. + * @param title Modal title. + * @param placeholder Placeholder of the input element. By default, "Password". + * @param type Type of the input element. By default, password. + * @return Promise resolved with the input data if the user clicks OK, rejected if cancels. */ showPrompt(message: string, title?: string, placeholder?: string, type: string = 'password'): Promise { return new Promise((resolve, reject): void => { @@ -1431,12 +1455,12 @@ export class CoreDomUtilsProvider { /** * Displays an autodimissable toast modal window. * - * @param {string} text The text of the toast. - * @param {boolean} [needsTranslate] Whether the 'text' needs to be translated. - * @param {number} [duration=2000] Duration in ms of the dimissable toast. - * @param {string} [cssClass=""] Class to add to the toast. - * @param {boolean} [dismissOnPageChange=true] Dismiss the Toast on page change. - * @return {Toast} Toast instance. + * @param text The text of the toast. + * @param needsTranslate Whether the 'text' needs to be translated. + * @param duration Duration in ms of the dimissable toast. + * @param cssClass Class to add to the toast. + * @param dismissOnPageChange Dismiss the Toast on page change. + * @return Toast instance. */ showToast(text: string, needsTranslate?: boolean, duration: number = 2000, cssClass: string = '', dismissOnPageChange: boolean = true): Toast { @@ -1461,9 +1485,9 @@ export class CoreDomUtilsProvider { /** * Stores a component/directive instance. * - * @param {Element} element The root element of the component/directive. - * @param {any} instance The instance to store. - * @return {string} ID to identify the instance. + * @param element The root element of the component/directive. + * @param instance The instance to store. + * @return ID to identify the instance. */ storeInstanceByElement(element: Element, instance: any): string { const id = String(this.lastInstanceId++); @@ -1477,8 +1501,8 @@ export class CoreDomUtilsProvider { /** * Check if an element supports input via keyboard. * - * @param {any} el HTML element to check. - * @return {boolean} Whether it supports input using keyboard. + * @param el HTML element to check. + * @return Whether it supports input using keyboard. */ supportsInputKeyboard(el: any): boolean { return el && !el.disabled && (el.tagName.toLowerCase() == 'textarea' || @@ -1488,8 +1512,8 @@ export class CoreDomUtilsProvider { /** * Converts HTML formatted text to DOM element(s). * - * @param {string} text HTML text. - * @return {HTMLCollection} Same text converted to HTMLCollection. + * @param text HTML text. + * @return Same text converted to HTMLCollection. */ toDom(text: string): HTMLCollection { const element = this.convertToElement(text); @@ -1500,7 +1524,7 @@ export class CoreDomUtilsProvider { /** * Treat anchors inside alert/modals. * - * @param {HTMLElement} container The HTMLElement that can contain anchors. + * @param container The HTMLElement that can contain anchors. */ treatAnchors(container: HTMLElement): void { const anchors = Array.from(container.querySelectorAll('a')); @@ -1536,10 +1560,10 @@ export class CoreDomUtilsProvider { /** * View an image in a new page or modal. * - * @param {string} image URL of the image. - * @param {string} title Title of the page or modal. - * @param {string} [component] Component to link the image to if needed. - * @param {string|number} [componentId] An ID to use in conjunction with the component. + * @param image URL of the image. + * @param title Title of the page or modal. + * @param component Component to link the image to if needed. + * @param componentId An ID to use in conjunction with the component. */ viewImage(image: string, title?: string, component?: string, componentId?: string | number): void { if (image) { @@ -1558,8 +1582,8 @@ export class CoreDomUtilsProvider { /** * Wait for images to load. * - * @param {HTMLElement} element The element to search in. - * @return {Promise} Promise resolved with a boolean: whether there was any image to load. + * @param element The element to search in. + * @return Promise resolved with a boolean: whether there was any image to load. */ waitForImages(element: HTMLElement): Promise { const imgs = Array.from(element.querySelectorAll('img')), @@ -1592,8 +1616,8 @@ export class CoreDomUtilsProvider { /** * Wrap an HTMLElement with another element. * - * @param {HTMLElement} el The element to wrap. - * @param {HTMLElement} wrapper Wrapper. + * @param el The element to wrap. + * @param wrapper Wrapper. */ wrapElement(el: HTMLElement, wrapper: HTMLElement): void { // Insert the wrapper before the element. diff --git a/src/providers/utils/iframe.ts b/src/providers/utils/iframe.ts index 2952c22b1..e60c19b58 100644 --- a/src/providers/utils/iframe.ts +++ b/src/providers/utils/iframe.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -46,9 +46,9 @@ export class CoreIframeUtilsProvider { /** * Check if a frame uses an online URL but the app is offline. If it does, the iframe is hidden and a warning is shown. * - * @param {any} element The frame to check (iframe, embed, ...). - * @param {boolean} [isSubframe] Whether it's a frame inside another frame. - * @return {boolean} True if frame is online and the app is offline, false otherwise. + * @param element The frame to check (iframe, embed, ...). + * @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; @@ -146,8 +146,8 @@ export class CoreIframeUtilsProvider { * Given an element, return the content window and document. * Please notice that the element should be an iframe, embed or similar. * - * @param {any} element Element to treat (iframe, embed, ...). - * @return {{ window: Window, document: Document }} Window and Document. + * @param element Element to treat (iframe, embed, ...). + * @return Window and Document. */ getContentWindowAndDocument(element: any): { window: Window, document: Document } { let contentWindow: Window = element.contentWindow, @@ -188,10 +188,10 @@ export class CoreIframeUtilsProvider { * Redefine the open method in the contentWindow of an element and the sub frames. * Please notice that the element should be an iframe, embed or similar. * - * @param {any} element Element to treat (iframe, embed, ...). - * @param {Window} contentWindow The window of the element contents. - * @param {Document} contentDocument The document of the element contents. - * @param {NavController} [navCtrl] NavController to use if a link can be opened in the app. + * @param element Element to treat (iframe, embed, ...). + * @param contentWindow The window of the element contents. + * @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?: NavController): void { if (contentWindow) { @@ -264,9 +264,9 @@ export class CoreIframeUtilsProvider { * Intercept window.open in a frame and its subframes, shows an error modal instead. * Search links () and open them in browser or InAppBrowser if needed. * - * @param {any} element Element to treat (iframe, embed, ...). - * @param {boolean} [isSubframe] Whether it's a frame inside another frame. - * @param {NavController} [navCtrl] NavController to use if a link can be opened in the app. + * @param element Element to treat (iframe, embed, ...). + * @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?: NavController): void { if (element) { @@ -300,8 +300,8 @@ export class CoreIframeUtilsProvider { * Search links () in a frame and open them in browser or InAppBrowser if needed. * Only links that haven't been treated by the frame's Javascript will be treated. * - * @param {any} element Element to treat (iframe, embed, ...). - * @param {Document} contentDocument The document of the element contents. + * @param element Element to treat (iframe, embed, ...). + * @param contentDocument The document of the element contents. */ treatFrameLinks(element: any, contentDocument: Document): void { if (!contentDocument) { @@ -319,58 +319,86 @@ export class CoreIframeUtilsProvider { while (el && el.tagName !== 'A') { el = el.parentElement; } - if (!el || el.tagName !== 'A') { - return; - } - const link = el; - const scheme = this.urlUtils.getUrlScheme(link.href); - if (!link.href || (scheme && scheme == 'javascript')) { - // Links with no URL and Javascript links are ignored. + const link = el; + if (!link || link.treated) { return; } - if (scheme && scheme != 'file' && scheme != 'filesystem') { - // Scheme suggests it's an external resource. - event.preventDefault(); + // Add click listener to the link, this way if the iframe has added a listener to the link it will be executed first. + link.treated = true; + link.addEventListener('click', this.linkClicked.bind(this, element, link)); + }, { + capture: true // Use capture to fix this listener not called if the element clicked is too deep in the DOM. + }); + } - const frameSrc = element.src || element.data, - frameScheme = this.urlUtils.getUrlScheme(frameSrc); + /** + * A link inside a frame was clicked. + * + * @param element Frame element. + * @param link Link clicked. + * @param event Click event. + */ + protected linkClicked(element: HTMLFrameElement | HTMLObjectElement, link: HTMLAnchorElement, event: Event): void { + if (event.defaultPrevented) { + // Event already prevented by some other code. + return; + } - // If the frame is not local, check the target to identify how to treat the link. - if (frameScheme && frameScheme != 'file' && frameScheme != 'filesystem' && - (!link.target || link.target == '_self')) { - // Load the link inside the frame itself. - if (element.tagName.toLowerCase() == 'object') { - element.setAttribute('data', link.href); - } else { - element.setAttribute('src', link.href); - } + const scheme = this.urlUtils.getUrlScheme(link.href); + if (!link.href || (scheme && scheme == 'javascript')) { + // Links with no URL and Javascript links are ignored. + return; + } - return; - } + if (scheme && scheme != 'file' && scheme != 'filesystem') { + // Scheme suggests it's an external resource. + event.preventDefault(); - // The frame is local or the link needs to be opened in a new window. Open in browser. - if (!this.sitesProvider.isLoggedIn()) { - this.utils.openInBrowser(link.href); - } else { - this.sitesProvider.getCurrentSite().openInBrowserWithAutoLoginIfSameSite(link.href); - } - } else if (link.target == '_parent' || link.target == '_top' || link.target == '_blank') { - // Opening links with _parent, _top or _blank can break the app. We'll open it in InAppBrowser. - event.preventDefault(); - this.utils.openFile(link.href).catch((error) => { - this.domUtils.showErrorModal(error); - }); - } else if (this.platform.is('ios') && (!link.target || link.target == '_self')) { - // In cordova ios 4.1.0 links inside iframes stopped working. We'll manually treat them. - event.preventDefault(); + const frameSrc = ( element).src || ( element).data, + frameScheme = this.urlUtils.getUrlScheme(frameSrc); + + // If the frame is not local, check the target to identify how to treat the link. + if (frameScheme && frameScheme != 'file' && frameScheme != 'filesystem' && + (!link.target || link.target == '_self')) { + // Load the link inside the frame itself. if (element.tagName.toLowerCase() == 'object') { element.setAttribute('data', link.href); } else { element.setAttribute('src', link.href); } + + return; } - }); + + // The frame is local or the link needs to be opened in a new window. Open in browser. + if (!this.sitesProvider.isLoggedIn()) { + this.utils.openInBrowser(link.href); + } else { + this.sitesProvider.getCurrentSite().openInBrowserWithAutoLoginIfSameSite(link.href); + } + } else if (link.target == '_parent' || link.target == '_top' || link.target == '_blank') { + // Opening links with _parent, _top or _blank can break the app. We'll open it in InAppBrowser. + event.preventDefault(); + this.utils.openFile(link.href).catch((error) => { + this.domUtils.showErrorModal(error); + }); + } else if (this.platform.is('ios') && (!link.target || link.target == '_self')) { + // In cordova ios 4.1.0 links inside iframes stopped working. We'll manually treat them. + event.preventDefault(); + if (element.tagName.toLowerCase() == 'object') { + element.setAttribute('data', link.href); + } else { + element.setAttribute('src', link.href); + } + } } } + +/** + * Subtype of HTMLAnchorElement, with some calculated data. + */ +type CoreIframeHTMLAnchorElement = HTMLAnchorElement & { + treated?: boolean; // Whether the element has been treated already. +}; diff --git a/src/providers/utils/mimetype.ts b/src/providers/utils/mimetype.ts index cac90bcb0..c162f3dc7 100644 --- a/src/providers/utils/mimetype.ts +++ b/src/providers/utils/mimetype.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -49,8 +49,8 @@ export class CoreMimetypeUtilsProvider { /** * Check if a file extension can be embedded without using iframes. * - * @param {string} extension Extension. - * @return {boolean} Whether it can be embedded. + * @param extension Extension. + * @return Whether it can be embedded. */ canBeEmbedded(extension: string): boolean { return this.isExtensionInGroup(extension, ['web_image', 'web_video', 'web_audio']); @@ -59,8 +59,8 @@ export class CoreMimetypeUtilsProvider { /** * Clean a extension, removing the dot, hash, extra params... * - * @param {string} extension Extension to clean. - * @return {string} Clean extension. + * @param extension Extension to clean. + * @return Clean extension. */ cleanExtension(extension: string): string { if (!extension) { @@ -68,7 +68,13 @@ export class CoreMimetypeUtilsProvider { } // If the extension has parameters, remove them. - const position = extension.indexOf('?'); + let position = extension.indexOf('?'); + if (position > -1) { + extension = extension.substr(0, position); + } + + // If the extension has an anchor, remove it. + position = extension.indexOf('#'); if (position > -1) { extension = extension.substr(0, position); } @@ -87,7 +93,7 @@ export class CoreMimetypeUtilsProvider { /** * Fill the mimetypes and extensions info for a certain group. * - * @param {string} group Group name. + * @param group Group name. */ protected fillGroupMimeInfo(group: string): void { const mimetypes = {}, // Use an object to prevent duplicates. @@ -111,9 +117,9 @@ export class CoreMimetypeUtilsProvider { /** * Get the extension of a mimetype. Returns undefined if not found. * - * @param {string} mimetype Mimetype. - * @param {string} [url] URL of the file. It will be used if there's more than one possible extension. - * @return {string} Extension. + * @param mimetype Mimetype. + * @param url URL of the file. It will be used if there's more than one possible extension. + * @return Extension. */ getExtension(mimetype: string, url?: string): string { mimetype = mimetype || ''; @@ -138,11 +144,46 @@ 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. + */ + getEmbeddedHtml(file: any, path?: string): string { + let ext; + const filename = file.filename || file.name; + + if (file.mimetype) { + ext = this.getExtension(file.mimetype); + } else { + ext = this.getFileExtension(filename); + file.mimetype = this.getMimeType(ext); + } + + if (this.canBeEmbedded(ext)) { + file.embedType = this.getExtensionType(ext); + + path = path || file.fileurl || (file.toURL && file.toURL()); + + if (file.embedType == 'image') { + return ''; + } + if (file.embedType == 'audio' || file.embedType == 'video') { + return '<' + file.embedType + ' controls title="' + filename + '" src="' + path + '">' + + '' + + ''; + } + } + + return ''; + } + /** * Get the URL of the icon of an extension. * - * @param {string} extension Extension. - * @return {string} Icon URL. + * @param extension Extension. + * @return Icon URL. */ getExtensionIcon(extension: string): string { const icon = this.getExtensionIconName(extension) || 'unknown'; @@ -153,8 +194,8 @@ export class CoreMimetypeUtilsProvider { /** * Get the name of the icon of an extension. * - * @param {string} extension Extension. - * @return {string} Icon. Undefined if not found. + * @param extension Extension. + * @return Icon. Undefined if not found. */ getExtensionIconName(extension: string): string { if (this.extToMime[extension]) { @@ -172,8 +213,8 @@ export class CoreMimetypeUtilsProvider { /** * Get the "type" (string) of an extension, something like "image", "video" or "audio". * - * @param {string} extension Extension. - * @return {string} Type of the extension. + * @param extension Extension. + * @return Type of the extension. */ getExtensionType(extension: string): string { extension = this.cleanExtension(extension); @@ -186,8 +227,8 @@ export class CoreMimetypeUtilsProvider { /** * Get all the possible extensions of a mimetype. Returns empty array if not found. * - * @param {string} mimetype Mimetype. - * @return {string[]} Extensions. + * @param mimetype Mimetype. + * @return Extensions. */ getExtensions(mimetype: string): string[] { mimetype = mimetype || ''; @@ -199,8 +240,8 @@ export class CoreMimetypeUtilsProvider { /** * Get a file icon URL based on its file name. * - * @param {string} The name of the file. - * @return {string} The path to a file icon. + * @param The name of the file. + * @return The path to a file icon. */ getFileIcon(filename: string): string { const ext = this.getFileExtension(filename), @@ -212,7 +253,7 @@ export class CoreMimetypeUtilsProvider { /** * Get the folder icon URL. * - * @return {string} The path to a folder icon. + * @return The path to a folder icon. */ getFolderIcon(): string { return 'assets/img/files/folder-64.png'; @@ -221,8 +262,8 @@ export class CoreMimetypeUtilsProvider { /** * Given a type (audio, video, html, ...), return its file icon path. * - * @param {string} type The type to get the icon. - * @return {string} The icon path. + * @param type The type to get the icon. + * @return The icon path. */ getFileIconForType(type: string): string { return 'assets/img/files/' + type + '-64.png'; @@ -232,8 +273,8 @@ export class CoreMimetypeUtilsProvider { * Guess the extension of a file from its URL. * This is very weak and unreliable. * - * @param {string} fileUrl The file URL. - * @return {string} The lowercased extension without the dot, or undefined. + * @param fileUrl The file URL. + * @return The lowercased extension without the dot, or undefined. */ guessExtensionFromUrl(fileUrl: string): string { const split = fileUrl.split('.'); @@ -268,8 +309,8 @@ export class CoreMimetypeUtilsProvider { * Returns the file extension of a file. * When the file does not have an extension, it returns undefined. * - * @param {string} filename The file name. - * @return {string} The lowercased extension, or undefined. + * @param filename The file name. + * @return The lowercased extension, or undefined. */ getFileExtension(filename: string): string { const dot = filename.lastIndexOf('.'); @@ -293,9 +334,9 @@ export class CoreMimetypeUtilsProvider { /** * Get the mimetype/extension info belonging to a certain group. * - * @param {string} group Group name. - * @param {string} [field] The field to get. If not supplied, all the info will be returned. - * @return {any} Info for the group. + * @param group Group name. + * @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 { if (typeof this.groupsMimeInfo[group] == 'undefined') { @@ -312,8 +353,8 @@ export class CoreMimetypeUtilsProvider { /** * Get the mimetype of an extension. Returns undefined if not found. * - * @param {string} extension Extension. - * @return {string} Mimetype. + * @param extension Extension. + * @return Mimetype. */ getMimeType(extension: string): string { extension = this.cleanExtension(extension); @@ -327,9 +368,9 @@ export class CoreMimetypeUtilsProvider { * Obtains descriptions for file types (e.g. 'Microsoft Word document') from the language file. * Based on Moodle's get_mimetype_description. * - * @param {any} obj Instance of FileEntry OR object with 'filename' and 'mimetype' OR string with mimetype. - * @param {boolean} [capitalise] If true, capitalises first character of result. - * @return {string} Type description. + * @param obj Instance of FileEntry OR object with 'filename' and 'mimetype' OR string with mimetype. + * @param capitalise If true, capitalises first character of result. + * @return Type description. */ getMimetypeDescription(obj: any, capitalise?: boolean): string { const langPrefix = 'assets.mimetypes.'; @@ -408,8 +449,8 @@ export class CoreMimetypeUtilsProvider { /** * Get the "type" (string) of a mimetype, something like "image", "video" or "audio". * - * @param {string} mimetype Mimetype. - * @return {string} Type of the mimetype. + * @param mimetype Mimetype. + * @return Type of the mimetype. */ getMimetypeType(mimetype: string): string { mimetype = mimetype.split(';')[0]; // Remove codecs from the mimetype if any. @@ -430,8 +471,8 @@ export class CoreMimetypeUtilsProvider { /** * Get the icon of a mimetype. * - * @param {string} mimetype Mimetype. - * @return {string} Type of the mimetype. + * @param mimetype Mimetype. + * @return Type of the mimetype. */ getMimetypeIcon(mimetype: string): string { mimetype = mimetype.split(';')[0]; // Remove codecs from the mimetype if any. @@ -454,8 +495,8 @@ export class CoreMimetypeUtilsProvider { /** * Given a group name, return the translated name. * - * @param {string} name Group name. - * @return {string} Translated name. + * @param name Group name. + * @return Translated name. */ getTranslatedGroupName(name: string): string { const key = 'assets.mimetypes.group:' + name, @@ -468,9 +509,9 @@ export class CoreMimetypeUtilsProvider { * Check if an extension belongs to at least one of the groups. * Similar to Moodle's file_mimetype_in_typegroup, but using the extension instead of mimetype. * - * @param {string} extension Extension. - * @param {string[]} groups List of groups to check. - * @return {boolean} Whether the extension belongs to any of the groups. + * @param extension Extension. + * @param groups List of groups to check. + * @return Whether the extension belongs to any of the groups. */ isExtensionInGroup(extension: string, groups: string[]): boolean { extension = this.cleanExtension(extension); @@ -490,8 +531,8 @@ export class CoreMimetypeUtilsProvider { /** * Remove the extension from a path (if any). * - * @param {string} path Path. - * @return {string} Path without extension. + * @param path Path. + * @return Path without extension. */ removeExtension(path: string): string { const position = path.lastIndexOf('.'); diff --git a/src/providers/utils/text.ts b/src/providers/utils/text.ts index d6e2b86fe..0f1202161 100644 --- a/src/providers/utils/text.ts +++ b/src/providers/utils/text.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -78,8 +78,8 @@ export class CoreTextUtilsProvider { /** * Add ending slash from a path or URL. * - * @param {string} text Text to treat. - * @return {string} Treated text. + * @param text Text to treat. + * @return Treated text. */ addEndingSlash(text: string): string { if (!text) { @@ -96,8 +96,8 @@ export class CoreTextUtilsProvider { /** * Given an address as a string, return a URL to open the address in maps. * - * @param {string} address The address. - * @return {SafeUrl} URL to view the address. + * @param address The address. + * @return URL to view the address. */ buildAddressURL(address: string): SafeUrl { return this.sanitizer.bypassSecurityTrustUrl((this.platform.is('android') ? 'geo:0,0?q=' : 'http://maps.google.com?q=') + @@ -107,8 +107,8 @@ export class CoreTextUtilsProvider { /** * Given a list of sentences, build a message with all of them wrapped in

. * - * @param {string[]} messages Messages to show. - * @return {string} Message with all the messages. + * @param messages Messages to show. + * @return Message with all the messages. */ buildMessage(messages: string[]): string { let result = ''; @@ -125,9 +125,9 @@ export class CoreTextUtilsProvider { /** * Convert size in bytes into human readable format * - * @param {number} bytes Number of bytes to convert. - * @param {number} [precision=2] Number of digits after the decimal separator. - * @return {string} Size in human readable format. + * @param bytes Number of bytes to convert. + * @param precision Number of digits after the decimal separator. + * @return Size in human readable format. */ bytesToSize(bytes: number, precision: number = 2): string { @@ -158,9 +158,9 @@ export class CoreTextUtilsProvider { /** * Clean HTML tags. * - * @param {string} text The text to be cleaned. - * @param {boolean} [singleLine] True if new lines should be removed (all the text in a single line). - * @return {string} Clean text. + * @param text The text to be cleaned. + * @param singleLine True if new lines should be removed (all the text in a single line). + * @return Clean text. */ cleanTags(text: string, singleLine?: boolean): string { if (typeof text != 'string') { @@ -185,9 +185,9 @@ export class CoreTextUtilsProvider { /** * Concatenate two paths, adding a slash between them if needed. * - * @param {string} leftPath Left path. - * @param {string} rightPath Right path. - * @return {string} Concatenated path. + * @param leftPath Left path. + * @param rightPath Right path. + * @return Concatenated path. */ concatenatePaths(leftPath: string, rightPath: string): string { if (!leftPath) { @@ -212,8 +212,8 @@ export class CoreTextUtilsProvider { * Convert some HTML as text into an HTMLElement. This HTML is put inside a div or a body. * This function is the same as in DomUtils, but we cannot use that one because of circular dependencies. * - * @param {string} html Text to convert. - * @return {HTMLElement} Element. + * @param html Text to convert. + * @return Element. */ protected convertToElement(html: string): HTMLElement { // Add a div to hold the content, that's the element that will be returned. @@ -225,8 +225,8 @@ export class CoreTextUtilsProvider { /** * Count words in a text. * - * @param {string} text Text to count. - * @return {number} Number of words. + * @param text Text to count. + * @return Number of words. */ countWords(text: string): number { if (!text || typeof text != 'string') { @@ -258,8 +258,8 @@ export class CoreTextUtilsProvider { /** * Decode an escaped HTML text. This implementation is based on PHP's htmlspecialchars_decode. * - * @param {string|number} text Text to decode. - * @return {string} Decoded text. + * @param text Text to decode. + * @return Decoded text. */ decodeHTML(text: string | number): string { if (typeof text == 'undefined' || text === null || (typeof text == 'number' && isNaN(text))) { @@ -280,8 +280,8 @@ export class CoreTextUtilsProvider { /** * Decode HTML entities in a text. Equivalent to PHP html_entity_decode. * - * @param {string} text Text to decode. - * @return {string} Decoded text. + * @param text Text to decode. + * @return Decoded text. */ decodeHTMLEntities(text: string): string { if (text) { @@ -295,8 +295,8 @@ export class CoreTextUtilsProvider { /** * Same as Javascript's decodeURI, but if an exception is thrown it will return the original URI. * - * @param {string} uri URI to decode. - * @return {string} Decoded URI, or original URI if an exception is thrown. + * @param uri URI to decode. + * @return Decoded URI, or original URI if an exception is thrown. */ decodeURI(uri: string): string { try { @@ -311,8 +311,8 @@ export class CoreTextUtilsProvider { /** * Same as Javascript's decodeURIComponent, but if an exception is thrown it will return the original URI. * - * @param {string} uri URI to decode. - * @return {string} Decoded URI, or original URI if an exception is thrown. + * @param uri URI to decode. + * @return Decoded URI, or original URI if an exception is thrown. */ decodeURIComponent(uri: string): string { try { @@ -327,8 +327,8 @@ export class CoreTextUtilsProvider { /** * Escapes some characters in a string to be used as a regular expression. * - * @param {string} text Text to escape. - * @return {string} Escaped text. + * @param text Text to escape. + * @return Escaped text. */ escapeForRegex(text: string): string { if (!text || typeof text != 'string') { @@ -341,8 +341,8 @@ export class CoreTextUtilsProvider { /** * Escape an HTML text. This implementation is based on PHP's htmlspecialchars. * - * @param {string|number} text Text to escape. - * @return {string} Escaped text. + * @param text Text to escape. + * @return Escaped text. */ escapeHTML(text: string | number): string { if (typeof text == 'undefined' || text === null || (typeof text == 'number' && isNaN(text))) { @@ -362,20 +362,29 @@ export class CoreTextUtilsProvider { /** * Shows a text on a new page. * - * @param {string} title Title of the new state. - * @param {string} text Content of the text to be expanded. - * @param {string} [component] Component to link the embedded files to. - * @param {string|number} [componentId] An ID to use in conjunction with the component. - * @param {any[]} [files] List of files to display along with the text. + * @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. */ - expandText(title: string, text: string, component?: string, componentId?: string | number, files?: any[]): void { + expandText(title: string, text: string, component?: string, componentId?: string | number, files?: any[], + filter?: boolean, contextLevel?: string, instanceId?: number, courseId?: number): void { if (text.length > 0) { const params: any = { title: title, content: text, component: component, componentId: componentId, - files: files + files: files, + filter: filter, + contextLevel: contextLevel, + instanceId: instanceId, + courseId: courseId }; // Open a modal with the contents. @@ -389,8 +398,8 @@ export class CoreTextUtilsProvider { /** * Formats a text, in HTML replacing new lines by correct html new lines. * - * @param {string} text Text to format. - * @return {string} Formatted text. + * @param text Text to format. + * @return Formatted text. */ formatHtmlLines(text: string): string { const hasHTMLTags = this.hasHTMLTags(text); @@ -410,12 +419,13 @@ export class CoreTextUtilsProvider { /** * Formats a text, treating multilang tags and cleaning HTML if needed. * - * @param {string} text Text to format. - * @param {boolean} [clean] Whether HTML tags should be removed. - * @param {boolean} [singleLine] Whether new lines should be removed. Only valid if clean is true. - * @param {number} [shortenLength] Number of characters to shorten the text. - * @param {number} [highlight] Text to highlight. - * @return {Promise} Promise resolved with the formatted text. + * @param text Text to format. + * @param clean Whether HTML tags should be removed. + * @param singleLine Whether new lines should be removed. Only valid if clean is true. + * @param shortenLength Number of characters to shorten the text. + * @param highlight Text to highlight. + * @return Promise resolved with the formatted text. + * @deprecated since 3.8.0. Please use CoreFilterProvider.formatText instead. */ formatText(text: string, clean?: boolean, singleLine?: boolean, shortenLength?: number, highlight?: string): Promise { return this.treatMultilangTags(text).then((formatted) => { @@ -436,8 +446,8 @@ export class CoreTextUtilsProvider { /** * Get the error message from an error object. * - * @param {any} error Error object. - * @return {string} Error message, undefined if not found. + * @param error Error object. + * @return Error message, undefined if not found. */ getErrorMessageFromError(error: any): string { if (typeof error == 'string') { @@ -450,8 +460,8 @@ export class CoreTextUtilsProvider { /** * Get the pluginfile URL to replace @@PLUGINFILE@@ wildcards. * - * @param {any[]} files Files to extract the URL from. They need to have the URL in a 'url' or 'fileurl' attribute. - * @return {string} Pluginfile URL, undefined if no files found. + * @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 { if (files && files.length) { @@ -467,8 +477,8 @@ export class CoreTextUtilsProvider { /** * Check if a text contains HTML tags. * - * @param {string} text Text to check. - * @return {boolean} Whether it has HTML tags. + * @param text Text to check. + * @return Whether it has HTML tags. */ hasHTMLTags(text: string): boolean { return /<[a-z][\s\S]*>/i.test(text); @@ -477,9 +487,9 @@ export class CoreTextUtilsProvider { /** * Highlight all occurrences of a certain text inside another text. It will add some HTML code to highlight it. * - * @param {string} text Full text. - * @param {string} searchText Text to search and highlight. - * @return {string} Highlighted text. + * @param text Full text. + * @param searchText Text to search and highlight. + * @return Highlighted text. */ highlightText(text: string, searchText: string): string { if (!text || typeof text != 'string') { @@ -496,26 +506,25 @@ export class CoreTextUtilsProvider { /** * Check if HTML content is blank. * - * @param {string} content HTML content. - * @return {boolean} True if the string does not contain actual content: text, images, etc. + * @param content HTML content. + * @return True if the string does not contain actual content: text, images, etc. */ htmlIsBlank(content: string): boolean { if (!content) { return true; } - const div = document.createElement('div'); - div.innerHTML = content; + this.template.innerHTML = content; - return div.textContent === '' && div.querySelector('img, object, hr') === null; + return this.template.content.textContent == '' && this.template.content.querySelector('img, object, hr') === null; } /** * Check if a text contains Unicode long chars. * Using as threshold Hex value D800 * - * @param {string} text Text to check. - * @return {boolean} True if has Unicode chars, false otherwise. + * @param text Text to check. + * @return True if has Unicode chars, false otherwise. */ hasUnicode(text: string): boolean { for (let x = 0; x < text.length; x++) { @@ -530,8 +539,8 @@ export class CoreTextUtilsProvider { /** * Check if an object has any long Unicode char. * - * @param {object} data Object to be checked. - * @return {boolean} If the data has any long Unicode char on it. + * @param data Object to be checked. + * @return If the data has any long Unicode char on it. */ hasUnicodeData(data: object): boolean { for (const el in data) { @@ -550,10 +559,10 @@ export class CoreTextUtilsProvider { /** * Same as Javascript's JSON.parse, but it will handle errors. * - * @param {string} json JSON text. - * @param {any} [defaultValue] Default value t oreturn if the parse fails. Defaults to the original value. - * @param {Function} [logErrorFn] An error to call with the exception to log the error. If not supplied, no error. - * @return {any} JSON parsed as object or what it gets. + * @param json JSON text. + * @param defaultValue Default value t oreturn if the parse fails. Defaults to the original value. + * @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?: Function): any { try { @@ -572,8 +581,8 @@ export class CoreTextUtilsProvider { /** * Remove ending slash from a path or URL. * - * @param {string} text Text to treat. - * @return {string} Treated text. + * @param text Text to treat. + * @return Treated text. */ removeEndingSlash(text: string): string { if (!text) { @@ -590,8 +599,8 @@ export class CoreTextUtilsProvider { /** * Replace all characters that cause problems with files in Android and iOS. * - * @param {string} text Text to treat. - * @return {string} Treated text. + * @param text Text to treat. + * @return Treated text. */ removeSpecialCharactersForFiles(text: string): string { if (!text || typeof text != 'string') { @@ -604,9 +613,9 @@ export class CoreTextUtilsProvider { /** * Replace all the new lines on a certain text. * - * @param {string} text The text to be treated. - * @param {string} newValue Text to use instead of new lines. - * @return {string} Treated text. + * @param text The text to be treated. + * @param newValue Text to use instead of new lines. + * @return Treated text. */ replaceNewLines(text: string, newValue: string): string { if (!text || typeof text != 'string') { @@ -619,9 +628,9 @@ export class CoreTextUtilsProvider { /** * Replace @@PLUGINFILE@@ wildcards with the real URL in a text. * - * @param {string} Text to treat. - * @param {any[]} files Files to extract the pluginfile URL from. They need to have the URL in a url or fileurl attribute. - * @return {string} Treated text. + * @param Text to treat. + * @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 { if (text && typeof text == 'string') { @@ -637,9 +646,9 @@ export class CoreTextUtilsProvider { /** * Replace pluginfile URLs with @@PLUGINFILE@@ wildcards. * - * @param {string} text Text to treat. - * @param {any[]} files Files to extract the pluginfile URL from. They need to have the URL in a url or fileurl attribute. - * @return {string} Treated text. + * @param text Text to treat. + * @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 { if (text && typeof text == 'string') { @@ -658,9 +667,9 @@ export class CoreTextUtilsProvider { * 7.toFixed(2) -> 7.00 * roundToDecimals(7, 2) -> 7 * - * @param {number} num Number to round. - * @param {number} [decimals=2] Number of decimals. By default, 2. - * @return {number} Rounded number. + * @param num Number to round. + * @param decimals Number of decimals. By default, 2. + * @return Rounded number. */ roundToDecimals(num: number, decimals: number = 2): number { const multiplier = Math.pow(10, decimals); @@ -674,8 +683,8 @@ export class CoreTextUtilsProvider { * Returns text with HTML characters (like "<", ">", etc.) properly quoted. * Based on Moodle's s() function. * - * @param {string} text Text to treat. - * @return {string} Treated text. + * @param text Text to treat. + * @return Treated text. */ s(text: string): string { if (!text) { @@ -688,9 +697,9 @@ export class CoreTextUtilsProvider { /** * Shortens a text to length and adds an ellipsis. * - * @param {string} text The text to be shortened. - * @param {number} length The desired length. - * @return {string} Shortened text. + * @param text The text to be shortened. + * @param length The desired length. + * @return Shortened text. */ shortenText(text: string, length: number): string { if (text.length > length) { @@ -711,8 +720,8 @@ export class CoreTextUtilsProvider { * Strip Unicode long char of a given text. * Using as threshold Hex value D800 * - * @param {string} text Text to check. - * @return {string} Without the Unicode chars. + * @param text Text to check. + * @return Without the Unicode chars. */ stripUnicode(text: string): string { let stripped = ''; @@ -725,11 +734,41 @@ export class CoreTextUtilsProvider { return stripped; } + /** + * 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. + * @param start The index where to start putting the new string. If negative, it will count from the end of the string. + * @param length Length of the portion of string which is to be replaced. If negative, it represents the number of characters + * from the end of string at which to stop replacing. If not provided, replace until the end of the string. + * @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(''); + } + /** * Treat the list of disabled features, replacing old nomenclature with the new one. * - * @param {string} features List of disabled features. - * @return {string} Treated list. + * @param features List of disabled features. + * @return Treated list. */ treatDisabledFeatures(features: string): string { if (!features) { @@ -748,8 +787,9 @@ export class CoreTextUtilsProvider { /** * Treat the multilang tags from a HTML code, leaving only the current language. * - * @param {string} text The text to be treated. - * @return {Promise} Promise resolved with the formatted text. + * @param text The text to be treated. + * @return Promise resolved with the formatted text. + * @deprecated since 3.8.0. Now this is handled by AddonFilterMultilangHandler. */ treatMultilangTags(text: string): Promise { if (!text || typeof text != 'string') { @@ -784,8 +824,8 @@ export class CoreTextUtilsProvider { /** * If a number has only 1 digit, add a leading zero to it. * - * @param {string|number} num Number to convert. - * @return {string} Number with leading zeros. + * @param num Number to convert. + * @return Number with leading zeros. */ twoDigits(num: string | number): string { if (num < 10) { @@ -798,8 +838,8 @@ export class CoreTextUtilsProvider { /** * Make a string's first character uppercase. * - * @param {string} text Text to treat. - * @return {string} Treated text. + * @param text Text to treat. + * @return Treated text. */ ucFirst(text: string): string { return text.charAt(0).toUpperCase() + text.slice(1); @@ -809,9 +849,9 @@ export class CoreTextUtilsProvider { * Unserialize Array from PHP. * Taken from: https://github.com/kvz/locutus/blob/master/src/php/var/unserialize.js * - * @param {string} data String to unserialize. - * @param {Function} [logErrorFn] An error to call with the exception to log the error. If not supplied, no error. - * @return {any} Unserialized data. + * @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?: Function): any { // Discuss at: http://locutus.io/php/unserialize/ diff --git a/src/providers/utils/time.ts b/src/providers/utils/time.ts index 9be16dd58..ca0d464fe 100644 --- a/src/providers/utils/time.ts +++ b/src/providers/utils/time.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -72,8 +72,8 @@ export class CoreTimeUtilsProvider { /** * Convert a PHP format to a Moment format. * - * @param {string} format PHP format. - * @return {string} Converted format. + * @param format PHP format. + * @return Converted format. */ convertPHPToMoment(format: string): string { if (typeof format != 'string') { @@ -121,8 +121,8 @@ export class CoreTimeUtilsProvider { /** * Fix format to use in an ion-datetime. * - * @param {string} format Format to use. - * @return {string} Fixed format. + * @param format Format to use. + * @return Fixed format. */ fixFormatForDatetime(format: string): string { if (!format) { @@ -144,8 +144,8 @@ export class CoreTimeUtilsProvider { /** * Returns hours, minutes and seconds in a human readable format * - * @param {number} seconds A number of seconds - * @return {string} Seconds in a human readable format. + * @param seconds A number of seconds + * @return Seconds in a human readable format. */ formatTime(seconds: number): string { let totalSecs, @@ -218,9 +218,9 @@ export class CoreTimeUtilsProvider { /** * Returns hours, minutes and seconds in a human readable format. * - * @param {number} duration Duration in seconds - * @param {number} [precision] Number of elements to have in precission. 0 or undefined to full precission. - * @return {string} Duration in a human readable format. + * @param duration Duration in seconds + * @param precision Number of elements to have in precission. 0 or undefined to full precission. + * @return Duration in a human readable format. */ formatDuration(duration: number, precision?: number): string { precision = precision || 5; @@ -255,7 +255,7 @@ export class CoreTimeUtilsProvider { /** * Return the current timestamp in a "readable" format: YYYYMMDDHHmmSS. * - * @return {string} The readable timestamp. + * @return The readable timestamp. */ readableTimestamp(): string { return moment(Date.now()).format('YYYYMMDDHHmmSS'); @@ -264,7 +264,7 @@ export class CoreTimeUtilsProvider { /** * Return the current timestamp (UNIX format, seconds). * - * @return {number} The current timestamp in seconds. + * @return The current timestamp in seconds. */ timestamp(): number { return Math.round(Date.now() / 1000); @@ -273,12 +273,12 @@ export class CoreTimeUtilsProvider { /** * Convert a timestamp into a readable date. * - * @param {number} timestamp Timestamp in milliseconds. - * @param {string} [format] The format to use (lang key). Defaults to core.strftimedaydatetime. - * @param {boolean} [convert=true] If true (default), convert the format from PHP to Moment. Set it to false for Moment formats. - * @param {boolean} [fixDay=true] If true (default) then the leading zero from %d is removed. - * @param {boolean} [fixHour=true] If true (default) then the leading zero from %I is removed. - * @return {string} Readable date. + * @param timestamp Timestamp in milliseconds. + * @param format The format to use (lang key). Defaults to core.strftimedaydatetime. + * @param convert If true (default), convert the format from PHP to Moment. Set it to false for Moment formats. + * @param fixDay If true (default) then the leading zero from %d is removed. + * @param fixHour If true (default) then the leading zero from %I is removed. + * @return Readable date. */ userDate(timestamp: number, format?: string, convert: boolean = true, fixDay: boolean = true, fixHour: boolean = true): string { format = this.translate.instant(format ? format : 'core.strftimedaydatetime'); @@ -302,8 +302,8 @@ export class CoreTimeUtilsProvider { /** * Convert a timestamp to the format to set to a datetime input. * - * @param {number} [timestamp] Timestamp to convert (in ms). If not provided, current time. - * @return {string} Formatted time. + * @param timestamp Timestamp to convert (in ms). If not provided, current time. + * @return Formatted time. */ toDatetimeFormat(timestamp?: number): string { timestamp = timestamp || Date.now(); @@ -314,8 +314,8 @@ export class CoreTimeUtilsProvider { /** * Convert a text into user timezone timestamp. * - * @param {number} date To convert to timestamp. - * @return {number} Converted timestamp. + * @param date To convert to timestamp. + * @return Converted timestamp. */ convertToTimestamp(date: string): number { if (typeof date == 'string' && date.slice(-1) == 'Z') { @@ -329,8 +329,8 @@ export class CoreTimeUtilsProvider { * Return the localized ISO format (i.e DDMMYY) from the localized moment format. Useful for translations. * DO NOT USE this function for ion-datetime format. Moment escapes characters with [], but ion-datetime doesn't support it. * - * @param {any} localizedFormat Format to use. - * @return {string} Localized ISO format + * @param localizedFormat Format to use. + * @return Localized ISO format */ getLocalizedDateFormat(localizedFormat: any): string { return moment.localeData().longDateFormat(localizedFormat); @@ -342,8 +342,8 @@ export class CoreTimeUtilsProvider { * The calculation is performed relative to the user's midnight timestamp * for today to ensure that timezones are preserved. * - * @param {number} [timestamp] The timestamp to calculate from. If not defined, return today's midnight. - * @return {number} The midnight value of the user's timestamp. + * @param timestamp The timestamp to calculate from. If not defined, return today's midnight. + * @return The midnight value of the user's timestamp. */ getMidnightForTimestamp(timestamp?: number): number { if (timestamp) { diff --git a/src/providers/utils/url.ts b/src/providers/utils/url.ts index 7027da2bf..af89f56c3 100644 --- a/src/providers/utils/url.ts +++ b/src/providers/utils/url.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -27,8 +27,8 @@ export class CoreUrlUtilsProvider { /** * Add or remove 'www' from a URL. The url needs to have http or https protocol. * - * @param {string} url URL to modify. - * @return {string} Modified URL. + * @param url URL to modify. + * @return Modified URL. */ addOrRemoveWWW(url: string): string { if (url) { @@ -44,22 +44,72 @@ export class CoreUrlUtilsProvider { return url; } + /** + * Add params to a URL. + * + * @param url URL to add the params to. + * @param params Object with the params to add. + * @param anchor Anchor text if needed. + * @param boolToNumber Whether to convert bools to 1 or 0. + * @return URL with params. + */ + addParamsToUrl(url: string, params?: {[key: string]: any}, anchor?: string, boolToNumber?: boolean): string { + let separator = url.indexOf('?') != -1 ? '&' : '?'; + + for (const key in params) { + let value = params[key]; + + if (boolToNumber && typeof value == 'boolean') { + // Convert booleans to 1 or 0. + value = value ? 1 : 0; + } + + // Ignore objects. + if (typeof value != 'object') { + url += separator + key + '=' + value; + separator = '&'; + } + } + + if (anchor) { + url += '#' + anchor; + } + + return url; + } + /** * Given a URL and a text, return an HTML link. * - * @param {string} url URL. - * @param {string} text Text of the link. - * @return {string} Link. + * @param url URL. + * @param text Text of the link. + * @return Link. */ buildLink(url: string, text: string): string { return '' + text + ''; } + /** + * Check whether we can use tokenpluginfile.php endpoint for a certain URL. + * + * @param url URL to check. + * @param siteUrl The URL of the site the URL belongs to. + * @param accessKey User access key for tokenpluginfile. + * @return Whether tokenpluginfile.php can be used. + */ + canUseTokenPluginFile(url: string, siteUrl: string, accessKey?: string): boolean { + // Do not use tokenpluginfile if site doesn't use slash params, the URL doesn't work. + // Also, only use it for "core" pluginfile endpoints. Some plugins can implement their own endpoint (like customcert). + return accessKey && !url.match(/[\&?]file=/) && ( + url.indexOf(this.textUtils.concatenatePaths(siteUrl, 'pluginfile.php')) === 0 || + url.indexOf(this.textUtils.concatenatePaths(siteUrl, 'webservice/pluginfile.php')) === 0); + } + /** * Extracts the parameters from a URL and stores them in an object. * - * @param {string} url URL to treat. - * @return {any} Object with the params. + * @param url URL to treat. + * @return Object with the params. */ extractUrlParams(url: string): any { const regex = /[?&]+([^=&]+)=?([^&]*)?/gi, @@ -104,20 +154,23 @@ export class CoreUrlUtilsProvider { * For download remote files from Moodle we need to use the special /webservice/pluginfile passing * the ws token as a get parameter. * - * @param {string} url The url to be fixed. - * @param {string} token Token to use. - * @param {string} siteUrl The URL of the site the URL belongs to. - * @return {string} Fixed URL. + * @param url The url to be fixed. + * @param token Token to use. + * @param siteUrl The URL of the site the URL belongs to. + * @param accessKey User access key for tokenpluginfile. + * @return Fixed URL. */ - fixPluginfileURL(url: string, token: string, siteUrl: string): string { + fixPluginfileURL(url: string, token: string, siteUrl: string, accessKey?: string): string { if (!url) { return ''; } url = url.replace(/&/g, '&'); + const canUseTokenPluginFile = accessKey && this.canUseTokenPluginFile(url, siteUrl, accessKey); + // First check if we need to fix this url or is already fixed. - if (url.indexOf('token=') != -1) { + if (!canUseTokenPluginFile && url.indexOf('token=') != -1) { return url; } @@ -126,7 +179,19 @@ export class CoreUrlUtilsProvider { return url; } - // Check if the URL already has params. + // Check if is a valid URL (contains the pluginfile endpoint) and belongs to the site. + if (!this.isPluginFileUrl(url) || url.indexOf(this.textUtils.addEndingSlash(siteUrl)) !== 0) { + return url; + } + + if (canUseTokenPluginFile) { + // Use tokenpluginfile.php. + url = url.replace(/(\/webservice)?\/pluginfile\.php/, '/tokenpluginfile.php/' + accessKey); + + return url; + } + + // No access key, use pluginfile.php. Check if the URL already has params. if (url.match(/\?[^=]+=/)) { url += '&'; } else { @@ -146,8 +211,8 @@ export class CoreUrlUtilsProvider { /** * Formats a URL, trim, lowercase, etc... * - * @param {string} url The url to be formatted. - * @return {string} Fromatted url. + * @param url The url to be formatted. + * @return Fromatted url. */ formatURL(url: string): string { url = url.trim(); @@ -171,9 +236,9 @@ export class CoreUrlUtilsProvider { /** * Returns the URL to the documentation of the app, based on Moodle version and current language. * - * @param {string} [release] Moodle release. - * @param {string} [page=Mobile_app] Docs page to go to. - * @return {Promise} Promise resolved with the Moodle docs URL. + * @param release Moodle release. + * @param page Docs page to go to. + * @return Promise resolved with the Moodle docs URL. */ getDocsUrl(release?: string, page: string = 'Mobile_app'): Promise { let docsUrl = 'https://docs.moodle.org/en/' + page; @@ -194,13 +259,65 @@ export class CoreUrlUtilsProvider { }); } + /** + * Returns the Youtube Embed Video URL or null if not found. + * + * @param url URL + * @return Youtube Embed Video URL or null if not found. + */ + getYoutubeEmbedUrl(url: string): string { + if (!url) { + return; + } + + let videoId; + const params: any = {}; + + url = this.textUtils.decodeHTML(url); + + // Get the video ID. + let match = url.match(/^.*(youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=|\&v=)([^#\&\?]*).*/); + + if (match && match[2].length === 11) { + videoId = match[2]; + } + + // No videoId, do not continue. + if (!videoId) { + return; + } + + // Now get the playlist (if any). + match = url.match(/[?&]list=([^#\&\?]+)/); + + if (match && match[1]) { + params.list = match[1]; + } + + // Now get the start time (if any). + match = url.match(/[?&]start=(\d+)/); + + if (match && match[1]) { + params.start = parseInt(match[1], 10); + } else { + // No start param, but it could have a time param. + match = url.match(/[?&]t=(\d+h)?(\d+m)?(\d+s)?/); + if (match) { + params.start = (match[1] ? parseInt(match[1], 10) * 3600 : 0) + (match[2] ? parseInt(match[2], 10) * 60 : 0) + + (match[3] ? parseInt(match[3], 10) : 0); + } + } + + return this.addParamsToUrl('https://www.youtube.com/embed/' + videoId, params); + } + /** * Given a URL, returns what's after the last '/' without params. * Example: * http://mysite.com/a/course.html?id=1 -> course.html * - * @param {string} url URL to treat. - * @return {string} Last file without params. + * @param url URL to treat. + * @return Last file without params. */ getLastFileWithoutParams(url: string): string { let filename = url.substr(url.lastIndexOf('/') + 1); @@ -215,8 +332,8 @@ export class CoreUrlUtilsProvider { * Get the protocol from a URL. * E.g. http://www.google.com returns 'http'. * - * @param {string} url URL to treat. - * @return {string} Protocol, undefined if no protocol found. + * @param url URL to treat. + * @return Protocol, undefined if no protocol found. */ getUrlProtocol(url: string): string { if (!url) { @@ -233,8 +350,8 @@ export class CoreUrlUtilsProvider { * Get the scheme from a URL. Please notice that, if a URL has protocol, it will return the protocol. * E.g. javascript:doSomething() returns 'javascript'. * - * @param {string} url URL to treat. - * @return {string} Scheme, undefined if no scheme found. + * @param url URL to treat. + * @return Scheme, undefined if no scheme found. */ getUrlScheme(url: string): string { if (!url) { @@ -250,8 +367,8 @@ export class CoreUrlUtilsProvider { /* * Gets a username from a URL like: user@mysite.com. * - * @param {string} url URL to treat. - * @return {string} Username. Undefined if no username found. + * @param url URL to treat. + * @return Username. Undefined if no username found. */ getUsernameFromUrl(url: string): string { if (url.indexOf('@') > -1) { @@ -269,8 +386,8 @@ export class CoreUrlUtilsProvider { /** * Returns if a URL has any protocol (not a relative URL). * - * @param {string} url The url to test against the pattern. - * @return {boolean} Whether the url is absolute. + * @param url The url to test against the pattern. + * @return Whether the url is absolute. */ isAbsoluteURL(url: string): boolean { return /^[^:]{2,}:\/\//i.test(url) || /^(tel:|mailto:|geo:)/.test(url); @@ -279,8 +396,8 @@ export class CoreUrlUtilsProvider { /** * Returns if a URL is downloadable: plugin file OR theme/image.php OR gravatar. * - * @param {string} url The URL to test. - * @return {boolean} Whether the URL is downloadable. + * @param url The URL to test. + * @return Whether the URL is downloadable. */ isDownloadableUrl(url: string): boolean { return this.isPluginFileUrl(url) || this.isThemeImageUrl(url) || this.isGravatarUrl(url); @@ -289,8 +406,8 @@ export class CoreUrlUtilsProvider { /** * Returns if a URL is a gravatar URL. * - * @param {string} url The URL to test. - * @return {boolean} Whether the URL is a gravatar URL. + * @param url The URL to test. + * @return Whether the URL is a gravatar URL. */ isGravatarUrl(url: string): boolean { return url && url.indexOf('gravatar.com/avatar') !== -1; @@ -299,8 +416,8 @@ export class CoreUrlUtilsProvider { /** * Check if a URL uses http or https protocol. * - * @param {string} url The url to test. - * @return {boolean} Whether the url uses http or https protocol. + * @param url The url to test. + * @return Whether the url uses http or https protocol. */ isHttpURL(url: string): boolean { return /^https?\:\/\/.+/i.test(url); @@ -309,8 +426,8 @@ export class CoreUrlUtilsProvider { /** * Returns if a URL is a pluginfile URL. * - * @param {string} url The URL to test. - * @return {boolean} Whether the URL is a pluginfile URL. + * @param url The URL to test. + * @return Whether the URL is a pluginfile URL. */ isPluginFileUrl(url: string): boolean { return url && url.indexOf('/pluginfile.php') !== -1; @@ -319,8 +436,8 @@ export class CoreUrlUtilsProvider { /** * Returns if a URL is a theme image URL. * - * @param {string} url The URL to test. - * @return {boolean} Whether the URL is a theme image URL. + * @param url The URL to test. + * @return Whether the URL is a theme image URL. */ isThemeImageUrl(url: string): boolean { return url && url.indexOf('/theme/image.php') !== -1; @@ -329,8 +446,8 @@ export class CoreUrlUtilsProvider { /** * Remove protocol and www from a URL. * - * @param {string} url URL to treat. - * @return {string} Treated URL. + * @param url URL to treat. + * @return Treated URL. */ removeProtocolAndWWW(url: string): string { // Remove protocol. @@ -344,8 +461,8 @@ export class CoreUrlUtilsProvider { /** * Remove the parameters from a URL, returning the URL without them. * - * @param {string} url URL to treat. - * @return {string} URL without params. + * @param url URL to treat. + * @return URL without params. */ removeUrlParams(url: string): string { const matches = url.match(/^[^\?]+/); diff --git a/src/providers/utils/utils.ts b/src/providers/utils/utils.ts index bc73ce6ca..624371ac1 100644 --- a/src/providers/utils/utils.ts +++ b/src/providers/utils/utils.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -34,21 +34,20 @@ import { CoreWSProvider, CoreWSError } from '../ws'; export interface PromiseDefer { /** * The promise. - * @type {Promise} */ promise?: Promise; /** * Function to resolve the promise. * - * @param {any} [value] The resolve value. + * @param value The resolve value. */ resolve?: (value?: any) => void; // Function to resolve the promise. /** * Function to reject the promise. * - * @param {any} [reason] The reject param. + * @param reason The reject param. */ reject?: (reason?: any) => void; } @@ -74,9 +73,9 @@ export class CoreUtilsProvider { /** * Given an error, add an extra warning to the error message and return the new error message. * - * @param {any} error Error object or message. - * @param {any} [defaultError] Message to show if the error is not a string. - * @return {string} New error message. + * @param error Error object or message. + * @param defaultError Message to show if the error is not a string. + * @return New error message. */ addDataNotDownloadedError(error: any, defaultError?: string): string { let errorMessage = error; @@ -100,8 +99,8 @@ export class CoreUtilsProvider { /** * Similar to Promise.all, but if a promise fails this function's promise won't be rejected until ALL promises have finished. * - * @param {Promise[]} promises Promises. - * @return {Promise} Promise resolved if all promises are resolved and rejected if at least 1 promise fails. + * @param promises Promises. + * @return Promise resolved if all promises are resolved and rejected if at least 1 promise fails. */ allPromises(promises: Promise[]): Promise { if (!promises || !promises.length) { @@ -136,17 +135,19 @@ export class CoreUtilsProvider { /** * Converts an array of objects to an object, using a property of each entry as the key. + * It can also be used to convert an array of strings to an object where the keys are the elements of the array. * E.g. [{id: 10, name: 'A'}, {id: 11, name: 'B'}] => {10: {id: 10, name: 'A'}, 11: {id: 11, name: 'B'}} * - * @param {any[]} array The array to convert. - * @param {string} propertyName The name of the property to use as the key. - * @param {any} [result] Object where to put the properties. If not defined, a new object will be created. - * @return {any} The object. + * @param array The array to convert. + * @param propertyName The name of the property to use as the key. If not provided, the whole item will be used. + * @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: any[], propertyName?: string, result?: any): any { result = result || {}; array.forEach((entry) => { - result[entry[propertyName]] = entry; + const key = propertyName ? entry[propertyName] : entry; + result[key] = entry; }); return result; @@ -157,12 +158,12 @@ export class CoreUtilsProvider { * Also, this will only check if itemA's properties are in itemB with same value. This function will still * return true if itemB has more properties than itemA. * - * @param {any} itemA First object. - * @param {any} itemB Second object. - * @param {number} [maxLevels=0] Number of levels to reach if 2 objects are compared. - * @param {number} [level=0] Current deep level (when comparing objects). - * @param {boolean} [undefinedIsNull=true] True if undefined is equal to null. Defaults to true. - * @return {boolean} Whether both items are equal. + * @param itemA First object. + * @param itemB Second object. + * @param maxLevels Number of levels to reach if 2 objects are compared. + * @param level Current deep level (when comparing objects). + * @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 { if (typeof itemA == 'function' || typeof itemB == 'function') { @@ -215,8 +216,8 @@ export class CoreUtilsProvider { /** * Check if a URL has a redirect. * - * @param {string} url The URL to check. - * @return {Promise} Promise resolved with boolean_ whether there is a redirect. + * @param url The URL to check. + * @return Promise resolved with boolean_ whether there is a redirect. */ checkRedirect(url: string): Promise { if (window.fetch) { @@ -253,7 +254,7 @@ export class CoreUtilsProvider { /** * Close the InAppBrowser window. * - * @param {boolean} [closeAll] Desktop only. True to close all secondary windows, false to close only the "current" one. + * @param closeAll Desktop only. True to close all secondary windows, false to close only the "current" one. */ closeInAppBrowser(closeAll?: boolean): void { if (this.iabInstance) { @@ -267,9 +268,9 @@ export class CoreUtilsProvider { /** * Clone a variable. It should be an object, array or primitive type. * - * @param {any} source The variable to clone. - * @param {number} [level=0] Depth we are right now inside a cloned object. It's used to prevent reaching max call stack size. - * @return {any} Cloned variable. + * @param source The variable to clone. + * @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 { if (level >= 20) { @@ -310,9 +311,9 @@ export class CoreUtilsProvider { /** * Copy properties from one object to another. * - * @param {any} from Object to copy the properties from. - * @param {any} to Object where to store the properties. - * @param {boolean} [clone=true] Whether the properties should be cloned (so they are different instances). + * @param from Object to copy the properties from. + * @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 { for (const name in from) { @@ -327,8 +328,8 @@ export class CoreUtilsProvider { /** * Copies a text to clipboard and shows a toast message. * - * @param {string} text Text to be copied - * @return {Promise} Promise resolved when text is copied. + * @param text Text to be copied + * @return Promise resolved when text is copied. */ copyToClipboard(text: string): Promise { return this.clipboard.copy(text).then(() => { @@ -342,9 +343,9 @@ export class CoreUtilsProvider { /** * Create a "fake" WS error for local errors. * - * @param {string} message The message to include in the error. - * @param {boolean} [needsTranslate] If the message needs to be translated. - * @return {CoreWSError} Fake WS error. + * @param message The message to include in the error. + * @param needsTranslate If the message needs to be translated. + * @return Fake WS error. */ createFakeWSError(message: string, needsTranslate?: boolean): CoreWSError { return this.wsProvider.createFakeWSError(message, needsTranslate); @@ -353,7 +354,7 @@ export class CoreUtilsProvider { /** * Empties an array without losing its reference. * - * @param {any[]} array Array to empty. + * @param array Array to empty. */ emptyArray(array: any[]): void { array.length = 0; // Empty array without losing its reference. @@ -362,7 +363,7 @@ export class CoreUtilsProvider { /** * Removes all properties from an object without losing its reference. * - * @param {object} object Object to remove the properties. + * @param object Object to remove the properties. */ emptyObject(object: object): void { for (const key in object) { @@ -375,12 +376,12 @@ export class CoreUtilsProvider { /** * Execute promises one depending on the previous. * - * @param {any[]} orderedPromisesData Data to be executed including the following values: - * - func: Function to be executed. - * - context: Context to pass to the function. This allows using "this" inside the function. - * - params: Array of data to be sent to the function. - * - blocking: Boolean. If promise should block the following. - * @return {Promise} Promise resolved when all promises are resolved. + * @param orderedPromisesData Data to be executed including the following values: + * - func: Function to be executed. + * - context: Context to pass to the function. This allows using "this" inside the function. + * - params: Array of data to be sent to the function. + * - blocking: Boolean. If promise should block the following. + * @return Promise resolved when all promises are resolved. */ executeOrderedPromises(orderedPromisesData: any[]): Promise { const promises = []; @@ -422,9 +423,9 @@ export class CoreUtilsProvider { * It supports 2 notations: dot notation and square brackets. * E.g.: {a: {b: 1, c: 2}, d: 3} -> {'a.b': 1, 'a.c': 2, d: 3} * - * @param {object} obj Object to flatten. - * @param {boolean} [useDotNotation] Whether to use dot notation '.' or square brackets '['. - * @return {object} Flattened object. + * @param obj Object to flatten. + * @param useDotNotation Whether to use dot notation '.' or square brackets '['. + * @return Flattened object. */ flattenObject(obj: object, useDotNotation?: boolean): object { const toReturn = {}; @@ -456,9 +457,9 @@ export class CoreUtilsProvider { /** * Given an array of strings, return only the ones that match a regular expression. * - * @param {string[]} array Array to filter. - * @param {RegExp} regex RegExp to apply to each string. - * @return {string[]} Filtered array. + * @param array Array to filter. + * @param regex RegExp to apply to each string. + * @return Filtered array. */ filterByRegexp(array: string[], regex: RegExp): string[] { if (!array || !array.length) { @@ -475,13 +476,13 @@ export class CoreUtilsProvider { /** * Filter the list of site IDs based on a isEnabled function. * - * @param {string[]} siteIds Site IDs to filter. - * @param {Function} isEnabledFn Function to call for each site. Must return true or a promise resolved with true if enabled. + * @param siteIds Site IDs to filter. + * @param isEnabledFn Function to call for each site. Must return true or a promise resolved with true if enabled. * It receives a siteId param and all the params sent to this function after 'checkAll'. - * @param {boolean} [checkAll] True if it should check all the sites, false if it should check only 1 and treat them all - * depending on this result. - * @param {any} ...args All the params sent after checkAll will be passed to isEnabledFn. - * @return {Promise} Promise resolved with the list of enabled sites. + * @param checkAll True if it should check all the sites, false if it should check only 1 and treat them all + * depending on this result. + * @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: Function, checkAll?: boolean, ...args: any[]): Promise { const promises = [], @@ -514,8 +515,8 @@ export class CoreUtilsProvider { * Given a float, prints it nicely. Localized floats must not be used in calculations! * Based on Moodle's format_float. * - * @param {any} float The float to print. - * @return {string} Locale float. + * @param float The float to print. + * @return Locale float. */ formatFloat(float: any): string { if (typeof float == 'undefined' || float === null || typeof float == 'boolean') { @@ -535,12 +536,12 @@ export class CoreUtilsProvider { * List has to be sorted by depth to allow this function to work correctly. Errors can be thrown if a child node is * processed before a parent node. * - * @param {any[]} list List to format. - * @param {string} [parentFieldName=parent] Name of the parent field to match with children. - * @param {string} [idFieldName=id] Name of the children field to match with parent. - * @param {number} [rootParentId=0] The id of the root. - * @param {number} [maxDepth=5] Max Depth to convert to tree. Children found will be in the last level of depth. - * @return {any[]} Array with the formatted tree, children will be on each node under children field. + * @param list List to format. + * @param parentFieldName Name of the parent field to match with children. + * @param idFieldName Name of the children field to match with parent. + * @param rootParentId The id of the root. + * @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[] { @@ -592,8 +593,8 @@ export class CoreUtilsProvider { /** * Get country name based on country code. * - * @param {string} code Country code (AF, ES, US, ...). - * @return {string} Country name. If the country is not found, return the country code. + * @param code Country code (AF, ES, US, ...). + * @return Country name. If the country is not found, return the country code. */ getCountryName(code: string): string { const countryKey = 'assets.countries.' + code, @@ -605,7 +606,7 @@ export class CoreUtilsProvider { /** * Get list of countries with their code and translated name. * - * @return {Promise} Promise resolved with the list of countries. + * @return Promise resolved with the list of countries. */ getCountryList(): Promise { // Get the keys of the countries. @@ -624,10 +625,29 @@ export class CoreUtilsProvider { }); } + /** + * Get list of countries with their code and translated name. Sorted by the name of the country. + * + * @return Promise resolved with the list of countries. + */ + getCountryListSorted(): Promise { + // Get the keys of the countries. + return this.getCountryList().then((countries) => { + // Sort translations. + const sortedCountries = []; + + Object.keys(countries).sort((a, b) => countries[a].localeCompare(countries[b])).forEach((key) => { + sortedCountries.push({code: key, name: countries[key]}); + }); + + return sortedCountries; + }); + } + /** * Get the list of language keys of the countries. * - * @return {Promise} Promise resolved with the countries list. Rejected if not translated. + * @return Promise resolved with the countries list. Rejected if not translated. */ protected getCountryKeysList(): Promise { // It's possible that the current language isn't translated, so try with default language first. @@ -649,8 +669,8 @@ export class CoreUtilsProvider { /** * Get the list of language keys of the countries, based on the translation table for a certain language. * - * @param {string} lang Language to check. - * @return {Promise} Promise resolved with the countries list. Rejected if not translated. + * @param lang Language to check. + * @return Promise resolved with the countries list. Rejected if not translated. */ protected getCountryKeysListForLanguage(lang: string): Promise { // Get the translation table for the language. @@ -678,8 +698,8 @@ export class CoreUtilsProvider { * perform a HEAD request to get it. It's done in this order because pluginfile.php can return wrong mimetypes. * This function is in here instead of MimetypeUtils to prevent circular dependencies. * - * @param {string} url The URL of the file. - * @return {Promise} Promise resolved with the mimetype. + * @param url The URL of the file. + * @return Promise resolved with the mimetype. */ getMimeTypeFromUrl(url: string): Promise { // First check if it can be guessed from the URL. @@ -699,8 +719,8 @@ export class CoreUtilsProvider { /** * Get a unique ID for a certain name. * - * @param {string} name The name to get the ID for. - * @return {number} Unique ID. + * @param name The name to get the ID for. + * @return Unique ID. */ getUniqueId(name: string): number { if (!this.uniqueIds[name]) { @@ -713,8 +733,8 @@ export class CoreUtilsProvider { /** * Given a list of files, check if there are repeated names. * - * @param {any[]} files List of files. - * @return {string|boolean} String with error message if repeated, false if no repeated. + * @param files List of files. + * @return String with error message if repeated, false if no repeated. */ hasRepeatedFilenames(files: any[]): string | boolean { if (!files || !files.length) { @@ -739,9 +759,9 @@ export class CoreUtilsProvider { /** * Gets the index of the first string that matches a regular expression. * - * @param {string[]} array Array to search. - * @param {RegExp} regex RegExp to apply to each string. - * @return {number} Index of the first string that matches the RegExp. -1 if not found. + * @param array Array to search. + * @param regex RegExp to apply to each string. + * @return Index of the first string that matches the RegExp. -1 if not found. */ indexOfRegexp(array: string[], regex: RegExp): number { if (!array || !array.length) { @@ -763,8 +783,8 @@ export class CoreUtilsProvider { /** * Return true if the param is false (bool), 0 (number) or "0" (string). * - * @param {any} value Value to check. - * @return {boolean} Whether the value is false, 0 or "0". + * @param value Value to check. + * @return Whether the value is false, 0 or "0". */ isFalseOrZero(value: any): boolean { return typeof value != 'undefined' && (value === false || value === 'false' || parseInt(value, 10) === 0); @@ -773,8 +793,8 @@ export class CoreUtilsProvider { /** * Return true if the param is true (bool), 1 (number) or "1" (string). * - * @param {any} value Value to check. - * @return {boolean} Whether the value is true, 1 or "1". + * @param value Value to check. + * @return Whether the value is true, 1 or "1". */ isTrueOrOne(value: any): boolean { return typeof value != 'undefined' && (value === true || value === 'true' || parseInt(value, 10) === 1); @@ -783,8 +803,8 @@ export class CoreUtilsProvider { /** * Given an error returned by a WS call, check if the error is generated by the app or it has been returned by the WebSwervice. * - * @param {any} error Error to check. - * @return {boolean} Whether the error was returned by the WebService. + * @param error Error to check. + * @return Whether the error was returned by the WebService. */ isWebServiceError(error: any): boolean { return error && (typeof error.warningcode != 'undefined' || (typeof error.errorcode != 'undefined' && @@ -798,11 +818,11 @@ export class CoreUtilsProvider { * Given a list (e.g. a,b,c,d,e) this function returns an array of 1->a, 2->b, 3->c etc. * Taken from make_menu_from_list on moodlelib.php (not the same but similar). * - * @param {string} list The string to explode into array bits - * @param {string} [defaultLabel] Element that will become default option, if not defined, it won't be added. - * @param {string} [separator] The separator used within the list string. Default ','. - * @param {any} [defaultValue] Element that will become default option value. Default 0. - * @return {any[]} The now assembled array + * @param list The string to explode into array bits + * @param defaultLabel Element that will become default option, if not defined, it won't be added. + * @param separator The separator used within the list string. Default ','. + * @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[] { // Split and format the list. @@ -826,10 +846,10 @@ export class CoreUtilsProvider { /** * Merge two arrays, removing duplicate values. * - * @param {any[]} array1 The first array. - * @param {any[]} array2 The second array. + * @param array1 The first array. + * @param array2 The second array. * @param [key] Key of the property that must be unique. If not specified, the whole entry. - * @return {any[]} Merged array. + * @return Merged array. */ mergeArraysWithoutDuplicates(array1: any[], array2: any[], key?: string): any[] { return this.uniqueArray(array1.concat(array2), key); @@ -838,8 +858,8 @@ export class CoreUtilsProvider { /** * Open a file using platform specific method. * - * @param {string} path The local path of the file to be open. - * @return {Promise} Promise resolved when done. + * @param path The local path of the file to be open. + * @return Promise resolved when done. */ openFile(path: string): Promise { const extension = this.mimetypeUtils.getFileExtension(path), @@ -871,9 +891,9 @@ export class CoreUtilsProvider { * Open a URL using InAppBrowser. * Do not use for files, refer to {@link openFile}. * - * @param {string} url The URL to open. - * @param {any} [options] Override default options passed to InAppBrowser. - * @return {InAppBrowserObject} The opened window. + * @param url The URL to open. + * @param options Override default options passed to InAppBrowser. + * @return The opened window. */ openInApp(url: string, options?: any): InAppBrowserObject { if (!url) { @@ -886,6 +906,10 @@ export class CoreUtilsProvider { options.enableViewPortScale = 'yes'; // Enable zoom on iOS. } + if (!options.allowInlineMediaPlayback) { + options.allowInlineMediaPlayback = 'yes'; // Allow playing inline videos in iOS. + } + if (!options.location && this.platform.is('ios') && url.indexOf('file://') === 0) { // The URL uses file protocol, don't show it on iOS. // In Android we keep it because otherwise we lose the whole toolbar. @@ -943,7 +967,7 @@ export class CoreUtilsProvider { * Open a URL using a browser. * Do not use for files, refer to {@link openFile}. * - * @param {string} url The URL to open. + * @param url The URL to open. */ openInBrowser(url: string): void { if (this.appProvider.isDesktop()) { @@ -962,8 +986,8 @@ export class CoreUtilsProvider { * Open an online file using platform specific method. * Specially useful for audio and video since they can be streamed. * - * @param {string} url The URL of the file. - * @return {Promise} Promise resolved when opened. + * @param url The URL of the file. + * @return Promise resolved when opened. */ openOnlineFile(url: string): Promise { if (this.platform.is('android')) { @@ -1000,8 +1024,8 @@ export class CoreUtilsProvider { /** * Converts an object into an array, losing the keys. * - * @param {object} obj Object to convert. - * @return {any[]} Array with the values of the object but losing the keys. + * @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) => { @@ -1014,12 +1038,12 @@ export class CoreUtilsProvider { * the key and value of the original object. * For example, it can convert {size: 2} into [{name: 'size', value: 2}]. * - * @param {object} obj Object to convert. - * @param {string} keyName Name of the properties where to store the keys. - * @param {string} valueName Name of the properties where to store the values. - * @param {boolean} [sortByKey] True to sort keys alphabetically, false otherwise. Has priority over sortByValue. - * @param {boolean} [sortByValue] True to sort values alphabetically, false otherwise. - * @return {any[]} Array of objects with the name & value of each property. + * @param obj Object to convert. + * @param keyName Name of the properties where to store the keys. + * @param valueName Name of the properties where to store the values. + * @param sortByKey True to sort keys alphabetically, false otherwise. Has priority over sortByValue. + * @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[] { // Get the entries from an object or primitive value. @@ -1075,11 +1099,11 @@ export class CoreUtilsProvider { * Converts an array of objects into an object with key and value. The opposite of objectToArrayOfObjects. * For example, it can convert [{name: 'size', value: 2}] into {size: 2}. * - * @param {object[]} objects List of objects to convert. - * @param {string} keyName Name of the properties where the keys are stored. - * @param {string} valueName Name of the properties where the values are stored. - * @param {string} [keyPrefix] Key prefix if neededs to delete it. - * @return {object} Object. + * @param objects List of objects to convert. + * @param keyName Name of the properties where the keys are stored. + * @param valueName Name of the properties where the values are stored. + * @param keyPrefix Key prefix if neededs to delete it. + * @return Object. */ objectToKeyValueMap(objects: object[], keyName: string, valueName: string, keyPrefix?: string): object { if (!objects) { @@ -1099,9 +1123,9 @@ export class CoreUtilsProvider { /** * Convert an object to a format of GET param. E.g.: {a: 1, b: 2} -> a=1&b=2 * - * @param {any} object Object to convert. - * @param {boolean} [removeEmpty=true] Whether to remove params whose value is null/undefined. - * @return {string} GET params. + * @param object Object to convert. + * @param removeEmpty Whether to remove params whose value is null/undefined. + * @return GET params. */ objectToGetParams(object: any, removeEmpty: boolean = true): string { // First of all, flatten the object so all properties are in the first level. @@ -1130,9 +1154,9 @@ export class CoreUtilsProvider { /** * Add a prefix to all the keys in an object. * - * @param {any} data Object. - * @param {string} prefix Prefix to add. - * @return {any} Prefixed object. + * @param data Object. + * @param prefix Prefix to add. + * @return Prefixed object. */ prefixKeys(data: any, prefix: string): any { const newObj = {}, @@ -1148,7 +1172,7 @@ export class CoreUtilsProvider { /** * Similar to AngularJS $q.defer(). * - * @return {PromiseDefer} The deferred promise. + * @return The deferred promise. */ promiseDefer(): PromiseDefer { const deferred: PromiseDefer = {}; @@ -1163,8 +1187,8 @@ export class CoreUtilsProvider { /** * Given a promise, returns true if it's rejected or false if it's resolved. * - * @param {Promise} promise Promise to check - * @return {Promise} Promise resolved with boolean: true if the promise is rejected or false if it's resolved. + * @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): Promise { return promise.then(() => { @@ -1177,8 +1201,8 @@ export class CoreUtilsProvider { /** * Given a promise, returns true if it's resolved or false if it's rejected. * - * @param {Promise} promise Promise to check - * @return {Promise} Promise resolved with boolean: true if the promise it's resolved or false if it's rejected. + * @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): Promise { return promise.then(() => { @@ -1193,10 +1217,10 @@ export class CoreUtilsProvider { * Missing values are replaced by '', and the values are compared with ===. * Booleans and numbers are cast to string before comparing. * - * @param {any} obj1 The first object or array. - * @param {any} obj2 The second object or array. - * @param {string} key Key to check. - * @return {boolean} Whether the two objects/arrays have the same value (or lack of one) for a given key. + * @param obj1 The first object or array. + * @param obj2 The second object or array. + * @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 { let value1 = typeof obj1[key] != 'undefined' ? obj1[key] : '', @@ -1216,8 +1240,8 @@ export class CoreUtilsProvider { * Stringify an object, sorting the properties. It doesn't sort arrays, only object properties. E.g.: * {b: 2, a: 1} -> '{"a":1,"b":2}' * - * @param {object} obj Object to stringify. - * @return {string} Stringified object. + * @param obj Object to stringify. + * @return Stringified object. */ sortAndStringify(obj: object): string { return JSON.stringify(this.sortProperties(obj)); @@ -1226,11 +1250,11 @@ export class CoreUtilsProvider { /** * Given an object, sort its properties and the properties of all the nested objects. * - * @param {object} obj The object to sort. If it isn't an object, the original value will be returned. - * @return {object} Sorted object. + * @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 { - if (typeof obj == 'object' && !Array.isArray(obj)) { + if (obj != null && typeof obj == 'object' && !Array.isArray(obj)) { // It's an object, sort it. return Object.keys(obj).sort().reduce((accumulator, key) => { // Always call sort with the value. If it isn't an object, the original value will be returned. @@ -1246,8 +1270,8 @@ export class CoreUtilsProvider { /** * Given an object, sort its values. Values need to be primitive values, it cannot have subobjects. * - * @param {object} obj The object to sort. If it isn't an object, the original value will be returned. - * @return {object} Sorted object. + * @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 { if (typeof obj == 'object' && !Array.isArray(obj)) { @@ -1263,8 +1287,9 @@ export class CoreUtilsProvider { /** * Sum the filesizes from a list of files checking if the size will be partial or totally calculated. * - * @param {any[]} files List of files to sum its filesize. - * @return {{size: number, total: boolean}} File size and a boolean to indicate if it is the total size or only partial. + * @param files List of files to sum its filesize. + * @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 } { const result = { @@ -1288,9 +1313,9 @@ export class CoreUtilsProvider { * Set a timeout to a Promise. If the time passes before the Promise is resolved or rejected, it will be automatically * rejected. * - * @param {Promise} promise The promise to timeout. - * @param {number} time Number of milliseconds of the timeout. - * @return {Promise} Promise with the timeout. + * @param promise The promise to timeout. + * @param time Number of milliseconds of the timeout. + * @return Promise with the timeout. */ timeoutPromise(promise: Promise, time: number): Promise { return new Promise((resolve, reject): void => { @@ -1309,9 +1334,9 @@ export class CoreUtilsProvider { * Do NOT try to do any math operations before this conversion on any user submitted floats! * Based on Moodle's unformat_float function. * - * @param {any} localeFloat Locale aware float representation. - * @param {boolean} [strict] If true, then check the input and return false if it is not a valid number. - * @return {any} False if bad format, empty string if empty value or the parsed float if not. + * @param localeFloat Locale aware float representation. + * @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 { // Bad format on input type number. @@ -1348,9 +1373,9 @@ export class CoreUtilsProvider { /** * Return an array without duplicate values. * - * @param {any[]} array The array to treat. + * @param array The array to treat. * @param [key] Key of the property that must be unique. If not specified, the whole entry. - * @return {any[]} Array without duplicate values. + * @return Array without duplicate values. */ uniqueArray(array: any[], key?: string): any[] { const filtered = [], diff --git a/src/providers/ws.ts b/src/providers/ws.ts index 636b00e0b..cf8aa1ef8 100644 --- a/src/providers/ws.ts +++ b/src/providers/ws.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (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. @@ -32,31 +32,26 @@ import { CoreInterceptor } from '@classes/interceptor'; export interface CoreWSPreSets { /** * The site URL. - * @type {string} */ siteUrl: string; /** * The Webservice token. - * @type {string} */ wsToken: string; /** * Defaults to true. Set to false when the expected response is null. - * @type {boolean} */ responseExpected?: boolean; /** * Defaults to 'object'. Use it when you expect a type that's not an object|array. - * @type {string} */ typeExpected?: string; /** * Defaults to false. Clean multibyte Unicode chars from data. - * @type {string} */ cleanUnicode?: boolean; } @@ -67,69 +62,25 @@ export interface CoreWSPreSets { export interface CoreWSAjaxPreSets { /** * The site URL. - * @type {string} */ siteUrl: string; /** * Defaults to true. Set to false when the expected response is null. - * @type {boolean} */ responseExpected?: boolean; /** * Whether to use the no-login endpoint instead of the normal one. Use it for requests that don't require authentication. - * @type {boolean} */ noLogin?: boolean; /** * Whether to send the parameters via GET. Only if noLogin is true. - * @type {boolean} */ useGet?: boolean; } -/** - * Error returned by a WS call. - */ -export interface CoreWSError { - /** - * The error message. - * @type {string} - */ - message: string; - - /** - * Name of the exception. Undefined for local errors (fake WS errors). - * @type {string} - */ - exception?: string; - - /** - * The error code. Undefined for local errors (fake WS errors). - * @type {string} - */ - errorcode?: string; -} - -/** - * File upload options. - */ -export interface CoreWSFileUploadOptions extends FileUploadOptions { - /** - * The file area where to put the file. By default, 'draft'. - * @type {string} - */ - fileArea?: string; - - /** - * Item ID of the area where to put the file. By default, 0. - * @type {number} - */ - itemId?: number; -} - /** * This service allows performing WS calls and download/upload files. */ @@ -151,12 +102,12 @@ export class CoreWSProvider { /** * Adds the call data to an special queue to be processed when retrying. * - * @param {string} method The WebService method to be called. - * @param {string} siteUrl Complete site url to perform the call. - * @param {any} ajaxData Arguments to pass to the method. - * @param {CoreWSPreSets} preSets Extra settings and information. - * @return {Promise} Deferred promise resolved with the response data in success and rejected with the error message - * if it fails. + * @param method The WebService method to be called. + * @param siteUrl Complete site url to perform the call. + * @param ajaxData Arguments to pass to the method. + * @param preSets Extra settings and information. + * @return Deferred promise resolved with the response data in success and rejected with the error message + * if it fails. */ protected addToRetryQueue(method: string, siteUrl: string, ajaxData: any, preSets: CoreWSPreSets): Promise { const call: any = { @@ -180,10 +131,10 @@ export class CoreWSProvider { /** * A wrapper function for a moodle WebService call. * - * @param {string} method The WebService method to be called. - * @param {any} data Arguments to pass to the method. It's recommended to call convertValuesToString before passing the data. - * @param {CoreWSPreSets} preSets Extra settings and information. - * @return {Promise} Promise resolved with the response data in success and rejected if it fails. + * @param method The WebService method to be called. + * @param data Arguments to pass to the method. It's recommended to call convertValuesToString before passing the data. + * @param preSets Extra settings and information. + * @return Promise resolved with the response data in success and rejected if it fails. */ call(method: string, data: any, preSets: CoreWSPreSets): Promise { @@ -218,13 +169,13 @@ export class CoreWSProvider { /** * Call a Moodle WS using the AJAX API. Please use it if the WS layer is not an option. * - * @param {string} method The WebService method to be called. - * @param {any} data Arguments to pass to the method. - * @param {CoreWSAjaxPreSets} preSets Extra settings and information. Only some - * @return {Promise} Promise resolved with the response data in success and rejected with an object containing: - * - error: Error message. - * - errorcode: Error code returned by the site (if any). - * - available: 0 if unknown, 1 if available, -1 if not available. + * @param method The WebService method to be called. + * @param data Arguments to pass to the method. + * @param preSets Extra settings and information. Only some + * @return Promise resolved with the response data in success and rejected with an object containing: + * - error: Error message. + * - errorcode: Error code returned by the site (if any). + * - available: 0 if unknown, 1 if available, -1 if not available. */ callAjax(method: string, data: any, preSets: CoreWSAjaxPreSets): Promise { let promise; @@ -306,9 +257,9 @@ export class CoreWSProvider { * Converts an objects values to strings where appropriate. * Arrays (associative or otherwise) will be maintained, null values will be removed. * - * @param {object} data The data that needs all the non-object values set to strings. - * @param {boolean} [stripUnicode] If Unicode long chars need to be stripped. - * @return {object} The cleaned object or null if some strings becomes empty after stripping Unicode. + * @param data The data that needs all the non-object values set to strings. + * @param stripUnicode If Unicode long chars need to be stripped. + * @return The cleaned object or null if some strings becomes empty after stripping Unicode. */ convertValuesToString(data: any, stripUnicode?: boolean): any { const result: any = Array.isArray(data) ? [] : {}; @@ -362,13 +313,14 @@ export class CoreWSProvider { /** * Create a "fake" WS error for local errors. * - * @param {string} message The message to include in the error. - * @param {boolean} [needsTranslate] If the message needs to be translated. - * @return {CoreWSError} Fake WS error. + * @param message The message to include in the error. + * @param needsTranslate If the message needs to be translated. + * @param translateParams Translation params, if needed. + * @return Fake WS error. */ - createFakeWSError(message: string, needsTranslate?: boolean): CoreWSError { + createFakeWSError(message: string, needsTranslate?: boolean, translateParams?: {}): CoreWSError { if (needsTranslate) { - message = this.translate.instant(message); + message = this.translate.instant(message, translateParams); } return { @@ -379,11 +331,11 @@ export class CoreWSProvider { /** * Downloads a file from Moodle using Cordova File API. * - * @param {string} url Download url. - * @param {string} path Local path to store the file. - * @param {boolean} [addExtension] True if extension need to be added to the final path. - * @param {Function} [onProgress] Function to call on progress. - * @return {Promise} Promise resolved with the downloaded file. + * @param url Download url. + * @param path Local path to store the file. + * @param addExtension True if extension need to be added to the final path. + * @param onProgress Function to call on progress. + * @return Promise resolved with the downloaded file. */ downloadFile(url: string, path: string, addExtension?: boolean, onProgress?: (event: ProgressEvent) => any): Promise { this.logger.debug('Downloading file', url, path, addExtension); @@ -455,9 +407,9 @@ export class CoreWSProvider { /** * Get a promise from the cache. * - * @param {string} method Method of the HTTP request. - * @param {string} url Base URL of the HTTP request. - * @param {any} [params] Params of the HTTP request. + * @param method Method of the HTTP request. + * @param url Base URL of the HTTP request. + * @param params Params of the HTTP request. */ protected getPromiseHttp(method: string, url: string, params?: any): any { const queueItemId = this.getQueueItemId(method, url, params); @@ -471,9 +423,9 @@ export class CoreWSProvider { /** * Perform a HEAD request to get the mimetype of a remote file. * - * @param {string} url File URL. - * @param {boolean} [ignoreCache] True to ignore cache, false otherwise. - * @return {Promise} Promise resolved with the mimetype or '' if failure. + * @param url File URL. + * @param ignoreCache True to ignore cache, false otherwise. + * @return Promise resolved with the mimetype or '' if failure. */ getRemoteFileMimeType(url: string, ignoreCache?: boolean): Promise { if (this.mimeTypeCache[url] && !ignoreCache) { @@ -498,8 +450,8 @@ export class CoreWSProvider { /** * Perform a HEAD request to get the size of a remote file. * - * @param {string} url File URL. - * @return {Promise} Promise resolved with the size or -1 if failure. + * @param url File URL. + * @return Promise resolved with the size or -1 if failure. */ getRemoteFileSize(url: string): Promise { return this.performHead(url).then((data) => { @@ -519,7 +471,7 @@ export class CoreWSProvider { /** * Get a request timeout based on the network connection. * - * @return {number} Timeout in ms. + * @return Timeout in ms. */ getRequestTimeout(): number { return this.appProvider.isNetworkAccessLimited() ? CoreConstants.WS_TIMEOUT : CoreConstants.WS_TIMEOUT_WIFI; @@ -528,10 +480,10 @@ export class CoreWSProvider { /** * Get the unique queue item id of the cache for a HTTP request. * - * @param {string} method Method of the HTTP request. - * @param {string} url Base URL of the HTTP request. - * @param {object} [params] Params of the HTTP request. - * @return {string} Queue item ID. + * @param method Method of the HTTP request. + * @param url Base URL of the HTTP request. + * @param params Params of the HTTP request. + * @return Queue item ID. */ protected getQueueItemId(method: string, url: string, params?: any): string { if (params) { @@ -544,8 +496,8 @@ export class CoreWSProvider { /** * Perform a HEAD request and save the promise while waiting to be resolved. * - * @param {string} url URL to perform the request. - * @return {Promise} Promise resolved with the response. + * @param url URL to perform the request. + * @return Promise resolved with the response. */ performHead(url: string): Promise { let promise = this.getPromiseHttp('head', url); @@ -561,11 +513,11 @@ export class CoreWSProvider { /** * Perform the post call and save the promise while waiting to be resolved. * - * @param {string} method The WebService method to be called. - * @param {string} siteUrl Complete site url to perform the call. - * @param {any} ajaxData Arguments to pass to the method. - * @param {CoreWSPreSets} preSets Extra settings and information. - * @return {Promise} Promise resolved with the response data in success and rejected with CoreWSError if it fails. + * @param method The WebService method to be called. + * @param siteUrl Complete site url to perform the call. + * @param ajaxData Arguments to pass to the method. + * @param preSets Extra settings and information. + * @return Promise resolved with the response data in success and rejected with CoreWSError if it fails. */ performPost(method: string, siteUrl: string, ajaxData: any, preSets: CoreWSPreSets): Promise { const options = {}; @@ -687,11 +639,11 @@ export class CoreWSProvider { /** * Save promise on the cache. * - * @param {Promise} promise Promise to be saved. - * @param {string} method Method of the HTTP request. - * @param {string} url Base URL of the HTTP request. - * @param {any} [params] Params of the HTTP request. - * @return {Promise} The promise saved. + * @param promise Promise to be saved. + * @param method Method of the HTTP request. + * @param url Base URL of the HTTP request. + * @param params Params of the HTTP request. + * @return The promise saved. */ protected setPromiseHttp(promise: Promise, method: string, url: string, params?: any): Promise { const queueItemId = this.getQueueItemId(method, url, params); @@ -716,11 +668,11 @@ export class CoreWSProvider { * A wrapper function for a synchronous Moodle WebService call. * Warning: This function should only be used if synchronous is a must. It's recommended to use call. * - * @param {string} method The WebService method to be called. - * @param {any} data Arguments to pass to the method. - * @param {CoreWSPreSets} preSets Extra settings and information. - * @return {Promise} Promise resolved with the response data in success and rejected with the error message if it fails. - * @return {any} Request response. If the request fails, returns an object with 'error'=true and 'message' properties. + * @param method The WebService method to be called. + * @param data Arguments to pass to the method. + * @param preSets Extra settings and information. + * @return Promise resolved with the response data in success and rejected with the error message if it fails. + * @return Request response. If the request fails, returns an object with 'error'=true and 'message' properties. */ syncCall(method: string, data: any, preSets: CoreWSPreSets): any { const errorResponse = { @@ -809,11 +761,11 @@ export class CoreWSProvider { /* * Uploads a file. * - * @param {string} filePath File path. - * @param {CoreWSFileUploadOptions} options File upload options. - * @param {CoreWSPreSets} preSets Must contain siteUrl and wsToken. - * @param {Function} [onProgress] Function to call on progress. - * @return {Promise} Promise resolved when uploaded. + * @param filePath File path. + * @param options File upload options. + * @param preSets Must contain siteUrl and wsToken. + * @param onProgress Function to call on progress. + * @return Promise resolved when uploaded. */ uploadFile(filePath: string, options: CoreWSFileUploadOptions, preSets: CoreWSPreSets, onProgress?: (event: ProgressEvent) => any): Promise { @@ -877,3 +829,127 @@ export class CoreWSProvider { }); } } + +/** + * Error returned by a WS call. + */ +export interface CoreWSError { + /** + * The error message. + */ + message: string; + + /** + * Name of the exception. Undefined for local errors (fake WS errors). + */ + exception?: string; + + /** + * The error code. Undefined for local errors (fake WS errors). + */ + errorcode?: string; +} + +/** + * File upload options. + */ +export interface CoreWSFileUploadOptions extends FileUploadOptions { + /** + * The file area where to put the file. By default, 'draft'. + */ + fileArea?: string; + + /** + * Item ID of the area where to put the file. By default, 0. + */ + itemId?: number; +} + +/** + * Structure of warnings returned by WS. + */ +export type CoreWSExternalWarning = { + /** + * Item. + */ + item?: string; + + /** + * Item id. + */ + itemid?: number; + + /** + * The warning code can be used by the client app to implement specific behaviour. + */ + warningcode: string; + + /** + * Untranslated english message to explain the warning. + */ + message: string; + +}; + +/** + * Structure of files returned by WS. + */ +export type CoreWSExternalFile = { + /** + * File name. + */ + filename?: string; + + /** + * File path. + */ + filepath?: string; + + /** + * File size. + */ + filesize?: number; + + /** + * Downloadable file url. + */ + fileurl?: string; + + /** + * Time modified. + */ + timemodified?: number; + + /** + * File mime type. + */ + mimetype?: string; + + /** + * Whether is an external file. + */ + isexternalfile?: number; + + /** + * The repository type for external files. + */ + repositorytype?: string; + +}; + +/** + * Data returned by date_exporter. + */ +export type CoreWSDate = { + seconds: number; // Seconds. + minutes: number; // Minutes. + hours: number; // Hours. + mday: number; // Mday. + wday: number; // Wday. + mon: number; // Mon. + year: number; // Year. + yday: number; // Yday. + weekday: string; // Weekday. + month: string; // Month. + timestamp: number; // Timestamp. +}; diff --git a/src/theme/dark.scss b/src/theme/dark.scss new file mode 100644 index 000000000..01c373d06 --- /dev/null +++ b/src/theme/dark.scss @@ -0,0 +1,125 @@ +$core-dark-text-color: $white !default; +$core-dark-text-secondary-color: $gray-light !default; +$core-dark-link-color: $blue-light !default; + +$core-dark-item-bg-color: $gray-darker !default; +$core-dark-item-divider-bg-color: $gray-dark !default; +$core-dark-background-color: $black !default; + +// Login. +$core-dark-login-page-background-color: radial-gradient(white, $gray-dark) !default; +$core-dark-login-box-background-color: $black !default; +$core-dark-login-box-background-border: $core-login-box-background-border !default; +$core-dark-login-box-text-color: $core-dark-text-color !default; +$core-dark-login-item-inner-background-color: $core-dark-login-box-background-color !default; +$core-dark-login-item-background-color: $core-dark-login-box-background-color !default; +$core-dark-login-button-outline: $core-login-button-outline !default; +$core-dark-login-loading-color: $core-dark-text-color !default; + +ion-app.app-root .ion-page { + @include darkmode() { + color: $core-dark-text-color; + background-color: $core-dark-item-bg-color; + + a:not(.button) { + color: $core-dark-link-color; + } + + .core-tabs-bar, + .core-tabs-bar *, + .core-tabs-bar .tab-slide, + .ion-page, + .item, + .item-select, + ion-card, + .card-header, + .card-content { + color: $core-dark-text-color; + background-color: $core-dark-item-bg-color; + + h1, h2, h3, h4, h5, h6, + ion-icon, + .label { + color: $core-dark-text-color; + } + + @each $color-name, $color-base, $color-contrast in get-colors($colors-dark) { + .icon-md-#{$color-name}, + .icon-ios-#{$color-name}, + .icon-wp-#{$color-name} { + color: $color-base; + } + } + p { + color: $core-dark-text-secondary-color; + } + } + + .item-divider, + .item-divider .item-inner { + color: $core-dark-text-color; + background-color: $core-dark-item-divider-bg-color; + } + + .item.item-ios:active, + .item.item-ios.activated, + .item.item-md:active, + .item.item-md.activated, + .item.item-wp:active, + .item.item-wp.activated { + background-color: $core-dark-background-color; + } + + .content, + .content-md, + .content-ios, + .content-wp { + color: $core-dark-text-color; + background-color: $core-dark-background-color; + } + + .button, + .button-md-light, + .button-ios-light, + .button-wp-light, + .button-outline { + ion-icon { + color: inherit; + } + } + + .toolbar-md-light .toolbar-background, + .toolbar-ios-light .toolbar-background, + .toolbar-wp-light .toolbar-background { + background-color: $core-dark-item-divider-bg-color; + color: $core-dark-text-color; + } + + .button.button-clear-md-dark, + .button.button-clear-ios-dark, + .button.button-clear-wp-dark { + .button-inner ion-icon { + color: $core-dark-text-color; + } + } + + .button-outline { + background-color: $core-dark-item-bg-color; + } + + ion-refresher { + .refresher-pulling-icon, + .refresher-refreshing-icon, + .refresher-pulling-icon ion-icon, + .refresher-refreshing-icon ion-icon, + ion-icon { + color: $refresher-icon-color; + } + + .refresher-pulling-text, + .refresher-refreshing-text { + color: $core-dark-text-color; + } + } + } +} diff --git a/src/theme/variables.scss b/src/theme/variables.scss index 0cd36e33e..8688e1339 100644 --- a/src/theme/variables.scss +++ b/src/theme/variables.scss @@ -30,7 +30,6 @@ $red: #cb3d4d; $orange: #f98012; // Accent (never text). $yellow: #fbad1a; // Accent (never text). $purple: #8e24aa; // Accent (never text). -$core-color: $orange; // Branded apps customization // -------------------------------------------------- @@ -53,6 +52,7 @@ $orange-light: lighten($orange, 10%) !default; $yellow-light: mix($yellow, white, 20%) !default; $yellow-dark: mix($yellow, black, 40%) !default; +$core-color: $orange !default; $core-color-light: lighten($core-color, 10%) !default; $core-color-dark: darken($core-color, 10%) !default; @@ -88,6 +88,15 @@ $info: $blue !default; $inverted-base: $white !default; $inverted-contrast: $primary !default; +$primary-dark: lighten($primary, 10%) !default; +$secondary-dark: mix($secondary, white, 20%) !default; +$danger-dark: mix($danger, white, 20%) !default; +$warning-dark: mix($warning, white, 20%) !default; +$success-dark: mix($success, white, 20%) !default; +$info-dark: mix($info, white, 20%) !default; +$inverted-base-dark: $dark !default; +$inverted-contrast-dark: $primary-dark !default; + $colors: ( primary: $primary, secondary: $secondary, @@ -104,6 +113,22 @@ $colors: ( ) ); +$colors-dark: ( + primary: $primary-dark, + secondary: $secondary-dark, + danger: $danger-dark, + light: $dark, + gray: $color-gray, + dark: $light, + warning: $warning-dark, + success: $success-dark, + info: $info-dark, + inverted: ( + base: $inverted-base-dark, + contrast: $inverted-contrast-dark + ) +); + $text-color: $black !default; $link-color: $blue !default; $background-color: $gray-light !default; @@ -163,6 +188,8 @@ $core-loading-spinner-color: $core-color !default; $core-spinner-color: $core-color !default; $core-button-outline-background-color: $white !default; +$core-network-message-height: 16px !default; + // Login. $core-login-page-background-color: radial-gradient(white, $gray-light) !default; $core-login-box-background-color: $white !default; @@ -388,7 +415,6 @@ $core-dd-question-colors: $white, $blue-light, #DCDCDC, #D8BFD8, #87CEFA, #DAA52 } } - @mixin safe-area-border-end($px, $type, $color) { $safe-area-position: calc(constant(safe-area-inset-right) + #{$px}); $safe-area-position-env: calc(env(safe-area-inset-right) + #{$px}); @@ -400,6 +426,29 @@ $core-dd-question-colors: $white, $blue-light, #DCDCDC, #D8BFD8, #87CEFA, #DAA52 } } +@mixin safe-area-margin-horizontal($start, $end: $start) { + $safe-area-end: null; + $safe-area-start: null; + $safe-area-start-env: null; + $safe-area-end-env: null; + + @if ($end) { + $safe-area-end: calc(constant(safe-area-inset-right) + #{$end}); + $safe-area-end-env: calc(env(safe-area-inset-right) + #{$end}); + } + @if ($start) { + $safe-area-start: calc(constant(safe-area-inset-left) + #{$start}); + $safe-area-start-env: calc(env(safe-area-inset-left) + #{$start}); + } + + @include margin-horizontal($start, $end); + + @media screen and (orientation: landscape) { + @include margin-horizontal($safe-area-start, $safe-area-end); + @include margin-horizontal($safe-area-start-env, $safe-area-end-env); + } +} + @mixin safe-area-padding-start($start, $end) { $safe-area-start: calc(constant(safe-area-inset-left) + #{$start}); $safe-area-start-env: calc(env(safe-area-inset-left) + #{$start}); @@ -424,6 +473,13 @@ $core-dd-question-colors: $white, $blue-light, #DCDCDC, #D8BFD8, #87CEFA, #DAA52 } } +@mixin safe-area-position($top: null, $end: null, $bottom: null, $start: null) { + @include position-horizontal($start, $end); + @include safe-position-horizontal($start, $end); + top: $top; + bottom: $bottom; +} + @mixin horizontal_scroll_item($width, $min-width, $max-width) { flex: 0 0 $width; min-width: $min-width; @@ -531,16 +587,109 @@ $core-dd-question-colors: $white, $blue-light, #DCDCDC, #D8BFD8, #87CEFA, #DAA52 } @mixin core-selected-item($selected-color) { - @include border-start(5px, solid, $selected-color); + @include safe-area-border-start(5px, solid, $selected-color); &.item-md { - @include padding(null, null, null, $item-md-padding-start - 5px); + @include padding-horizontal($item-md-padding-start - 5px, null); } &.item-ios { - @include padding(null, null, null, $item-ios-padding-start - 5px); + @include padding-horizontal($item-ios-padding-start - 5px, null); } &.item-wp { - @include padding(null, null, null, $item-wp-padding-start - 5px); + @include padding-horizontal($item-wp-padding-start - 5px, null); + } +} + + +@mixin core-split-area-start() { + .safe-padding-horizontal, + [padding].safe-padding-horizontal { + @include safe-area-padding-start(0px, $content-padding); + } + + .safe-area-page { + @include safe-area-padding-start(0px, 0px); + + .core-split-item-selected { + @include border-start(5px, solid, $core-splitview-selected); + } + } + + core-loading.safe-area-page { + > .core-loading-content > *, + > .core-loading-content-loading > * { + @include safe-area-padding-start(0px, 0px); + } + } + + // Disable safe area padding on the "end" side. + .item-ios.item-block .item-inner { + @include padding-horizontal(null, $item-ios-padding-end / 2); + } + + @if $item-ios-detail-push-show == true { + .item-ios[detail-push] .item-inner, + button.item-ios:not([detail-none]) .item-inner, + a.item-ios:not([detail-none]) .item-inner { + @include padding-horizontal(null, 32px); + @include background-position(end, $item-ios-padding-end - 2, center); + [item-end] { + @include margin-horizontal(($item-ios-padding-start / 2), ($item-ios-padding-end / 2)); + } + } + } + + ion-fab[end] { + @include position-horizontal(null, $fab-content-margin); + } +} + +@mixin core-split-area-end() { + .safe-padding-horizontal, + [padding].safe-padding-horizontal { + @include safe-area-padding-end($content-padding, 0px); + } + + .safe-area-page { + @include safe-area-padding-end(0px, 0px); + } + + core-loading.safe-area-page { + > .core-loading-content > *, + > .core-loading-content-loading > * { + @include safe-area-padding-end(0px, 0px); + } + } + + // Disable safe area padding on the "start" side. + .item-ios { + @include padding-horizontal($item-ios-padding-end / 2, null); + } + + .toolbar { + @include safe-area-padding-end(0px, 0px); + } + + .core-nav-item-selected, .item.core-nav-item-selected { + @include border-start(5px, solid, $core-splitview-selected); + } +} + +@mixin darkmode() { + $root: #{&}; + + @at-root body.scheme-auto { + #{$root} { + @media (prefers-color-scheme: dark) { + @content; + } + } + } + + @at-root body.scheme-dark { + #{$root} { + @content; + } } } @@ -550,3 +699,6 @@ $fa-font-path: $font-path; // Format text styles. @import "format-text"; + +// Dark mode. +@import "dark"; \ No newline at end of file diff --git a/upgrade.txt b/upgrade.txt index 6599608e6..75333c0c4 100644 --- a/upgrade.txt +++ b/upgrade.txt @@ -1,6 +1,10 @@ This files describes API changes in the Moodle Mobile app, information provided here is intended especially for developers. +=== 3.8.0 === + +- CoreDomUtilsProvider.extractDownloadableFilesFromHtml and CoreDomUtilsProvider.extractDownloadableFilesFromHtmlAsFakeFileObjects have been deprecated. Please use CoreFilepoolProvider.extractDownloadableFilesFromHtml and CoreFilepoolProvider.extractDownloadableFilesFromHtmlAsFakeFileObjects. We had to move them to prevent a circular dependency. + === 3.7.1 === - CoreGroupsProvider.getActivityAllowedGroups and CoreGroupsProvider.getActivityAllowedGroupsIfEnabled now return the full response of core_group_get_activity_allowed_groups instead of just the groups.